From c0d72e89d752fc3c1152848f21dd740d59df04cb Mon Sep 17 00:00:00 2001 From: zarzet Date: Mon, 16 Mar 2026 03:48:40 +0700 Subject: [PATCH] fix: skip already-downloaded tracks in Download All for albums and playlists Album and playlist Download All buttons now check download history and local library before enqueuing, matching the existing behavior in artist discography and CSV import. Tracks already in library are skipped with a summary snackbar. --- lib/screens/album_screen.dart | 67 +++++++++++++++++++++++++------- lib/screens/playlist_screen.dart | 67 +++++++++++++++++++++++++------- 2 files changed, 104 insertions(+), 30 deletions(-) diff --git a/lib/screens/album_screen.dart b/lib/screens/album_screen.dart index 139a5720..9b8da6a4 100644 --- a/lib/screens/album_screen.dart +++ b/lib/screens/album_screen.dart @@ -574,37 +574,74 @@ class _AlbumScreenState extends ConsumerState { void _downloadAll(BuildContext context) { final tracks = _tracks; if (tracks == null || tracks.isEmpty) return; + + // Filter out tracks already in download history or local library + final historyState = ref.read(downloadHistoryProvider); final settings = ref.read(settingsProvider); + final localLibState = (settings.localLibraryEnabled && settings.localLibraryShowDuplicates) + ? ref.read(localLibraryProvider) + : null; + final tracksToQueue = []; + int skippedCount = 0; + + for (final track in tracks) { + final isInHistory = historyState.isDownloaded(track.id) || + (track.isrc != null && historyState.getByIsrc(track.isrc!) != null) || + historyState.findByTrackAndArtist(track.name, track.artistName) != null; + final isInLocal = localLibState?.existsInLibrary( + isrc: track.isrc, + trackName: track.name, + artistName: track.artistName, + ) ?? + false; + + if (isInHistory || isInLocal) { + skippedCount++; + } else { + tracksToQueue.add(track); + } + } + + if (tracksToQueue.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + context.l10n.discographySkippedDownloaded(0, skippedCount), + ), + ), + ); + return; + } + if (settings.askQualityBeforeDownload) { DownloadServicePicker.show( context, - trackName: '${tracks.length} tracks', + trackName: '${tracksToQueue.length} tracks', artistName: widget.albumName, onSelect: (quality, service) { ref .read(downloadQueueProvider.notifier) - .addMultipleToQueue(tracks, service, qualityOverride: quality); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.snackbarAddedTracksToQueue(tracks.length), - ), - ), - ); + .addMultipleToQueue(tracksToQueue, service, qualityOverride: quality); + _showQueuedSnackbar(context, tracksToQueue.length, skippedCount); }, ); } else { ref .read(downloadQueueProvider.notifier) - .addMultipleToQueue(tracks, settings.defaultService); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.snackbarAddedTracksToQueue(tracks.length)), - ), - ); + .addMultipleToQueue(tracksToQueue, settings.defaultService); + _showQueuedSnackbar(context, tracksToQueue.length, skippedCount); } } + void _showQueuedSnackbar(BuildContext context, int added, int skipped) { + final message = skipped > 0 + ? context.l10n.discographySkippedDownloaded(added, skipped) + : context.l10n.snackbarAddedTracksToQueue(added); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message)), + ); + } + Widget _buildLoveAllButton() { final collectionsState = ref.watch(libraryCollectionsProvider); final tracks = _tracks; diff --git a/lib/screens/playlist_screen.dart b/lib/screens/playlist_screen.dart index 8a035def..67d377b9 100644 --- a/lib/screens/playlist_screen.dart +++ b/lib/screens/playlist_screen.dart @@ -608,45 +608,82 @@ class _PlaylistScreenState extends ConsumerState { void _downloadTracks(BuildContext context, List tracks) { if (tracks.isEmpty) return; + + // Filter out tracks already in download history or local library + final historyState = ref.read(downloadHistoryProvider); final settings = ref.read(settingsProvider); + final localLibState = (settings.localLibraryEnabled && settings.localLibraryShowDuplicates) + ? ref.read(localLibraryProvider) + : null; + final tracksToQueue = []; + int skippedCount = 0; + + for (final track in tracks) { + final isInHistory = historyState.isDownloaded(track.id) || + (track.isrc != null && historyState.getByIsrc(track.isrc!) != null) || + historyState.findByTrackAndArtist(track.name, track.artistName) != null; + final isInLocal = localLibState?.existsInLibrary( + isrc: track.isrc, + trackName: track.name, + artistName: track.artistName, + ) ?? + false; + + if (isInHistory || isInLocal) { + skippedCount++; + } else { + tracksToQueue.add(track); + } + } + + if (tracksToQueue.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar( + content: Text( + context.l10n.discographySkippedDownloaded(0, skippedCount), + ), + ), + ); + return; + } + if (settings.askQualityBeforeDownload) { DownloadServicePicker.show( context, - trackName: '${tracks.length} tracks', + trackName: '${tracksToQueue.length} tracks', artistName: _playlistName, onSelect: (quality, service) { ref .read(downloadQueueProvider.notifier) .addMultipleToQueue( - tracks, + tracksToQueue, service, qualityOverride: quality, playlistName: _playlistName, ); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text( - context.l10n.snackbarAddedTracksToQueue(tracks.length), - ), - ), - ); + _showQueuedSnackbar(context, tracksToQueue.length, skippedCount); }, ); } else { ref .read(downloadQueueProvider.notifier) .addMultipleToQueue( - tracks, + tracksToQueue, settings.defaultService, playlistName: _playlistName, ); - ScaffoldMessenger.of(context).showSnackBar( - SnackBar( - content: Text(context.l10n.snackbarAddedTracksToQueue(tracks.length)), - ), - ); + _showQueuedSnackbar(context, tracksToQueue.length, skippedCount); } } + + void _showQueuedSnackbar(BuildContext context, int added, int skipped) { + final message = skipped > 0 + ? context.l10n.discographySkippedDownloaded(added, skipped) + : context.l10n.snackbarAddedTracksToQueue(added); + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(message)), + ); + } } /// Separate Consumer widget for each track - only rebuilds when this specific track's status changes