mirror of
				https://github.com/yattee/yattee.git
				synced 2025-11-03 22:22:02 +00:00 
			
		
		
		
	Show badge for channels subscriptions
This commit is contained in:
		@@ -70,6 +70,7 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel {
 | 
			
		||||
                    if let channels: [Channel] = resource.typedContent() {
 | 
			
		||||
                        self.channels = channels
 | 
			
		||||
                        self.storeChannels(account: account, channels: channels)
 | 
			
		||||
                        FeedModel.shared.calculateUnwatchedFeed()
 | 
			
		||||
                        onSuccess()
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 
 | 
			
		||||
@@ -11,6 +11,7 @@ final class FeedModel: ObservableObject, CacheModel {
 | 
			
		||||
    @Published var videos = [Video]()
 | 
			
		||||
    @Published private var page = 1
 | 
			
		||||
    @Published var unwatched = [Account: Int]()
 | 
			
		||||
    @Published var unwatchedByChannel = [Account: [Channel.ID: Int]]()
 | 
			
		||||
 | 
			
		||||
    private var cacheModel = FeedCacheModel.shared
 | 
			
		||||
    private var accounts = AccountsModel.shared
 | 
			
		||||
@@ -112,43 +113,66 @@ final class FeedModel: ObservableObject, CacheModel {
 | 
			
		||||
        backgroundContext.perform { [weak self] in
 | 
			
		||||
            guard let self else { return }
 | 
			
		||||
 | 
			
		||||
            let watched = self.watchFetchRequestResult(feed, context: self.backgroundContext).filter { $0.finished }.count
 | 
			
		||||
            let unwatched = feed.count - watched
 | 
			
		||||
            let watched = self.watchFetchRequestResult(feed, context: self.backgroundContext).filter { $0.finished }
 | 
			
		||||
            let unwatched = feed.filter { video in !watched.contains { $0.videoID == video.videoID } }
 | 
			
		||||
            let unwatchedCount = feed.count - watched.count
 | 
			
		||||
 | 
			
		||||
            DispatchQueue.main.async { [weak self] in
 | 
			
		||||
                guard let self else { return }
 | 
			
		||||
                if unwatched != self.unwatched[account] {
 | 
			
		||||
                    self.unwatched[account] = unwatched
 | 
			
		||||
                if unwatchedCount != self.unwatched[account] {
 | 
			
		||||
                    self.unwatched[account] = unwatchedCount
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                let byChannel = Dictionary(grouping: unwatched) { $0.channel.id }.mapValues(\.count)
 | 
			
		||||
                self.unwatchedByChannel[account] = byChannel
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func markAllFeedAsWatched() {
 | 
			
		||||
        guard let account = accounts.current else { return }
 | 
			
		||||
        guard !videos.isEmpty else { return }
 | 
			
		||||
 | 
			
		||||
        backgroundContext.perform { [weak self] in
 | 
			
		||||
            guard let self else { return }
 | 
			
		||||
            self.videos.forEach { Watch.markAsWatched(videoID: $0.videoID, account: account, duration: $0.length, context: self.backgroundContext) }
 | 
			
		||||
        let mark = { [weak self] in
 | 
			
		||||
            self?.backgroundContext.perform { [weak self] in
 | 
			
		||||
                guard let self else { return }
 | 
			
		||||
                self.videos.forEach { Watch.markAsWatched(videoID: $0.videoID, account: account, duration: $0.length, context: self.backgroundContext) }
 | 
			
		||||
 | 
			
		||||
            self.calculateUnwatchedFeed()
 | 
			
		||||
                self.calculateUnwatchedFeed()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if videos.isEmpty {
 | 
			
		||||
            loadCachedFeed { mark() }
 | 
			
		||||
        } else {
 | 
			
		||||
            mark()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var canMarkAllFeedAsWatched: Bool {
 | 
			
		||||
        guard let account = accounts.current else { return false }
 | 
			
		||||
        return (unwatched[account] ?? 0) > 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func markAllFeedAsUnwatched() {
 | 
			
		||||
        guard accounts.current != nil,
 | 
			
		||||
              !videos.isEmpty else { return }
 | 
			
		||||
        guard accounts.current != nil else { return }
 | 
			
		||||
 | 
			
		||||
        backgroundContext.perform { [weak self] in
 | 
			
		||||
            guard let self else { return }
 | 
			
		||||
        let mark = { [weak self] in
 | 
			
		||||
            self?.backgroundContext.perform { [weak self] in
 | 
			
		||||
                guard let self else { return }
 | 
			
		||||
 | 
			
		||||
            let watches = self.watchFetchRequestResult(self.videos, context: self.backgroundContext)
 | 
			
		||||
            watches.forEach { self.backgroundContext.delete($0) }
 | 
			
		||||
                let watches = self.watchFetchRequestResult(self.videos, context: self.backgroundContext)
 | 
			
		||||
                watches.forEach { self.backgroundContext.delete($0) }
 | 
			
		||||
 | 
			
		||||
            try? self.backgroundContext.save()
 | 
			
		||||
                try? self.backgroundContext.save()
 | 
			
		||||
 | 
			
		||||
            self.calculateUnwatchedFeed()
 | 
			
		||||
                self.calculateUnwatchedFeed()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        if videos.isEmpty {
 | 
			
		||||
            loadCachedFeed { mark() }
 | 
			
		||||
        } else {
 | 
			
		||||
            mark()
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
@@ -183,6 +207,11 @@ final class FeedModel: ObservableObject, CacheModel {
 | 
			
		||||
        PlayerModel.shared.play(unwatched)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var canPlayUnwatchedFeed: Bool {
 | 
			
		||||
        guard let account = accounts.current else { return false }
 | 
			
		||||
        return (unwatched[account] ?? 0) > 0
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var feedTime: Date? {
 | 
			
		||||
        if let account = accounts.current {
 | 
			
		||||
            return cacheModel.getFeedTime(account: account)
 | 
			
		||||
@@ -195,12 +224,13 @@ final class FeedModel: ObservableObject, CacheModel {
 | 
			
		||||
        getFormattedDate(feedTime)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    private func loadCachedFeed() {
 | 
			
		||||
    private func loadCachedFeed(_ onCompletion: @escaping () -> Void = {}) {
 | 
			
		||||
        guard let account = accounts.current else { return }
 | 
			
		||||
        let cache = cacheModel.retrieveFeed(account: account)
 | 
			
		||||
        if !cache.isEmpty {
 | 
			
		||||
            DispatchQueue.main.async(qos: .userInteractive) { [weak self] in
 | 
			
		||||
                self?.videos = cache
 | 
			
		||||
                onCompletion()
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 
 | 
			
		||||
@@ -3,24 +3,29 @@ import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct AppSidebarSubscriptions: View {
 | 
			
		||||
    @ObservedObject private var navigation = NavigationModel.shared
 | 
			
		||||
    @ObservedObject private var feed = FeedModel.shared
 | 
			
		||||
    @ObservedObject private var subscriptions = SubscribedChannelsModel.shared
 | 
			
		||||
 | 
			
		||||
    @ObservedObject private var accounts = AccountsModel.shared
 | 
			
		||||
 | 
			
		||||
    var body: some View {
 | 
			
		||||
        Section(header: Text("Subscriptions")) {
 | 
			
		||||
            ForEach(subscriptions.all) { channel in
 | 
			
		||||
                NavigationLink(tag: TabSelection.channel(channel.id), selection: $navigation.tabSelection) {
 | 
			
		||||
                    LazyView(ChannelVideosView(channel: channel).modifier(PlayerOverlayModifier()))
 | 
			
		||||
                } label: {
 | 
			
		||||
                    if channel.thumbnailURL != nil {
 | 
			
		||||
                        HStack {
 | 
			
		||||
                    HStack {
 | 
			
		||||
                        if channel.thumbnailURL != nil {
 | 
			
		||||
                            ChannelAvatarView(channel: channel, subscribedBadge: false)
 | 
			
		||||
                                .frame(width: 20, height: 20)
 | 
			
		||||
 | 
			
		||||
                            Text(channel.name)
 | 
			
		||||
                        } else {
 | 
			
		||||
                            Label(channel.name, systemImage: RecentsModel.symbolSystemImage(channel.name))
 | 
			
		||||
                        }
 | 
			
		||||
                    } else {
 | 
			
		||||
                        Label(channel.name, systemImage: RecentsModel.symbolSystemImage(channel.name))
 | 
			
		||||
                    }
 | 
			
		||||
                    .backport
 | 
			
		||||
                    .badge(channelBadge(channel))
 | 
			
		||||
                }
 | 
			
		||||
                .contextMenu {
 | 
			
		||||
                    Button("Unsubscribe") {
 | 
			
		||||
@@ -31,6 +36,14 @@ struct AppSidebarSubscriptions: View {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func channelBadge(_ channel: Channel) -> Text? {
 | 
			
		||||
        if let count = feed.unwatchedByChannel[accounts.current]?[channel.id] {
 | 
			
		||||
            return Text(String(count))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return nil
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct AppSidebarSubscriptions_Previews: PreviewProvider {
 | 
			
		||||
 
 | 
			
		||||
@@ -3,6 +3,7 @@ import SDWebImageSwiftUI
 | 
			
		||||
import SwiftUI
 | 
			
		||||
 | 
			
		||||
struct ChannelsView: View {
 | 
			
		||||
    @ObservedObject private var feed = FeedModel.shared
 | 
			
		||||
    @ObservedObject private var subscriptions = SubscribedChannelsModel.shared
 | 
			
		||||
    @ObservedObject private var accounts = AccountsModel.shared
 | 
			
		||||
 | 
			
		||||
@@ -23,6 +24,8 @@ struct ChannelsView: View {
 | 
			
		||||
                                Label(channel.name, systemImage: RecentsModel.symbolSystemImage(channel.name))
 | 
			
		||||
                            }
 | 
			
		||||
                        }
 | 
			
		||||
                        .backport
 | 
			
		||||
                        .badge(channelBadge(channel))
 | 
			
		||||
                    }
 | 
			
		||||
                    .contextMenu {
 | 
			
		||||
                        Button {
 | 
			
		||||
@@ -78,6 +81,14 @@ struct ChannelsView: View {
 | 
			
		||||
        #endif
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    func channelBadge(_ channel: Channel) -> Text? {
 | 
			
		||||
        if let count = feed.unwatchedByChannel[accounts.current]?[channel.id] {
 | 
			
		||||
            return Text(String(count))
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        return nil
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    var header: some View {
 | 
			
		||||
        HStack {
 | 
			
		||||
            #if os(tvOS)
 | 
			
		||||
 
 | 
			
		||||
@@ -39,6 +39,10 @@ struct SubscriptionsView: View {
 | 
			
		||||
            ToolbarItem {
 | 
			
		||||
                ListingStyleButtons(listingStyle: $subscriptionsListingStyle)
 | 
			
		||||
            }
 | 
			
		||||
 | 
			
		||||
            ToolbarItem {
 | 
			
		||||
                toggleWatchedButton
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
        #endif
 | 
			
		||||
    }
 | 
			
		||||
@@ -53,26 +57,12 @@ struct SubscriptionsView: View {
 | 
			
		||||
 | 
			
		||||
                if subscriptionsViewPage == .feed {
 | 
			
		||||
                    ListingStyleButtons(listingStyle: $subscriptionsListingStyle)
 | 
			
		||||
 | 
			
		||||
                    Button {
 | 
			
		||||
                        feed.playUnwatchedFeed()
 | 
			
		||||
                    } label: {
 | 
			
		||||
                        Label("Play unwatched", systemImage: "play")
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    Button {
 | 
			
		||||
                        feed.markAllFeedAsWatched()
 | 
			
		||||
                    } label: {
 | 
			
		||||
                        Label("Mark all as watched", systemImage: "checkmark.circle.fill")
 | 
			
		||||
                    }
 | 
			
		||||
 | 
			
		||||
                    Button {
 | 
			
		||||
                        feed.markAllFeedAsUnwatched()
 | 
			
		||||
                    } label: {
 | 
			
		||||
                        Label("Mark all as unwatched", systemImage: "checkmark.circle")
 | 
			
		||||
                    }
 | 
			
		||||
                }
 | 
			
		||||
 | 
			
		||||
                playUnwatchedButton
 | 
			
		||||
 | 
			
		||||
                toggleWatchedButton
 | 
			
		||||
 | 
			
		||||
                Section {
 | 
			
		||||
                    SettingsButtons()
 | 
			
		||||
                }
 | 
			
		||||
@@ -98,6 +88,40 @@ struct SubscriptionsView: View {
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    #endif
 | 
			
		||||
 | 
			
		||||
    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")
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
struct SubscriptionsView_Previews: PreviewProvider {
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user