diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 0ea69f2b..d8e291ca 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -4659,6 +4659,30 @@ abstract class AppLocalizations { /// **'Artist Name Filters'** String get downloadArtistNameFilters; + /// Setting title for adding a playlist folder prefix before the normal organization structure + /// + /// In en, this message translates to: + /// **'Create playlist source folder'** + String get downloadCreatePlaylistSourceFolder; + + /// Subtitle when playlist source folder prefix is enabled + /// + /// In en, this message translates to: + /// **'Playlist downloads use Playlist/ plus your normal folder structure.'** + String get downloadCreatePlaylistSourceFolderEnabled; + + /// Subtitle when playlist source folder prefix is disabled + /// + /// In en, this message translates to: + /// **'Playlist downloads use the normal folder structure only.'** + String get downloadCreatePlaylistSourceFolderDisabled; + + /// Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist + /// + /// In en, this message translates to: + /// **'By Playlist already places downloads inside a playlist folder.'** + String get downloadCreatePlaylistSourceFolderRedundant; + /// Setting title for SongLink country region /// /// In en, this message translates to: diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 7ad45e21..b58fd78c 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -2712,6 +2712,22 @@ class AppLocalizationsDe extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 13208d5b..328a4a9d 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -2685,6 +2685,22 @@ class AppLocalizationsEn extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 113c08f1..6308f8f4 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -2685,6 +2685,22 @@ class AppLocalizationsEs extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index 6521f755..f16c40f8 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -2686,6 +2686,22 @@ class AppLocalizationsFr extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index 04227142..2022128f 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -2684,6 +2684,22 @@ class AppLocalizationsHi extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index b23768e3..dce7f058 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -2692,6 +2692,22 @@ class AppLocalizationsId extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Buat folder sumber playlist'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Unduhan dari playlist memakai Playlist/ lalu struktur folder normal Anda.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Unduhan dari playlist hanya memakai struktur folder normal.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'Mode Berdasarkan Playlist sudah menaruh unduhan ke dalam folder playlist.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index caf7d16e..5f1fc709 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -2671,6 +2671,22 @@ class AppLocalizationsJa extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index 1cac05e7..5d4f256f 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -2664,6 +2664,22 @@ class AppLocalizationsKo extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index ac2c8b57..ef14b913 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -2684,6 +2684,22 @@ class AppLocalizationsNl extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 3c5c8896..d1fb718a 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -2685,6 +2685,22 @@ class AppLocalizationsPt extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 31a1a1dc..47398466 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -2743,6 +2743,22 @@ class AppLocalizationsRu extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index 518bcf77..7c77c93e 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -2696,6 +2696,22 @@ class AppLocalizationsTr extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index d36cf154..5ce4df91 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -2685,6 +2685,22 @@ class AppLocalizationsZh extends AppLocalizations { @override String get downloadArtistNameFilters => 'Artist Name Filters'; + @override + String get downloadCreatePlaylistSourceFolder => + 'Create playlist source folder'; + + @override + String get downloadCreatePlaylistSourceFolderEnabled => + 'Playlist downloads use Playlist/ plus your normal folder structure.'; + + @override + String get downloadCreatePlaylistSourceFolderDisabled => + 'Playlist downloads use the normal folder structure only.'; + + @override + String get downloadCreatePlaylistSourceFolderRedundant => + 'By Playlist already places downloads inside a playlist folder.'; + @override String get downloadSongLinkRegion => 'SongLink Region'; diff --git a/lib/l10n/arb/app_en.arb b/lib/l10n/arb/app_en.arb index 7055840d..69aa2068 100644 --- a/lib/l10n/arb/app_en.arb +++ b/lib/l10n/arb/app_en.arb @@ -3586,6 +3586,22 @@ "@downloadArtistNameFilters": { "description": "Setting title for artist folder filter options" }, + "downloadCreatePlaylistSourceFolder": "Create playlist source folder", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Playlist downloads use Playlist/ plus your normal folder structure.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Playlist downloads use the normal folder structure only.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "By Playlist already places downloads inside a playlist folder.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, "downloadSongLinkRegion": "SongLink Region", "@downloadSongLinkRegion": { "description": "Setting title for SongLink country region" diff --git a/lib/l10n/arb/app_id.arb b/lib/l10n/arb/app_id.arb index ff764ec8..bb02bc50 100644 --- a/lib/l10n/arb/app_id.arb +++ b/lib/l10n/arb/app_id.arb @@ -1797,6 +1797,22 @@ "@downloadUseAlbumArtistForFolders": { "description": "Setting - choose whether artist folders use Album Artist or Track Artist" }, + "downloadCreatePlaylistSourceFolder": "Buat folder sumber playlist", + "@downloadCreatePlaylistSourceFolder": { + "description": "Setting title for adding a playlist folder prefix before the normal organization structure" + }, + "downloadCreatePlaylistSourceFolderEnabled": "Unduhan dari playlist memakai Playlist/ lalu struktur folder normal Anda.", + "@downloadCreatePlaylistSourceFolderEnabled": { + "description": "Subtitle when playlist source folder prefix is enabled" + }, + "downloadCreatePlaylistSourceFolderDisabled": "Unduhan dari playlist hanya memakai struktur folder normal.", + "@downloadCreatePlaylistSourceFolderDisabled": { + "description": "Subtitle when playlist source folder prefix is disabled" + }, + "downloadCreatePlaylistSourceFolderRedundant": "Mode Berdasarkan Playlist sudah menaruh unduhan ke dalam folder playlist.", + "@downloadCreatePlaylistSourceFolderRedundant": { + "description": "Subtitle when playlist folder prefix setting is redundant because folder organization is already by playlist" + }, "downloadUsePrimaryArtistOnly": "Primary artist only for folders", "@downloadUsePrimaryArtistOnly": { "description": "Setting - strip featured artists from folder name" diff --git a/lib/models/settings.dart b/lib/models/settings.dart index 87c31e1b..c8848ab7 100644 --- a/lib/models/settings.dart +++ b/lib/models/settings.dart @@ -20,6 +20,7 @@ class AppSettings { final String updateChannel; final bool hasSearchedBefore; final String folderOrganization; + final bool createPlaylistFolder; final bool useAlbumArtistForFolders; final bool usePrimaryArtistOnly; // Strip featured artists from folder name final bool filterContributingArtistsInAlbumArtist; @@ -96,6 +97,7 @@ class AppSettings { this.updateChannel = 'stable', this.hasSearchedBefore = false, this.folderOrganization = 'none', + this.createPlaylistFolder = false, this.useAlbumArtistForFolders = true, this.usePrimaryArtistOnly = false, this.filterContributingArtistsInAlbumArtist = false, @@ -159,6 +161,7 @@ class AppSettings { String? updateChannel, bool? hasSearchedBefore, String? folderOrganization, + bool? createPlaylistFolder, bool? useAlbumArtistForFolders, bool? usePrimaryArtistOnly, bool? filterContributingArtistsInAlbumArtist, @@ -215,6 +218,7 @@ class AppSettings { updateChannel: updateChannel ?? this.updateChannel, hasSearchedBefore: hasSearchedBefore ?? this.hasSearchedBefore, folderOrganization: folderOrganization ?? this.folderOrganization, + createPlaylistFolder: createPlaylistFolder ?? this.createPlaylistFolder, useAlbumArtistForFolders: useAlbumArtistForFolders ?? this.useAlbumArtistForFolders, usePrimaryArtistOnly: usePrimaryArtistOnly ?? this.usePrimaryArtistOnly, @@ -255,8 +259,7 @@ class AppSettings { localLibraryBookmark: localLibraryBookmark ?? this.localLibraryBookmark, localLibraryShowDuplicates: localLibraryShowDuplicates ?? this.localLibraryShowDuplicates, - localLibraryAutoScan: - localLibraryAutoScan ?? this.localLibraryAutoScan, + localLibraryAutoScan: localLibraryAutoScan ?? this.localLibraryAutoScan, hasCompletedTutorial: hasCompletedTutorial ?? this.hasCompletedTutorial, lyricsProviders: lyricsProviders ?? this.lyricsProviders, lyricsIncludeTranslationNetease: diff --git a/lib/models/settings.g.dart b/lib/models/settings.g.dart index 2b6a35cf..026569dd 100644 --- a/lib/models/settings.g.dart +++ b/lib/models/settings.g.dart @@ -23,6 +23,7 @@ AppSettings _$AppSettingsFromJson(Map json) => AppSettings( updateChannel: json['updateChannel'] as String? ?? 'stable', hasSearchedBefore: json['hasSearchedBefore'] as bool? ?? false, folderOrganization: json['folderOrganization'] as String? ?? 'none', + createPlaylistFolder: json['createPlaylistFolder'] as bool? ?? false, useAlbumArtistForFolders: json['useAlbumArtistForFolders'] as bool? ?? true, usePrimaryArtistOnly: json['usePrimaryArtistOnly'] as bool? ?? false, filterContributingArtistsInAlbumArtist: @@ -100,6 +101,7 @@ Map _$AppSettingsToJson( 'updateChannel': instance.updateChannel, 'hasSearchedBefore': instance.hasSearchedBefore, 'folderOrganization': instance.folderOrganization, + 'createPlaylistFolder': instance.createPlaylistFolder, 'useAlbumArtistForFolders': instance.useAlbumArtistForFolders, 'usePrimaryArtistOnly': instance.usePrimaryArtistOnly, 'filterContributingArtistsInAlbumArtist': diff --git a/lib/providers/download_queue_provider.dart b/lib/providers/download_queue_provider.dart index 8901c647..4a308850 100644 --- a/lib/providers/download_queue_provider.dart +++ b/lib/providers/download_queue_provider.dart @@ -1651,12 +1651,23 @@ class DownloadQueueNotifier extends Notifier { String folderOrganization, { bool separateSingles = false, String albumFolderStructure = 'artist_album', + bool createPlaylistFolder = false, bool useAlbumArtistForFolders = true, bool usePrimaryArtistOnly = false, bool filterContributingArtistsInAlbumArtist = false, String? playlistName, }) async { String baseDir = state.outputDir; + if (createPlaylistFolder && + folderOrganization != 'playlist' && + playlistName != null && + playlistName.isNotEmpty) { + final playlistFolder = _sanitizeFolderName(playlistName); + if (playlistFolder.isNotEmpty) { + baseDir = '$baseDir${Platform.pathSeparator}$playlistFolder'; + await _ensureDirExists(baseDir, label: 'Playlist folder'); + } + } final normalizedAlbumArtist = normalizeOptionalString(track.albumArtist); var folderArtist = useAlbumArtistForFolders ? normalizedAlbumArtist ?? track.artistName @@ -1809,11 +1820,19 @@ class DownloadQueueNotifier extends Notifier { String folderOrganization, { bool separateSingles = false, String albumFolderStructure = 'artist_album', + bool createPlaylistFolder = false, bool useAlbumArtistForFolders = true, bool usePrimaryArtistOnly = false, bool filterContributingArtistsInAlbumArtist = false, String? playlistName, }) async { + final playlistPrefix = + createPlaylistFolder && + folderOrganization != 'playlist' && + playlistName != null && + playlistName.isNotEmpty + ? _sanitizeFolderName(playlistName) + : ''; final normalizedAlbumArtist = normalizeOptionalString(track.albumArtist); var folderArtist = useAlbumArtistForFolders ? normalizedAlbumArtist ?? track.artistName @@ -1833,34 +1852,40 @@ class DownloadQueueNotifier extends Notifier { if (albumFolderStructure == 'artist_album_singles') { if (isSingle) { - return '$artistName/Singles'; + return _joinRelativePath(playlistPrefix, '$artistName/Singles'); } final albumName = _sanitizeFolderName(track.albumName); - return '$artistName/$albumName'; + return _joinRelativePath(playlistPrefix, '$artistName/$albumName'); } if (isSingle) { - return 'Singles'; + return _joinRelativePath(playlistPrefix, 'Singles'); } final albumName = _sanitizeFolderName(track.albumName); final year = _extractYear(track.releaseDate); switch (albumFolderStructure) { case 'album_only': - return 'Albums/$albumName'; + return _joinRelativePath(playlistPrefix, 'Albums/$albumName'); case 'artist_year_album': final yearAlbum = year != null ? '[$year] $albumName' : albumName; - return 'Albums/$artistName/$yearAlbum'; + return _joinRelativePath( + playlistPrefix, + 'Albums/$artistName/$yearAlbum', + ); case 'year_album': final yearAlbum = year != null ? '[$year] $albumName' : albumName; - return 'Albums/$yearAlbum'; + return _joinRelativePath(playlistPrefix, 'Albums/$yearAlbum'); default: - return 'Albums/$artistName/$albumName'; + return _joinRelativePath( + playlistPrefix, + 'Albums/$artistName/$albumName', + ); } } if (folderOrganization == 'none') { - return ''; + return playlistPrefix; } switch (folderOrganization) { @@ -1870,18 +1895,30 @@ class DownloadQueueNotifier extends Notifier { } return ''; case 'artist': - return _sanitizeFolderName(folderArtist); + return _joinRelativePath( + playlistPrefix, + _sanitizeFolderName(folderArtist), + ); case 'album': - return _sanitizeFolderName(track.albumName); + return _joinRelativePath( + playlistPrefix, + _sanitizeFolderName(track.albumName), + ); case 'artist_album': final artistName = _sanitizeFolderName(folderArtist); final albumName = _sanitizeFolderName(track.albumName); - return '$artistName/$albumName'; + return _joinRelativePath(playlistPrefix, '$artistName/$albumName'); default: - return ''; + return playlistPrefix; } } + String _joinRelativePath(String prefix, String suffix) { + if (prefix.isEmpty) return suffix; + if (suffix.isEmpty) return prefix; + return '$prefix/$suffix'; + } + String _determineOutputExt(String quality, String service) { if (service.toLowerCase() == 'youtube') { if (quality.toLowerCase().contains('mp3')) { @@ -3547,6 +3584,7 @@ class DownloadQueueNotifier extends Notifier { settings.folderOrganization, separateSingles: settings.separateSingles, albumFolderStructure: settings.albumFolderStructure, + createPlaylistFolder: settings.createPlaylistFolder, useAlbumArtistForFolders: settings.useAlbumArtistForFolders, usePrimaryArtistOnly: settings.usePrimaryArtistOnly, filterContributingArtistsInAlbumArtist: @@ -3562,6 +3600,7 @@ class DownloadQueueNotifier extends Notifier { settings.folderOrganization, separateSingles: settings.separateSingles, albumFolderStructure: settings.albumFolderStructure, + createPlaylistFolder: settings.createPlaylistFolder, useAlbumArtistForFolders: settings.useAlbumArtistForFolders, usePrimaryArtistOnly: settings.usePrimaryArtistOnly, filterContributingArtistsInAlbumArtist: @@ -3905,10 +3944,12 @@ class DownloadQueueNotifier extends Notifier { settings.folderOrganization, separateSingles: settings.separateSingles, albumFolderStructure: settings.albumFolderStructure, + createPlaylistFolder: settings.createPlaylistFolder, useAlbumArtistForFolders: settings.useAlbumArtistForFolders, usePrimaryArtistOnly: settings.usePrimaryArtistOnly, filterContributingArtistsInAlbumArtist: settings.filterContributingArtistsInAlbumArtist, + playlistName: item.playlistName, ); final fallbackResult = await runDownload( useSaf: false, diff --git a/lib/providers/settings_provider.dart b/lib/providers/settings_provider.dart index fcab3631..44251a03 100644 --- a/lib/providers/settings_provider.dart +++ b/lib/providers/settings_provider.dart @@ -375,6 +375,11 @@ class SettingsNotifier extends Notifier { _saveSettings(); } + void setCreatePlaylistFolder(bool enabled) { + state = state.copyWith(createPlaylistFolder: enabled); + _saveSettings(); + } + void setUseAlbumArtistForFolders(bool enabled) { state = state.copyWith(useAlbumArtistForFolders: enabled); _saveSettings(); diff --git a/lib/screens/settings/download_settings_page.dart b/lib/screens/settings/download_settings_page.dart index fb583df7..218871f0 100644 --- a/lib/screens/settings/download_settings_page.dart +++ b/lib/screens/settings/download_settings_page.dart @@ -6,6 +6,7 @@ import 'package:path_provider/path_provider.dart'; import 'package:permission_handler/permission_handler.dart'; import 'package:device_info_plus/device_info_plus.dart'; import 'package:spotiflac_android/l10n/l10n.dart'; +import 'package:spotiflac_android/models/settings.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'; @@ -436,7 +437,8 @@ class _DownloadSettingsPageState extends ConsumerState { ], SettingsItem( title: context.l10n.youtubeOpusBitrateTitle, - subtitle: '${settings.youtubeOpusBitrate}kbps (128/256/320)', + subtitle: + '${settings.youtubeOpusBitrate}kbps (128/256/320)', onTap: () => _showYoutubeBitratePicker( context: context, title: context.l10n.youtubeOpusBitrateTitle, @@ -515,8 +517,12 @@ class _DownloadSettingsPageState extends ConsumerState { icon: Icons.translate_outlined, title: context.l10n.downloadNeteaseIncludeTranslation, subtitle: settings.lyricsIncludeTranslationNetease - ? context.l10n.downloadNeteaseIncludeTranslationEnabled - : context.l10n.downloadNeteaseIncludeTranslationDisabled, + ? context + .l10n + .downloadNeteaseIncludeTranslationEnabled + : context + .l10n + .downloadNeteaseIncludeTranslationDisabled, value: settings.lyricsIncludeTranslationNetease, onChanged: (value) => ref .read(settingsProvider.notifier) @@ -526,8 +532,12 @@ class _DownloadSettingsPageState extends ConsumerState { icon: Icons.text_fields_outlined, title: context.l10n.downloadNeteaseIncludeRomanization, subtitle: settings.lyricsIncludeRomanizationNetease - ? context.l10n.downloadNeteaseIncludeRomanizationEnabled - : context.l10n.downloadNeteaseIncludeRomanizationDisabled, + ? context + .l10n + .downloadNeteaseIncludeRomanizationEnabled + : context + .l10n + .downloadNeteaseIncludeRomanizationDisabled, value: settings.lyricsIncludeRomanizationNetease, onChanged: (value) => ref .read(settingsProvider.notifier) @@ -627,6 +637,15 @@ class _DownloadSettingsPageState extends ConsumerState { settings.folderOrganization, ), ), + SettingsSwitchItem( + icon: Icons.playlist_play_outlined, + title: context.l10n.downloadCreatePlaylistSourceFolder, + subtitle: _getPlaylistFolderSubtitle(settings), + value: settings.createPlaylistFolder, + onChanged: (value) => ref + .read(settingsProvider.notifier) + .setCreatePlaylistFolder(value), + ), SettingsSwitchItem( icon: Icons.person_search_outlined, title: context.l10n.downloadUseAlbumArtistForFolders, @@ -642,7 +661,7 @@ class _DownloadSettingsPageState extends ConsumerState { .read(settingsProvider.notifier) .setUseAlbumArtistForFolders(value), ), - SettingsItem( + SettingsItem( icon: Icons.filter_alt_outlined, title: context.l10n.downloadArtistNameFilters, subtitle: _getArtistFolderFilterSubtitle( @@ -1407,6 +1426,16 @@ class _DownloadSettingsPageState extends ConsumerState { } } + String _getPlaylistFolderSubtitle(AppSettings settings) { + if (settings.folderOrganization == 'playlist') { + return context.l10n.downloadCreatePlaylistSourceFolderRedundant; + } + if (settings.createPlaylistFolder) { + return context.l10n.downloadCreatePlaylistSourceFolderEnabled; + } + return context.l10n.downloadCreatePlaylistSourceFolderDisabled; + } + String _getArtistFolderFilterSubtitle( BuildContext context, { required bool usePrimaryArtistOnly, @@ -1776,17 +1805,17 @@ class _DownloadSettingsPageState extends ConsumerState { children: [ Padding( padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), - child: Text( - context.l10n.downloadSongLinkRegion, - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), + child: Text( + context.l10n.downloadSongLinkRegion, + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), + ), ), - ), - Padding( - padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), - child: Text( - context.l10n.downloadSongLinkRegionDesc, + Padding( + padding: const EdgeInsets.fromLTRB(24, 0, 24, 16), + child: Text( + context.l10n.downloadSongLinkRegionDesc, style: Theme.of(context).textTheme.bodyMedium?.copyWith( color: colorScheme.onSurfaceVariant, ), @@ -1847,12 +1876,12 @@ class _DownloadSettingsPageState extends ConsumerState { children: [ Padding( padding: const EdgeInsets.fromLTRB(24, 24, 24, 8), - child: Text( - context.l10n.downloadFolderOrganization, - style: Theme.of( - context, - ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), - ), + child: Text( + context.l10n.downloadFolderOrganization, + style: Theme.of( + context, + ).textTheme.titleLarge?.copyWith(fontWeight: FontWeight.bold), + ), ), Padding( padding: const EdgeInsets.fromLTRB(24, 0, 24, 16),