Show badge for channels subscriptions

This commit is contained in:
Arkadiusz Fal 2022-12-13 13:14:20 +01:00
parent d5626b877c
commit b3ddf4a153
5 changed files with 119 additions and 40 deletions

View File

@ -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()
}
}

View File

@ -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,23 +113,27 @@ 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
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) }
@ -136,11 +141,23 @@ final class FeedModel: ObservableObject, CacheModel {
}
}
func markAllFeedAsUnwatched() {
guard accounts.current != nil,
!videos.isEmpty else { return }
if videos.isEmpty {
loadCachedFeed { mark() }
} else {
mark()
}
}
backgroundContext.perform { [weak self] in
var canMarkAllFeedAsWatched: Bool {
guard let account = accounts.current else { return false }
return (unwatched[account] ?? 0) > 0
}
func markAllFeedAsUnwatched() {
guard accounts.current != nil 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)
@ -152,6 +169,13 @@ final class FeedModel: ObservableObject, CacheModel {
}
}
if videos.isEmpty {
loadCachedFeed { mark() }
} else {
mark()
}
}
func watchFetchRequestResult(_ videos: [Video], context: NSManagedObjectContext) -> [Watch] {
let watchFetchRequest = Watch.fetchRequest()
watchFetchRequest.predicate = NSPredicate(format: "videoID IN %@", videos.map(\.videoID) as [String])
@ -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()
}
}
}

View File

@ -3,25 +3,30 @@ 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 {
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))
}
}
.backport
.badge(channelBadge(channel))
}
.contextMenu {
Button("Unsubscribe") {
navigation.presentUnsubscribeAlert(channel, subscriptions: subscriptions)
@ -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 {

View File

@ -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)

View File

@ -39,6 +39,10 @@ struct SubscriptionsView: View {
ToolbarItem {
ListingStyleButtons(listingStyle: $subscriptionsListingStyle)
}
ToolbarItem {
toggleWatchedButton
}
}
#endif
}
@ -53,25 +57,11 @@ 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")
}
playUnwatchedButton
Button {
feed.markAllFeedAsUnwatched()
} label: {
Label("Mark all as unwatched", systemImage: "checkmark.circle")
}
}
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 {