Add action to mark channel feed as watched/unwatched

This commit is contained in:
Arkadiusz Fal 2022-12-14 18:10:01 +01:00
parent b55c6f8619
commit a156ef6a3f
5 changed files with 196 additions and 16 deletions

View File

@ -158,20 +158,18 @@ final class FeedModel: ObservableObject, CacheModel {
return (unwatched[account] ?? 0) > 0 return (unwatched[account] ?? 0) > 0
} }
func markAllFeedAsUnwatched() { func canMarkChannelAsWatched(_ channelID: Channel.ID) -> Bool {
guard accounts.current != nil else { return } guard let account = accounts.current, accounts.signedIn else { return false }
return unwatchedByChannel[account]?.keys.contains(channelID) ?? false
}
func markChannelAsWatched(_ channelID: Channel.ID) {
guard accounts.signedIn else { return }
let mark = { [weak self] in let mark = { [weak self] in
self?.backgroundContext.perform { [weak self] in guard let self else { return }
guard let self else { return } self.markVideos(self.videos.filter { $0.channel.id == channelID }, watched: true)
let watches = self.watchFetchRequestResult(self.videos, context: self.backgroundContext)
watches.forEach { self.backgroundContext.delete($0) }
try? self.backgroundContext.save()
self.calculateUnwatchedFeed()
}
} }
if videos.isEmpty { if videos.isEmpty {
@ -181,10 +179,53 @@ final class FeedModel: ObservableObject, CacheModel {
} }
} }
func watchFetchRequestResult(_ videos: [Video], context: NSManagedObjectContext) -> [Watch] { func markChannelAsUnwatched(_ channelID: Channel.ID) {
let watchFetchRequest = Watch.fetchRequest() guard accounts.signedIn else { return }
watchFetchRequest.predicate = NSPredicate(format: "videoID IN %@", videos.map(\.videoID) as [String])
return (try? context.fetch(watchFetchRequest)) ?? [] let mark = { [weak self] in
guard let self else { return }
self.markVideos(self.videos.filter { $0.channel.id == channelID }, watched: false)
}
if videos.isEmpty {
loadCachedFeed { mark() }
} else {
mark()
}
}
func markAllFeedAsUnwatched() {
guard accounts.current != nil else { return }
let mark = { [weak self] in
guard let self else { return }
self.markVideos(self.videos, watched: false)
}
if videos.isEmpty {
loadCachedFeed { mark() }
} else {
mark()
}
}
func markVideos(_ videos: [Video], watched: Bool) {
guard accounts.signedIn, let account = accounts.current else { return }
backgroundContext.perform { [weak self] in
guard let self else { return }
if watched {
videos.forEach { Watch.markAsWatched(videoID: $0.videoID, account: account, duration: $0.length, context: self.backgroundContext) }
} else {
let watches = self.watchFetchRequestResult(videos, context: self.backgroundContext)
watches.forEach { self.backgroundContext.delete($0) }
}
try? self.backgroundContext.save()
self.calculateUnwatchedFeed()
}
} }
func playUnwatchedFeed() { func playUnwatchedFeed() {
@ -247,4 +288,10 @@ final class FeedModel: ObservableObject, CacheModel {
return resource.loadIfNeeded() return resource.loadIfNeeded()
} }
private func watchFetchRequestResult(_ videos: [Video], context: NSManagedObjectContext) -> [Watch] {
let watchFetchRequest = Watch.fetchRequest()
watchFetchRequest.predicate = NSPredicate(format: "videoID IN %@", videos.map(\.videoID) as [String])
return (try? context.fetch(watchFetchRequest)) ?? []
}
} }

View File

@ -23,6 +23,7 @@ struct ChannelVideosView: View {
#endif #endif
@ObservedObject private var accounts = AccountsModel.shared @ObservedObject private var accounts = AccountsModel.shared
@ObservedObject private var feed = FeedModel.shared
@ObservedObject private var navigation = NavigationModel.shared @ObservedObject private var navigation = NavigationModel.shared
@ObservedObject private var recents = RecentsModel.shared @ObservedObject private var recents = RecentsModel.shared
@ObservedObject private var subscriptions = SubscribedChannelsModel.shared @ObservedObject private var subscriptions = SubscribedChannelsModel.shared
@ -129,6 +130,10 @@ struct ChannelVideosView: View {
FavoriteButton(item: FavoriteItem(section: .channel(accounts.app.appType.rawValue, presentedChannel.id, presentedChannel.name))) FavoriteButton(item: FavoriteItem(section: .channel(accounts.app.appType.rawValue, presentedChannel.id, presentedChannel.name)))
} }
} }
ToolbarItem {
toggleWatchedButton
}
#endif #endif
} }
#endif #endif
@ -231,6 +236,10 @@ struct ChannelVideosView: View {
FavoriteButton(item: FavoriteItem(section: .channel(accounts.app.appType.rawValue, channel.id, channel.name))) FavoriteButton(item: FavoriteItem(section: .channel(accounts.app.appType.rawValue, channel.id, channel.name)))
} }
if subscriptions.isSubscribing(channel.id) {
toggleWatchedButton
}
ListingStyleButtons(listingStyle: $channelPlaylistListingStyle) ListingStyleButtons(listingStyle: $channelPlaylistListingStyle)
} }
} label: { } label: {
@ -341,6 +350,35 @@ struct ChannelVideosView: View {
private var navigationTitle: String { private var navigationTitle: String {
presentedChannel?.name ?? "No channel" presentedChannel?.name ?? "No channel"
} }
@ViewBuilder var toggleWatchedButton: some View {
if let channel = presentedChannel {
if feed.canMarkChannelAsWatched(channel.id) {
markChannelAsWatchedButton
} else {
markChannelAsUnwatchedButton
}
}
}
var markChannelAsWatchedButton: some View {
Button {
guard let channel = presentedChannel else { return }
feed.markChannelAsWatched(channel.id)
} label: {
Label("Mark channel feed as watched", systemImage: "checkmark.circle.fill")
}
.disabled(!feed.canMarkAllFeedAsWatched)
}
var markChannelAsUnwatchedButton: some View {
Button {
guard let channel = presentedChannel else { return }
feed.markChannelAsUnwatched(channel.id)
} label: {
Label("Mark channel feed as unwatched", systemImage: "checkmark.circle")
}
}
} }
struct ChannelVideosView_Previews: PreviewProvider { struct ChannelVideosView_Previews: PreviewProvider {

View File

@ -28,6 +28,10 @@ struct AppSidebarSubscriptions: View {
.badge(channelBadge(channel)) .badge(channelBadge(channel))
} }
.contextMenu { .contextMenu {
if subscriptions.isSubscribing(channel.id) {
toggleWatchedButton(channel)
}
Button("Unsubscribe") { Button("Unsubscribe") {
navigation.presentUnsubscribeAlert(channel, subscriptions: subscriptions) navigation.presentUnsubscribeAlert(channel, subscriptions: subscriptions)
} }
@ -44,6 +48,31 @@ struct AppSidebarSubscriptions: View {
return nil return nil
} }
@ViewBuilder func toggleWatchedButton(_ channel: Channel) -> some View {
if feed.canMarkChannelAsWatched(channel.id) {
markChannelAsWatchedButton(channel)
} else {
markChannelAsUnwatchedButton(channel)
}
}
func markChannelAsWatchedButton(_ channel: Channel) -> some View {
Button {
feed.markChannelAsWatched(channel.id)
} label: {
Label("Mark channel feed as watched", systemImage: "checkmark.circle.fill")
}
.disabled(!feed.canMarkAllFeedAsWatched)
}
func markChannelAsUnwatchedButton(_ channel: Channel) -> some View {
Button {
feed.markChannelAsUnwatched(channel.id)
} label: {
Label("Mark channel feed as unwatched", systemImage: "checkmark.circle")
}
}
} }
struct AppSidebarSubscriptions_Previews: PreviewProvider { struct AppSidebarSubscriptions_Previews: PreviewProvider {

View File

@ -79,6 +79,10 @@ struct Sidebar: View {
} }
.backport .backport
.badge(subscriptionsBadge) .badge(subscriptionsBadge)
.contextMenu {
playUnwatchedButton
toggleWatchedButton
}
.id("subscriptions") .id("subscriptions")
} }
@ -108,6 +112,40 @@ struct Sidebar: View {
} }
} }
var playUnwatchedButton: some View {
Button {
feed.playUnwatchedFeed()
} label: {
Label("Play all unwatched", systemImage: "play")
}
.disabled(!feed.canPlayUnwatchedFeed)
}
@ViewBuilder var toggleWatchedButton: some View {
if feed.canMarkAllFeedAsWatched {
markAllFeedAsWatchedButton
} else {
markAllFeedAsUnwatchedButton
}
}
var markAllFeedAsWatchedButton: some View {
Button {
feed.markAllFeedAsWatched()
} label: {
Label("Mark all as watched", systemImage: "checkmark.circle.fill")
}
.disabled(!feed.canMarkAllFeedAsWatched)
}
var markAllFeedAsUnwatchedButton: some View {
Button {
feed.markAllFeedAsUnwatched()
} label: {
Label("Mark all as unwatched", systemImage: "checkmark.circle")
}
}
private var subscriptionsBadge: Text? { private var subscriptionsBadge: Text? {
guard let account = accounts.current, guard let account = accounts.current,
let unwatched = feed.unwatched[account], let unwatched = feed.unwatched[account],

View File

@ -28,6 +28,9 @@ struct ChannelsView: View {
.badge(channelBadge(channel)) .badge(channelBadge(channel))
} }
.contextMenu { .contextMenu {
if subscriptions.isSubscribing(channel.id) {
toggleWatchedButton(channel)
}
Button { Button {
subscriptions.unsubscribe(channel.id) subscriptions.unsubscribe(channel.id)
} label: { } label: {
@ -124,6 +127,31 @@ struct ChannelsView: View {
.padding(.top, 15) .padding(.top, 15)
#endif #endif
} }
@ViewBuilder func toggleWatchedButton(_ channel: Channel) -> some View {
if feed.canMarkChannelAsWatched(channel.id) {
markChannelAsWatchedButton(channel)
} else {
markChannelAsUnwatchedButton(channel)
}
}
func markChannelAsWatchedButton(_ channel: Channel) -> some View {
Button {
feed.markChannelAsWatched(channel.id)
} label: {
Label("Mark channel feed as watched", systemImage: "checkmark.circle.fill")
}
.disabled(!feed.canMarkAllFeedAsWatched)
}
func markChannelAsUnwatchedButton(_ channel: Channel) -> some View {
Button {
feed.markChannelAsUnwatched(channel.id)
} label: {
Label("Mark channel feed as unwatched", systemImage: "checkmark.circle")
}
}
} }
struct ChannelsView_Previews: PreviewProvider { struct ChannelsView_Previews: PreviewProvider {