fix: downloaded album navigation from recents

This commit is contained in:
zarzet
2026-01-18 12:46:23 +07:00
parent da574f895c
commit 5ea454a0b0
5 changed files with 121 additions and 51 deletions
+5
View File
@@ -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
+49 -31
View File
@@ -1671,8 +1671,12 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
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<DownloadQueueState> {
}
// 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<DownloadQueueState> {
? 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<DownloadQueueState> {
? backendYear
: trackToDownload.releaseDate,
quality: actualQuality,
bitDepth: backendBitDepth,
sampleRate: backendSampleRate,
bitDepth: historyBitDepth,
sampleRate: historySampleRate,
),
);
+22 -7
View File
@@ -56,7 +56,13 @@ class _DownloadedAlbumScreenState extends ConsumerState<DownloadedAlbumScreen> {
}
Future<void> _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<DownloadedAlbumScreen> {
/// Get tracks for this album from history provider (reactive)
List<DownloadHistoryItem> _getAlbumTracks(List<DownloadHistoryItem> 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<DownloadedAlbumScreen> {
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();
+20 -11
View File
@@ -654,8 +654,11 @@ class _HomeTabState extends ConsumerState<HomeTab> with AutomaticKeepAliveClient
// Group download history by album to avoid flooding recents with individual tracks
final albumMap = <String, DownloadHistoryItem>{};
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<HomeTab> 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));
+25 -2
View File
@@ -490,8 +490,14 @@ class _TrackMetadataScreenState extends ConsumerState<TrackMetadataScreen> {
}
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<TrackMetadataScreen> {
),
),
),
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(