mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-05-19 06:38:08 +02:00
feat: selective auto-fill from online in Edit Metadata sheet
Add 'Auto-fill from online' expandable section to the metadata editor that lets users choose exactly which fields to populate from online metadata search. Users can select individual fields via filter chips, use 'All' or 'Empty only' quick-select buttons, then tap 'Fetch & Fill' to search metadata providers and fill only the selected controllers. The search uses existing searchTracksWithMetadataProviders API with ISRC-preferring best-match selection. Extended metadata (genre, label, copyright) is fetched via Deezer extended metadata API when available. Cover art is downloaded from the match's cover_url. All results are previewed in the editor before saving — nothing is written to the file until the user taps Save. Add 21 new l10n keys (editMetadata* namespace) for all UI strings.
This commit is contained in:
@@ -4817,6 +4817,132 @@ abstract class AppLocalizations {
|
||||
/// In en, this message translates to:
|
||||
/// **'{count, plural, =1{1 playlist} other{{count} playlists}}'**
|
||||
String playlistsCount(int count);
|
||||
|
||||
/// Section title for selective online metadata auto-fill in the edit metadata sheet
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Auto-fill from online'**
|
||||
String get editMetadataAutoFill;
|
||||
|
||||
/// Description for the auto-fill section
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select fields to fill automatically from online metadata'**
|
||||
String get editMetadataAutoFillDesc;
|
||||
|
||||
/// Button label to fetch online metadata and fill selected fields
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Fetch & Fill'**
|
||||
String get editMetadataAutoFillFetch;
|
||||
|
||||
/// Snackbar shown while searching for online metadata
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Searching online...'**
|
||||
String get editMetadataAutoFillSearching;
|
||||
|
||||
/// Snackbar when online metadata search returns no results
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No matching metadata found online'**
|
||||
String get editMetadataAutoFillNoResults;
|
||||
|
||||
/// Snackbar confirming how many fields were auto-filled
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Filled {count} {count, plural, =1{field} other{fields}} from online metadata'**
|
||||
String editMetadataAutoFillDone(int count);
|
||||
|
||||
/// Snackbar when user taps Fetch without selecting any fields
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Select at least one field to auto-fill'**
|
||||
String get editMetadataAutoFillNoneSelected;
|
||||
|
||||
/// Chip label for title field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Title'**
|
||||
String get editMetadataFieldTitle;
|
||||
|
||||
/// Chip label for artist field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Artist'**
|
||||
String get editMetadataFieldArtist;
|
||||
|
||||
/// Chip label for album field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Album'**
|
||||
String get editMetadataFieldAlbum;
|
||||
|
||||
/// Chip label for album artist field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Album Artist'**
|
||||
String get editMetadataFieldAlbumArtist;
|
||||
|
||||
/// Chip label for date field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Date'**
|
||||
String get editMetadataFieldDate;
|
||||
|
||||
/// Chip label for track number field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Track #'**
|
||||
String get editMetadataFieldTrackNum;
|
||||
|
||||
/// Chip label for disc number field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Disc #'**
|
||||
String get editMetadataFieldDiscNum;
|
||||
|
||||
/// Chip label for genre field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Genre'**
|
||||
String get editMetadataFieldGenre;
|
||||
|
||||
/// Chip label for ISRC field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'ISRC'**
|
||||
String get editMetadataFieldIsrc;
|
||||
|
||||
/// Chip label for label field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Label'**
|
||||
String get editMetadataFieldLabel;
|
||||
|
||||
/// Chip label for copyright field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Copyright'**
|
||||
String get editMetadataFieldCopyright;
|
||||
|
||||
/// Chip label for cover art field in auto-fill selector
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Cover Art'**
|
||||
String get editMetadataFieldCover;
|
||||
|
||||
/// Button to select all fields for auto-fill
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'All'**
|
||||
String get editMetadataSelectAll;
|
||||
|
||||
/// Button to select only fields that are currently empty
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Empty only'**
|
||||
String get editMetadataSelectEmpty;
|
||||
}
|
||||
|
||||
class _AppLocalizationsDelegate
|
||||
|
||||
@@ -2826,4 +2826,78 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
@@ -2798,4 +2798,78 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
@@ -2798,6 +2798,80 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
/// The translations for Spanish Castilian, as used in Spain (`es_ES`).
|
||||
|
||||
@@ -2800,4 +2800,78 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
@@ -2798,4 +2798,78 @@ class AppLocalizationsHi extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
@@ -2805,4 +2805,78 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
@@ -2785,4 +2785,78 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
@@ -2778,4 +2778,78 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
@@ -2798,4 +2798,78 @@ class AppLocalizationsNl extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
@@ -2798,6 +2798,80 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
/// The translations for Portuguese, as used in Portugal (`pt_PT`).
|
||||
|
||||
@@ -2857,4 +2857,78 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
@@ -2810,4 +2810,78 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
@@ -2798,6 +2798,80 @@ class AppLocalizationsZh extends AppLocalizations {
|
||||
);
|
||||
return '$_temp0';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFill => 'Auto-fill from online';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillDesc =>
|
||||
'Select fields to fill automatically from online metadata';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillFetch => 'Fetch & Fill';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillSearching => 'Searching online...';
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoResults =>
|
||||
'No matching metadata found online';
|
||||
|
||||
@override
|
||||
String editMetadataAutoFillDone(int count) {
|
||||
String _temp0 = intl.Intl.pluralLogic(
|
||||
count,
|
||||
locale: localeName,
|
||||
other: 'fields',
|
||||
one: 'field',
|
||||
);
|
||||
return 'Filled $count $_temp0 from online metadata';
|
||||
}
|
||||
|
||||
@override
|
||||
String get editMetadataAutoFillNoneSelected =>
|
||||
'Select at least one field to auto-fill';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTitle => 'Title';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldArtist => 'Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbum => 'Album';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldAlbumArtist => 'Album Artist';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDate => 'Date';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldTrackNum => 'Track #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldDiscNum => 'Disc #';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldGenre => 'Genre';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldIsrc => 'ISRC';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldLabel => 'Label';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCopyright => 'Copyright';
|
||||
|
||||
@override
|
||||
String get editMetadataFieldCover => 'Cover Art';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectAll => 'All';
|
||||
|
||||
@override
|
||||
String get editMetadataSelectEmpty => 'Empty only';
|
||||
}
|
||||
|
||||
/// The translations for Chinese, as used in China (`zh_CN`).
|
||||
|
||||
@@ -3706,5 +3706,94 @@
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"editMetadataAutoFill": "Auto-fill from online",
|
||||
"@editMetadataAutoFill": {
|
||||
"description": "Section title for selective online metadata auto-fill in the edit metadata sheet"
|
||||
},
|
||||
"editMetadataAutoFillDesc": "Select fields to fill automatically from online metadata",
|
||||
"@editMetadataAutoFillDesc": {
|
||||
"description": "Description for the auto-fill section"
|
||||
},
|
||||
"editMetadataAutoFillFetch": "Fetch & Fill",
|
||||
"@editMetadataAutoFillFetch": {
|
||||
"description": "Button label to fetch online metadata and fill selected fields"
|
||||
},
|
||||
"editMetadataAutoFillSearching": "Searching online...",
|
||||
"@editMetadataAutoFillSearching": {
|
||||
"description": "Snackbar shown while searching for online metadata"
|
||||
},
|
||||
"editMetadataAutoFillNoResults": "No matching metadata found online",
|
||||
"@editMetadataAutoFillNoResults": {
|
||||
"description": "Snackbar when online metadata search returns no results"
|
||||
},
|
||||
"editMetadataAutoFillDone": "Filled {count} {count, plural, =1{field} other{fields}} from online metadata",
|
||||
"@editMetadataAutoFillDone": {
|
||||
"description": "Snackbar confirming how many fields were auto-filled",
|
||||
"placeholders": {
|
||||
"count": {
|
||||
"type": "int"
|
||||
}
|
||||
}
|
||||
},
|
||||
"editMetadataAutoFillNoneSelected": "Select at least one field to auto-fill",
|
||||
"@editMetadataAutoFillNoneSelected": {
|
||||
"description": "Snackbar when user taps Fetch without selecting any fields"
|
||||
},
|
||||
"editMetadataFieldTitle": "Title",
|
||||
"@editMetadataFieldTitle": {
|
||||
"description": "Chip label for title field in auto-fill selector"
|
||||
},
|
||||
"editMetadataFieldArtist": "Artist",
|
||||
"@editMetadataFieldArtist": {
|
||||
"description": "Chip label for artist field in auto-fill selector"
|
||||
},
|
||||
"editMetadataFieldAlbum": "Album",
|
||||
"@editMetadataFieldAlbum": {
|
||||
"description": "Chip label for album field in auto-fill selector"
|
||||
},
|
||||
"editMetadataFieldAlbumArtist": "Album Artist",
|
||||
"@editMetadataFieldAlbumArtist": {
|
||||
"description": "Chip label for album artist field in auto-fill selector"
|
||||
},
|
||||
"editMetadataFieldDate": "Date",
|
||||
"@editMetadataFieldDate": {
|
||||
"description": "Chip label for date field in auto-fill selector"
|
||||
},
|
||||
"editMetadataFieldTrackNum": "Track #",
|
||||
"@editMetadataFieldTrackNum": {
|
||||
"description": "Chip label for track number field in auto-fill selector"
|
||||
},
|
||||
"editMetadataFieldDiscNum": "Disc #",
|
||||
"@editMetadataFieldDiscNum": {
|
||||
"description": "Chip label for disc number field in auto-fill selector"
|
||||
},
|
||||
"editMetadataFieldGenre": "Genre",
|
||||
"@editMetadataFieldGenre": {
|
||||
"description": "Chip label for genre field in auto-fill selector"
|
||||
},
|
||||
"editMetadataFieldIsrc": "ISRC",
|
||||
"@editMetadataFieldIsrc": {
|
||||
"description": "Chip label for ISRC field in auto-fill selector"
|
||||
},
|
||||
"editMetadataFieldLabel": "Label",
|
||||
"@editMetadataFieldLabel": {
|
||||
"description": "Chip label for label field in auto-fill selector"
|
||||
},
|
||||
"editMetadataFieldCopyright": "Copyright",
|
||||
"@editMetadataFieldCopyright": {
|
||||
"description": "Chip label for copyright field in auto-fill selector"
|
||||
},
|
||||
"editMetadataFieldCover": "Cover Art",
|
||||
"@editMetadataFieldCover": {
|
||||
"description": "Chip label for cover art field in auto-fill selector"
|
||||
},
|
||||
"editMetadataSelectAll": "All",
|
||||
"@editMetadataSelectAll": {
|
||||
"description": "Button to select all fields for auto-fill"
|
||||
},
|
||||
"editMetadataSelectEmpty": "Empty only",
|
||||
"@editMetadataSelectEmpty": {
|
||||
"description": "Button to select only fields that are currently empty"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3932,6 +3932,8 @@ class _EditMetadataSheet extends StatefulWidget {
|
||||
class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
bool _saving = false;
|
||||
bool _showAdvanced = false;
|
||||
bool _showAutoFill = false;
|
||||
bool _fetching = false;
|
||||
String? _selectedCoverPath;
|
||||
String? _selectedCoverTempDir;
|
||||
String? _selectedCoverName;
|
||||
@@ -3939,6 +3941,25 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
String? _currentCoverTempDir;
|
||||
bool _loadingCurrentCover = false;
|
||||
|
||||
// Auto-fill field selection — which fields the user wants to fetch
|
||||
final Set<String> _autoFillFields = {};
|
||||
|
||||
// All auto-fillable fields and their mapping
|
||||
static const _fieldDefs = <String, String>{
|
||||
'title': 'title',
|
||||
'artist': 'artist',
|
||||
'album': 'album',
|
||||
'album_artist': 'album_artist',
|
||||
'date': 'date',
|
||||
'track_number': 'track_number',
|
||||
'disc_number': 'disc_number',
|
||||
'genre': 'genre',
|
||||
'isrc': 'isrc',
|
||||
'label': 'label',
|
||||
'copyright': 'copyright',
|
||||
'cover': 'cover',
|
||||
};
|
||||
|
||||
late final TextEditingController _titleCtrl;
|
||||
late final TextEditingController _artistCtrl;
|
||||
late final TextEditingController _albumCtrl;
|
||||
@@ -4132,6 +4153,286 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
}
|
||||
}
|
||||
|
||||
String _fieldLabel(String key) {
|
||||
final l10n = context.l10n;
|
||||
switch (key) {
|
||||
case 'title':
|
||||
return l10n.editMetadataFieldTitle;
|
||||
case 'artist':
|
||||
return l10n.editMetadataFieldArtist;
|
||||
case 'album':
|
||||
return l10n.editMetadataFieldAlbum;
|
||||
case 'album_artist':
|
||||
return l10n.editMetadataFieldAlbumArtist;
|
||||
case 'date':
|
||||
return l10n.editMetadataFieldDate;
|
||||
case 'track_number':
|
||||
return l10n.editMetadataFieldTrackNum;
|
||||
case 'disc_number':
|
||||
return l10n.editMetadataFieldDiscNum;
|
||||
case 'genre':
|
||||
return l10n.editMetadataFieldGenre;
|
||||
case 'isrc':
|
||||
return l10n.editMetadataFieldIsrc;
|
||||
case 'label':
|
||||
return l10n.editMetadataFieldLabel;
|
||||
case 'copyright':
|
||||
return l10n.editMetadataFieldCopyright;
|
||||
case 'cover':
|
||||
return l10n.editMetadataFieldCover;
|
||||
default:
|
||||
return key;
|
||||
}
|
||||
}
|
||||
|
||||
TextEditingController? _controllerForKey(String key) {
|
||||
switch (key) {
|
||||
case 'title':
|
||||
return _titleCtrl;
|
||||
case 'artist':
|
||||
return _artistCtrl;
|
||||
case 'album':
|
||||
return _albumCtrl;
|
||||
case 'album_artist':
|
||||
return _albumArtistCtrl;
|
||||
case 'date':
|
||||
return _dateCtrl;
|
||||
case 'track_number':
|
||||
return _trackNumCtrl;
|
||||
case 'disc_number':
|
||||
return _discNumCtrl;
|
||||
case 'genre':
|
||||
return _genreCtrl;
|
||||
case 'isrc':
|
||||
return _isrcCtrl;
|
||||
case 'label':
|
||||
return _labelCtrl;
|
||||
case 'copyright':
|
||||
return _copyrightCtrl;
|
||||
default:
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
void _selectAllFields() {
|
||||
setState(() {
|
||||
_autoFillFields.addAll(_fieldDefs.keys);
|
||||
});
|
||||
}
|
||||
|
||||
void _selectEmptyFields() {
|
||||
setState(() {
|
||||
_autoFillFields.clear();
|
||||
for (final key in _fieldDefs.keys) {
|
||||
if (key == 'cover') {
|
||||
if (!_hasValue(_currentCoverPath) && !_hasValue(_selectedCoverPath)) {
|
||||
_autoFillFields.add(key);
|
||||
}
|
||||
continue;
|
||||
}
|
||||
final ctrl = _controllerForKey(key);
|
||||
if (ctrl != null && ctrl.text.trim().isEmpty) {
|
||||
_autoFillFields.add(key);
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Future<void> _fetchAndFill() async {
|
||||
if (_autoFillFields.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.editMetadataAutoFillNoneSelected)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
setState(() => _fetching = true);
|
||||
|
||||
try {
|
||||
// Build search query from current field values
|
||||
final title = _titleCtrl.text.trim();
|
||||
final artist = _artistCtrl.text.trim();
|
||||
final album = _albumCtrl.text.trim();
|
||||
final queryParts = <String>[];
|
||||
if (title.isNotEmpty) queryParts.add(title);
|
||||
if (artist.isNotEmpty) queryParts.add(artist);
|
||||
if (album.isNotEmpty) queryParts.add(album);
|
||||
|
||||
if (queryParts.isEmpty) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.editMetadataAutoFillNoResults),
|
||||
),
|
||||
);
|
||||
}
|
||||
return;
|
||||
}
|
||||
|
||||
final query = queryParts.join(' ');
|
||||
final results = await PlatformBridge.searchTracksWithMetadataProviders(
|
||||
query,
|
||||
limit: 5,
|
||||
);
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
if (results.isEmpty) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.editMetadataAutoFillNoResults)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
// Pick best match: prefer ISRC match, then first result
|
||||
final currentIsrc = _isrcCtrl.text.trim().toUpperCase();
|
||||
Map<String, dynamic>? best;
|
||||
if (currentIsrc.isNotEmpty) {
|
||||
for (final r in results) {
|
||||
final candidateIsrc =
|
||||
(r['isrc']?.toString() ?? '').trim().toUpperCase();
|
||||
if (candidateIsrc == currentIsrc) {
|
||||
best = r;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
best ??= results.first;
|
||||
|
||||
// Extract metadata from best match
|
||||
final enriched = <String, String>{
|
||||
'title': (best['name'] ?? '').toString(),
|
||||
'artist': (best['artists'] ?? best['artist'] ?? '').toString(),
|
||||
'album': (best['album_name'] ?? best['album'] ?? '').toString(),
|
||||
'album_artist': (best['album_artist'] ?? '').toString(),
|
||||
'date': (best['release_date'] ?? '').toString(),
|
||||
'track_number': (best['track_number'] ?? '').toString(),
|
||||
'disc_number': (best['disc_number'] ?? '').toString(),
|
||||
'isrc': (best['isrc'] ?? '').toString(),
|
||||
};
|
||||
|
||||
// Try to get extended metadata (genre, label, copyright) from Deezer
|
||||
final trackId =
|
||||
(best['spotify_id'] ?? best['id'] ?? '').toString();
|
||||
final source = (best['source'] ?? best['provider_id'] ?? '').toString();
|
||||
|
||||
if ((_autoFillFields.contains('genre') ||
|
||||
_autoFillFields.contains('label') ||
|
||||
_autoFillFields.contains('copyright')) &&
|
||||
trackId.isNotEmpty) {
|
||||
try {
|
||||
// If source is Deezer, fetch extended metadata directly
|
||||
Map<String, String>? extended;
|
||||
if (source.toLowerCase().contains('deezer')) {
|
||||
extended = await PlatformBridge.getDeezerExtendedMetadata(trackId);
|
||||
} else {
|
||||
// Try ISRC lookup via Deezer for genre/label/copyright
|
||||
final isrcForLookup = enriched['isrc'] ?? '';
|
||||
if (isrcForLookup.isNotEmpty) {
|
||||
try {
|
||||
final deezerResult = await PlatformBridge.searchDeezerByISRC(
|
||||
isrcForLookup,
|
||||
);
|
||||
final deezerTrackId =
|
||||
(deezerResult['id'] ?? deezerResult['track_id'] ?? '')
|
||||
.toString();
|
||||
if (deezerTrackId.isNotEmpty) {
|
||||
extended = await PlatformBridge.getDeezerExtendedMetadata(
|
||||
deezerTrackId,
|
||||
);
|
||||
}
|
||||
} catch (_) {}
|
||||
}
|
||||
}
|
||||
if (extended != null) {
|
||||
enriched['genre'] = extended['genre'] ?? '';
|
||||
enriched['label'] = extended['label'] ?? '';
|
||||
enriched['copyright'] = extended['copyright'] ?? '';
|
||||
}
|
||||
} catch (_) {
|
||||
// Extended metadata is best-effort
|
||||
}
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
|
||||
// Apply selected fields to controllers
|
||||
var filledCount = 0;
|
||||
for (final key in _autoFillFields) {
|
||||
if (key == 'cover') continue; // Handle cover separately below
|
||||
final value = enriched[key];
|
||||
if (value != null && value.isNotEmpty && value != '0' && value != 'null') {
|
||||
final ctrl = _controllerForKey(key);
|
||||
if (ctrl != null) {
|
||||
ctrl.text = value;
|
||||
filledCount++;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Handle cover art download
|
||||
if (_autoFillFields.contains('cover')) {
|
||||
final coverUrl =
|
||||
(best['cover_url'] ?? best['images'] ?? '').toString();
|
||||
if (coverUrl.isNotEmpty) {
|
||||
try {
|
||||
final tempDir = await Directory.systemTemp.createTemp(
|
||||
'autofill_cover_',
|
||||
);
|
||||
final coverOutput =
|
||||
'${tempDir.path}${Platform.pathSeparator}cover.jpg';
|
||||
final response = await HttpClient()
|
||||
.getUrl(Uri.parse(coverUrl))
|
||||
.then((req) => req.close());
|
||||
final file = File(coverOutput);
|
||||
final sink = file.openWrite();
|
||||
await response.pipe(sink);
|
||||
if (await file.exists() && await file.length() > 0) {
|
||||
await _cleanupSelectedCoverTemp();
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_selectedCoverPath = coverOutput;
|
||||
_selectedCoverTempDir = tempDir.path;
|
||||
_selectedCoverName = 'Online cover';
|
||||
});
|
||||
filledCount++;
|
||||
}
|
||||
} else {
|
||||
try {
|
||||
await tempDir.delete(recursive: true);
|
||||
} catch (_) {}
|
||||
}
|
||||
} catch (_) {
|
||||
// Cover download is best-effort
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (mounted) {
|
||||
setState(() {});
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(
|
||||
filledCount > 0
|
||||
? context.l10n.editMetadataAutoFillDone(filledCount)
|
||||
: context.l10n.editMetadataAutoFillNoResults,
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
if (mounted) {
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(
|
||||
content: Text(context.l10n.snackbarError(e.toString())),
|
||||
),
|
||||
);
|
||||
}
|
||||
} finally {
|
||||
if (mounted) setState(() => _fetching = false);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
@@ -4416,6 +4717,7 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
children: [
|
||||
const SizedBox(height: 6),
|
||||
_buildCoverEditor(cs),
|
||||
_buildAutoFillSection(cs),
|
||||
_field('Title', _titleCtrl),
|
||||
_field('Artist', _artistCtrl),
|
||||
_field('Album', _albumCtrl),
|
||||
@@ -4487,6 +4789,183 @@ class _EditMetadataSheetState extends State<_EditMetadataSheet> {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildAutoFillSection(ColorScheme cs) {
|
||||
return Padding(
|
||||
padding: const EdgeInsets.only(bottom: 12),
|
||||
child: Container(
|
||||
decoration: BoxDecoration(
|
||||
color: cs.surfaceContainerHighest.withValues(alpha: 0.5),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
border: Border.all(color: cs.outlineVariant.withValues(alpha: 0.5)),
|
||||
),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
InkWell(
|
||||
onTap: () => setState(() => _showAutoFill = !_showAutoFill),
|
||||
borderRadius: BorderRadius.circular(12),
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.symmetric(
|
||||
horizontal: 12,
|
||||
vertical: 10,
|
||||
),
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.travel_explore,
|
||||
size: 20,
|
||||
color: cs.primary,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
Expanded(
|
||||
child: Text(
|
||||
context.l10n.editMetadataAutoFill,
|
||||
style: Theme.of(context).textTheme.labelLarge?.copyWith(
|
||||
color: cs.onSurface,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
Icon(
|
||||
_showAutoFill ? Icons.expand_less : Icons.expand_more,
|
||||
size: 20,
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_showAutoFill) ...[
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Text(
|
||||
context.l10n.editMetadataAutoFillDesc,
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: cs.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Quick select buttons
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Row(
|
||||
children: [
|
||||
_quickSelectButton(
|
||||
label: context.l10n.editMetadataSelectAll,
|
||||
onTap: _selectAllFields,
|
||||
cs: cs,
|
||||
),
|
||||
const SizedBox(width: 8),
|
||||
_quickSelectButton(
|
||||
label: context.l10n.editMetadataSelectEmpty,
|
||||
onTap: _selectEmptyFields,
|
||||
cs: cs,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
// Field chips
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 12),
|
||||
child: Wrap(
|
||||
spacing: 6,
|
||||
runSpacing: 4,
|
||||
children: _fieldDefs.keys.map((key) {
|
||||
final selected = _autoFillFields.contains(key);
|
||||
return FilterChip(
|
||||
label: Text(_fieldLabel(key)),
|
||||
selected: selected,
|
||||
onSelected: _fetching
|
||||
? null
|
||||
: (val) {
|
||||
setState(() {
|
||||
if (val) {
|
||||
_autoFillFields.add(key);
|
||||
} else {
|
||||
_autoFillFields.remove(key);
|
||||
}
|
||||
});
|
||||
},
|
||||
selectedColor: cs.primaryContainer,
|
||||
checkmarkColor: cs.onPrimaryContainer,
|
||||
labelStyle: Theme.of(context).textTheme.labelSmall
|
||||
?.copyWith(
|
||||
color: selected
|
||||
? cs.onPrimaryContainer
|
||||
: cs.onSurfaceVariant,
|
||||
),
|
||||
visualDensity: VisualDensity.compact,
|
||||
materialTapTargetSize: MaterialTapTargetSize.shrinkWrap,
|
||||
);
|
||||
}).toList(),
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 10),
|
||||
// Fetch button
|
||||
Padding(
|
||||
padding: const EdgeInsets.only(
|
||||
left: 12,
|
||||
right: 12,
|
||||
bottom: 12,
|
||||
),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton.icon(
|
||||
onPressed:
|
||||
(_fetching || _saving || _autoFillFields.isEmpty)
|
||||
? null
|
||||
: _fetchAndFill,
|
||||
icon: _fetching
|
||||
? const SizedBox(
|
||||
width: 16,
|
||||
height: 16,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
color: Colors.white,
|
||||
),
|
||||
)
|
||||
: const Icon(Icons.auto_fix_high),
|
||||
label: Text(
|
||||
_fetching
|
||||
? context.l10n.editMetadataAutoFillSearching
|
||||
: context.l10n.editMetadataAutoFillFetch,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _quickSelectButton({
|
||||
required String label,
|
||||
required VoidCallback onTap,
|
||||
required ColorScheme cs,
|
||||
}) {
|
||||
return InkWell(
|
||||
onTap: _fetching ? null : onTap,
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
child: Container(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 4),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(16),
|
||||
border: Border.all(color: cs.outline.withValues(alpha: 0.5)),
|
||||
),
|
||||
child: Text(
|
||||
label,
|
||||
style: Theme.of(context).textTheme.labelSmall?.copyWith(
|
||||
color: cs.primary,
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildCoverEditor(ColorScheme cs) {
|
||||
final hasSelectedCover = _hasValue(_selectedCoverPath);
|
||||
final hasCurrentCover = _hasValue(_currentCoverPath);
|
||||
|
||||
Reference in New Issue
Block a user