mirror of
https://github.com/zarzet/SpotiFLAC-Mobile.git
synced 2026-08-27 21:30:23 +02:00
feat(metadata): review batch tag enrichment #511
This commit is contained in:
@@ -4270,6 +4270,84 @@ abstract class AppLocalizations {
|
||||
/// **'Select All'**
|
||||
String get trackReEnrichSelectAll;
|
||||
|
||||
/// Batch metadata mode that only writes ISRC
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'ISRC only'**
|
||||
String get trackReEnrichModeIsrc;
|
||||
|
||||
/// Explanation for the ISRC-only batch metadata mode
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Find and add the recording identifier without changing other tags'**
|
||||
String get trackReEnrichModeIsrcSubtitle;
|
||||
|
||||
/// Batch metadata mode that fills only empty tags
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Fill missing tags'**
|
||||
String get trackReEnrichModeMissing;
|
||||
|
||||
/// Explanation for the fill-missing batch metadata mode
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Keep existing values and fill only fields that are empty'**
|
||||
String get trackReEnrichModeMissingSubtitle;
|
||||
|
||||
/// Batch metadata mode that replaces selected tag groups
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Update selected tags'**
|
||||
String get trackReEnrichModeReplace;
|
||||
|
||||
/// Explanation for the selected-tag batch metadata mode
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Choose which existing values may be replaced by online metadata'**
|
||||
String get trackReEnrichModeReplaceSubtitle;
|
||||
|
||||
/// Heading above batch re-enrich field checkboxes
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Tags to update'**
|
||||
String get trackReEnrichFieldsTitle;
|
||||
|
||||
/// Button that searches metadata and opens the batch change review
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Review changes'**
|
||||
String get trackReEnrichReview;
|
||||
|
||||
/// Title of the batch metadata review sheet
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Review metadata changes'**
|
||||
String get trackReEnrichReviewTitle;
|
||||
|
||||
/// Summary shown above proposed batch metadata changes
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'{changeCount} proposed changes across {trackCount} tracks'**
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount);
|
||||
|
||||
/// Message when batch metadata preview has no proposed changes
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'No metadata changes were found for the selected tracks.'**
|
||||
String get trackReEnrichNoChanges;
|
||||
|
||||
/// Confirmation button in the batch metadata review sheet
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Apply changes'**
|
||||
String get trackReEnrichApplyChanges;
|
||||
|
||||
/// Proposed value when lyrics will be refreshed during re-enrich
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
/// **'Refresh from online'**
|
||||
String get trackReEnrichRefreshOnline;
|
||||
|
||||
/// Menu action - edit embedded metadata
|
||||
///
|
||||
/// In en, this message translates to:
|
||||
|
||||
@@ -2431,6 +2431,51 @@ class AppLocalizationsDe extends AppLocalizations {
|
||||
@override
|
||||
String get trackReEnrichSelectAll => 'Alles Auswählen';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrc => 'ISRC only';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrcSubtitle =>
|
||||
'Find and add the recording identifier without changing other tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissing => 'Fill missing tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissingSubtitle =>
|
||||
'Keep existing values and fill only fields that are empty';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplace => 'Update selected tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplaceSubtitle =>
|
||||
'Choose which existing values may be replaced by online metadata';
|
||||
|
||||
@override
|
||||
String get trackReEnrichFieldsTitle => 'Tags to update';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReview => 'Review changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReviewTitle => 'Review metadata changes';
|
||||
|
||||
@override
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount) {
|
||||
return '$changeCount proposed changes across $trackCount tracks';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackReEnrichNoChanges =>
|
||||
'No metadata changes were found for the selected tracks.';
|
||||
|
||||
@override
|
||||
String get trackReEnrichApplyChanges => 'Apply changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichRefreshOnline => 'Refresh from online';
|
||||
|
||||
@override
|
||||
String get trackEditMetadata => 'Metadaten bearbeiten';
|
||||
|
||||
|
||||
@@ -2403,6 +2403,51 @@ class AppLocalizationsEn extends AppLocalizations {
|
||||
@override
|
||||
String get trackReEnrichSelectAll => 'Select All';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrc => 'ISRC only';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrcSubtitle =>
|
||||
'Find and add the recording identifier without changing other tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissing => 'Fill missing tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissingSubtitle =>
|
||||
'Keep existing values and fill only fields that are empty';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplace => 'Update selected tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplaceSubtitle =>
|
||||
'Choose which existing values may be replaced by online metadata';
|
||||
|
||||
@override
|
||||
String get trackReEnrichFieldsTitle => 'Tags to update';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReview => 'Review changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReviewTitle => 'Review metadata changes';
|
||||
|
||||
@override
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount) {
|
||||
return '$changeCount proposed changes across $trackCount tracks';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackReEnrichNoChanges =>
|
||||
'No metadata changes were found for the selected tracks.';
|
||||
|
||||
@override
|
||||
String get trackReEnrichApplyChanges => 'Apply changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichRefreshOnline => 'Refresh from online';
|
||||
|
||||
@override
|
||||
String get trackEditMetadata => 'Edit Metadata';
|
||||
|
||||
|
||||
@@ -2403,6 +2403,51 @@ class AppLocalizationsEs extends AppLocalizations {
|
||||
@override
|
||||
String get trackReEnrichSelectAll => 'Select All';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrc => 'ISRC only';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrcSubtitle =>
|
||||
'Find and add the recording identifier without changing other tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissing => 'Fill missing tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissingSubtitle =>
|
||||
'Keep existing values and fill only fields that are empty';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplace => 'Update selected tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplaceSubtitle =>
|
||||
'Choose which existing values may be replaced by online metadata';
|
||||
|
||||
@override
|
||||
String get trackReEnrichFieldsTitle => 'Tags to update';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReview => 'Review changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReviewTitle => 'Review metadata changes';
|
||||
|
||||
@override
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount) {
|
||||
return '$changeCount proposed changes across $trackCount tracks';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackReEnrichNoChanges =>
|
||||
'No metadata changes were found for the selected tracks.';
|
||||
|
||||
@override
|
||||
String get trackReEnrichApplyChanges => 'Apply changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichRefreshOnline => 'Refresh from online';
|
||||
|
||||
@override
|
||||
String get trackEditMetadata => 'Edit Metadata';
|
||||
|
||||
|
||||
@@ -2461,6 +2461,51 @@ class AppLocalizationsFr extends AppLocalizations {
|
||||
@override
|
||||
String get trackReEnrichSelectAll => 'Tout sélectionner';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrc => 'ISRC only';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrcSubtitle =>
|
||||
'Find and add the recording identifier without changing other tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissing => 'Fill missing tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissingSubtitle =>
|
||||
'Keep existing values and fill only fields that are empty';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplace => 'Update selected tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplaceSubtitle =>
|
||||
'Choose which existing values may be replaced by online metadata';
|
||||
|
||||
@override
|
||||
String get trackReEnrichFieldsTitle => 'Tags to update';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReview => 'Review changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReviewTitle => 'Review metadata changes';
|
||||
|
||||
@override
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount) {
|
||||
return '$changeCount proposed changes across $trackCount tracks';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackReEnrichNoChanges =>
|
||||
'No metadata changes were found for the selected tracks.';
|
||||
|
||||
@override
|
||||
String get trackReEnrichApplyChanges => 'Apply changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichRefreshOnline => 'Refresh from online';
|
||||
|
||||
@override
|
||||
String get trackEditMetadata => 'Modifier les métadonnées';
|
||||
|
||||
|
||||
@@ -2385,7 +2385,7 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
String get trackSaveLyricsProgress => 'Saving lyrics...';
|
||||
|
||||
@override
|
||||
String get trackReEnrich => 'Re-enrich';
|
||||
String get trackReEnrich => 'Perkaya ulang';
|
||||
|
||||
@override
|
||||
String get trackReEnrichOnlineSubtitle =>
|
||||
@@ -2412,6 +2412,51 @@ class AppLocalizationsId extends AppLocalizations {
|
||||
@override
|
||||
String get trackReEnrichSelectAll => 'Select All';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrc => 'ISRC saja';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrcSubtitle =>
|
||||
'Cari dan tambahkan pengenal rekaman tanpa mengubah tag lain';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissing => 'Isi tag yang kosong';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissingSubtitle =>
|
||||
'Pertahankan nilai yang ada dan isi hanya kolom yang kosong';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplace => 'Perbarui tag yang dipilih';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplaceSubtitle =>
|
||||
'Pilih nilai yang boleh diganti oleh metadata online';
|
||||
|
||||
@override
|
||||
String get trackReEnrichFieldsTitle => 'Tag yang diperbarui';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReview => 'Tinjau perubahan';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReviewTitle => 'Tinjau perubahan metadata';
|
||||
|
||||
@override
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount) {
|
||||
return '$changeCount perubahan diusulkan untuk $trackCount trek';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackReEnrichNoChanges =>
|
||||
'Tidak ada perubahan metadata yang ditemukan untuk trek yang dipilih.';
|
||||
|
||||
@override
|
||||
String get trackReEnrichApplyChanges => 'Terapkan perubahan';
|
||||
|
||||
@override
|
||||
String get trackReEnrichRefreshOnline => 'Perbarui dari online';
|
||||
|
||||
@override
|
||||
String get trackEditMetadata => 'Edit Metadata';
|
||||
|
||||
|
||||
@@ -2392,6 +2392,51 @@ class AppLocalizationsJa extends AppLocalizations {
|
||||
@override
|
||||
String get trackReEnrichSelectAll => 'Select All';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrc => 'ISRC only';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrcSubtitle =>
|
||||
'Find and add the recording identifier without changing other tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissing => 'Fill missing tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissingSubtitle =>
|
||||
'Keep existing values and fill only fields that are empty';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplace => 'Update selected tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplaceSubtitle =>
|
||||
'Choose which existing values may be replaced by online metadata';
|
||||
|
||||
@override
|
||||
String get trackReEnrichFieldsTitle => 'Tags to update';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReview => 'Review changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReviewTitle => 'Review metadata changes';
|
||||
|
||||
@override
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount) {
|
||||
return '$changeCount proposed changes across $trackCount tracks';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackReEnrichNoChanges =>
|
||||
'No metadata changes were found for the selected tracks.';
|
||||
|
||||
@override
|
||||
String get trackReEnrichApplyChanges => 'Apply changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichRefreshOnline => 'Refresh from online';
|
||||
|
||||
@override
|
||||
String get trackEditMetadata => 'メタデータを編集';
|
||||
|
||||
|
||||
@@ -2340,6 +2340,51 @@ class AppLocalizationsKo extends AppLocalizations {
|
||||
@override
|
||||
String get trackReEnrichSelectAll => '모두 선택';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrc => 'ISRC only';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrcSubtitle =>
|
||||
'Find and add the recording identifier without changing other tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissing => 'Fill missing tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissingSubtitle =>
|
||||
'Keep existing values and fill only fields that are empty';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplace => 'Update selected tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplaceSubtitle =>
|
||||
'Choose which existing values may be replaced by online metadata';
|
||||
|
||||
@override
|
||||
String get trackReEnrichFieldsTitle => 'Tags to update';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReview => 'Review changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReviewTitle => 'Review metadata changes';
|
||||
|
||||
@override
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount) {
|
||||
return '$changeCount proposed changes across $trackCount tracks';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackReEnrichNoChanges =>
|
||||
'No metadata changes were found for the selected tracks.';
|
||||
|
||||
@override
|
||||
String get trackReEnrichApplyChanges => 'Apply changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichRefreshOnline => 'Refresh from online';
|
||||
|
||||
@override
|
||||
String get trackEditMetadata => '메타데이터 편집';
|
||||
|
||||
|
||||
@@ -2403,6 +2403,51 @@ class AppLocalizationsPt extends AppLocalizations {
|
||||
@override
|
||||
String get trackReEnrichSelectAll => 'Select All';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrc => 'ISRC only';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrcSubtitle =>
|
||||
'Find and add the recording identifier without changing other tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissing => 'Fill missing tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissingSubtitle =>
|
||||
'Keep existing values and fill only fields that are empty';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplace => 'Update selected tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplaceSubtitle =>
|
||||
'Choose which existing values may be replaced by online metadata';
|
||||
|
||||
@override
|
||||
String get trackReEnrichFieldsTitle => 'Tags to update';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReview => 'Review changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReviewTitle => 'Review metadata changes';
|
||||
|
||||
@override
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount) {
|
||||
return '$changeCount proposed changes across $trackCount tracks';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackReEnrichNoChanges =>
|
||||
'No metadata changes were found for the selected tracks.';
|
||||
|
||||
@override
|
||||
String get trackReEnrichApplyChanges => 'Apply changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichRefreshOnline => 'Refresh from online';
|
||||
|
||||
@override
|
||||
String get trackEditMetadata => 'Edit Metadata';
|
||||
|
||||
|
||||
@@ -2432,6 +2432,51 @@ class AppLocalizationsRu extends AppLocalizations {
|
||||
@override
|
||||
String get trackReEnrichSelectAll => 'Выбрать всё';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrc => 'ISRC only';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrcSubtitle =>
|
||||
'Find and add the recording identifier without changing other tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissing => 'Fill missing tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissingSubtitle =>
|
||||
'Keep existing values and fill only fields that are empty';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplace => 'Update selected tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplaceSubtitle =>
|
||||
'Choose which existing values may be replaced by online metadata';
|
||||
|
||||
@override
|
||||
String get trackReEnrichFieldsTitle => 'Tags to update';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReview => 'Review changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReviewTitle => 'Review metadata changes';
|
||||
|
||||
@override
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount) {
|
||||
return '$changeCount proposed changes across $trackCount tracks';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackReEnrichNoChanges =>
|
||||
'No metadata changes were found for the selected tracks.';
|
||||
|
||||
@override
|
||||
String get trackReEnrichApplyChanges => 'Apply changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichRefreshOnline => 'Refresh from online';
|
||||
|
||||
@override
|
||||
String get trackEditMetadata => 'Редактировать метаданные';
|
||||
|
||||
|
||||
@@ -2429,6 +2429,51 @@ class AppLocalizationsTr extends AppLocalizations {
|
||||
@override
|
||||
String get trackReEnrichSelectAll => 'Tümünü Seç';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrc => 'ISRC only';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrcSubtitle =>
|
||||
'Find and add the recording identifier without changing other tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissing => 'Fill missing tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissingSubtitle =>
|
||||
'Keep existing values and fill only fields that are empty';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplace => 'Update selected tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplaceSubtitle =>
|
||||
'Choose which existing values may be replaced by online metadata';
|
||||
|
||||
@override
|
||||
String get trackReEnrichFieldsTitle => 'Tags to update';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReview => 'Review changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReviewTitle => 'Review metadata changes';
|
||||
|
||||
@override
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount) {
|
||||
return '$changeCount proposed changes across $trackCount tracks';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackReEnrichNoChanges =>
|
||||
'No metadata changes were found for the selected tracks.';
|
||||
|
||||
@override
|
||||
String get trackReEnrichApplyChanges => 'Apply changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichRefreshOnline => 'Refresh from online';
|
||||
|
||||
@override
|
||||
String get trackEditMetadata => 'Edit Metadata';
|
||||
|
||||
|
||||
@@ -2436,6 +2436,51 @@ class AppLocalizationsUk extends AppLocalizations {
|
||||
@override
|
||||
String get trackReEnrichSelectAll => 'Вибрати все';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrc => 'ISRC only';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeIsrcSubtitle =>
|
||||
'Find and add the recording identifier without changing other tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissing => 'Fill missing tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeMissingSubtitle =>
|
||||
'Keep existing values and fill only fields that are empty';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplace => 'Update selected tags';
|
||||
|
||||
@override
|
||||
String get trackReEnrichModeReplaceSubtitle =>
|
||||
'Choose which existing values may be replaced by online metadata';
|
||||
|
||||
@override
|
||||
String get trackReEnrichFieldsTitle => 'Tags to update';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReview => 'Review changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichReviewTitle => 'Review metadata changes';
|
||||
|
||||
@override
|
||||
String trackReEnrichReviewSubtitle(int changeCount, int trackCount) {
|
||||
return '$changeCount proposed changes across $trackCount tracks';
|
||||
}
|
||||
|
||||
@override
|
||||
String get trackReEnrichNoChanges =>
|
||||
'No metadata changes were found for the selected tracks.';
|
||||
|
||||
@override
|
||||
String get trackReEnrichApplyChanges => 'Apply changes';
|
||||
|
||||
@override
|
||||
String get trackReEnrichRefreshOnline => 'Refresh from online';
|
||||
|
||||
@override
|
||||
String get trackEditMetadata => 'Редагувати метадані';
|
||||
|
||||
|
||||
@@ -3161,6 +3161,62 @@
|
||||
"@trackReEnrichSelectAll": {
|
||||
"description": "Select all fields checkbox in re-enrich"
|
||||
},
|
||||
"trackReEnrichModeIsrc": "ISRC only",
|
||||
"@trackReEnrichModeIsrc": {
|
||||
"description": "Batch metadata mode that only writes ISRC"
|
||||
},
|
||||
"trackReEnrichModeIsrcSubtitle": "Find and add the recording identifier without changing other tags",
|
||||
"@trackReEnrichModeIsrcSubtitle": {
|
||||
"description": "Explanation for the ISRC-only batch metadata mode"
|
||||
},
|
||||
"trackReEnrichModeMissing": "Fill missing tags",
|
||||
"@trackReEnrichModeMissing": {
|
||||
"description": "Batch metadata mode that fills only empty tags"
|
||||
},
|
||||
"trackReEnrichModeMissingSubtitle": "Keep existing values and fill only fields that are empty",
|
||||
"@trackReEnrichModeMissingSubtitle": {
|
||||
"description": "Explanation for the fill-missing batch metadata mode"
|
||||
},
|
||||
"trackReEnrichModeReplace": "Update selected tags",
|
||||
"@trackReEnrichModeReplace": {
|
||||
"description": "Batch metadata mode that replaces selected tag groups"
|
||||
},
|
||||
"trackReEnrichModeReplaceSubtitle": "Choose which existing values may be replaced by online metadata",
|
||||
"@trackReEnrichModeReplaceSubtitle": {
|
||||
"description": "Explanation for the selected-tag batch metadata mode"
|
||||
},
|
||||
"trackReEnrichFieldsTitle": "Tags to update",
|
||||
"@trackReEnrichFieldsTitle": {
|
||||
"description": "Heading above batch re-enrich field checkboxes"
|
||||
},
|
||||
"trackReEnrichReview": "Review changes",
|
||||
"@trackReEnrichReview": {
|
||||
"description": "Button that searches metadata and opens the batch change review"
|
||||
},
|
||||
"trackReEnrichReviewTitle": "Review metadata changes",
|
||||
"@trackReEnrichReviewTitle": {
|
||||
"description": "Title of the batch metadata review sheet"
|
||||
},
|
||||
"trackReEnrichReviewSubtitle": "{changeCount} proposed changes across {trackCount} tracks",
|
||||
"@trackReEnrichReviewSubtitle": {
|
||||
"description": "Summary shown above proposed batch metadata changes",
|
||||
"placeholders": {
|
||||
"changeCount": {"type": "int"},
|
||||
"trackCount": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"trackReEnrichNoChanges": "No metadata changes were found for the selected tracks.",
|
||||
"@trackReEnrichNoChanges": {
|
||||
"description": "Message when batch metadata preview has no proposed changes"
|
||||
},
|
||||
"trackReEnrichApplyChanges": "Apply changes",
|
||||
"@trackReEnrichApplyChanges": {
|
||||
"description": "Confirmation button in the batch metadata review sheet"
|
||||
},
|
||||
"trackReEnrichRefreshOnline": "Refresh from online",
|
||||
"@trackReEnrichRefreshOnline": {
|
||||
"description": "Proposed value when lyrics will be refreshed during re-enrich"
|
||||
},
|
||||
"trackEditMetadata": "Edit Metadata",
|
||||
"@trackEditMetadata": {
|
||||
"description": "Menu action - edit embedded metadata"
|
||||
|
||||
+20
-1
@@ -717,7 +717,26 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"trackReEnrich": "Re-enrich",
|
||||
"trackReEnrich": "Perkaya ulang",
|
||||
"trackReEnrichModeIsrc": "ISRC saja",
|
||||
"trackReEnrichModeIsrcSubtitle": "Cari dan tambahkan pengenal rekaman tanpa mengubah tag lain",
|
||||
"trackReEnrichModeMissing": "Isi tag yang kosong",
|
||||
"trackReEnrichModeMissingSubtitle": "Pertahankan nilai yang ada dan isi hanya kolom yang kosong",
|
||||
"trackReEnrichModeReplace": "Perbarui tag yang dipilih",
|
||||
"trackReEnrichModeReplaceSubtitle": "Pilih nilai yang boleh diganti oleh metadata online",
|
||||
"trackReEnrichFieldsTitle": "Tag yang diperbarui",
|
||||
"trackReEnrichReview": "Tinjau perubahan",
|
||||
"trackReEnrichReviewTitle": "Tinjau perubahan metadata",
|
||||
"trackReEnrichReviewSubtitle": "{changeCount} perubahan diusulkan untuk {trackCount} trek",
|
||||
"@trackReEnrichReviewSubtitle": {
|
||||
"placeholders": {
|
||||
"changeCount": {"type": "int"},
|
||||
"trackCount": {"type": "int"}
|
||||
}
|
||||
},
|
||||
"trackReEnrichNoChanges": "Tidak ada perubahan metadata yang ditemukan untuk trek yang dipilih.",
|
||||
"trackReEnrichApplyChanges": "Terapkan perubahan",
|
||||
"trackReEnrichRefreshOnline": "Perbarui dari online",
|
||||
"@trackReEnrich": {
|
||||
"description": "Menu action - re-embed metadata into audio file"
|
||||
},
|
||||
|
||||
@@ -16,13 +16,14 @@ import 'package:spotiflac_android/utils/file_access.dart';
|
||||
import 'package:spotiflac_android/utils/image_cache_utils.dart';
|
||||
import 'package:spotiflac_android/utils/lyrics_metadata_helper.dart';
|
||||
import 'package:spotiflac_android/utils/nav_bar_inset.dart';
|
||||
import 'package:spotiflac_android/utils/re_enrich_release_policy.dart';
|
||||
import 'package:spotiflac_android/services/library_database.dart';
|
||||
import 'package:spotiflac_android/services/batch_track_actions.dart';
|
||||
import 'package:spotiflac_android/services/batch_metadata_re_enrich.dart';
|
||||
import 'package:spotiflac_android/models/unified_library_item.dart';
|
||||
import 'package:spotiflac_android/services/local_track_redownload_service.dart';
|
||||
import 'package:spotiflac_android/widgets/batch_progress_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/re_enrich_field_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/re_enrich_review_sheet.dart';
|
||||
import 'package:spotiflac_android/services/platform_bridge.dart';
|
||||
import 'package:spotiflac_android/providers/local_library_provider.dart';
|
||||
import 'package:spotiflac_android/providers/playback_provider.dart';
|
||||
@@ -433,39 +434,18 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen>
|
||||
|
||||
Future<bool> _reEnrichLocalTrack(
|
||||
LocalLibraryItem item, {
|
||||
List<String>? updateFields,
|
||||
required List<String> updateFields,
|
||||
required Map<String, dynamic> resolvedMetadata,
|
||||
}) async {
|
||||
final durationMs = (item.duration ?? 0) * 1000;
|
||||
final settings = ref.read(settingsProvider);
|
||||
final artistTagMode = settings.artistTagMode;
|
||||
await ref.read(settingsProvider.notifier).syncLyricsSettingsToBackend();
|
||||
final request = <String, dynamic>{
|
||||
'file_path': item.filePath,
|
||||
'cover_url': '',
|
||||
'max_quality': true,
|
||||
'embed_lyrics': settings.embedLyrics,
|
||||
'lyrics_mode': settings.lyricsMode,
|
||||
'artist_tag_mode': artistTagMode,
|
||||
'spotify_id': '',
|
||||
'track_name': item.trackName,
|
||||
'artist_name': item.artistName,
|
||||
'album_name': item.albumName,
|
||||
'album_artist': item.albumArtist ?? '',
|
||||
'track_number': item.trackNumber ?? 0,
|
||||
'disc_number': item.discNumber ?? 0,
|
||||
'release_date': item.releaseDate ?? '',
|
||||
'isrc': item.isrc ?? '',
|
||||
'genre': item.genre ?? '',
|
||||
'label': '',
|
||||
'copyright': '',
|
||||
'duration_ms': durationMs,
|
||||
'search_online': true,
|
||||
'replace_release_metadata': allowsReleaseIdentityReplacement(
|
||||
ReEnrichOperationScope.batch,
|
||||
),
|
||||
// ignore: use_null_aware_elements
|
||||
if (updateFields != null) 'update_fields': updateFields,
|
||||
};
|
||||
final request = buildBatchReEnrichRequest(
|
||||
item: item,
|
||||
settings: settings,
|
||||
updateFields: updateFields,
|
||||
resolvedMetadata: resolvedMetadata,
|
||||
);
|
||||
|
||||
final result = await PlatformBridge.reEnrichFile(request);
|
||||
final method = result['method'] as String?;
|
||||
@@ -481,7 +461,7 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen>
|
||||
return applyFfmpegReEnrichResult(
|
||||
item: item,
|
||||
result: result,
|
||||
artistTagMode: ref.read(settingsProvider).artistTagMode,
|
||||
artistTagMode: artistTagMode,
|
||||
);
|
||||
}
|
||||
return false;
|
||||
@@ -653,12 +633,89 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen>
|
||||
return;
|
||||
}
|
||||
|
||||
final updateFields = selection.isAll ? null : selection.fields;
|
||||
await ref.read(settingsProvider.notifier).syncLyricsSettingsToBackend();
|
||||
if (!mounted) return;
|
||||
final settings = ref.read(settingsProvider);
|
||||
final previews = <BatchReEnrichPreview>[];
|
||||
var cancelled = false;
|
||||
BatchProgressDialog.show(
|
||||
context: context,
|
||||
title: context.l10n.trackReEnrichSearching,
|
||||
total: selected.length,
|
||||
icon: Icons.manage_search,
|
||||
onCancel: () {
|
||||
cancelled = true;
|
||||
BatchProgressDialog.dismiss(context);
|
||||
},
|
||||
);
|
||||
|
||||
for (var i = 0; i < selected.length; i++) {
|
||||
if (!mounted || cancelled) break;
|
||||
final item = selected[i];
|
||||
BatchProgressDialog.update(
|
||||
current: i + 1,
|
||||
detail: '${item.trackName} - ${item.artistName}',
|
||||
);
|
||||
final updateFields = selection.updateFieldsFor(item);
|
||||
if (updateFields.isEmpty) continue;
|
||||
try {
|
||||
final result = await PlatformBridge.reEnrichFile(
|
||||
buildBatchReEnrichRequest(
|
||||
item: item,
|
||||
settings: settings,
|
||||
updateFields: updateFields,
|
||||
previewOnly: true,
|
||||
),
|
||||
);
|
||||
final rawMetadata = result['enriched_metadata'];
|
||||
if (result['method'] != 'preview' || rawMetadata is! Map) continue;
|
||||
final enrichedMetadata = rawMetadata.map(
|
||||
(key, value) => MapEntry(key.toString(), value),
|
||||
);
|
||||
final changes = buildReEnrichMetadataChanges(
|
||||
item,
|
||||
enrichedMetadata,
|
||||
updateFields,
|
||||
);
|
||||
if (changes.isEmpty) continue;
|
||||
previews.add(
|
||||
BatchReEnrichPreview(
|
||||
item: item,
|
||||
updateFields: updateFields,
|
||||
enrichedMetadata: enrichedMetadata,
|
||||
changes: changes,
|
||||
),
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
if (!cancelled) BatchProgressDialog.dismiss(context);
|
||||
if (cancelled) {
|
||||
setState(() => isSelectionMode = true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (previews.isEmpty) {
|
||||
setState(() => isSelectionMode = true);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.trackReEnrichNoChanges)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final confirmed = await showReEnrichReviewSheet(
|
||||
context,
|
||||
previews: previews,
|
||||
);
|
||||
if (!confirmed || !mounted) {
|
||||
if (mounted) setState(() => isSelectionMode = true);
|
||||
return;
|
||||
}
|
||||
|
||||
var successCount = 0;
|
||||
final total = selected.length;
|
||||
|
||||
var cancelled = false;
|
||||
final total = previews.length;
|
||||
cancelled = false;
|
||||
BatchProgressDialog.show(
|
||||
context: context,
|
||||
title: context.l10n.trackReEnrichProgress,
|
||||
@@ -672,26 +729,24 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen>
|
||||
|
||||
for (var i = 0; i < total; i++) {
|
||||
if (!mounted || cancelled) break;
|
||||
final item = selected[i];
|
||||
|
||||
final preview = previews[i];
|
||||
BatchProgressDialog.update(
|
||||
current: i + 1,
|
||||
detail: '${item.trackName} - ${item.artistName}',
|
||||
detail: '${preview.item.trackName} - ${preview.item.artistName}',
|
||||
);
|
||||
|
||||
try {
|
||||
final ok = await _reEnrichLocalTrack(item, updateFields: updateFields);
|
||||
if (ok) {
|
||||
successCount++;
|
||||
}
|
||||
final ok = await _reEnrichLocalTrack(
|
||||
preview.item,
|
||||
updateFields: preview.updateFields,
|
||||
resolvedMetadata: preview.enrichedMetadata,
|
||||
);
|
||||
if (ok) successCount++;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
if (!mounted) return;
|
||||
if (!cancelled) BatchProgressDialog.dismiss(context);
|
||||
|
||||
final settings = ref.read(settingsProvider);
|
||||
final localLibraryPath = settings.localLibraryPath.trim();
|
||||
final iosBookmark = settings.localLibraryBookmark;
|
||||
try {
|
||||
@@ -716,9 +771,6 @@ class _LocalAlbumScreenState extends ConsumerState<LocalAlbumScreen>
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cancelled) {
|
||||
BatchProgressDialog.dismiss(context);
|
||||
}
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
final failedCount = total - successCount;
|
||||
final summary = failedCount <= 0
|
||||
|
||||
@@ -16,7 +16,6 @@ import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/utils/adaptive_layout.dart';
|
||||
import 'package:spotiflac_android/utils/audio_quality_badge_policy.dart';
|
||||
import 'package:spotiflac_android/utils/nav_bar_inset.dart';
|
||||
import 'package:spotiflac_android/utils/re_enrich_release_policy.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
import 'package:spotiflac_android/utils/ffmpeg_reenrich.dart';
|
||||
import 'package:spotiflac_android/utils/file_access.dart';
|
||||
@@ -36,11 +35,13 @@ import 'package:spotiflac_android/services/music_player_service.dart';
|
||||
import 'package:spotiflac_android/services/library_database.dart';
|
||||
import 'package:spotiflac_android/services/local_track_redownload_service.dart';
|
||||
import 'package:spotiflac_android/services/batch_track_actions.dart';
|
||||
import 'package:spotiflac_android/services/batch_metadata_re_enrich.dart';
|
||||
import 'package:spotiflac_android/services/downloaded_embedded_cover_resolver.dart';
|
||||
import 'package:spotiflac_android/screens/track_metadata_screen.dart';
|
||||
import 'package:spotiflac_android/screens/favorite_artists_screen.dart';
|
||||
import 'package:spotiflac_android/screens/downloaded_album_screen.dart';
|
||||
import 'package:spotiflac_android/widgets/re_enrich_field_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/re_enrich_review_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/batch_progress_dialog.dart';
|
||||
import 'package:spotiflac_android/widgets/cached_cover_image.dart';
|
||||
import 'package:cached_network_image/cached_network_image.dart';
|
||||
|
||||
@@ -3,39 +3,18 @@ part of 'queue_tab.dart';
|
||||
extension _QueueTabBatchActions on _QueueTabState {
|
||||
Future<bool> _reEnrichQueueLocalTrack(
|
||||
LocalLibraryItem item, {
|
||||
List<String>? updateFields,
|
||||
required List<String> updateFields,
|
||||
required Map<String, dynamic> resolvedMetadata,
|
||||
}) async {
|
||||
final durationMs = (item.duration ?? 0) * 1000;
|
||||
final settings = ref.read(settingsProvider);
|
||||
final artistTagMode = settings.artistTagMode;
|
||||
await ref.read(settingsProvider.notifier).syncLyricsSettingsToBackend();
|
||||
final request = <String, dynamic>{
|
||||
'file_path': item.filePath,
|
||||
'cover_url': '',
|
||||
'max_quality': true,
|
||||
'embed_lyrics': settings.embedLyrics,
|
||||
'lyrics_mode': settings.lyricsMode,
|
||||
'artist_tag_mode': artistTagMode,
|
||||
'spotify_id': '',
|
||||
'track_name': item.trackName,
|
||||
'artist_name': item.artistName,
|
||||
'album_name': item.albumName,
|
||||
'album_artist': item.albumArtist ?? '',
|
||||
'track_number': item.trackNumber ?? 0,
|
||||
'disc_number': item.discNumber ?? 0,
|
||||
'release_date': item.releaseDate ?? '',
|
||||
'isrc': item.isrc ?? '',
|
||||
'genre': item.genre ?? '',
|
||||
'label': '',
|
||||
'copyright': '',
|
||||
'duration_ms': durationMs,
|
||||
'search_online': true,
|
||||
'replace_release_metadata': allowsReleaseIdentityReplacement(
|
||||
ReEnrichOperationScope.batch,
|
||||
),
|
||||
// ignore: use_null_aware_elements
|
||||
if (updateFields != null) 'update_fields': updateFields,
|
||||
};
|
||||
final request = buildBatchReEnrichRequest(
|
||||
item: item,
|
||||
settings: settings,
|
||||
updateFields: updateFields,
|
||||
resolvedMetadata: resolvedMetadata,
|
||||
);
|
||||
|
||||
final result = await PlatformBridge.reEnrichFile(request);
|
||||
final method = result['method'] as String?;
|
||||
@@ -232,12 +211,89 @@ extension _QueueTabBatchActions on _QueueTabState {
|
||||
return;
|
||||
}
|
||||
|
||||
final updateFields = selection.isAll ? null : selection.fields;
|
||||
await ref.read(settingsProvider.notifier).syncLyricsSettingsToBackend();
|
||||
if (!mounted) return;
|
||||
final settings = ref.read(settingsProvider);
|
||||
final previews = <BatchReEnrichPreview>[];
|
||||
var cancelled = false;
|
||||
BatchProgressDialog.show(
|
||||
context: context,
|
||||
title: context.l10n.trackReEnrichSearching,
|
||||
total: selectedLocalItems.length,
|
||||
icon: Icons.manage_search,
|
||||
onCancel: () {
|
||||
cancelled = true;
|
||||
BatchProgressDialog.dismiss(context);
|
||||
},
|
||||
);
|
||||
|
||||
for (var i = 0; i < selectedLocalItems.length; i++) {
|
||||
if (!mounted || cancelled) break;
|
||||
final item = selectedLocalItems[i];
|
||||
BatchProgressDialog.update(
|
||||
current: i + 1,
|
||||
detail: '${item.trackName} - ${item.artistName}',
|
||||
);
|
||||
final updateFields = selection.updateFieldsFor(item);
|
||||
if (updateFields.isEmpty) continue;
|
||||
try {
|
||||
final result = await PlatformBridge.reEnrichFile(
|
||||
buildBatchReEnrichRequest(
|
||||
item: item,
|
||||
settings: settings,
|
||||
updateFields: updateFields,
|
||||
previewOnly: true,
|
||||
),
|
||||
);
|
||||
final rawMetadata = result['enriched_metadata'];
|
||||
if (result['method'] != 'preview' || rawMetadata is! Map) continue;
|
||||
final enrichedMetadata = rawMetadata.map(
|
||||
(key, value) => MapEntry(key.toString(), value),
|
||||
);
|
||||
final changes = buildReEnrichMetadataChanges(
|
||||
item,
|
||||
enrichedMetadata,
|
||||
updateFields,
|
||||
);
|
||||
if (changes.isEmpty) continue;
|
||||
previews.add(
|
||||
BatchReEnrichPreview(
|
||||
item: item,
|
||||
updateFields: updateFields,
|
||||
enrichedMetadata: enrichedMetadata,
|
||||
changes: changes,
|
||||
),
|
||||
);
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (!mounted) return;
|
||||
if (!cancelled) BatchProgressDialog.dismiss(context);
|
||||
if (cancelled) {
|
||||
_setState(() => _isSelectionMode = true);
|
||||
return;
|
||||
}
|
||||
|
||||
if (previews.isEmpty) {
|
||||
_setState(() => _isSelectionMode = true);
|
||||
ScaffoldMessenger.of(context).showSnackBar(
|
||||
SnackBar(content: Text(context.l10n.trackReEnrichNoChanges)),
|
||||
);
|
||||
return;
|
||||
}
|
||||
|
||||
final confirmed = await showReEnrichReviewSheet(
|
||||
context,
|
||||
previews: previews,
|
||||
);
|
||||
if (!confirmed || !mounted) {
|
||||
if (mounted) _setState(() => _isSelectionMode = true);
|
||||
return;
|
||||
}
|
||||
|
||||
var successCount = 0;
|
||||
final total = selectedLocalItems.length;
|
||||
|
||||
var cancelled = false;
|
||||
final total = previews.length;
|
||||
cancelled = false;
|
||||
BatchProgressDialog.show(
|
||||
context: context,
|
||||
title: context.l10n.trackReEnrichProgress,
|
||||
@@ -251,29 +307,24 @@ extension _QueueTabBatchActions on _QueueTabState {
|
||||
|
||||
for (var i = 0; i < total; i++) {
|
||||
if (!mounted || cancelled) break;
|
||||
final item = selectedLocalItems[i];
|
||||
|
||||
final preview = previews[i];
|
||||
BatchProgressDialog.update(
|
||||
current: i + 1,
|
||||
detail: '${item.trackName} - ${item.artistName}',
|
||||
detail: '${preview.item.trackName} - ${preview.item.artistName}',
|
||||
);
|
||||
|
||||
try {
|
||||
final ok = await _reEnrichQueueLocalTrack(
|
||||
item,
|
||||
updateFields: updateFields,
|
||||
preview.item,
|
||||
updateFields: preview.updateFields,
|
||||
resolvedMetadata: preview.enrichedMetadata,
|
||||
);
|
||||
if (ok) {
|
||||
successCount++;
|
||||
}
|
||||
if (ok) successCount++;
|
||||
} catch (_) {}
|
||||
}
|
||||
|
||||
if (!mounted) {
|
||||
return;
|
||||
}
|
||||
if (!mounted) return;
|
||||
if (!cancelled) BatchProgressDialog.dismiss(context);
|
||||
|
||||
final settings = ref.read(settingsProvider);
|
||||
final localLibraryPath = settings.localLibraryPath.trim();
|
||||
final iosBookmark = settings.localLibraryBookmark;
|
||||
try {
|
||||
@@ -298,9 +349,6 @@ extension _QueueTabBatchActions on _QueueTabState {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!cancelled) {
|
||||
BatchProgressDialog.dismiss(context);
|
||||
}
|
||||
ScaffoldMessenger.of(context).clearSnackBars();
|
||||
final failedCount = total - successCount;
|
||||
final summary = failedCount <= 0
|
||||
|
||||
@@ -0,0 +1,194 @@
|
||||
import 'package:spotiflac_android/models/settings.dart';
|
||||
import 'package:spotiflac_android/services/library_database.dart';
|
||||
|
||||
/// Field group keys understood by the Go re-enrich backend.
|
||||
class ReEnrichFields {
|
||||
static const String cover = 'cover';
|
||||
static const String lyrics = 'lyrics';
|
||||
static const String basicTags = 'basic_tags';
|
||||
static const String trackInfo = 'track_info';
|
||||
static const String releaseInfo = 'release_info';
|
||||
static const String extra = 'extra';
|
||||
|
||||
static const List<String> all = [
|
||||
cover,
|
||||
lyrics,
|
||||
basicTags,
|
||||
trackInfo,
|
||||
releaseInfo,
|
||||
extra,
|
||||
];
|
||||
}
|
||||
|
||||
enum ReEnrichBatchMode { isrcOnly, missingOnly, selectedFields }
|
||||
|
||||
class ReEnrichFieldSelection {
|
||||
final ReEnrichBatchMode mode;
|
||||
final List<String> fields;
|
||||
|
||||
const ReEnrichFieldSelection({required this.mode, this.fields = const []});
|
||||
|
||||
List<String> updateFieldsFor(LocalLibraryItem item) {
|
||||
switch (mode) {
|
||||
case ReEnrichBatchMode.isrcOnly:
|
||||
return const ['isrc'];
|
||||
case ReEnrichBatchMode.selectedFields:
|
||||
return fields;
|
||||
case ReEnrichBatchMode.missingOnly:
|
||||
return missingReEnrichFields(item);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
bool _missingText(String? value) {
|
||||
final normalized = value?.trim().toLowerCase() ?? '';
|
||||
return normalized.isEmpty ||
|
||||
normalized == 'unknown' ||
|
||||
normalized == 'unknown title' ||
|
||||
normalized == 'unknown artist' ||
|
||||
normalized == 'unknown album';
|
||||
}
|
||||
|
||||
/// Returns granular tag keys, preventing a missing value from causing its
|
||||
/// already-populated neighbors in the same backend group to be overwritten.
|
||||
List<String> missingReEnrichFields(LocalLibraryItem item) {
|
||||
final fields = <String>[];
|
||||
if (_missingText(item.trackName)) fields.add('track_name');
|
||||
if (_missingText(item.artistName)) fields.add('artist_name');
|
||||
if (_missingText(item.albumName)) fields.add('album_name');
|
||||
if (_missingText(item.albumArtist)) fields.add('album_artist');
|
||||
if ((item.trackNumber ?? 0) <= 0) fields.add('track_number');
|
||||
if ((item.totalTracks ?? 0) <= 0) fields.add('total_tracks');
|
||||
if ((item.discNumber ?? 0) <= 0) fields.add('disc_number');
|
||||
if ((item.totalDiscs ?? 0) <= 0) fields.add('total_discs');
|
||||
if (_missingText(item.releaseDate)) fields.add('release_date');
|
||||
if (_missingText(item.isrc)) fields.add('isrc');
|
||||
if (_missingText(item.genre)) fields.add('genre');
|
||||
if (_missingText(item.composer)) fields.add('composer');
|
||||
if (_missingText(item.label)) fields.add('label');
|
||||
if (_missingText(item.copyright)) fields.add('copyright');
|
||||
if (_missingText(item.coverPath)) fields.add('cover');
|
||||
return fields;
|
||||
}
|
||||
|
||||
Map<String, dynamic> buildBatchReEnrichRequest({
|
||||
required LocalLibraryItem item,
|
||||
required AppSettings settings,
|
||||
required List<String> updateFields,
|
||||
bool previewOnly = false,
|
||||
Map<String, dynamic>? resolvedMetadata,
|
||||
}) {
|
||||
final request = <String, dynamic>{
|
||||
'file_path': item.filePath,
|
||||
'cover_url': '',
|
||||
'max_quality': true,
|
||||
'embed_lyrics': settings.embedLyrics,
|
||||
'lyrics_mode': settings.lyricsMode,
|
||||
'artist_tag_mode': settings.artistTagMode,
|
||||
'spotify_id': '',
|
||||
'track_name': item.trackName,
|
||||
'artist_name': item.artistName,
|
||||
'album_name': item.albumName,
|
||||
'album_artist': item.albumArtist ?? '',
|
||||
'track_number': item.trackNumber ?? 0,
|
||||
'total_tracks': item.totalTracks ?? 0,
|
||||
'disc_number': item.discNumber ?? 0,
|
||||
'total_discs': item.totalDiscs ?? 0,
|
||||
'release_date': item.releaseDate ?? '',
|
||||
'isrc': item.isrc ?? '',
|
||||
'genre': item.genre ?? '',
|
||||
'composer': item.composer ?? '',
|
||||
'label': item.label ?? '',
|
||||
'copyright': item.copyright ?? '',
|
||||
'duration_ms': (item.duration ?? 0) * 1000,
|
||||
'search_online': resolvedMetadata == null,
|
||||
'replace_release_metadata': false,
|
||||
'update_fields': updateFields,
|
||||
if (previewOnly) 'preview_only': true,
|
||||
};
|
||||
if (resolvedMetadata != null) {
|
||||
request.addAll(resolvedMetadata);
|
||||
}
|
||||
return request;
|
||||
}
|
||||
|
||||
class ReEnrichMetadataChange {
|
||||
final String field;
|
||||
final String oldValue;
|
||||
final String newValue;
|
||||
|
||||
const ReEnrichMetadataChange({
|
||||
required this.field,
|
||||
required this.oldValue,
|
||||
required this.newValue,
|
||||
});
|
||||
}
|
||||
|
||||
class BatchReEnrichPreview {
|
||||
final LocalLibraryItem item;
|
||||
final List<String> updateFields;
|
||||
final Map<String, dynamic> enrichedMetadata;
|
||||
final List<ReEnrichMetadataChange> changes;
|
||||
|
||||
const BatchReEnrichPreview({
|
||||
required this.item,
|
||||
required this.updateFields,
|
||||
required this.enrichedMetadata,
|
||||
required this.changes,
|
||||
});
|
||||
}
|
||||
|
||||
String _displayValue(Object? value) {
|
||||
if (value == null) return '';
|
||||
if (value is num && value == 0) return '';
|
||||
return value.toString().trim();
|
||||
}
|
||||
|
||||
List<ReEnrichMetadataChange> buildReEnrichMetadataChanges(
|
||||
LocalLibraryItem item,
|
||||
Map<String, dynamic> enrichedMetadata,
|
||||
List<String> updateFields,
|
||||
) {
|
||||
final current = <String, Object?>{
|
||||
'track_name': item.trackName,
|
||||
'artist_name': item.artistName,
|
||||
'album_name': item.albumName,
|
||||
'album_artist': item.albumArtist,
|
||||
'track_number': item.trackNumber,
|
||||
'total_tracks': item.totalTracks,
|
||||
'disc_number': item.discNumber,
|
||||
'total_discs': item.totalDiscs,
|
||||
'release_date': item.releaseDate,
|
||||
'isrc': item.isrc,
|
||||
'genre': item.genre,
|
||||
'composer': item.composer,
|
||||
'label': item.label,
|
||||
'copyright': item.copyright,
|
||||
'cover_url': item.coverPath,
|
||||
};
|
||||
final changes = <ReEnrichMetadataChange>[];
|
||||
for (final field in current.keys) {
|
||||
if (!enrichedMetadata.containsKey(field)) continue;
|
||||
final oldValue = _displayValue(current[field]);
|
||||
final newValue = _displayValue(enrichedMetadata[field]);
|
||||
if (newValue.isEmpty || oldValue == newValue) continue;
|
||||
changes.add(
|
||||
ReEnrichMetadataChange(
|
||||
field: field,
|
||||
oldValue: oldValue,
|
||||
newValue: newValue,
|
||||
),
|
||||
);
|
||||
}
|
||||
if (updateFields.contains(ReEnrichFields.lyrics) ||
|
||||
updateFields.contains('lyrics')) {
|
||||
changes.add(
|
||||
const ReEnrichMetadataChange(
|
||||
field: 'lyrics',
|
||||
oldValue: '',
|
||||
newValue: '__refresh_online__',
|
||||
),
|
||||
);
|
||||
}
|
||||
return changes;
|
||||
}
|
||||
@@ -1,43 +1,19 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../l10n/app_localizations.dart';
|
||||
|
||||
/// Field group keys matching the Go backend `update_fields` values.
|
||||
class ReEnrichFields {
|
||||
static const String cover = 'cover';
|
||||
static const String lyrics = 'lyrics';
|
||||
static const String basicTags = 'basic_tags';
|
||||
static const String trackInfo = 'track_info';
|
||||
static const String releaseInfo = 'release_info';
|
||||
static const String extra = 'extra';
|
||||
|
||||
static const List<String> all = [
|
||||
cover,
|
||||
lyrics,
|
||||
basicTags,
|
||||
trackInfo,
|
||||
releaseInfo,
|
||||
extra,
|
||||
];
|
||||
}
|
||||
|
||||
/// Result returned by the re-enrich field selection sheet.
|
||||
class ReEnrichFieldSelection {
|
||||
final List<String> fields;
|
||||
const ReEnrichFieldSelection(this.fields);
|
||||
|
||||
/// True when every available field is selected (or update_fields can be omitted).
|
||||
bool get isAll => fields.length == ReEnrichFields.all.length;
|
||||
}
|
||||
import 'package:spotiflac_android/services/batch_metadata_re_enrich.dart';
|
||||
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
|
||||
Future<ReEnrichFieldSelection?> showReEnrichFieldDialog(
|
||||
BuildContext context, {
|
||||
required int selectedCount,
|
||||
}) {
|
||||
return showModalBottomSheet<ReEnrichFieldSelection>(
|
||||
return showAppBottomSheet<ReEnrichFieldSelection>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
showDragHandle: true,
|
||||
isScrollControlled: true,
|
||||
title: AppLocalizations.of(context).trackReEnrich,
|
||||
subtitle: AppLocalizations.of(context).trackReEnrichOnlineSubtitle,
|
||||
maxHeightFactor: 0.9,
|
||||
builder: (ctx) => _ReEnrichFieldSheet(selectedCount: selectedCount),
|
||||
);
|
||||
}
|
||||
@@ -52,6 +28,7 @@ class _ReEnrichFieldSheet extends StatefulWidget {
|
||||
|
||||
class _ReEnrichFieldSheetState extends State<_ReEnrichFieldSheet> {
|
||||
final Set<String> _selected = Set<String>.from(ReEnrichFields.all);
|
||||
ReEnrichBatchMode _mode = ReEnrichBatchMode.missingOnly;
|
||||
|
||||
bool get _allSelected => _selected.length == ReEnrichFields.all.length;
|
||||
|
||||
@@ -117,80 +94,121 @@ class _ReEnrichFieldSheetState extends State<_ReEnrichFieldSheet> {
|
||||
Widget build(BuildContext context) {
|
||||
final l10n = AppLocalizations.of(context);
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final textTheme = Theme.of(context).textTheme;
|
||||
|
||||
return SafeArea(
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 4),
|
||||
child: Text(
|
||||
l10n.trackReEnrich,
|
||||
style: textTheme.titleLarge?.copyWith(
|
||||
fontWeight: FontWeight.bold,
|
||||
top: false,
|
||||
child: SingleChildScrollView(
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 8),
|
||||
child: Text(
|
||||
l10n.downloadedAlbumSelectedCount(widget.selectedCount),
|
||||
style: Theme.of(context).textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 4),
|
||||
child: Text(
|
||||
l10n.trackReEnrichOnlineSubtitle,
|
||||
style: textTheme.bodyMedium?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
SettingsGroup(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.fingerprint),
|
||||
title: Text(l10n.trackReEnrichModeIsrc),
|
||||
subtitle: Text(l10n.trackReEnrichModeIsrcSubtitle),
|
||||
trailing: _mode == ReEnrichBatchMode.isrcOnly
|
||||
? Icon(Icons.check, color: colorScheme.primary)
|
||||
: null,
|
||||
onTap: () =>
|
||||
setState(() => _mode = ReEnrichBatchMode.isrcOnly),
|
||||
),
|
||||
const Divider(height: 1, indent: 56),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.playlist_add_check),
|
||||
title: Text(l10n.trackReEnrichModeMissing),
|
||||
subtitle: Text(l10n.trackReEnrichModeMissingSubtitle),
|
||||
trailing: _mode == ReEnrichBatchMode.missingOnly
|
||||
? Icon(Icons.check, color: colorScheme.primary)
|
||||
: null,
|
||||
onTap: () =>
|
||||
setState(() => _mode = ReEnrichBatchMode.missingOnly),
|
||||
),
|
||||
const Divider(height: 1, indent: 56),
|
||||
ListTile(
|
||||
leading: const Icon(Icons.tune),
|
||||
title: Text(l10n.trackReEnrichModeReplace),
|
||||
subtitle: Text(l10n.trackReEnrichModeReplaceSubtitle),
|
||||
trailing: _mode == ReEnrichBatchMode.selectedFields
|
||||
? Icon(Icons.check, color: colorScheme.primary)
|
||||
: null,
|
||||
onTap: () =>
|
||||
setState(() => _mode = ReEnrichBatchMode.selectedFields),
|
||||
),
|
||||
],
|
||||
),
|
||||
if (_mode == ReEnrichBatchMode.selectedFields) ...[
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.symmetric(horizontal: 24),
|
||||
child: Text(
|
||||
l10n.trackReEnrichFieldsTitle,
|
||||
style: Theme.of(context).textTheme.titleSmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
),
|
||||
SettingsGroup(
|
||||
children: [
|
||||
CheckboxListTile(
|
||||
title: Text(
|
||||
l10n.trackReEnrichSelectAll,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
value: _allSelected,
|
||||
tristate: true,
|
||||
onChanged: _toggleAll,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
),
|
||||
for (final field in ReEnrichFields.all) ...[
|
||||
const Divider(height: 1, indent: 56),
|
||||
CheckboxListTile(
|
||||
secondary: Icon(_iconFor(field), size: 20),
|
||||
title: Text(_labelFor(field, l10n)),
|
||||
value: _selected.contains(field),
|
||||
onChanged: (value) => _toggle(field, value),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
],
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton.icon(
|
||||
onPressed:
|
||||
_mode == ReEnrichBatchMode.selectedFields &&
|
||||
_selected.isEmpty
|
||||
? null
|
||||
: () => Navigator.pop(
|
||||
context,
|
||||
ReEnrichFieldSelection(
|
||||
mode: _mode,
|
||||
fields: _selected.toList(),
|
||||
),
|
||||
),
|
||||
icon: const Icon(Icons.preview_outlined, size: 18),
|
||||
label: Text(l10n.trackReEnrichReview),
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(24, 0, 24, 8),
|
||||
child: Text(
|
||||
l10n.downloadedAlbumSelectedCount(widget.selectedCount),
|
||||
style: textTheme.bodySmall?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
),
|
||||
const Divider(height: 1),
|
||||
CheckboxListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
title: Text(
|
||||
l10n.trackReEnrichSelectAll,
|
||||
style: const TextStyle(fontWeight: FontWeight.w600),
|
||||
),
|
||||
value: _allSelected,
|
||||
tristate: true,
|
||||
onChanged: _toggleAll,
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
),
|
||||
const Divider(height: 1, indent: 16, endIndent: 16),
|
||||
for (final field in ReEnrichFields.all)
|
||||
CheckboxListTile(
|
||||
contentPadding: const EdgeInsets.symmetric(horizontal: 16),
|
||||
secondary: Icon(_iconFor(field), size: 20),
|
||||
title: Text(_labelFor(field, l10n)),
|
||||
value: _selected.contains(field),
|
||||
onChanged: (v) => _toggle(field, v),
|
||||
controlAffinity: ListTileControlAffinity.leading,
|
||||
),
|
||||
const SizedBox(height: 8),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton.icon(
|
||||
onPressed: _selected.isEmpty
|
||||
? null
|
||||
: () => Navigator.pop(
|
||||
context,
|
||||
ReEnrichFieldSelection(_selected.toList()),
|
||||
),
|
||||
icon: const Icon(Icons.auto_fix_high, size: 18),
|
||||
label: Text(l10n.trackReEnrich),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@@ -0,0 +1,193 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:spotiflac_android/l10n/l10n.dart';
|
||||
import 'package:spotiflac_android/services/batch_metadata_re_enrich.dart';
|
||||
import 'package:spotiflac_android/widgets/app_bottom_sheet.dart';
|
||||
import 'package:spotiflac_android/widgets/settings_group.dart';
|
||||
|
||||
Future<bool> showReEnrichReviewSheet(
|
||||
BuildContext context, {
|
||||
required List<BatchReEnrichPreview> previews,
|
||||
}) async {
|
||||
final changeCount = previews.fold<int>(
|
||||
0,
|
||||
(total, preview) => total + preview.changes.length,
|
||||
);
|
||||
if (changeCount == 0) return false;
|
||||
|
||||
final confirmed = await showAppBottomSheet<bool>(
|
||||
context: context,
|
||||
useRootNavigator: true,
|
||||
title: context.l10n.trackReEnrichReviewTitle,
|
||||
subtitle: context.l10n.trackReEnrichReviewSubtitle(
|
||||
changeCount,
|
||||
previews.length,
|
||||
),
|
||||
maxHeightFactor: 0.9,
|
||||
builder: (sheetContext) => _ReEnrichReviewContent(previews: previews),
|
||||
);
|
||||
return confirmed == true;
|
||||
}
|
||||
|
||||
class _ReEnrichReviewContent extends StatelessWidget {
|
||||
final List<BatchReEnrichPreview> previews;
|
||||
|
||||
const _ReEnrichReviewContent({required this.previews});
|
||||
|
||||
String _fieldLabel(BuildContext context, String field) {
|
||||
switch (field) {
|
||||
case 'track_name':
|
||||
return context.l10n.editMetadataFieldTitle;
|
||||
case 'artist_name':
|
||||
return context.l10n.editMetadataFieldArtist;
|
||||
case 'album_name':
|
||||
return context.l10n.editMetadataFieldAlbum;
|
||||
case 'album_artist':
|
||||
return context.l10n.editMetadataFieldAlbumArtist;
|
||||
case 'track_number':
|
||||
return context.l10n.editMetadataFieldTrackNum;
|
||||
case 'total_tracks':
|
||||
return context.l10n.editMetadataFieldTrackTotal;
|
||||
case 'disc_number':
|
||||
return context.l10n.editMetadataFieldDiscNum;
|
||||
case 'total_discs':
|
||||
return context.l10n.editMetadataFieldDiscTotal;
|
||||
case 'release_date':
|
||||
return context.l10n.editMetadataFieldDate;
|
||||
case 'isrc':
|
||||
return context.l10n.editMetadataFieldIsrc;
|
||||
case 'genre':
|
||||
return context.l10n.editMetadataFieldGenre;
|
||||
case 'composer':
|
||||
return context.l10n.editMetadataFieldComposer;
|
||||
case 'label':
|
||||
return context.l10n.editMetadataFieldLabel;
|
||||
case 'copyright':
|
||||
return context.l10n.editMetadataFieldCopyright;
|
||||
case 'cover_url':
|
||||
return context.l10n.editMetadataFieldCover;
|
||||
case 'lyrics':
|
||||
return context.l10n.trackReEnrichFieldLyrics;
|
||||
default:
|
||||
return field;
|
||||
}
|
||||
}
|
||||
|
||||
String _oldValue(BuildContext context, ReEnrichMetadataChange change) {
|
||||
if (change.field == 'cover_url' && change.oldValue.isNotEmpty) {
|
||||
return context.l10n.trackCoverCurrent;
|
||||
}
|
||||
return change.oldValue.isEmpty ? '—' : change.oldValue;
|
||||
}
|
||||
|
||||
String _newValue(BuildContext context, ReEnrichMetadataChange change) {
|
||||
if (change.field == 'cover_url') {
|
||||
return context.l10n.editMetadataAutoFillCoverAvailable;
|
||||
}
|
||||
if (change.newValue == '__refresh_online__') {
|
||||
return context.l10n.trackReEnrichRefreshOnline;
|
||||
}
|
||||
return change.newValue;
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final colorScheme = Theme.of(context).colorScheme;
|
||||
final visible = previews
|
||||
.where((preview) => preview.changes.isNotEmpty)
|
||||
.toList(growable: false);
|
||||
|
||||
return SafeArea(
|
||||
top: false,
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Flexible(
|
||||
child: ListView.builder(
|
||||
shrinkWrap: true,
|
||||
padding: const EdgeInsets.only(bottom: 8),
|
||||
itemCount: visible.length,
|
||||
itemBuilder: (context, index) {
|
||||
final preview = visible[index];
|
||||
return SettingsGroup(
|
||||
children: [
|
||||
ListTile(
|
||||
leading: const Icon(Icons.audio_file_outlined),
|
||||
title: Text(
|
||||
preview.item.trackName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
subtitle: Text(
|
||||
preview.item.artistName,
|
||||
maxLines: 1,
|
||||
overflow: TextOverflow.ellipsis,
|
||||
),
|
||||
),
|
||||
for (final change in preview.changes) ...[
|
||||
const Divider(height: 1, indent: 56),
|
||||
Semantics(
|
||||
label:
|
||||
'${_fieldLabel(context, change.field)}: ${_oldValue(context, change)}, ${_newValue(context, change)}',
|
||||
child: Padding(
|
||||
padding: const EdgeInsets.fromLTRB(56, 10, 16, 10),
|
||||
child: Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
Text(
|
||||
_fieldLabel(context, change.field),
|
||||
style: Theme.of(context).textTheme.labelMedium
|
||||
?.copyWith(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
const SizedBox(height: 3),
|
||||
Text.rich(
|
||||
TextSpan(
|
||||
children: [
|
||||
TextSpan(
|
||||
text: _oldValue(context, change),
|
||||
style: TextStyle(
|
||||
color: colorScheme.onSurfaceVariant,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: ' → ',
|
||||
style: TextStyle(
|
||||
color: colorScheme.primary,
|
||||
),
|
||||
),
|
||||
TextSpan(
|
||||
text: _newValue(context, change),
|
||||
style: const TextStyle(
|
||||
fontWeight: FontWeight.w600,
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
],
|
||||
);
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(16, 8, 16, 16),
|
||||
child: SizedBox(
|
||||
width: double.infinity,
|
||||
child: FilledButton.icon(
|
||||
onPressed: () => Navigator.pop(context, true),
|
||||
icon: const Icon(Icons.save_outlined),
|
||||
label: Text(context.l10n.trackReEnrichApplyChanges),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user