From f7be2c1e120e6fd8cd7d1d447f02f67866f97862 Mon Sep 17 00:00:00 2001 From: zarzet Date: Tue, 10 Feb 2026 09:07:18 +0700 Subject: [PATCH] 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 --- .../kotlin/com/zarz/spotiflac/MainActivity.kt | 42 ++++++++---- lib/l10n/app_localizations.dart | 18 ++++++ lib/l10n/app_localizations_de.dart | 11 ++++ lib/l10n/app_localizations_en.dart | 11 ++++ lib/l10n/app_localizations_es.dart | 11 ++++ lib/l10n/app_localizations_fr.dart | 11 ++++ lib/l10n/app_localizations_hi.dart | 11 ++++ lib/l10n/app_localizations_id.dart | 11 ++++ lib/l10n/app_localizations_ja.dart | 11 ++++ lib/l10n/app_localizations_ko.dart | 11 ++++ lib/l10n/app_localizations_nl.dart | 11 ++++ lib/l10n/app_localizations_pt.dart | 11 ++++ lib/l10n/app_localizations_ru.dart | 11 ++++ lib/l10n/app_localizations_tr.dart | 11 ++++ lib/l10n/app_localizations_zh.dart | 11 ++++ lib/l10n/arb/app_en.arb | 6 ++ lib/l10n/arb/app_id.arb | 12 ++++ lib/models/settings.dart | 5 ++ lib/models/settings.g.dart | 2 + lib/providers/download_queue_provider.dart | 28 +++++++- lib/providers/settings_provider.dart | 5 ++ .../settings/download_settings_page.dart | 12 ++++ lib/services/notification_service.dart | 62 +++++++++--------- pubspec.lock | 64 +++++++++++-------- 24 files changed, 326 insertions(+), 73 deletions(-) diff --git a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt index 30bfdd77..d0f27b65 100644 --- a/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt +++ b/android/app/src/main/kotlin/com/zarz/spotiflac/MainActivity.kt @@ -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? { diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index abe371bc..d7713832 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -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: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index ffeb6845..e11c2238 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -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'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 25a79292..784777f1 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -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'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 2ee6b9a5..d1eda587 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -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'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index d2b7fe4d..600c9468 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -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'; diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index 5e635a9f..88daceea 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -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'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index 6885b02b..532d79e1 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -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'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 8a46307c..41f762b1 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -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 => '形式を保存'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 63c65cba..f71686e0 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -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'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index e144e991..453c445f 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -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'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 6ffcf11c..722e3afe 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -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'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 14844d15..331eaea1 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -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 => 'Формат сохранения'; diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index 79eca336..dd794add 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -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'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 938b5f94..b3478881 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -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'; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 8187756d..6b7d627f 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -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", diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index 84ee93e9..648e367f 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -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" diff --git a/lib/models/settings.dart b/lib/models/settings.dart index fac29ac8..11c904c7 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -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, diff --git a/lib/models/settings.g.dart b/lib/models/settings.g.dart index 9805762e..51c83220 100644 --- a/lib/models/settings.g.dart +++ b/lib/models/settings.g.dart @@ -23,6 +23,7 @@ AppSettings _$AppSettingsFromJson(Map 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 _$AppSettingsToJson(AppSettings instance) => 'hasSearchedBefore': instance.hasSearchedBefore, 'folderOrganization': instance.folderOrganization, 'useAlbumArtistForFolders': instance.useAlbumArtistForFolders, + 'usePrimaryArtistOnly': instance.usePrimaryArtistOnly, 'historyViewMode': instance.historyViewMode, 'historyFilterMode': instance.historyFilterMode, 'askQualityBeforeDownload': instance.askQualityBeforeDownload, diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index 6e7fdf75..0a634496 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -1033,11 +1033,15 @@ class DownloadQueueNotifier extends Notifier { 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 { .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 { 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 { separateSingles: settings.separateSingles, albumFolderStructure: settings.albumFolderStructure, useAlbumArtistForFolders: settings.useAlbumArtistForFolders, + usePrimaryArtistOnly: settings.usePrimaryArtistOnly, ) : ''; String? appOutputDir; @@ -2576,6 +2598,7 @@ class DownloadQueueNotifier extends Notifier { 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 { separateSingles: settings.separateSingles, albumFolderStructure: settings.albumFolderStructure, useAlbumArtistForFolders: settings.useAlbumArtistForFolders, + usePrimaryArtistOnly: settings.usePrimaryArtistOnly, ); final fallbackResult = await runDownload( useSaf: false, diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index 7a572567..fb684bb7 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -231,6 +231,11 @@ class SettingsNotifier extends Notifier { _saveSettings(); } + void setUsePrimaryArtistOnly(bool enabled) { + state = state.copyWith(usePrimaryArtistOnly: enabled); + _saveSettings(); + } + void setHistoryViewMode(String mode) { state = state.copyWith(historyViewMode: mode); _saveSettings(); diff --git a/lib/screens/settings/download_settings_page.dart b/lib/screens/settings/download_settings_page.dart index ca18ed54..0638e847 100644 --- a/lib/screens/settings/download_settings_page.dart +++ b/lib/screens/settings/download_settings_page.dart @@ -365,6 +365,18 @@ class _DownloadSettingsPageState extends ConsumerState { .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, + ), ], ), ), diff --git a/lib/services/notification_service.dart b/lib/services/notification_service.dart index 430c779c..13d84e72 100644 --- a/lib/services/notification_service.dart +++ b/lib/services/notification_service.dart @@ -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 cancelDownloadNotification() async { - await _notifications.cancel(id: downloadProgressId); + await _notifications.cancel(downloadProgressId); } Future 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 cancelUpdateNotification() async { - await _notifications.cancel(id: updateDownloadId); + await _notifications.cancel(updateDownloadId); } } diff --git a/pubspec.lock b/pubspec.lock index a250b404..59901618 100644 --- a/pubspec.lock +++ b/pubspec.lock @@ -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: