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
+16
View File
@@ -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
View File
@@ -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)
+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
+28 -36
View File
@@ -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
View File
@@ -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