diff --git a/Model/Cache/SubscribedChannelsModel.swift b/Model/Cache/SubscribedChannelsModel.swift index 193d5dde..5032dfba 100644 --- a/Model/Cache/SubscribedChannelsModel.swift +++ b/Model/Cache/SubscribedChannelsModel.swift @@ -23,6 +23,7 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel { @Published var error: RequestError? var accounts: AccountsModel { .shared } + var unwatchedFeedCount: UnwatchedFeedCountModel { .shared } var resource: Resource? { accounts.api.subscriptions @@ -32,6 +33,19 @@ final class SubscribedChannelsModel: ObservableObject, CacheModel { channels.sorted { $0.name.lowercased() < $1.name.lowercased() } } + var allByUnwatchedCount: [Channel] { + if let account = accounts.current { + return all.sorted { c1, c2 in + let c1HasUnwatched = (unwatchedFeedCount.unwatchedByChannel[account]?[c1.id] ?? -1) > 0 + let c2HasUnwatched = (unwatchedFeedCount.unwatchedByChannel[account]?[c2.id] ?? -1) > 0 + let nameIncreasing = c1.name.lowercased() < c2.name.lowercased() + + return c1HasUnwatched ? (c2HasUnwatched ? nameIncreasing : true) : (c2HasUnwatched ? false : nameIncreasing) + } + } + return all + } + func subscribe(_ channelID: String, onSuccess: @escaping () -> Void = {}) { accounts.api.subscribe(channelID) { self.scheduleLoad(onSuccess: onSuccess) diff --git a/Shared/Defaults.swift b/Shared/Defaults.swift index 75ff382b..248af22c 100644 --- a/Shared/Defaults.swift +++ b/Shared/Defaults.swift @@ -51,6 +51,7 @@ extension Defaults.Keys { static let lockPortraitWhenBrowsing = Key("lockPortraitWhenBrowsing", default: UIDevice.current.userInterfaceIdiom == .phone) #endif static let showUnwatchedFeedBadges = Key("showUnwatchedFeedBadges", default: false) + static let keepChannelsWithUnwatchedFeedOnTop = Key("keepChannelsWithUnwatchedFeedOnTop", default: true) static let showToggleWatchedStatusButton = Key("showToggleWatchedStatusButton", default: false) static let expandChannelDescription = Key("expandChannelDescription", default: false) static let channelOnThumbnail = Key("channelOnThumbnail", default: false) diff --git a/Shared/Settings/BrowsingSettings.swift b/Shared/Settings/BrowsingSettings.swift index 4cd7d00d..bd1a5b4a 100644 --- a/Shared/Settings/BrowsingSettings.swift +++ b/Shared/Settings/BrowsingSettings.swift @@ -8,6 +8,7 @@ struct BrowsingSettings: View { #endif @Default(.accountPickerDisplaysAnonymousAccounts) private var accountPickerDisplaysAnonymousAccounts @Default(.showUnwatchedFeedBadges) private var showUnwatchedFeedBadges + @Default(.keepChannelsWithUnwatchedFeedOnTop) private var keepChannelsWithUnwatchedFeedOnTop #if os(iOS) @Default(.lockPortraitWhenBrowsing) private var lockPortraitWhenBrowsing @Default(.showDocuments) private var showDocuments @@ -180,9 +181,11 @@ struct BrowsingSettings: View { FeedModel.shared.calculateUnwatchedFeed() } } + + Toggle("Open channels with description expanded", isOn: $expandChannelDescription) } - Toggle("Open channels with description expanded", isOn: $expandChannelDescription) + Toggle("Keep channels with unwatched videos on top of subscriptions list", isOn: $keepChannelsWithUnwatchedFeedOnTop) } } diff --git a/Shared/Subscriptions/ChannelsView.swift b/Shared/Subscriptions/ChannelsView.swift index 563ad4e5..f3070506 100644 --- a/Shared/Subscriptions/ChannelsView.swift +++ b/Shared/Subscriptions/ChannelsView.swift @@ -11,20 +11,28 @@ struct ChannelsView: View { @Default(.showCacheStatus) private var showCacheStatus @Default(.showUnwatchedFeedBadges) private var showUnwatchedFeedBadges + @Default(.keepChannelsWithUnwatchedFeedOnTop) private var keepChannelsWithUnwatchedFeedOnTop + + @State private var channelLinkActive = false + @State private var channelForLink: Channel? var body: some View { List { Section(header: header) { - ForEach(subscriptions.all) { channel in + ForEach(channels) { channel in let label = HStack { if let url = channel.thumbnailURLOrCached { ThumbnailView(url: url) .frame(width: 35, height: 35) .clipShape(RoundedRectangle(cornerRadius: 35)) - Text(channel.name) } else { - Label(channel.name, systemImage: RecentsModel.symbolSystemImage(channel.name)) + Image(systemName: RecentsModel.symbolSystemImage(channel.name)) + .imageScale(.large) + .foregroundColor(.accentColor) + .frame(width: 35, height: 35) } + Text(channel.name) + .lineLimit(1) } .backport .badge(showUnwatchedFeedBadges ? feedCount.unwatchedByChannelText(channel) : nil) @@ -37,9 +45,15 @@ struct ChannelsView: View { label } #else - NavigationLink(destination: ChannelVideosView(channel: channel)) { + Button { + channelForLink = channel + channelLinkActive = channelForLink != nil + } label: { label + .contentShape(Rectangle()) + .foregroundColor(.primary) } + .buttonStyle(.plain) #endif } .contextMenu { @@ -63,6 +77,9 @@ struct ChannelsView: View { .listRowSeparator(false) } } + .background( + NavigationLink(destination: ChannelVideosView(channel: channelForLink), isActive: $channelLinkActive, label: EmptyView.init) + ) .onAppear { subscriptions.load() } @@ -99,6 +116,10 @@ struct ChannelsView: View { #endif } + var channels: [Channel] { + keepChannelsWithUnwatchedFeedOnTop ? subscriptions.allByUnwatchedCount : subscriptions.all + } + var header: some View { HStack { #if os(tvOS)