diff --git a/Model/FeedModel.swift b/Model/FeedModel.swift index 3e13b782..db768977 100644 --- a/Model/FeedModel.swift +++ b/Model/FeedModel.swift @@ -10,9 +10,8 @@ final class FeedModel: ObservableObject, CacheModel { @Published var isLoading = false @Published var videos = [Video]() @Published private var page = 1 - @Published var unwatched = [Account: Int]() - @Published var unwatchedByChannel = [Account: [Channel.ID: Int]]() + private var feedCount = UnwatchedFeedCountModel.shared private var cacheModel = FeedCacheModel.shared private var accounts = AccountsModel.shared @@ -125,12 +124,12 @@ final class FeedModel: ObservableObject, CacheModel { DispatchQueue.main.async { [weak self] in guard let self else { return } - if unwatchedCount != self.unwatched[account] { - self.unwatched[account] = unwatchedCount + if unwatchedCount != self.feedCount.unwatched[account] { + self.feedCount.unwatched[account] = unwatchedCount } let byChannel = Dictionary(grouping: unwatched) { $0.channel.id }.mapValues(\.count) - self.unwatchedByChannel[account] = byChannel + self.feedCount.unwatchedByChannel[account] = byChannel } } } @@ -156,13 +155,13 @@ final class FeedModel: ObservableObject, CacheModel { var canMarkAllFeedAsWatched: Bool { guard let account = accounts.current, accounts.signedIn else { return false } - return (unwatched[account] ?? 0) > 0 + return (feedCount.unwatched[account] ?? 0) > 0 } func canMarkChannelAsWatched(_ channelID: Channel.ID) -> Bool { guard let account = accounts.current, accounts.signedIn else { return false } - return unwatchedByChannel[account]?.keys.contains(channelID) ?? false + return feedCount.unwatchedByChannel[account]?.keys.contains(channelID) ?? false } func markChannelAsWatched(_ channelID: Channel.ID) { @@ -256,7 +255,7 @@ final class FeedModel: ObservableObject, CacheModel { var canPlayUnwatchedFeed: Bool { guard let account = accounts.current, accounts.signedIn else { return false } - return (unwatched[account] ?? 0) > 0 + return (feedCount.unwatched[account] ?? 0) > 0 } var feedTime: Date? { diff --git a/Model/UnwatchedFeedCountModel.swift b/Model/UnwatchedFeedCountModel.swift new file mode 100644 index 00000000..f87e8be8 --- /dev/null +++ b/Model/UnwatchedFeedCountModel.swift @@ -0,0 +1,32 @@ +import Foundation +import SwiftUI + +final class UnwatchedFeedCountModel: ObservableObject { + static let shared = UnwatchedFeedCountModel() + + @Published var unwatched = [Account: Int]() + @Published var unwatchedByChannel = [Account: [Channel.ID: Int]]() + + private var accounts = AccountsModel.shared + + var unwatchedText: Text? { + if let account = accounts.current, + !account.anonymous, + let count = unwatched[account] + { + return Text(String(count)) + } + + return nil + } + + func unwatchedByChannelText(_ channel: Channel) -> Text? { + if let account = accounts.current, + !account.anonymous, + let count = unwatchedByChannel[account]?[channel.id] + { + return Text(String(count)) + } + return nil + } +} diff --git a/Shared/Navigation/AppSidebarSubscriptions.swift b/Shared/Navigation/AppSidebarSubscriptions.swift index cd277b89..02a84a5f 100644 --- a/Shared/Navigation/AppSidebarSubscriptions.swift +++ b/Shared/Navigation/AppSidebarSubscriptions.swift @@ -4,8 +4,8 @@ import SwiftUI struct AppSidebarSubscriptions: View { @ObservedObject private var navigation = NavigationModel.shared @ObservedObject private var feed = FeedModel.shared + @ObservedObject private var feedCount = UnwatchedFeedCountModel.shared @ObservedObject private var subscriptions = SubscribedChannelsModel.shared - @ObservedObject private var accounts = AccountsModel.shared var body: some View { @@ -23,9 +23,9 @@ struct AppSidebarSubscriptions: View { } else { Label(channel.name, systemImage: RecentsModel.symbolSystemImage(channel.name)) } + + feedCount.unwatchedByChannelText(channel) } - .backport - .badge(channelBadge(channel)) } .contextMenu { if subscriptions.isSubscribing(channel.id) { @@ -41,14 +41,6 @@ struct AppSidebarSubscriptions: View { } } - func channelBadge(_ channel: Channel) -> Text? { - if let count = feed.unwatchedByChannel[accounts.current]?[channel.id] { - return Text(String(count)) - } - - return nil - } - @ViewBuilder func toggleWatchedButton(_ channel: Channel) -> some View { if feed.canMarkChannelAsWatched(channel.id) { markChannelAsWatchedButton(channel) diff --git a/Shared/Navigation/AppTabNavigation.swift b/Shared/Navigation/AppTabNavigation.swift index 368016e9..d375e325 100644 --- a/Shared/Navigation/AppTabNavigation.swift +++ b/Shared/Navigation/AppTabNavigation.swift @@ -7,6 +7,7 @@ struct AppTabNavigation: View { private var player = PlayerModel.shared @ObservedObject private var feed = FeedModel.shared @ObservedObject private var subscriptions = SubscribedChannelsModel.shared + @ObservedObject private var feedCount = UnwatchedFeedCountModel.shared @Default(.showHome) private var showHome @Default(.showDocuments) private var showDocuments @@ -94,18 +95,7 @@ struct AppTabNavigation: View { } .tag(TabSelection.subscriptions) .backport - .badge(subscriptionsBadge) - } - - var subscriptionsBadge: Text? { - guard let account = accounts.current, - let unwatched = feed.unwatched[account], - unwatched > 0 - else { - return nil - } - - return Text("\(String(unwatched))") + .badge(feedCount.unwatchedText) } private var subscriptionsVisible: Bool { diff --git a/Shared/Navigation/Sidebar.swift b/Shared/Navigation/Sidebar.swift index 1d1ecd30..c91f5a9d 100644 --- a/Shared/Navigation/Sidebar.swift +++ b/Shared/Navigation/Sidebar.swift @@ -5,6 +5,7 @@ struct Sidebar: View { @ObservedObject private var accounts = AccountsModel.shared @ObservedObject private var navigation = NavigationModel.shared @ObservedObject private var feed = FeedModel.shared + @ObservedObject private var feedCount = UnwatchedFeedCountModel.shared @Default(.showHome) private var showHome @Default(.visibleSections) private var visibleSections @@ -78,7 +79,7 @@ struct Sidebar: View { .accessibility(label: Text("Subscriptions")) } .backport - .badge(subscriptionsBadge) + .badge(feedCount.unwatchedText) .contextMenu { playUnwatchedButton toggleWatchedButton @@ -146,17 +147,6 @@ struct Sidebar: View { } } - private var subscriptionsBadge: Text? { - guard let account = accounts.current, - let unwatched = feed.unwatched[account], - unwatched > 0 - else { - return nil - } - - return Text("\(String(unwatched))") - } - private func scrollScrollViewToItem(scrollView: ScrollViewProxy, for selection: TabSelection) { if case .recentlyOpened = selection { scrollView.scrollTo("recentlyOpened") diff --git a/Shared/Subscriptions/ChannelsView.swift b/Shared/Subscriptions/ChannelsView.swift index 3cfb1f94..49b4ce3d 100644 --- a/Shared/Subscriptions/ChannelsView.swift +++ b/Shared/Subscriptions/ChannelsView.swift @@ -6,6 +6,7 @@ struct ChannelsView: View { @ObservedObject private var feed = FeedModel.shared @ObservedObject private var subscriptions = SubscribedChannelsModel.shared @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var feedCount = UnwatchedFeedCountModel.shared @Default(.showCacheStatus) private var showCacheStatus @@ -25,7 +26,7 @@ struct ChannelsView: View { } } .backport - .badge(channelBadge(channel)) + .badge(feedCount.unwatchedByChannelText(channel)) } .contextMenu { if subscriptions.isSubscribing(channel.id) { @@ -84,14 +85,6 @@ 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) diff --git a/Yattee.xcodeproj/project.pbxproj b/Yattee.xcodeproj/project.pbxproj index 328d45da..7e0d374a 100644 --- a/Yattee.xcodeproj/project.pbxproj +++ b/Yattee.xcodeproj/project.pbxproj @@ -185,6 +185,9 @@ 37192D5728B179D60012EEDD /* ChaptersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37192D5628B179D60012EEDD /* ChaptersView.swift */; }; 37192D5828B179D60012EEDD /* ChaptersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37192D5628B179D60012EEDD /* ChaptersView.swift */; }; 37192D5928B179D60012EEDD /* ChaptersView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 37192D5628B179D60012EEDD /* ChaptersView.swift */; }; + 371AC09F294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371AC09E294D13AA0085989E /* UnwatchedFeedCountModel.swift */; }; + 371AC0A0294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371AC09E294D13AA0085989E /* UnwatchedFeedCountModel.swift */; }; + 371AC0A1294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371AC09E294D13AA0085989E /* UnwatchedFeedCountModel.swift */; }; 371B7E5C27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; }; 371B7E5D27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; }; 371B7E5E27596B8400D21217 /* Comment.swift in Sources */ = {isa = PBXBuildFile; fileRef = 371B7E5B27596B8400D21217 /* Comment.swift */; }; @@ -1155,6 +1158,7 @@ 37169AA52729E2CC0011DE61 /* AccountsBridge.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsBridge.swift; sourceTree = ""; }; 3717407C2949D40800FDDBC7 /* ChannelLinkView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChannelLinkView.swift; sourceTree = ""; }; 37192D5628B179D60012EEDD /* ChaptersView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ChaptersView.swift; sourceTree = ""; }; + 371AC09E294D13AA0085989E /* UnwatchedFeedCountModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UnwatchedFeedCountModel.swift; sourceTree = ""; }; 371B7E5B27596B8400D21217 /* Comment.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Comment.swift; sourceTree = ""; }; 371B7E602759706A00D21217 /* CommentsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CommentsView.swift; sourceTree = ""; }; 371B7E652759786B00D21217 /* Comment+Fixtures.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Comment+Fixtures.swift"; sourceTree = ""; }; @@ -2328,10 +2332,6 @@ 3743B86627216A1E00261544 /* Accounts */, 3743B864272169E200261544 /* Applications */, 377F9F79294403DC0043F856 /* Cache */, - 3743B86527216A0600261544 /* Player */, - 3751BA8127E69131007B1A60 /* ReturnYouTubeDislike */, - 37FB283F2721B20800A57617 /* Search */, - 374C0539272436DA009BDDBE /* SponsorBlock */, 3776ADD5287381240078EBC4 /* Captions.swift */, 37AAF28F26740715007FC770 /* Channel.swift */, 37C3A24427235DA70087A57A /* ChannelPlaylist.swift */, @@ -2354,21 +2354,26 @@ 3756C2A92861151C00E4B059 /* NetworkStateModel.swift */, 377FF88A291A60310028EB0B /* OpenVideosModel.swift */, 37130A5E277657300033018A /* PersistenceController.swift */, + 3743B86527216A0600261544 /* Player */, 376578882685471400D4EA09 /* Playlist.swift */, 37BA794226DBA973002A0235 /* PlaylistsModel.swift */, 375EC95C289EEEE000751258 /* QualityProfile.swift */, 375EC969289F232600751258 /* QualityProfilesModel.swift */, 37C194C626F6A9C8005D3B96 /* RecentsModel.swift */, + 3751BA8127E69131007B1A60 /* ReturnYouTubeDislike */, + 37FB283F2721B20800A57617 /* Search */, 374AB3D628BCAF0000DF56FB /* SeekModel.swift */, 374AB3DA28BCAF7E00DF56FB /* SeekType.swift */, 37EAD86E267B9ED100D9E01B /* Segment.swift */, 37F0F4E9286F397E00C06C2E /* SettingsModel.swift */, 37CEE4BC2677B670005A1EFE /* SingleAssetStream.swift */, + 374C0539272436DA009BDDBE /* SponsorBlock */, 3797758A2689345500DD52A8 /* Store.swift */, 37CEE4C02677B697005A1EFE /* Stream.swift */, 373CFADA269663F1003CB2C6 /* Thumbnail.swift */, 37C0698127260B2100F7F6CB /* ThumbnailsModel.swift */, 3705B181267B4E4900704544 /* TrendingCategory.swift */, + 371AC09E294D13AA0085989E /* UnwatchedFeedCountModel.swift */, 37F5E8B5291BE9D0006C15F5 /* URLBookmarkModel.swift */, 37D4B19626717E1500C925CA /* Video.swift */, 3784CDDE27772EE40055BBF2 /* Watch.swift */, @@ -3207,6 +3212,7 @@ 3751BA8327E6914F007B1A60 /* ReturnYouTubeDislikeAPI.swift in Sources */, 373031F528383A89000CFD59 /* PiPDelegate.swift in Sources */, 37F5E8BA291BEF69006C15F5 /* BaseCacheModel.swift in Sources */, + 371AC09F294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */, 37DD9DC62785D63A00539416 /* UIResponder+Extensions.swift in Sources */, 370015A928BBAE7F000149FD /* ProgressBar.swift in Sources */, 37C3A24927235FAA0087A57A /* ChannelPlaylistCell.swift in Sources */, @@ -3327,6 +3333,7 @@ 374C053C2724614F009BDDBE /* PlayerTVMenu.swift in Sources */, 377FC7DD267A081A00A6BBAF /* PopularView.swift in Sources */, 374924DB2921050B0017D862 /* LocationsSettings.swift in Sources */, + 371AC0A0294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */, 37192D5828B179D60012EEDD /* ChaptersView.swift in Sources */, 3784CDE327772EE40055BBF2 /* Watch.swift in Sources */, 37E80F3D287B107F00561799 /* VideoDetailsOverlay.swift in Sources */, @@ -3786,6 +3793,7 @@ 3782B9542755667600990149 /* String+Format.swift in Sources */, 37D836BE294927E700005E5E /* ChannelsCacheModel.swift in Sources */, 37152EEC26EFEB95004FB96D /* LazyView.swift in Sources */, + 371AC0A1294D13AA0085989E /* UnwatchedFeedCountModel.swift in Sources */, 37EF9A78275BEB8E0043B585 /* CommentView.swift in Sources */, 37484C2726FC83E000287258 /* InstanceForm.swift in Sources */, 37E6D7A22944CD3800550C3D /* CacheStatusHeader.swift in Sources */,