From 7c1549ed356e44df260d64e212523bfa35b3b236 Mon Sep 17 00:00:00 2001 From: Arkadiusz Fal Date: Fri, 8 May 2026 20:58:13 +0200 Subject: [PATCH] Show watch progress bar on thumbnails in playlist, channel, and search views These views rendered video thumbnails without passing watchProgress, so the progress bar was silently missing. Apply the existing pattern from SubscriptionsView: maintain a watchEntriesMap and forward watchProgress(for:) to VideoRowView/VideoCardView at each call site. --- Yattee/Views/Channel/ChannelView.swift | 22 ++++++++++++------- .../Views/Playlist/PlaylistVideoRowView.swift | 5 ++++- .../Playlist/UnifiedPlaylistDetailView.swift | 17 ++++++++++++++ Yattee/Views/Search/SearchView.swift | 20 ++++++++++++++++- 4 files changed, 54 insertions(+), 10 deletions(-) diff --git a/Yattee/Views/Channel/ChannelView.swift b/Yattee/Views/Channel/ChannelView.swift index d78b9847..105605a3 100644 --- a/Yattee/Views/Channel/ChannelView.swift +++ b/Yattee/Views/Channel/ChannelView.swift @@ -176,6 +176,12 @@ struct ChannelView: View { watchEntriesMap = appEnvironment?.dataManager.watchEntriesMap() ?? [:] } + private func watchProgress(for video: Video) -> Double? { + guard let entry = watchEntriesMap[video.id.videoID] else { return nil } + let progress = entry.progress + return progress > 0 && progress < 1 ? progress : nil + } + // MARK: - Computed Properties for Scroll Animation /// Progress from 0 (fully expanded) to 1 (fully collapsed) @@ -1255,7 +1261,7 @@ struct ChannelView: View { rowStyle: rowStyle, listStyle: listStyle ) { - VideoRowView(video: video, style: rowStyle) + VideoRowView(video: video, style: rowStyle, watchProgress: watchProgress(for: video)) .tappableVideo( video, queueSource: videosQueueSource, @@ -1315,7 +1321,7 @@ struct ChannelView: View { private var videosGridContent: some View { VideoGridContent(columns: gridConfig.effectiveColumns) { ForEach(Array(filteredVideos.enumerated()), id: \.element.id) { index, video in - VideoCardView(video: video, isCompact: gridConfig.isCompactCards) + VideoCardView(video: video, watchProgress: watchProgress(for: video), isCompact: gridConfig.isCompactCards) .tappableVideo( video, queueSource: videosQueueSource, @@ -1500,7 +1506,7 @@ struct ChannelView: View { rowStyle: rowStyle, listStyle: listStyle ) { - VideoRowView(video: video, style: rowStyle) + VideoRowView(video: video, style: rowStyle, watchProgress: watchProgress(for: video)) .tappableVideo( video, queueSource: shortsQueueSource, @@ -1540,7 +1546,7 @@ struct ChannelView: View { VideoGridContent(columns: gridConfig.effectiveColumns) { ForEach(Array(filteredShorts.enumerated()), id: \.element.id) { index, video in - VideoCardView(video: video, isCompact: gridConfig.isCompactCards) + VideoCardView(video: video, watchProgress: watchProgress(for: video), isCompact: gridConfig.isCompactCards) .tappableVideo( video, queueSource: shortsQueueSource, @@ -1620,7 +1626,7 @@ struct ChannelView: View { rowStyle: rowStyle, listStyle: listStyle ) { - VideoRowView(video: video, style: rowStyle) + VideoRowView(video: video, style: rowStyle, watchProgress: watchProgress(for: video)) .tappableVideo( video, queueSource: streamsQueueSource, @@ -1660,7 +1666,7 @@ struct ChannelView: View { VideoGridContent(columns: gridConfig.effectiveColumns) { ForEach(Array(filteredStreams.enumerated()), id: \.element.id) { index, video in - VideoCardView(video: video, isCompact: gridConfig.isCompactCards) + VideoCardView(video: video, watchProgress: watchProgress(for: video), isCompact: gridConfig.isCompactCards) .tappableVideo( video, queueSource: streamsQueueSource, @@ -1736,7 +1742,7 @@ struct ChannelView: View { ) { switch item { case .video(let video): - VideoRowView(video: video, style: rowStyle) + VideoRowView(video: video, style: rowStyle, watchProgress: watchProgress(for: video)) .tappableVideo( video, queueSource: searchQueueSource, @@ -1770,7 +1776,7 @@ struct ChannelView: View { ForEach(searchResults.items) { item in switch item { case .video(let video): - VideoCardView(video: video, isCompact: gridConfig.isCompactCards) + VideoCardView(video: video, watchProgress: watchProgress(for: video), isCompact: gridConfig.isCompactCards) .tappableVideo( video, queueSource: searchQueueSource, diff --git a/Yattee/Views/Playlist/PlaylistVideoRowView.swift b/Yattee/Views/Playlist/PlaylistVideoRowView.swift index 44601702..21f5f309 100644 --- a/Yattee/Views/Playlist/PlaylistVideoRowView.swift +++ b/Yattee/Views/Playlist/PlaylistVideoRowView.swift @@ -13,12 +13,14 @@ import SwiftUI struct PlaylistVideoRowView: View { let index: Int let video: Video + var watchProgress: Double? = nil var onRemove: (() -> Void)? = nil var body: some View { VideoRowView( video: video, style: .regular, + watchProgress: watchProgress, index: index ) .videoContextMenu( @@ -40,9 +42,10 @@ struct PlaylistVideoRowView: View { extension PlaylistVideoRowView { /// Initialize from a LocalPlaylistItem model. - init(item: LocalPlaylistItem, index: Int, onRemove: @escaping () -> Void) { + init(item: LocalPlaylistItem, index: Int, watchProgress: Double? = nil, onRemove: @escaping () -> Void) { self.index = index self.video = item.toVideo() + self.watchProgress = watchProgress self.onRemove = onRemove } } diff --git a/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift b/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift index edc55517..37ef19d1 100644 --- a/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift +++ b/Yattee/Views/Playlist/UnifiedPlaylistDetailView.swift @@ -92,6 +92,8 @@ struct UnifiedPlaylistDetailView: View { @State private var hasLoadedDownloadState = false #endif + @State private var watchEntriesMap: [String: WatchEntry] = [:] + private var dataManager: DataManager? { appEnvironment?.dataManager } private var isQueueEnabled: Bool { appEnvironment?.settingsManager.queueEnabled ?? true } @@ -187,6 +189,9 @@ struct UnifiedPlaylistDetailView: View { .task { await loadPlaylist() } + .onReceive(NotificationCenter.default.publisher(for: .watchHistoryDidChange)) { _ in + loadWatchEntries() + } #if !os(tvOS) .batchDownload(coordinator: downloadCoordinator) .onAppear { @@ -663,6 +668,7 @@ struct UnifiedPlaylistDetailView: View { PlaylistVideoRowView( index: index + 1, video: video, + watchProgress: watchProgress(for: video), onRemove: isLocal ? { removeVideo(at: index) } : nil ) } @@ -697,6 +703,17 @@ struct UnifiedPlaylistDetailView: View { case .remote(let playlistID, let instance, _): await loadRemotePlaylist(playlistID: playlistID, instance: instance) } + loadWatchEntries() + } + + private func loadWatchEntries() { + watchEntriesMap = dataManager?.watchEntriesMap() ?? [:] + } + + private func watchProgress(for video: Video) -> Double? { + guard let entry = watchEntriesMap[video.id.videoID] else { return nil } + let progress = entry.progress + return progress > 0 && progress < 1 ? progress : nil } private func loadLocalPlaylist() { diff --git a/Yattee/Views/Search/SearchView.swift b/Yattee/Views/Search/SearchView.swift index 7ed6ce4d..5759b3da 100644 --- a/Yattee/Views/Search/SearchView.swift +++ b/Yattee/Views/Search/SearchView.swift @@ -64,6 +64,7 @@ struct SearchView: View { // Grid layout configuration @State private var viewWidth: CGFloat = 0 + @State private var watchEntriesMap: [String: WatchEntry] = [:] private var gridConfig: GridLayoutConfiguration { GridLayoutConfiguration(viewWidth: viewWidth, gridColumns: gridColumns) } @@ -152,6 +153,12 @@ struct SearchView: View { .onReceive(NotificationCenter.default.publisher(for: .searchHistoryDidChange)) { _ in loadSearchHistory() } + .onReceive(NotificationCenter.default.publisher(for: .watchHistoryDidChange)) { _ in + loadWatchEntries() + } + .task { + loadWatchEntries() + } .onReceive(NotificationCenter.default.publisher(for: .recentChannelsDidChange)) { _ in loadRecentChannels() } @@ -1110,7 +1117,7 @@ struct SearchView: View { ) { switch item { case .video(let video, let videoIndex): - VideoRowView(video: video, style: rowStyle) + VideoRowView(video: video, style: rowStyle, watchProgress: watchProgress(for: video)) .tappableVideo( video, queueSource: searchQueueSource, @@ -1181,6 +1188,7 @@ struct SearchView: View { case .video(let video, let videoIndex): VideoCardView( video: video, + watchProgress: watchProgress(for: video), isCompact: gridConfig.isCompactCards ) .frame(maxHeight: .infinity, alignment: .top) @@ -1252,6 +1260,16 @@ struct SearchView: View { // MARK: - Search History Helpers + private func loadWatchEntries() { + watchEntriesMap = appEnvironment?.dataManager.watchEntriesMap() ?? [:] + } + + private func watchProgress(for video: Video) -> Double? { + guard let entry = watchEntriesMap[video.id.videoID] else { return nil } + let progress = entry.progress + return progress > 0 && progress < 1 ? progress : nil + } + private func loadSearchHistory() { guard appEnvironment?.settingsManager.saveRecentSearches != false else { searchHistory = []