mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 21:43:41 +00:00
Add action to mark channel feed as watched/unwatched
This commit is contained in:
parent
b55c6f8619
commit
a156ef6a3f
@ -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)) ?? []
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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 {
|
||||||
|
@ -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 {
|
||||||
|
@ -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],
|
||||||
|
@ -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 {
|
||||||
|
Loading…
Reference in New Issue
Block a user