mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-23 00:09:51 +02:00
feat: add 'By Playlist' folder organization option (#111)
This commit is contained in:
@@ -1456,6 +1456,18 @@ abstract class AppLocalizations {
|
||||
/// **'No organization'**
|
||||
String get folderOrganizationNone;
|
||||
|
||||
/// Folder option - playlist folders
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'By Playlist'**
|
||||
String get folderOrganizationByPlaylist;
|
||||
|
||||
/// Subtitle for playlist folder option
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Separate folder for each playlist'**
|
||||
String get folderOrganizationByPlaylistSubtitle;
|
||||
|
||||
/// Folder option - artist folders
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -785,6 +785,13 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => 'Keine Organisation';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'Nach Künstler';
|
||||
|
||||
|
||||
@@ -772,6 +772,13 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => 'No organization';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'By Artist';
|
||||
|
||||
|
||||
@@ -772,6 +772,13 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => 'No organization';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'By Artist';
|
||||
|
||||
|
||||
@@ -774,6 +774,13 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => 'No organization';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'By Artist';
|
||||
|
||||
|
||||
@@ -772,6 +772,13 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => 'No organization';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'By Artist';
|
||||
|
||||
|
||||
@@ -775,6 +775,13 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => 'Tidak ada';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'Berdasarkan Artis';
|
||||
|
||||
|
||||
@@ -767,6 +767,13 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => '構成がありません';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'アーティスト別';
|
||||
|
||||
|
||||
@@ -754,6 +754,13 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => '정리하지 않음';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'By Artist';
|
||||
|
||||
|
||||
@@ -772,6 +772,13 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => 'No organization';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'By Artist';
|
||||
|
||||
|
||||
@@ -772,6 +772,13 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => 'No organization';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'By Artist';
|
||||
|
||||
|
||||
@@ -786,6 +786,13 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => 'Без организации';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'По исполнителю';
|
||||
|
||||
|
||||
@@ -777,6 +777,13 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => 'Organizasyon yok';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'Sanatçıya Göre';
|
||||
|
||||
|
||||
@@ -772,6 +772,13 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
@override
|
||||
String get folderOrganizationNone => 'No organization';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylist => 'By Playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByPlaylistSubtitle =>
|
||||
'Separate folder for each playlist';
|
||||
|
||||
@override
|
||||
String get folderOrganizationByArtist => 'By Artist';
|
||||
|
||||
|
||||
@@ -1015,6 +1015,14 @@
|
||||
"@folderOrganizationNone": {
|
||||
"description": "Folder option - flat structure"
|
||||
},
|
||||
"folderOrganizationByPlaylist": "By Playlist",
|
||||
"@folderOrganizationByPlaylist": {
|
||||
"description": "Folder option - playlist folders"
|
||||
},
|
||||
"folderOrganizationByPlaylistSubtitle": "Separate folder for each playlist",
|
||||
"@folderOrganizationByPlaylistSubtitle": {
|
||||
"description": "Subtitle for playlist folder option"
|
||||
},
|
||||
"folderOrganizationByArtist": "By Artist",
|
||||
"@folderOrganizationByArtist": {
|
||||
"description": "Folder option - artist folders"
|
||||
|
||||
@@ -34,6 +34,7 @@ class DownloadItem {
|
||||
final DownloadErrorType? errorType;
|
||||
final DateTime createdAt;
|
||||
final String? qualityOverride; // Override quality for this specific download
|
||||
final String? playlistName; // Playlist context for folder organization
|
||||
|
||||
const DownloadItem({
|
||||
required this.id,
|
||||
@@ -48,6 +49,7 @@ class DownloadItem {
|
||||
this.errorType,
|
||||
required this.createdAt,
|
||||
this.qualityOverride,
|
||||
this.playlistName,
|
||||
});
|
||||
|
||||
DownloadItem copyWith({
|
||||
@@ -63,6 +65,7 @@ class DownloadItem {
|
||||
DownloadErrorType? errorType,
|
||||
DateTime? createdAt,
|
||||
String? qualityOverride,
|
||||
String? playlistName,
|
||||
}) {
|
||||
return DownloadItem(
|
||||
id: id ?? this.id,
|
||||
@@ -77,6 +80,7 @@ class DownloadItem {
|
||||
errorType: errorType ?? this.errorType,
|
||||
createdAt: createdAt ?? this.createdAt,
|
||||
qualityOverride: qualityOverride ?? this.qualityOverride,
|
||||
playlistName: playlistName ?? this.playlistName,
|
||||
);
|
||||
}
|
||||
|
||||
|
||||
@@ -21,6 +21,7 @@ DownloadItem _$DownloadItemFromJson(Map<String, dynamic> json) => DownloadItem(
|
||||
errorType: $enumDecodeNullable(_$DownloadErrorTypeEnumMap, json['errorType']),
|
||||
createdAt: DateTime.parse(json['createdAt'] as String),
|
||||
qualityOverride: json['qualityOverride'] as String?,
|
||||
playlistName: json['playlistName'] as String?,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$DownloadItemToJson(DownloadItem instance) =>
|
||||
@@ -37,6 +38,7 @@ Map<String, dynamic> _$DownloadItemToJson(DownloadItem instance) =>
|
||||
'errorType': _$DownloadErrorTypeEnumMap[instance.errorType],
|
||||
'createdAt': instance.createdAt.toIso8601String(),
|
||||
'qualityOverride': instance.qualityOverride,
|
||||
'playlistName': instance.playlistName,
|
||||
};
|
||||
|
||||
const _$DownloadStatusEnumMap = {
|
||||
|
||||
@@ -1573,6 +1573,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
bool useAlbumArtistForFolders = true,
|
||||
bool usePrimaryArtistOnly = false,
|
||||
bool filterContributingArtistsInAlbumArtist = false,
|
||||
String? playlistName,
|
||||
}) async {
|
||||
String baseDir = state.outputDir;
|
||||
final normalizedAlbumArtist = normalizeOptionalString(track.albumArtist);
|
||||
@@ -1647,6 +1648,11 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
|
||||
String subPath = '';
|
||||
switch (folderOrganization) {
|
||||
case 'playlist':
|
||||
if (playlistName != null && playlistName.isNotEmpty) {
|
||||
subPath = _sanitizeFolderName(playlistName);
|
||||
}
|
||||
break;
|
||||
case 'artist':
|
||||
final artistName = _sanitizeFolderName(folderArtist);
|
||||
subPath = artistName;
|
||||
@@ -1725,6 +1731,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
bool useAlbumArtistForFolders = true,
|
||||
bool usePrimaryArtistOnly = false,
|
||||
bool filterContributingArtistsInAlbumArtist = false,
|
||||
String? playlistName,
|
||||
}) async {
|
||||
final normalizedAlbumArtist = normalizeOptionalString(track.albumArtist);
|
||||
var folderArtist = useAlbumArtistForFolders
|
||||
@@ -1776,6 +1783,11 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
}
|
||||
|
||||
switch (folderOrganization) {
|
||||
case 'playlist':
|
||||
if (playlistName != null && playlistName.isNotEmpty) {
|
||||
return _sanitizeFolderName(playlistName);
|
||||
}
|
||||
return '';
|
||||
case 'artist':
|
||||
return _sanitizeFolderName(folderArtist);
|
||||
case 'album':
|
||||
@@ -1900,7 +1912,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
);
|
||||
}
|
||||
|
||||
String addToQueue(Track track, String service, {String? qualityOverride}) {
|
||||
String addToQueue(Track track, String service, {String? qualityOverride, String? playlistName}) {
|
||||
final settings = ref.read(settingsProvider);
|
||||
updateSettings(settings);
|
||||
|
||||
@@ -1912,6 +1924,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
service: service,
|
||||
createdAt: DateTime.now(),
|
||||
qualityOverride: qualityOverride,
|
||||
playlistName: playlistName,
|
||||
);
|
||||
|
||||
state = state.copyWith(items: [...state.items, item]);
|
||||
@@ -1928,6 +1941,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
List<Track> tracks,
|
||||
String service, {
|
||||
String? qualityOverride,
|
||||
String? playlistName,
|
||||
}) {
|
||||
final settings = ref.read(settingsProvider);
|
||||
updateSettings(settings);
|
||||
@@ -1942,6 +1956,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
service: service,
|
||||
createdAt: DateTime.now(),
|
||||
qualityOverride: qualityOverride,
|
||||
playlistName: playlistName,
|
||||
);
|
||||
}).toList();
|
||||
|
||||
@@ -3317,6 +3332,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
usePrimaryArtistOnly: settings.usePrimaryArtistOnly,
|
||||
filterContributingArtistsInAlbumArtist:
|
||||
settings.filterContributingArtistsInAlbumArtist,
|
||||
playlistName: item.playlistName,
|
||||
)
|
||||
: '';
|
||||
String? appOutputDir;
|
||||
@@ -3331,6 +3347,7 @@ class DownloadQueueNotifier extends Notifier<DownloadQueueState> {
|
||||
usePrimaryArtistOnly: settings.usePrimaryArtistOnly,
|
||||
filterContributingArtistsInAlbumArtist:
|
||||
settings.filterContributingArtistsInAlbumArtist,
|
||||
playlistName: item.playlistName,
|
||||
);
|
||||
var effectiveOutputDir = initialOutputDir;
|
||||
var effectiveSafMode = isSafMode;
|
||||
|
||||
@@ -416,7 +416,7 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
|
||||
onSelect: (quality, service) {
|
||||
ref
|
||||
.read(downloadQueueProvider.notifier)
|
||||
.addToQueue(track, service, qualityOverride: quality);
|
||||
.addToQueue(track, service, qualityOverride: quality, playlistName: widget.playlistName);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.snackbarAddedToQueue(track.name)),
|
||||
@@ -427,7 +427,7 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
|
||||
} else {
|
||||
ref
|
||||
.read(downloadQueueProvider.notifier)
|
||||
.addToQueue(track, settings.defaultService);
|
||||
.addToQueue(track, settings.defaultService, playlistName: widget.playlistName);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.snackbarAddedToQueue(track.name))),
|
||||
);
|
||||
@@ -586,7 +586,7 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
|
||||
onSelect: (quality, service) {
|
||||
ref
|
||||
.read(downloadQueueProvider.notifier)
|
||||
.addMultipleToQueue(tracks, service, qualityOverride: quality);
|
||||
.addMultipleToQueue(tracks, service, qualityOverride: quality, playlistName: widget.playlistName);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
@@ -599,7 +599,7 @@ class _PlaylistScreenState extends ConsumerState<PlaylistScreen> {
|
||||
} else {
|
||||
ref
|
||||
.read(downloadQueueProvider.notifier)
|
||||
.addMultipleToQueue(tracks, settings.defaultService);
|
||||
.addMultipleToQueue(tracks, settings.defaultService, playlistName: widget.playlistName);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.snackbarAddedTracksToQueue(tracks.length)),
|
||||
|
||||
@@ -1405,6 +1405,8 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
|
||||
String _getFolderOrganizationLabel(String value) {
|
||||
switch (value) {
|
||||
case 'playlist':
|
||||
return 'By Playlist';
|
||||
case 'artist':
|
||||
return 'By Artist';
|
||||
case 'album':
|
||||
@@ -1982,6 +1984,18 @@ class _DownloadSettingsPageState extends ConsumerState<DownloadSettingsPage> {
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
_FolderOption(
|
||||
title: context.l10n.folderOrganizationByPlaylist,
|
||||
subtitle: context.l10n.folderOrganizationByPlaylistSubtitle,
|
||||
example: 'SpotiFLAC/Playlist Name/Track.flac',
|
||||
isSelected: current == 'playlist',
|
||||
onTap: () {
|
||||
ref
|
||||
.read(settingsProvider.notifier)
|
||||
.setFolderOrganization('playlist');
|
||||
Navigator.pop(context);
|
||||
},
|
||||
),
|
||||
_FolderOption(
|
||||
title: context.l10n.folderOrganizationByArtist,
|
||||
subtitle: context.l10n.folderOrganizationByArtistSubtitle,
|
||||
|
||||
Reference in New Issue
Block a user