mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-06-12 17:37:55 +02:00
fix: downloaded album navigation from recents
This commit is contained in:
@@ -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
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
);
|
||||
|
||||
|
||||
@@ -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
@@ -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));
|
||||
|
||||
@@ -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(
|
||||
|
||||
Reference in New Issue
Block a user