mirror of
https://github.com/yattee/yattee.git
synced 2024-12-22 21:43:41 +00:00
Show badge for channels subscriptions
This commit is contained in:
parent
d5626b877c
commit
b3ddf4a153
@ -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,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()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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 {
|
||||
|
@ -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,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 {
|
||||
|
Loading…
Reference in New Issue
Block a user