v3.6.1: fix lyrics_mode, notification v20, SAF duplicate, primary artist setting, unified download strategy

This commit is contained in:
zarzet
2026-02-10 10:11:02 +07:00
parent 7cc1fef989
commit 05d25d4d7c
10 changed files with 1811 additions and 1479 deletions
+2 -2
View File
@@ -1,8 +1,8 @@
/// App version and info constants
/// Update version here only - all other files will reference this
class AppInfo {
static const String version = '3.6.0';
static const String buildNumber = '77';
static const String version = '3.6.1';
static const String buildNumber = '78';
static const String fullVersion = '$version+$buildNumber';
+27 -91
View File
@@ -12,6 +12,7 @@ import 'package:spotiflac_android/models/track.dart';
import 'package:spotiflac_android/providers/settings_provider.dart';
import 'package:spotiflac_android/providers/extension_provider.dart';
import 'package:spotiflac_android/services/platform_bridge.dart';
import 'package:spotiflac_android/services/download_request_payload.dart';
import 'package:spotiflac_android/services/ffmpeg_service.dart';
import 'package:spotiflac_android/services/notification_service.dart';
import 'package:spotiflac_android/services/history_database.dart';
@@ -2756,129 +2757,64 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
final relativeDir = useSaf ? outputDir : '';
final fileName = useSaf ? (safFileName ?? '') : '';
final outputExt = useSaf ? safOutputExt : '';
final isYouTube = item.service == 'youtube';
final shouldUseExtensions = !isYouTube && useExtensions;
final shouldUseFallback =
!isYouTube && !shouldUseExtensions && state.autoFallback;
// YouTube provider - lossy only, bypasses fallback chain
if (item.service == 'youtube') {
if (isYouTube) {
_log.d('Using YouTube/Cobalt provider for download');
_log.d('Quality: $quality (lossy only)');
_log.d('Output dir: $outputDir');
return PlatformBridge.downloadFromYouTube(
trackName: trackToDownload.name,
artistName: trackToDownload.artistName,
albumName: trackToDownload.albumName,
albumArtist: normalizedAlbumArtist,
coverUrl: trackToDownload.coverUrl,
outputDir: outputDir,
filenameFormat: state.filenameFormat,
quality: quality,
trackNumber: trackToDownload.trackNumber ?? 1,
discNumber: trackToDownload.discNumber ?? 1,
releaseDate: trackToDownload.releaseDate,
itemId: item.id,
durationMs: trackToDownload.duration,
isrc: trackToDownload.isrc,
spotifyId: trackToDownload.id,
deezerId: deezerTrackId,
storageMode: storageMode,
safTreeUri: treeUri,
safRelativeDir: relativeDir,
safFileName: fileName,
safOutputExt: outputExt,
);
}
if (useExtensions) {
} else if (shouldUseExtensions) {
_log.d('Using extension providers for download');
_log.d(
'Quality: $quality${item.qualityOverride != null ? ' (override)' : ''}',
);
_log.d('Output dir: $outputDir');
return PlatformBridge.downloadWithExtensions(
isrc: trackToDownload.isrc ?? '',
spotifyId: trackToDownload.id,
trackName: trackToDownload.name,
artistName: trackToDownload.artistName,
albumName: trackToDownload.albumName,
albumArtist: normalizedAlbumArtist,
coverUrl: trackToDownload.coverUrl,
outputDir: outputDir,
filenameFormat: state.filenameFormat,
quality: quality,
trackNumber: trackToDownload.trackNumber ?? 1,
discNumber: trackToDownload.discNumber ?? 1,
releaseDate: trackToDownload.releaseDate,
itemId: item.id,
durationMs: trackToDownload.duration,
source: trackToDownload.source,
genre: genre,
label: label,
lyricsMode: settings.lyricsMode,
preferredService: item.service,
storageMode: storageMode,
safTreeUri: treeUri,
safRelativeDir: relativeDir,
safFileName: fileName,
safOutputExt: outputExt,
);
}
if (state.autoFallback) {
} else if (shouldUseFallback) {
_log.d('Using auto-fallback mode');
_log.d(
'Quality: $quality${item.qualityOverride != null ? ' (override)' : ''}',
);
_log.d('Output dir: $outputDir');
return PlatformBridge.downloadWithFallback(
isrc: trackToDownload.isrc ?? '',
spotifyId: trackToDownload.id,
trackName: trackToDownload.name,
artistName: trackToDownload.artistName,
albumName: trackToDownload.albumName,
albumArtist: normalizedAlbumArtist,
coverUrl: trackToDownload.coverUrl,
outputDir: outputDir,
filenameFormat: state.filenameFormat,
quality: quality,
trackNumber: trackToDownload.trackNumber ?? 1,
discNumber: trackToDownload.discNumber ?? 1,
releaseDate: trackToDownload.releaseDate,
preferredService: item.service,
itemId: item.id,
durationMs: trackToDownload.duration,
genre: genre,
label: label,
lyricsMode: settings.lyricsMode,
storageMode: storageMode,
safTreeUri: treeUri,
safRelativeDir: relativeDir,
safFileName: fileName,
safOutputExt: outputExt,
);
}
_log.d('Output dir: $outputDir');
return PlatformBridge.downloadTrack(
final payload = DownloadRequestPayload(
isrc: trackToDownload.isrc ?? '',
service: item.service,
spotifyId: trackToDownload.id,
trackName: trackToDownload.name,
artistName: trackToDownload.artistName,
albumName: trackToDownload.albumName,
albumArtist: normalizedAlbumArtist,
coverUrl: trackToDownload.coverUrl,
albumArtist: normalizedAlbumArtist ?? trackToDownload.artistName,
coverUrl: trackToDownload.coverUrl ?? '',
outputDir: outputDir,
filenameFormat: state.filenameFormat,
quality: quality,
// Keep prior behavior: non-YouTube paths were implicitly true.
embedLyrics: isYouTube ? settings.embedLyrics : true,
embedMaxQualityCover: settings.maxQualityCover,
trackNumber: trackToDownload.trackNumber ?? 1,
discNumber: trackToDownload.discNumber ?? 1,
releaseDate: trackToDownload.releaseDate,
releaseDate: trackToDownload.releaseDate ?? '',
itemId: item.id,
durationMs: trackToDownload.duration,
source: trackToDownload.source ?? '',
genre: genre ?? '',
label: label ?? '',
deezerId: deezerTrackId ?? '',
lyricsMode: settings.lyricsMode,
storageMode: storageMode,
safTreeUri: treeUri,
safRelativeDir: relativeDir,
safFileName: fileName,
safOutputExt: outputExt,
);
return PlatformBridge.downloadByStrategy(
payload: payload,
useExtensions: shouldUseExtensions,
useFallback: shouldUseFallback,
);
}
result = await runDownload(
+154
View File
@@ -0,0 +1,154 @@
class DownloadRequestPayload {
final String isrc;
final String service;
final String spotifyId;
final String trackName;
final String artistName;
final String albumName;
final String albumArtist;
final String coverUrl;
final String outputDir;
final String filenameFormat;
final String quality;
final bool embedLyrics;
final bool embedMaxQualityCover;
final int trackNumber;
final int discNumber;
final int totalTracks;
final String releaseDate;
final String itemId;
final int durationMs;
final String source;
final String genre;
final String label;
final String copyright;
final String tidalId;
final String qobuzId;
final String deezerId;
final String lyricsMode;
final bool useExtensions;
final bool useFallback;
final String storageMode;
final String safTreeUri;
final String safRelativeDir;
final String safFileName;
final String safOutputExt;
const DownloadRequestPayload({
this.isrc = '',
this.service = '',
this.spotifyId = '',
required this.trackName,
required this.artistName,
required this.albumName,
this.albumArtist = '',
this.coverUrl = '',
required this.outputDir,
required this.filenameFormat,
this.quality = 'LOSSLESS',
this.embedLyrics = true,
this.embedMaxQualityCover = true,
this.trackNumber = 1,
this.discNumber = 1,
this.totalTracks = 1,
this.releaseDate = '',
this.itemId = '',
this.durationMs = 0,
this.source = '',
this.genre = '',
this.label = '',
this.copyright = '',
this.tidalId = '',
this.qobuzId = '',
this.deezerId = '',
this.lyricsMode = 'embed',
this.useExtensions = false,
this.useFallback = false,
this.storageMode = 'app',
this.safTreeUri = '',
this.safRelativeDir = '',
this.safFileName = '',
this.safOutputExt = '',
});
Map<String, dynamic> toJson() {
return {
'isrc': isrc,
'service': service,
'spotify_id': spotifyId,
'track_name': trackName,
'artist_name': artistName,
'album_name': albumName,
'album_artist': albumArtist,
'cover_url': coverUrl,
'output_dir': outputDir,
'filename_format': filenameFormat,
'quality': quality,
'embed_lyrics': embedLyrics,
'embed_max_quality_cover': embedMaxQualityCover,
'track_number': trackNumber,
'disc_number': discNumber,
'total_tracks': totalTracks,
'release_date': releaseDate,
'item_id': itemId,
'duration_ms': durationMs,
'source': source,
'genre': genre,
'label': label,
'copyright': copyright,
'tidal_id': tidalId,
'qobuz_id': qobuzId,
'deezer_id': deezerId,
'lyrics_mode': lyricsMode,
'use_extensions': useExtensions,
'use_fallback': useFallback,
'storage_mode': storageMode,
'saf_tree_uri': safTreeUri,
'saf_relative_dir': safRelativeDir,
'saf_file_name': safFileName,
'saf_output_ext': safOutputExt,
};
}
DownloadRequestPayload withStrategy({
bool? useExtensions,
bool? useFallback,
}) {
return DownloadRequestPayload(
isrc: isrc,
service: service,
spotifyId: spotifyId,
trackName: trackName,
artistName: artistName,
albumName: albumName,
albumArtist: albumArtist,
coverUrl: coverUrl,
outputDir: outputDir,
filenameFormat: filenameFormat,
quality: quality,
embedLyrics: embedLyrics,
embedMaxQualityCover: embedMaxQualityCover,
trackNumber: trackNumber,
discNumber: discNumber,
totalTracks: totalTracks,
releaseDate: releaseDate,
itemId: itemId,
durationMs: durationMs,
source: source,
genre: genre,
label: label,
copyright: copyright,
tidalId: tidalId,
qobuzId: qobuzId,
deezerId: deezerId,
lyricsMode: lyricsMode,
useExtensions: useExtensions ?? this.useExtensions,
useFallback: useFallback ?? this.useFallback,
storageMode: storageMode,
safTreeUri: safTreeUri,
safRelativeDir: safRelativeDir,
safFileName: safFileName,
safOutputExt: safOutputExt,
);
}
}
+31 -31
View File
@@ -30,7 +30,7 @@ class NotificationService {
iOS: iosSettings,
);
await _notifications.initialize(initSettings);
await _notifications.initialize(settings: initSettings);
if (Platform.isAndroid) {
await _notifications
@@ -90,10 +90,10 @@ class NotificationService {
);
await _notifications.show(
downloadProgressId,
'Downloading $trackName',
'$artistName$percentage%',
details,
id: downloadProgressId,
title: 'Downloading $trackName',
body: '$artistName$percentage%',
notificationDetails: details,
);
}
@@ -133,10 +133,10 @@ class NotificationService {
);
await _notifications.show(
downloadProgressId,
'Finalizing $trackName',
'$artistName • Embedding metadata...',
details,
id: downloadProgressId,
title: 'Finalizing $trackName',
body: '$artistName • Embedding metadata...',
notificationDetails: details,
);
}
@@ -183,10 +183,10 @@ class NotificationService {
);
await _notifications.show(
downloadProgressId,
title,
'$trackName - $artistName',
details,
id: downloadProgressId,
title: title,
body: '$trackName - $artistName',
notificationDetails: details,
);
}
@@ -223,15 +223,15 @@ class NotificationService {
);
await _notifications.show(
downloadProgressId,
title,
'$completedCount tracks downloaded successfully',
details,
id: downloadProgressId,
title: title,
body: '$completedCount tracks downloaded successfully',
notificationDetails: details,
);
}
Future<void> cancelDownloadNotification() async {
await _notifications.cancel(downloadProgressId);
await _notifications.cancel(id: downloadProgressId);
}
Future<void> showUpdateDownloadProgress({
@@ -274,10 +274,10 @@ class NotificationService {
);
await _notifications.show(
updateDownloadId,
'Downloading SpotiFLAC v$version',
'$receivedMB / $totalMB MB • $percentage%',
details,
id: updateDownloadId,
title: 'Downloading SpotiFLAC v$version',
body: '$receivedMB / $totalMB MB • $percentage%',
notificationDetails: details,
);
}
@@ -307,10 +307,10 @@ class NotificationService {
);
await _notifications.show(
updateDownloadId,
'Update Ready',
'SpotiFLAC v$version downloaded. Tap to install.',
details,
id: updateDownloadId,
title: 'Update Ready',
body: 'SpotiFLAC v$version downloaded. Tap to install.',
notificationDetails: details,
);
}
@@ -339,14 +339,14 @@ class NotificationService {
);
await _notifications.show(
updateDownloadId,
'Update Failed',
'Could not download update. Try again later.',
details,
id: updateDownloadId,
title: 'Update Failed',
body: 'Could not download update. Try again later.',
notificationDetails: details,
);
}
Future<void> cancelUpdateNotification() async {
await _notifications.cancel(updateDownloadId);
await _notifications.cancel(id: updateDownloadId);
}
}
File diff suppressed because it is too large Load Diff