feat: primary artist only folders, fix notifications v20, fix SAF duplicate dirs

- Add 'Use Primary Artist Only' setting to strip featured artists from folder names
- Fix flutter_local_notifications v20 breaking changes (positional params)
- Fix SAF duplicate folder bug: synchronized ensureDocumentDir to prevent race condition creating empty folders with (1), (2) suffixes during concurrent downloads
This commit is contained in:
zarzet
2026-02-10 09:07:18 +07:00
parent ebe7d87da7
commit f7be2c1e12
24 changed files with 326 additions and 73 deletions
@@ -33,6 +33,7 @@ class MainActivity: FlutterFragmentActivity() {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Main)
private var pendingSafTreeResult: MethodChannel.Result? = null
private val safScanLock = Any()
private val safDirLock = Any()
private var safScanProgress = SafScanProgress()
@Volatile private var safScanCancel = false
@Volatile private var safScanActive = false
@@ -300,19 +301,36 @@ class MainActivity: FlutterFragmentActivity() {
}
private fun ensureDocumentDir(treeUri: Uri, relativeDir: String): DocumentFile? {
var current = DocumentFile.fromTreeUri(this, treeUri) ?: return null
if (relativeDir.isBlank()) return current
val parts = relativeDir.split("/").filter { it.isNotBlank() }
for (part in parts) {
val existing = current.findFile(part)
current = if (existing != null && existing.isDirectory) {
existing
} else {
current.createDirectory(part) ?: return null
}
if (relativeDir.isBlank()) {
return DocumentFile.fromTreeUri(this, treeUri)
}
// Synchronize to prevent concurrent downloads from creating duplicate
// directories with (1), (2) suffixes via SAF's auto-rename behavior.
synchronized(safDirLock) {
var current = DocumentFile.fromTreeUri(this, treeUri) ?: return null
val parts = relativeDir.split("/").filter { it.isNotBlank() }
for (part in parts) {
val existing = current.findFile(part)
current = if (existing != null && existing.isDirectory) {
existing
} else {
val created = current.createDirectory(part) ?: return null
// SAF may auto-rename to "part (1)" if another thread just created it.
// Re-check: if the created name differs, delete it and use the original.
val createdName = created.name ?: part
if (createdName != part) {
// Another thread won the race; delete the duplicate and use theirs.
created.delete()
current.findFile(part) ?: return null
} else {
created
}
}
}
return current
}
return current
}
private fun findDocumentDir(treeUri: Uri, relativeDir: String): DocumentFile? {
+18
View File
@@ -3550,6 +3550,24 @@ abstract class AppLocalizations {
/// **'Artist folders use Track Artist only'**
String get downloadUseAlbumArtistForFoldersTrackSubtitle;
/// Setting - strip featured artists from folder name
///
/// In en, this message translates to:
/// **'Primary artist only for folders'**
String get downloadUsePrimaryArtistOnly;
/// Subtitle when primary artist only is enabled
///
/// In en, this message translates to:
/// **'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)'**
String get downloadUsePrimaryArtistOnlyEnabled;
/// Subtitle when primary artist only is disabled
///
/// In en, this message translates to:
/// **'Full artist string used for folder name'**
String get downloadUsePrimaryArtistOnlyDisabled;
/// Setting - output file format
///
/// In en, this message translates to:
+11
View File
@@ -1960,6 +1960,17 @@ class AppLocalizationsDe extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => 'Save Format';
+11
View File
@@ -1945,6 +1945,17 @@ class AppLocalizationsEn extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => 'Save Format';
+11
View File
@@ -1945,6 +1945,17 @@ class AppLocalizationsEs extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => 'Save Format';
+11
View File
@@ -1945,6 +1945,17 @@ class AppLocalizationsFr extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => 'Save Format';
+11
View File
@@ -1945,6 +1945,17 @@ class AppLocalizationsHi extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => 'Save Format';
+11
View File
@@ -1958,6 +1958,17 @@ class AppLocalizationsId extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Folder artis hanya memakai Track Artist';
@override
String get downloadUsePrimaryArtistOnly => 'Hanya artis utama untuk folder';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artist dihapus dari nama folder (misal Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Nama artis lengkap dipakai untuk folder';
@override
String get downloadSaveFormat => 'Simpan Format';
+11
View File
@@ -1933,6 +1933,17 @@ class AppLocalizationsJa extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => '形式を保存';
+11
View File
@@ -1945,6 +1945,17 @@ class AppLocalizationsKo extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => 'Save Format';
+11
View File
@@ -1945,6 +1945,17 @@ class AppLocalizationsNl extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => 'Save Format';
+11
View File
@@ -1945,6 +1945,17 @@ class AppLocalizationsPt extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => 'Save Format';
+11
View File
@@ -1983,6 +1983,17 @@ class AppLocalizationsRu extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => 'Формат сохранения';
+11
View File
@@ -1960,6 +1960,17 @@ class AppLocalizationsTr extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => 'Save Format';
+11
View File
@@ -1945,6 +1945,17 @@ class AppLocalizationsZh extends AppLocalizations {
String get downloadUseAlbumArtistForFoldersTrackSubtitle =>
'Artist folders use Track Artist only';
@override
String get downloadUsePrimaryArtistOnly => 'Primary artist only for folders';
@override
String get downloadUsePrimaryArtistOnlyEnabled =>
'Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)';
@override
String get downloadUsePrimaryArtistOnlyDisabled =>
'Full artist string used for folder name';
@override
String get downloadSaveFormat => 'Save Format';
+6
View File
@@ -1431,6 +1431,12 @@
"@downloadUseAlbumArtistForFoldersAlbumSubtitle": {"description": "Subtitle when Album Artist is used for folder naming"},
"downloadUseAlbumArtistForFoldersTrackSubtitle": "Artist folders use Track Artist only",
"@downloadUseAlbumArtistForFoldersTrackSubtitle": {"description": "Subtitle when Track Artist is used for folder naming"},
"downloadUsePrimaryArtistOnly": "Primary artist only for folders",
"@downloadUsePrimaryArtistOnly": {"description": "Setting - strip featured artists from folder name"},
"downloadUsePrimaryArtistOnlyEnabled": "Featured artists removed from folder name (e.g. Justin Bieber, Quavo → Justin Bieber)",
"@downloadUsePrimaryArtistOnlyEnabled": {"description": "Subtitle when primary artist only is enabled"},
"downloadUsePrimaryArtistOnlyDisabled": "Full artist string used for folder name",
"@downloadUsePrimaryArtistOnlyDisabled": {"description": "Subtitle when primary artist only is disabled"},
"downloadSaveFormat": "Save Format",
"@downloadSaveFormat": {"description": "Setting - output file format"},
"downloadSelectService": "Select Service",
+12
View File
@@ -2489,6 +2489,18 @@
"@downloadUseAlbumArtistForFoldersTrackSubtitle": {
"description": "Subtitle when Track Artist is used for folder naming"
},
"downloadUsePrimaryArtistOnly": "Hanya artis utama untuk folder",
"@downloadUsePrimaryArtistOnly": {
"description": "Setting - strip featured artists from folder name"
},
"downloadUsePrimaryArtistOnlyEnabled": "Featured artist dihapus dari nama folder (misal Justin Bieber, Quavo → Justin Bieber)",
"@downloadUsePrimaryArtistOnlyEnabled": {
"description": "Subtitle when primary artist only is enabled"
},
"downloadUsePrimaryArtistOnlyDisabled": "Nama artis lengkap dipakai untuk folder",
"@downloadUsePrimaryArtistOnlyDisabled": {
"description": "Subtitle when primary artist only is disabled"
},
"downloadSaveFormat": "Simpan Format",
"@downloadSaveFormat": {
"description": "Setting - output file format"
+5
View File
@@ -20,6 +20,7 @@ class AppSettings {
final bool hasSearchedBefore;
final String folderOrganization;
final bool useAlbumArtistForFolders;
final bool usePrimaryArtistOnly; // Strip featured artists from folder name
final String historyViewMode;
final String historyFilterMode;
final bool askQualityBeforeDownload;
@@ -65,6 +66,7 @@ class AppSettings {
this.hasSearchedBefore = false,
this.folderOrganization = 'none',
this.useAlbumArtistForFolders = true,
this.usePrimaryArtistOnly = false,
this.historyViewMode = 'grid',
this.historyFilterMode = 'all',
this.askQualityBeforeDownload = true,
@@ -109,6 +111,7 @@ class AppSettings {
bool? hasSearchedBefore,
String? folderOrganization,
bool? useAlbumArtistForFolders,
bool? usePrimaryArtistOnly,
String? historyViewMode,
String? historyFilterMode,
bool? askQualityBeforeDownload,
@@ -154,6 +157,8 @@ class AppSettings {
folderOrganization: folderOrganization ?? this.folderOrganization,
useAlbumArtistForFolders:
useAlbumArtistForFolders ?? this.useAlbumArtistForFolders,
usePrimaryArtistOnly:
usePrimaryArtistOnly ?? this.usePrimaryArtistOnly,
historyViewMode: historyViewMode ?? this.historyViewMode,
historyFilterMode: historyFilterMode ?? this.historyFilterMode,
askQualityBeforeDownload: askQualityBeforeDownload ?? this.askQualityBeforeDownload,
+2
View File
@@ -23,6 +23,7 @@ AppSettings _$AppSettingsFromJson(Map<String, dynamic> json) => AppSettings(
hasSearchedBefore: json['hasSearchedBefore'] as bool? ?? false,
folderOrganization: json['folderOrganization'] as String? ?? 'none',
useAlbumArtistForFolders: json['useAlbumArtistForFolders'] as bool? ?? true,
usePrimaryArtistOnly: json['usePrimaryArtistOnly'] as bool? ?? false,
historyViewMode: json['historyViewMode'] as String? ?? 'grid',
historyFilterMode: json['historyFilterMode'] as String? ?? 'all',
askQualityBeforeDownload: json['askQualityBeforeDownload'] as bool? ?? true,
@@ -70,6 +71,7 @@ Map<String, dynamic> _$AppSettingsToJson(AppSettings instance) =>
'hasSearchedBefore': instance.hasSearchedBefore,
'folderOrganization': instance.folderOrganization,
'useAlbumArtistForFolders': instance.useAlbumArtistForFolders,
'usePrimaryArtistOnly': instance.usePrimaryArtistOnly,
'historyViewMode': instance.historyViewMode,
'historyFilterMode': instance.historyFilterMode,
'askQualityBeforeDownload': instance.askQualityBeforeDownload,
+26 -2
View File
@@ -1033,11 +1033,15 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
bool separateSingles = false,
String albumFolderStructure = 'artist_album',
bool useAlbumArtistForFolders = true,
bool usePrimaryArtistOnly = false,
}) async {
String baseDir = state.outputDir;
final folderArtist = useAlbumArtistForFolders
var folderArtist = useAlbumArtistForFolders
? _normalizeOptionalString(track.albumArtist) ?? track.artistName
: track.artistName;
if (usePrimaryArtistOnly) {
folderArtist = _extractPrimaryArtist(folderArtist);
}
if (separateSingles) {
final isSingle = track.isSingle;
@@ -1129,6 +1133,19 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
.trim();
}
static final _featuredArtistPattern = RegExp(
r'\s*[,;&]\s*|\s+(?:feat\.?|ft\.?|featuring|with|x)\s+',
caseSensitive: false,
);
String _extractPrimaryArtist(String artist) {
final match = _featuredArtistPattern.firstMatch(artist);
if (match != null && match.start > 0) {
return artist.substring(0, match.start).trim();
}
return artist;
}
bool _isSafMode(AppSettings settings) {
return Platform.isAndroid &&
settings.storageMode == 'saf' &&
@@ -1152,10 +1169,14 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
bool separateSingles = false,
String albumFolderStructure = 'artist_album',
bool useAlbumArtistForFolders = true,
bool usePrimaryArtistOnly = false,
}) async {
final folderArtist = useAlbumArtistForFolders
var folderArtist = useAlbumArtistForFolders
? _normalizeOptionalString(track.albumArtist) ?? track.artistName
: track.artistName;
if (usePrimaryArtistOnly) {
folderArtist = _extractPrimaryArtist(folderArtist);
}
if (separateSingles) {
final isSingle = track.isSingle;
@@ -2565,6 +2586,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
separateSingles: settings.separateSingles,
albumFolderStructure: settings.albumFolderStructure,
useAlbumArtistForFolders: settings.useAlbumArtistForFolders,
usePrimaryArtistOnly: settings.usePrimaryArtistOnly,
)
: '';
String? appOutputDir;
@@ -2576,6 +2598,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
separateSingles: settings.separateSingles,
albumFolderStructure: settings.albumFolderStructure,
useAlbumArtistForFolders: settings.useAlbumArtistForFolders,
usePrimaryArtistOnly: settings.usePrimaryArtistOnly,
);
var effectiveOutputDir = initialOutputDir;
var effectiveSafMode = isSafMode;
@@ -2873,6 +2896,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
separateSingles: settings.separateSingles,
albumFolderStructure: settings.albumFolderStructure,
useAlbumArtistForFolders: settings.useAlbumArtistForFolders,
usePrimaryArtistOnly: settings.usePrimaryArtistOnly,
);
final fallbackResult = await runDownload(
useSaf: false,
+5
View File
@@ -231,6 +231,11 @@ class SettingsNotifier extends Notifier<AppSettings> {
_saveSettings();
}
void setUsePrimaryArtistOnly(bool enabled) {
state = state.copyWith(usePrimaryArtistOnly: enabled);
_saveSettings();
}
void setHistoryViewMode(String mode) {
state = state.copyWith(historyViewMode: mode);
_saveSettings();
@@ -365,6 +365,18 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
.setUseAlbumArtistForFolders(value),
showDivider: false,
),
SettingsSwitchItem(
icon: Icons.person_outline,
title: context.l10n.downloadUsePrimaryArtistOnly,
subtitle: settings.usePrimaryArtistOnly
? context.l10n.downloadUsePrimaryArtistOnlyEnabled
: context.l10n.downloadUsePrimaryArtistOnlyDisabled,
value: settings.usePrimaryArtistOnly,
onChanged: (value) => ref
.read(settingsProvider.notifier)
.setUsePrimaryArtistOnly(value),
showDivider: false,
),
],
),
),
+31 -31
View File
@@ -30,7 +30,7 @@ class NotificationService {
iOS: iosSettings,
);
await _notifications.initialize(settings: initSettings);
await _notifications.initialize(initSettings);
if (Platform.isAndroid) {
await _notifications
@@ -90,10 +90,10 @@ class NotificationService {
);
await _notifications.show(
id: downloadProgressId,
title: 'Downloading $trackName',
body: '$artistName$percentage%',
notificationDetails: details,
downloadProgressId,
'Downloading $trackName',
'$artistName$percentage%',
details,
);
}
@@ -133,10 +133,10 @@ class NotificationService {
);
await _notifications.show(
id: downloadProgressId,
title: 'Finalizing $trackName',
body: '$artistName • Embedding metadata...',
notificationDetails: details,
downloadProgressId,
'Finalizing $trackName',
'$artistName • Embedding metadata...',
details,
);
}
@@ -183,10 +183,10 @@ class NotificationService {
);
await _notifications.show(
id: downloadProgressId,
title: title,
body: '$trackName - $artistName',
notificationDetails: details,
downloadProgressId,
title,
'$trackName - $artistName',
details,
);
}
@@ -223,15 +223,15 @@ class NotificationService {
);
await _notifications.show(
id: downloadProgressId,
title: title,
body: '$completedCount tracks downloaded successfully',
notificationDetails: details,
downloadProgressId,
title,
'$completedCount tracks downloaded successfully',
details,
);
}
Future<void> cancelDownloadNotification() async {
await _notifications.cancel(id: downloadProgressId);
await _notifications.cancel(downloadProgressId);
}
Future<void> showUpdateDownloadProgress({
@@ -274,10 +274,10 @@ class NotificationService {
);
await _notifications.show(
id: updateDownloadId,
title: 'Downloading SpotiFLAC v$version',
body: '$receivedMB / $totalMB MB • $percentage%',
notificationDetails: details,
updateDownloadId,
'Downloading SpotiFLAC v$version',
'$receivedMB / $totalMB MB • $percentage%',
details,
);
}
@@ -307,10 +307,10 @@ class NotificationService {
);
await _notifications.show(
id: updateDownloadId,
title: 'Update Ready',
body: 'SpotiFLAC v$version downloaded. Tap to install.',
notificationDetails: details,
updateDownloadId,
'Update Ready',
'SpotiFLAC v$version downloaded. Tap to install.',
details,
);
}
@@ -339,14 +339,14 @@ class NotificationService {
);
await _notifications.show(
id: updateDownloadId,
title: 'Update Failed',
body: 'Could not download update. Try again later.',
notificationDetails: details,
updateDownloadId,
'Update Failed',
'Could not download update. Try again later.',
details,
);
}
Future<void> cancelUpdateNotification() async {
await _notifications.cancel(id: updateDownloadId);
await _notifications.cancel(updateDownloadId);
}
}
+36 -28
View File
@@ -189,10 +189,10 @@ packages:
dependency: "direct main"
description:
name: connectivity_plus
sha256: "33bae12a398f841c6cda09d1064212957265869104c478e5ad51e2fb26c3973c"
sha256: b5e72753cf63becce2c61fd04dfe0f1c430cc5278b53a1342dc5ad839eab29ec
url: "https://pub.dev"
source: hosted
version: "7.0.0"
version: "6.1.5"
connectivity_plus_platform_interface:
dependency: transitive
description:
@@ -386,34 +386,34 @@ packages:
dependency: "direct main"
description:
name: flutter_local_notifications
sha256: "76cd20bcfa72fabe50ea27eeaf165527f446f55d3033021462084b87805b4cac"
sha256: "19ffb0a8bb7407875555e5e98d7343a633bb73707bae6c6a5f37c90014077875"
url: "https://pub.dev"
source: hosted
version: "20.0.0"
version: "19.5.0"
flutter_local_notifications_linux:
dependency: transitive
description:
name: flutter_local_notifications_linux
sha256: dce0116868cedd2cdf768af0365fc37ff1cbef7c02c4f51d0587482e625868d0
sha256: e3c277b2daab8e36ac5a6820536668d07e83851aeeb79c446e525a70710770a5
url: "https://pub.dev"
source: hosted
version: "7.0.0"
version: "6.0.0"
flutter_local_notifications_platform_interface:
dependency: transitive
description:
name: flutter_local_notifications_platform_interface
sha256: "23de31678a48c084169d7ae95866df9de5c9d2a44be3e5915a2ff067aeeba899"
sha256: "277d25d960c15674ce78ca97f57d0bae2ee401c844b6ac80fcd972a9c99d09fe"
url: "https://pub.dev"
source: hosted
version: "10.0.0"
version: "9.1.0"
flutter_local_notifications_windows:
dependency: transitive
description:
name: flutter_local_notifications_windows
sha256: "7ddd964fa85b6a23e96956c5b63ef55cdb9e5947b71b95712204db42ad46da61"
sha256: "8d658f0d367c48bd420e7cf2d26655e2d1130147bca1eea917e576ca76668aaf"
url: "https://pub.dev"
source: hosted
version: "2.0.0"
version: "1.0.3"
flutter_localizations:
dependency: "direct main"
description: flutter
@@ -439,50 +439,50 @@ packages:
dependency: "direct main"
description:
name: flutter_secure_storage
sha256: da922f2aab2d733db7e011a6bcc4a825b844892d4edd6df83ff156b09a9b2e40
sha256: "9cad52d75ebc511adfae3d447d5d13da15a55a92c9410e50f67335b6d21d16ea"
url: "https://pub.dev"
source: hosted
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"
version: "9.2.4"
flutter_secure_storage_linux:
dependency: transitive
description:
name: flutter_secure_storage_linux
sha256: "2b5c76dce569ab752d55a1cee6a2242bcc11fdba927078fb88c503f150767cda"
sha256: be76c1d24a97d0b98f8b54bce6b481a380a6590df992d0098f868ad54dc8f688
url: "https://pub.dev"
source: hosted
version: "3.0.0"
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"
flutter_secure_storage_platform_interface:
dependency: transitive
description:
name: flutter_secure_storage_platform_interface
sha256: "8ceea1223bee3c6ac1a22dabd8feefc550e4729b3675de4b5900f55afcb435d6"
sha256: cf91ad32ce5adef6fba4d736a542baca9daf3beac4db2d04be350b87f69ac4a8
url: "https://pub.dev"
source: hosted
version: "2.0.1"
version: "1.1.2"
flutter_secure_storage_web:
dependency: transitive
description:
name: flutter_secure_storage_web
sha256: "6a1137df62b84b54261dca582c1c09ea72f4f9a4b2fcee21b025964132d5d0c3"
sha256: f4ebff989b4f07b2656fb16b47852c0aab9fed9b4ec1c70103368337bc1886a9
url: "https://pub.dev"
source: hosted
version: "2.1.0"
version: "1.2.1"
flutter_secure_storage_windows:
dependency: transitive
description:
name: flutter_secure_storage_windows
sha256: "3b7c8e068875dfd46719ff57c90d8c459c87f2302ed6b00ff006b3c9fcad1613"
sha256: b20b07cb5ed4ed74fc567b78a72936203f587eba460af1df11281c9326cd3709
url: "https://pub.dev"
source: hosted
version: "4.1.0"
version: "3.1.2"
flutter_svg:
dependency: "direct main"
description:
@@ -741,6 +741,14 @@ 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: