mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-21 23:47:04 +02:00
v3.6.1: fix lyrics_mode, notification v20, SAF duplicate, primary artist setting, unified download strategy
This commit is contained in:
@@ -1,5 +1,21 @@
|
||||
# Changelog
|
||||
|
||||
## [3.6.1] - 2026-02-10
|
||||
|
||||
### Added
|
||||
|
||||
- "Use Primary Artist Only" setting: strips featured artists from folder names (e.g. "Justin Bieber, Quavo" becomes "Justin Bieber") for cleaner folder organization
|
||||
- Supports separators: `, ` `;` `&` `feat.` `ft.` `featuring` `with` `x`
|
||||
- Available in Settings > Download > below "Use Album Artist for folders"
|
||||
|
||||
### Fixed
|
||||
|
||||
- Fixed lyrics mode "External .LRC" still embedding lyrics into metadata - `lyrics_mode` was not being sent to Go backend for single-service downloads and YouTube provider, causing Go to default to "embed"
|
||||
- Fixed `flutter_local_notifications` v20 breaking changes - migrated all `initialize()`, `show()`, and `cancel()` calls from positional parameters to named parameters
|
||||
- Fixed SAF duplicate folder bug: concurrent batch downloads creating empty folders with `(1)`, `(2)`, `(3)` suffixes - added synchronized lock to `ensureDocumentDir` in Kotlin with duplicate detection and cleanup
|
||||
|
||||
---
|
||||
|
||||
## [3.6.0] - 2026-02-09
|
||||
|
||||
### Highlights
|
||||
|
||||
@@ -1312,6 +1312,15 @@ class MainActivity: FlutterFragmentActivity() {
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"downloadByStrategy" -> {
|
||||
val requestJson = call.arguments as String
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
handleSafDownload(requestJson) { json ->
|
||||
Gobackend.downloadByStrategy(json)
|
||||
}
|
||||
}
|
||||
result.success(response)
|
||||
}
|
||||
"getDownloadProgress" -> {
|
||||
val response = withContext(Dispatchers.IO) {
|
||||
Gobackend.getDownloadProgress()
|
||||
|
||||
+140
-65
@@ -150,6 +150,8 @@ type DownloadRequest struct {
|
||||
QobuzID string `json:"qobuz_id,omitempty"`
|
||||
DeezerID string `json:"deezer_id,omitempty"`
|
||||
LyricsMode string `json:"lyrics_mode,omitempty"`
|
||||
UseExtensions bool `json:"use_extensions,omitempty"`
|
||||
UseFallback bool `json:"use_fallback,omitempty"`
|
||||
}
|
||||
|
||||
type DownloadResponse struct {
|
||||
@@ -192,6 +194,73 @@ type DownloadResult struct {
|
||||
LyricsLRC string
|
||||
}
|
||||
|
||||
func buildDownloadSuccessResponse(
|
||||
req DownloadRequest,
|
||||
result DownloadResult,
|
||||
service string,
|
||||
message string,
|
||||
filePath string,
|
||||
alreadyExists bool,
|
||||
) DownloadResponse {
|
||||
title := result.Title
|
||||
if title == "" {
|
||||
title = req.TrackName
|
||||
}
|
||||
|
||||
artist := result.Artist
|
||||
if artist == "" {
|
||||
artist = req.ArtistName
|
||||
}
|
||||
|
||||
album := result.Album
|
||||
if album == "" {
|
||||
album = req.AlbumName
|
||||
}
|
||||
|
||||
releaseDate := result.ReleaseDate
|
||||
if releaseDate == "" {
|
||||
releaseDate = req.ReleaseDate
|
||||
}
|
||||
|
||||
trackNumber := result.TrackNumber
|
||||
if trackNumber == 0 {
|
||||
trackNumber = req.TrackNumber
|
||||
}
|
||||
|
||||
discNumber := result.DiscNumber
|
||||
if discNumber == 0 {
|
||||
discNumber = req.DiscNumber
|
||||
}
|
||||
|
||||
isrc := result.ISRC
|
||||
if isrc == "" {
|
||||
isrc = req.ISRC
|
||||
}
|
||||
|
||||
return DownloadResponse{
|
||||
Success: true,
|
||||
Message: message,
|
||||
FilePath: filePath,
|
||||
AlreadyExists: alreadyExists,
|
||||
ActualBitDepth: result.BitDepth,
|
||||
ActualSampleRate: result.SampleRate,
|
||||
Service: service,
|
||||
Title: title,
|
||||
Artist: artist,
|
||||
Album: album,
|
||||
AlbumArtist: req.AlbumArtist,
|
||||
ReleaseDate: releaseDate,
|
||||
TrackNumber: trackNumber,
|
||||
DiscNumber: discNumber,
|
||||
ISRC: isrc,
|
||||
CoverURL: req.CoverURL,
|
||||
Genre: req.Genre,
|
||||
Label: req.Label,
|
||||
Copyright: req.Copyright,
|
||||
LyricsLRC: result.LyricsLRC,
|
||||
}
|
||||
}
|
||||
|
||||
func DownloadTrack(requestJSON string) (string, error) {
|
||||
var req DownloadRequest
|
||||
if err := json.Unmarshal([]byte(requestJSON), &req); err != nil {
|
||||
@@ -301,22 +370,14 @@ func DownloadTrack(requestJSON string) (string, error) {
|
||||
result.BitDepth = quality.BitDepth
|
||||
result.SampleRate = quality.SampleRate
|
||||
}
|
||||
resp := DownloadResponse{
|
||||
Success: true,
|
||||
Message: "File already exists",
|
||||
FilePath: actualPath,
|
||||
AlreadyExists: true,
|
||||
ActualBitDepth: result.BitDepth,
|
||||
ActualSampleRate: result.SampleRate,
|
||||
Service: req.Service,
|
||||
Title: result.Title,
|
||||
Artist: result.Artist,
|
||||
Album: result.Album,
|
||||
ReleaseDate: result.ReleaseDate,
|
||||
TrackNumber: result.TrackNumber,
|
||||
DiscNumber: result.DiscNumber,
|
||||
ISRC: result.ISRC,
|
||||
}
|
||||
resp := buildDownloadSuccessResponse(
|
||||
req,
|
||||
result,
|
||||
req.Service,
|
||||
"File already exists",
|
||||
actualPath,
|
||||
true,
|
||||
)
|
||||
jsonBytes, _ := json.Marshal(resp)
|
||||
return string(jsonBytes), nil
|
||||
}
|
||||
@@ -330,27 +391,54 @@ func DownloadTrack(requestJSON string) (string, error) {
|
||||
GoLog("[Download] Could not read quality from file: %v\n", qErr)
|
||||
}
|
||||
|
||||
resp := DownloadResponse{
|
||||
Success: true,
|
||||
Message: "Download complete",
|
||||
FilePath: result.FilePath,
|
||||
ActualBitDepth: result.BitDepth,
|
||||
ActualSampleRate: result.SampleRate,
|
||||
Service: req.Service,
|
||||
Title: result.Title,
|
||||
Artist: result.Artist,
|
||||
Album: result.Album,
|
||||
ReleaseDate: result.ReleaseDate,
|
||||
TrackNumber: result.TrackNumber,
|
||||
DiscNumber: result.DiscNumber,
|
||||
ISRC: result.ISRC,
|
||||
LyricsLRC: result.LyricsLRC,
|
||||
}
|
||||
resp := buildDownloadSuccessResponse(
|
||||
req,
|
||||
result,
|
||||
req.Service,
|
||||
"Download complete",
|
||||
result.FilePath,
|
||||
false,
|
||||
)
|
||||
|
||||
jsonBytes, _ := json.Marshal(resp)
|
||||
return string(jsonBytes), nil
|
||||
}
|
||||
|
||||
// DownloadByStrategy routes a unified download request to the appropriate flow.
|
||||
// Routing priority: YouTube service > extension fallback > built-in fallback > direct service.
|
||||
func DownloadByStrategy(requestJSON string) (string, error) {
|
||||
var req DownloadRequest
|
||||
if err := json.Unmarshal([]byte(requestJSON), &req); err != nil {
|
||||
return errorResponse("Invalid request: " + err.Error())
|
||||
}
|
||||
|
||||
service := strings.TrimSpace(strings.ToLower(req.Service))
|
||||
req.Service = service
|
||||
normalizedBytes, err := json.Marshal(req)
|
||||
if err != nil {
|
||||
return errorResponse("Invalid request: " + err.Error())
|
||||
}
|
||||
normalizedJSON := string(normalizedBytes)
|
||||
|
||||
if service == "youtube" {
|
||||
return DownloadFromYouTube(normalizedJSON)
|
||||
}
|
||||
|
||||
if req.UseExtensions {
|
||||
resp, err := DownloadWithExtensionsJSON(normalizedJSON)
|
||||
if err != nil {
|
||||
return errorResponse(err.Error())
|
||||
}
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
if req.UseFallback {
|
||||
return DownloadWithFallback(normalizedJSON)
|
||||
}
|
||||
|
||||
return DownloadTrack(normalizedJSON)
|
||||
}
|
||||
|
||||
func DownloadWithFallback(requestJSON string) (string, error) {
|
||||
var req DownloadRequest
|
||||
if err := json.Unmarshal([]byte(requestJSON), &req); err != nil {
|
||||
@@ -470,23 +558,14 @@ func DownloadWithFallback(requestJSON string) (string, error) {
|
||||
result.BitDepth = quality.BitDepth
|
||||
result.SampleRate = quality.SampleRate
|
||||
}
|
||||
resp := DownloadResponse{
|
||||
Success: true,
|
||||
Message: "File already exists",
|
||||
FilePath: actualPath,
|
||||
AlreadyExists: true,
|
||||
ActualBitDepth: result.BitDepth,
|
||||
ActualSampleRate: result.SampleRate,
|
||||
Service: service,
|
||||
Title: result.Title,
|
||||
Artist: result.Artist,
|
||||
Album: result.Album,
|
||||
ReleaseDate: result.ReleaseDate,
|
||||
TrackNumber: result.TrackNumber,
|
||||
DiscNumber: result.DiscNumber,
|
||||
ISRC: result.ISRC,
|
||||
LyricsLRC: result.LyricsLRC,
|
||||
}
|
||||
resp := buildDownloadSuccessResponse(
|
||||
req,
|
||||
result,
|
||||
service,
|
||||
"File already exists",
|
||||
actualPath,
|
||||
true,
|
||||
)
|
||||
jsonBytes, _ := json.Marshal(resp)
|
||||
return string(jsonBytes), nil
|
||||
}
|
||||
@@ -500,22 +579,14 @@ func DownloadWithFallback(requestJSON string) (string, error) {
|
||||
GoLog("[Download] Could not read quality from file: %v\n", qErr)
|
||||
}
|
||||
|
||||
resp := DownloadResponse{
|
||||
Success: true,
|
||||
Message: "Downloaded from " + service,
|
||||
FilePath: result.FilePath,
|
||||
ActualBitDepth: result.BitDepth,
|
||||
ActualSampleRate: result.SampleRate,
|
||||
Service: service,
|
||||
Title: result.Title,
|
||||
Artist: result.Artist,
|
||||
Album: result.Album,
|
||||
ReleaseDate: result.ReleaseDate,
|
||||
TrackNumber: result.TrackNumber,
|
||||
DiscNumber: result.DiscNumber,
|
||||
ISRC: result.ISRC,
|
||||
LyricsLRC: result.LyricsLRC,
|
||||
}
|
||||
resp := buildDownloadSuccessResponse(
|
||||
req,
|
||||
result,
|
||||
service,
|
||||
"Downloaded from "+service,
|
||||
result.FilePath,
|
||||
false,
|
||||
)
|
||||
jsonBytes, _ := json.Marshal(resp)
|
||||
return string(jsonBytes), nil
|
||||
}
|
||||
@@ -1266,6 +1337,10 @@ func DownloadFromYouTube(requestJSON string) (string, error) {
|
||||
DiscNumber: youtubeResult.DiscNumber,
|
||||
ISRC: youtubeResult.ISRC,
|
||||
LyricsLRC: youtubeResult.LyricsLRC,
|
||||
CoverURL: req.CoverURL,
|
||||
Genre: req.Genre,
|
||||
Label: req.Label,
|
||||
Copyright: req.Copyright,
|
||||
}
|
||||
|
||||
jsonBytes, _ := json.Marshal(resp)
|
||||
|
||||
@@ -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';
|
||||
|
||||
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
+1403
-1253
File diff suppressed because it is too large
Load Diff
+28
-36
@@ -189,10 +189,10 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: connectivity_plus
|
||||
sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec
|
||||
sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.1.5"
|
||||
version: "7.0.0"
|
||||
connectivity_plus_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
@@ -386,34 +386,34 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_local_notifications
|
||||
sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875"
|
||||
sha256: "76cd20bcfa72fabe50ea27eeaf165527f446f55d3033021462084b87805b4cac"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "19.5.0"
|
||||
version: "20.0.0"
|
||||
flutter_local_notifications_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_linux
|
||||
sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5
|
||||
sha256: dce0116868cedd2cdf768af0365fc37ff1cbef7c02c4f51d0587482e625868d0
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "6.0.0"
|
||||
version: "7.0.0"
|
||||
flutter_local_notifications_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_platform_interface
|
||||
sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe"
|
||||
sha256: "23de31678a48c084169d7ae95866df9de5c9d2a44be3e5915a2ff067aeeba899"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.1.0"
|
||||
version: "10.0.0"
|
||||
flutter_local_notifications_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_local_notifications_windows
|
||||
sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf"
|
||||
sha256: "7ddd964fa85b6a23e96956c5b63ef55cdb9e5947b71b95712204db42ad46da61"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.0.3"
|
||||
version: "2.0.0"
|
||||
flutter_localizations:
|
||||
dependency: "direct main"
|
||||
description: flutter
|
||||
@@ -439,50 +439,50 @@ packages:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: flutter_secure_storage
|
||||
sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
|
||||
sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "9.2.4"
|
||||
version: "10.0.0"
|
||||
flutter_secure_storage_darwin:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_darwin
|
||||
sha256: "8878c25136a79def1668c75985e8e193d9d7d095453ec28730da0315dc69aee3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.2.0"
|
||||
flutter_secure_storage_linux:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_linux
|
||||
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
|
||||
sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.3"
|
||||
flutter_secure_storage_macos:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_macos
|
||||
sha256: "6c0a2795a2d1de26ae202a0d78527d163f4acbb11cde4c75c670f3a0fc064247"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.3"
|
||||
version: "3.0.0"
|
||||
flutter_secure_storage_platform_interface:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_platform_interface
|
||||
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
|
||||
sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.1.2"
|
||||
version: "2.0.1"
|
||||
flutter_secure_storage_web:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_web
|
||||
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
|
||||
sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.2.1"
|
||||
version: "2.1.0"
|
||||
flutter_secure_storage_windows:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: flutter_secure_storage_windows
|
||||
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
|
||||
sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
version: "4.1.0"
|
||||
flutter_svg:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
@@ -741,14 +741,6 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "2.2.0"
|
||||
palette_generator:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
name: palette_generator
|
||||
sha256: "4420f7ccc3f0a4a906144e73f8b6267cd940b64f57a7262e95cb8cec3a8ae0ed"
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "0.3.3+7"
|
||||
path:
|
||||
dependency: "direct main"
|
||||
description:
|
||||
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
name: spotiflac_android
|
||||
description: Download Spotify tracks in FLAC from Tidal, Qobuz & Amazon Music
|
||||
publish_to: "none"
|
||||
version: 3.6.0+77
|
||||
version: 3.6.1+78
|
||||
|
||||
environment:
|
||||
sdk: ^3.10.0
|
||||
|
||||
Reference in New Issue
Block a user