From 5ea454a0b037d6c47c904df19e8939da0907680a Mon Sep 17 00:00:00 2001 From: zarzet Date: Sun, 18 Jan 2026 12:46:23 +0700 Subject: [PATCH] fix: downloaded album navigation from recents --- CHANGELOG.md | 5 ++ lib/providers/download_queue_provider.dart | 80 +++++++++++++--------- lib/screens/downloaded_album_screen.dart | 29 ++++++-- lib/screens/home_tab.dart | 31 ++++++--- lib/screens/track_metadata_screen.dart | 27 +++++++- 5 files changed, 121 insertions(+), 51 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 9a958fca..438dac32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -62,6 +62,11 @@ ### Fixed +- **MP3 Quality Display in Track Metadata**: Fixed incorrect quality display for MP3 files + - MP3 files now show "320kbps" instead of FLAC's bit depth/sample rate + - History no longer stores FLAC audio specs for converted MP3 files + - Both File Info badges and metadata grid show correct MP3 quality + - **Empty Catch Blocks**: Fixed analyzer warnings for empty catch blocks - `download_queue_provider.dart`: Added comments explaining why polling errors are silently ignored - `track_provider.dart`: Added comments explaining why availability check errors are silently ignored diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index b94125b6..c846dbd0 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -1671,8 +1671,12 @@ class DownloadQueueNotifier extends Notifier { if (result['success'] == true) { var filePath = result['file_path'] as String?; - if (filePath != null && filePath.startsWith('EXISTS:')) { + // Track if this was an existing file (not a new download) + // This is important to prevent converting existing FLAC files to MP3 + final wasExisting = filePath != null && filePath.startsWith('EXISTS:'); + if (wasExisting) { filePath = filePath.substring(7); // Remove "EXISTS:" prefix + _log.i('Using existing file: $filePath'); } _log.i('Download success, file: $filePath'); @@ -1806,39 +1810,48 @@ class DownloadQueueNotifier extends Notifier { } // Convert FLAC to MP3 if MP3 quality was selected + // IMPORTANT: Only convert NEW downloads, never convert existing files + // to prevent overwriting the user's existing FLAC files if (quality == 'MP3' && filePath != null && filePath.endsWith('.flac')) { - _log.i('MP3 quality selected, converting FLAC to MP3...'); - updateItemStatus( - item.id, - DownloadStatus.downloading, - progress: 0.97, - ); - - try { - final mp3Path = await FFmpegService.convertFlacToMp3( - filePath, - bitrate: '320k', - deleteOriginal: true, + if (wasExisting) { + // User wanted MP3 but an existing FLAC file was found + // Do NOT convert it - that would delete their existing FLAC + _log.i('MP3 requested but existing FLAC found - skipping conversion to preserve original file'); + // Keep the existing FLAC file as-is + } else { + _log.i('MP3 quality selected, converting FLAC to MP3...'); + updateItemStatus( + item.id, + DownloadStatus.downloading, + progress: 0.97, ); - if (mp3Path != null) { - filePath = mp3Path; - actualQuality = 'MP3 320kbps'; - _log.i('Successfully converted to MP3: $mp3Path'); - - // Embed metadata, lyrics, and cover to the MP3 file - _log.i('Embedding metadata to MP3...'); - updateItemStatus( - item.id, - DownloadStatus.downloading, - progress: 0.99, + try { + final mp3Path = await FFmpegService.convertFlacToMp3( + filePath, + bitrate: '320k', + deleteOriginal: true, ); - await _embedMetadataToMp3(mp3Path, trackToDownload); - } else { - _log.w('MP3 conversion failed, keeping FLAC file'); + + if (mp3Path != null) { + filePath = mp3Path; + actualQuality = 'MP3 320kbps'; + _log.i('Successfully converted to MP3: $mp3Path'); + + // Embed metadata, lyrics, and cover to the MP3 file + _log.i('Embedding metadata to MP3...'); + updateItemStatus( + item.id, + DownloadStatus.downloading, + progress: 0.99, + ); + await _embedMetadataToMp3(mp3Path, trackToDownload); + } else { + _log.w('MP3 conversion failed, keeping FLAC file'); + } + } catch (e) { + _log.e('MP3 conversion error: $e, keeping FLAC file'); } - } catch (e) { - _log.e('MP3 conversion error: $e, keeping FLAC file'); } } @@ -1881,6 +1894,11 @@ class DownloadQueueNotifier extends Notifier { ? normalizedAlbumArtist : null; + // For MP3 files, don't save FLAC bitDepth/sampleRate - they're not applicable + final isMp3 = filePath.endsWith('.mp3'); + final historyBitDepth = isMp3 ? null : backendBitDepth; + final historySampleRate = isMp3 ? null : backendSampleRate; + ref .read(downloadHistoryProvider.notifier) .addToHistory( @@ -1915,8 +1933,8 @@ class DownloadQueueNotifier extends Notifier { ? backendYear : trackToDownload.releaseDate, quality: actualQuality, - bitDepth: backendBitDepth, - sampleRate: backendSampleRate, + bitDepth: historyBitDepth, + sampleRate: historySampleRate, ), ); diff --git a/lib/screens/downloaded_album_screen.dart b/lib/screens/downloaded_album_screen.dart index 6031687c..7536911b 100644 --- a/lib/screens/downloaded_album_screen.dart +++ b/lib/screens/downloaded_album_screen.dart @@ -56,7 +56,13 @@ class _DownloadedAlbumScreenState extends ConsumerState { } Future _extractDominantColor() async { - if (widget.coverUrl == null) return; + if (widget.coverUrl == null || widget.coverUrl!.isEmpty) return; + + // Only use network images for palette extraction + final isNetworkUrl = widget.coverUrl!.startsWith('http://') || + widget.coverUrl!.startsWith('https://'); + if (!isNetworkUrl) return; + try { final paletteGenerator = await PaletteGenerator.fromImageProvider( CachedNetworkImageProvider(widget.coverUrl!), @@ -77,7 +83,11 @@ class _DownloadedAlbumScreenState extends ConsumerState { /// Get tracks for this album from history provider (reactive) List _getAlbumTracks(List allItems) { return allItems.where((item) { - final itemKey = '${item.albumName}|${item.albumArtist ?? item.artistName}'; + // Use albumArtist if available and not empty, otherwise artistName + final itemArtist = (item.albumArtist != null && item.albumArtist!.isNotEmpty) + ? item.albumArtist! + : item.artistName; + final itemKey = '${item.albumName}|$itemArtist'; final albumKey = '${widget.albumName}|${widget.artistName}'; return itemKey == albumKey; }).toList() @@ -229,11 +239,16 @@ class _DownloadedAlbumScreenState extends ConsumerState { final allHistoryItems = ref.watch(downloadHistoryProvider.select((s) => s.items)); final tracks = _getAlbumTracks(allHistoryItems); - if (tracks.length < 2) { - WidgetsBinding.instance.addPostFrameCallback((_) { - if (mounted) Navigator.pop(context); - }); - return const SizedBox.shrink(); + // Show empty state if no tracks found + if (tracks.isEmpty) { + return Scaffold( + appBar: AppBar( + title: Text(widget.albumName), + ), + body: Center( + child: Text('No tracks found for this album'), + ), + ); } final validIds = tracks.map((t) => t.id).toSet(); diff --git a/lib/screens/home_tab.dart b/lib/screens/home_tab.dart index 06e7c27c..fd02357c 100644 --- a/lib/screens/home_tab.dart +++ b/lib/screens/home_tab.dart @@ -654,8 +654,11 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient // Group download history by album to avoid flooding recents with individual tracks final albumMap = {}; for (final h in historyItems) { - // Use album name + artist as unique key - final albumKey = '${h.albumName}|${h.albumArtist ?? h.artistName}'; + // Use album name + artist as unique key (handle empty albumArtist) + final artistForKey = (h.albumArtist != null && h.albumArtist!.isNotEmpty) + ? h.albumArtist! + : h.artistName; + final albumKey = '${h.albumName}|$artistForKey'; // Keep the most recent download for each album if (!albumMap.containsKey(albumKey) || h.downloadedAt.isAfter(albumMap[albumKey]!.downloadedAt)) { @@ -664,15 +667,21 @@ class _HomeTabState extends ConsumerState with AutomaticKeepAliveClient } // Convert grouped albums to RecentAccessItem with album type - final downloadItems = albumMap.values.take(10).map((h) => RecentAccessItem( - id: '${h.albumName}|${h.albumArtist ?? h.artistName}', // Use album key as ID - name: h.albumName, - subtitle: h.albumArtist ?? h.artistName, - imageUrl: h.coverUrl, - type: RecentAccessType.album, - accessedAt: h.downloadedAt, - providerId: 'download', - )).toList(); + final downloadItems = albumMap.values.take(10).map((h) { + // Use albumArtist if available and not empty, otherwise artistName + final artistForKey = (h.albumArtist != null && h.albumArtist!.isNotEmpty) + ? h.albumArtist! + : h.artistName; + return RecentAccessItem( + id: '${h.albumName}|$artistForKey', // Use album key as ID + name: h.albumName, + subtitle: artistForKey, + imageUrl: h.coverUrl, + type: RecentAccessType.album, + accessedAt: h.downloadedAt, + providerId: 'download', + ); + }).toList(); final allItems = [...items, ...downloadItems]; allItems.sort((a, b) => b.accessedAt.compareTo(a.accessedAt)); diff --git a/lib/screens/track_metadata_screen.dart b/lib/screens/track_metadata_screen.dart index 43e7eaa4..52b25ea6 100644 --- a/lib/screens/track_metadata_screen.dart +++ b/lib/screens/track_metadata_screen.dart @@ -490,8 +490,14 @@ class _TrackMetadataScreenState extends ConsumerState { } Widget _buildMetadataGrid(BuildContext context, ColorScheme colorScheme) { + // Determine audio quality string based on file type String? audioQualityStr; - if (bitDepth != null && sampleRate != null) { + final fileName = item.filePath.split('/').last; + final fileExt = fileName.contains('.') ? fileName.split('.').last.toUpperCase() : ''; + + if (fileExt == 'MP3') { + audioQualityStr = '320kbps'; + } else if (bitDepth != null && sampleRate != null) { final sampleRateKHz = (sampleRate! / 1000).toStringAsFixed(1); audioQualityStr = '$bitDepth-bit/${sampleRateKHz}kHz'; } @@ -643,7 +649,24 @@ class _TrackMetadataScreenState extends ConsumerState { ), ), ), - if (bitDepth != null && sampleRate != null) + // Show 320kbps for MP3, bit depth/sample rate for FLAC + if (fileExtension == 'MP3') + Container( + padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), + decoration: BoxDecoration( + color: colorScheme.tertiaryContainer, + borderRadius: BorderRadius.circular(20), + ), + child: Text( + '320kbps', + style: TextStyle( + color: colorScheme.onTertiaryContainer, + fontWeight: FontWeight.w600, + fontSize: 12, + ), + ), + ) + else if (bitDepth != null && sampleRate != null) Container( padding: const EdgeInsets.symmetric(horizontal: 12, vertical: 6), decoration: BoxDecoration(