From 94f19d55c858941eddf2772dd70dba1fdbaaeff0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Toni=20F=C3=B6rster?= Date: Fri, 24 May 2024 13:49:07 +0200 Subject: [PATCH] more responsive UI when Favorites are used. - The reloading of items in the favorite widgets is now done async, so it does not block the UI when opening Home. - Tasks that check for changes of the widget and favorites are now terminated when another view e.g. Subscriptions is opened. - We only update the Favourites when the player is not in the foreground, to avoid unresponsiveness. --- Shared/Home/FavoriteItemView.swift | 100 ++++++++++++++++++----------- Shared/Home/HomeView.swift | 22 ++++++- 2 files changed, 83 insertions(+), 39 deletions(-) diff --git a/Shared/Home/FavoriteItemView.swift b/Shared/Home/FavoriteItemView.swift index 324f1172..ba8b864c 100644 --- a/Shared/Home/FavoriteItemView.swift +++ b/Shared/Home/FavoriteItemView.swift @@ -88,26 +88,38 @@ struct FavoriteItemView: View { reloadVisibleWatches() } else { resource?.addObserver(store) - loadCacheAndResource() + DispatchQueue.main.async { + self.loadCacheAndResource() + } } } .onDisappear { resource?.removeObservers(ownedBy: store) } - .onChange(of: player.currentVideo) { _ in reloadVisibleWatches() } - .onChange(of: hideShorts) { _ in reloadVisibleWatches() } - .onChange(of: hideWatched) { _ in reloadVisibleWatches() } - .onChange(of: favoritesChanged) { _ in reloadVisibleWatches() } + .onChange(of: player.currentVideo) { _ in if !player.presentingPlayer { reloadVisibleWatches() } } + .onChange(of: hideShorts) { _ in if !player.presentingPlayer { reloadVisibleWatches() } } + .onChange(of: hideWatched) { _ in if !player.presentingPlayer { reloadVisibleWatches() } } + // Delay is necessary to update the list with the new items. + .onChange(of: favoritesChanged) { _ in if !player.presentingPlayer { Delay.by(1.0) { reloadVisibleWatches() } } } + .onChange(of: player.presentingPlayer) { _ in + if player.presentingPlayer { + resource?.removeObservers(ownedBy: store) + } else { + resource?.addObserver(store) + } + } } } .id(watchModel.historyToken) .onChange(of: accounts.current) { _ in - resource?.removeObservers(ownedBy: store) - resource?.addObserver(store) - loadCacheAndResource(force: true) + DispatchQueue.main.async { + loadCacheAndResource(force: true) + } } .onChange(of: watchModel.historyToken) { _ in - reloadVisibleWatches() + if !player.presentingPlayer { + reloadVisibleWatches() + } } } @@ -159,24 +171,26 @@ struct FavoriteItemView: View { } func reloadVisibleWatches() { - guard item.section == .history else { return } + DispatchQueue.main.async { + guard item.section == .history else { return } - visibleWatches = [] + visibleWatches = [] - let watches = Array( - watches - .filter { $0.videoID != player.currentVideo?.videoID && itemVisible(.init(video: $0.video)) } - .prefix(favoritesModel.limit(item)) - ) - let last = watches.last + let watches = Array( + watches + .filter { $0.videoID != player.currentVideo?.videoID && itemVisible(.init(video: $0.video)) } + .prefix(favoritesModel.limit(item)) + ) + let last = watches.last - for watch in watches { - player.loadHistoryVideoDetails(watch) { - guard let video = player.historyVideo(watch.videoID), itemVisible(.init(video: video)) else { return } - visibleWatches.append(watch) + for watch in watches { + player.loadHistoryVideoDetails(watch) { + guard let video = player.historyVideo(watch.videoID), itemVisible(.init(video: video)) else { return } + visibleWatches.append(watch) - if watch == last { - visibleWatches.sort { $0.watchedAt ?? Date() > $1.watchedAt ?? Date() } + if watch == last { + visibleWatches.sort { $0.watchedAt ?? Date() > $1.watchedAt ?? Date() } + } } } } @@ -227,6 +241,9 @@ struct FavoriteItemView: View { onSuccess = { response in if let videos: [Video] = response.typedContent() { FeedCacheModel.shared.storeFeed(account: accounts.current, videos: videos) + DispatchQueue.main.async { + store.contentItems = contentItems + } } } case let .channel(_, id, name): @@ -239,19 +256,21 @@ struct FavoriteItemView: View { } onSuccess = { response in - if let channel: Channel = response.typedContent() { - ChannelsCacheModel.shared.store(channel) - store.contentItems = ContentItem.array(of: channel.videos) - } else if let videos: [Video] = response.typedContent() { - channel.videos = videos - ChannelsCacheModel.shared.store(channel) - store.contentItems = ContentItem.array(of: videos) - } else if let channelPage: ChannelPage = response.typedContent() { - if let channel = channelPage.channel { + DispatchQueue.main.async { + if let channel: Channel = response.typedContent() { ChannelsCacheModel.shared.store(channel) - } + store.contentItems = ContentItem.array(of: channel.videos) + } else if let videos: [Video] = response.typedContent() { + channel.videos = videos + ChannelsCacheModel.shared.store(channel) + store.contentItems = ContentItem.array(of: videos) + } else if let channelPage: ChannelPage = response.typedContent() { + if let channel = channelPage.channel { + ChannelsCacheModel.shared.store(channel) + } - store.contentItems = channelPage.results + store.contentItems = channelPage.results + } } } case let .channelPlaylist(_, id, title): @@ -264,6 +283,9 @@ struct FavoriteItemView: View { onSuccess = { response in if let playlist: ChannelPlaylist = response.typedContent() { ChannelPlaylistsCacheModel.shared.storePlaylist(playlist: playlist) + DispatchQueue.main.async { + store.contentItems = contentItems + } } } case let .playlist(_, id): @@ -272,12 +294,16 @@ struct FavoriteItemView: View { if let playlist = playlists.first(where: { $0.id == id }) { contentItems = ContentItem.array(of: playlist.videos) } + + DispatchQueue.main.async { + store.contentItems = contentItems + } default: contentItems = [] - } - if !contentItems.isEmpty { - store.contentItems = contentItems + DispatchQueue.main.async { + store.contentItems = contentItems + } } if force { diff --git a/Shared/Home/HomeView.swift b/Shared/Home/HomeView.swift index 3a655420..bc46556e 100644 --- a/Shared/Home/HomeView.swift +++ b/Shared/Home/HomeView.swift @@ -5,9 +5,11 @@ import UniformTypeIdentifiers struct HomeView: View { @ObservedObject private var accounts = AccountsModel.shared + @ObservedObject private var player = PlayerModel.shared @State private var presentingHomeSettings = false @State private var favoritesChanged = false + @State private var updateTask: Task? @FetchRequest(sortDescriptors: [.init(key: "watchedAt", ascending: false)]) var watches: FetchedResults @@ -16,8 +18,6 @@ struct HomeView: View { @State private var recentDocumentsID = UUID() #endif - var favoritesObserver: Any? - #if !os(tvOS) @Default(.favorites) private var favorites @Default(.widgetsSettings) private var widgetsSettings @@ -124,6 +124,24 @@ struct HomeView: View { } } } + .onDisappear { + updateTask?.cancel() + } + + .onChange(of: player.presentingPlayer) { _ in + if player.presentingPlayer { + updateTask?.cancel() + } else { + Task { + for await _ in Defaults.updates(.favorites) { + favoritesChanged.toggle() + } + for await _ in Defaults.updates(.widgetsSettings) { + favoritesChanged.toggle() + } + } + } + } .redrawOn(change: favoritesChanged)