diff --git a/lib/l10n/app_en.arb b/lib/l10n/app_en.arb index 996340bae..1291a6dcd 100644 --- a/lib/l10n/app_en.arb +++ b/lib/l10n/app_en.arb @@ -8945,5 +8945,49 @@ "watchlistUpdateFailed": "Couldn't update watchlist", "@watchlistUpdateFailed": { "description": "Snackbar shown when adding/removing a Seerr watchlist item fails" + }, + "adminSearchParameters": "Search Parameters", + "@adminSearchParameters": { + "description": "Column header above the editable fields used to search metadata providers in the Identify dialog" + }, + "adminCurrentMetadata": "Current Metadata", + "@adminCurrentMetadata": { + "description": "Column header above the read-only values currently stored on the item in the Identify dialog" + }, + "adminLabelYear": "Year", + "@adminLabelYear": { + "description": "Label for the release year field in the Identify dialog" + }, + "adminLabelImdbId": "IMDb Id", + "@adminLabelImdbId": { + "description": "Label for the IMDb identifier field in the Identify dialog. IMDb is a brand name and is normally left untranslated" + }, + "adminLabelTmdbMovieId": "TheMovieDb Movie Id", + "@adminLabelTmdbMovieId": { + "description": "Label for the TheMovieDb movie identifier field in the Identify dialog. TheMovieDb is a brand name and is normally left untranslated" + }, + "adminLabelTmdbBoxSetId": "TheMovieDb Box Set Id", + "@adminLabelTmdbBoxSetId": { + "description": "Label for the TheMovieDb collection identifier field in the Identify dialog. TheMovieDb is a brand name and is normally left untranslated" + }, + "adminLabelTvdbBoxSetId": "TheTVDB Box Set Id", + "@adminLabelTvdbBoxSetId": { + "description": "Label for the TheTVDB collection identifier field in the Identify dialog. TheTVDB is a brand name and is normally left untranslated" + }, + "adminLabelTvdbId": "TheTVDB Numerical Id", + "@adminLabelTvdbId": { + "description": "Label for the numeric TheTVDB identifier field in the Identify dialog. TheTVDB is a brand name and is normally left untranslated" + }, + "adminLabelTvdbSlug": "TheTVDB Slug Movie Id", + "@adminLabelTvdbSlug": { + "description": "Label for the TheTVDB slug identifier field in the Identify dialog. TheTVDB is a brand name and is normally left untranslated" + }, + "adminReplaceImages": "Replace existing images", + "@adminReplaceImages": { + "description": "Checkbox label in the confirmation dialog shown before applying an identified metadata match" + }, + "adminBackToSearch": "Back to Search Criteria", + "@adminBackToSearch": { + "description": "Tooltip on the button that returns from the Identify results list to the search form" } } diff --git a/lib/l10n/app_localizations.dart b/lib/l10n/app_localizations.dart index 61554197e..b118a80b4 100644 --- a/lib/l10n/app_localizations.dart +++ b/lib/l10n/app_localizations.dart @@ -18021,6 +18021,72 @@ abstract class AppLocalizations { /// In en, this message translates to: /// **'Couldn\'t update watchlist'** String get watchlistUpdateFailed; + + /// Column header above the editable fields used to search metadata providers in the Identify dialog + /// + /// In en, this message translates to: + /// **'Search Parameters'** + String get adminSearchParameters; + + /// Column header above the read-only values currently stored on the item in the Identify dialog + /// + /// In en, this message translates to: + /// **'Current Metadata'** + String get adminCurrentMetadata; + + /// Label for the release year field in the Identify dialog + /// + /// In en, this message translates to: + /// **'Year'** + String get adminLabelYear; + + /// Label for the IMDb identifier field in the Identify dialog. IMDb is a brand name and is normally left untranslated + /// + /// In en, this message translates to: + /// **'IMDb Id'** + String get adminLabelImdbId; + + /// Label for the TheMovieDb movie identifier field in the Identify dialog. TheMovieDb is a brand name and is normally left untranslated + /// + /// In en, this message translates to: + /// **'TheMovieDb Movie Id'** + String get adminLabelTmdbMovieId; + + /// Label for the TheMovieDb collection identifier field in the Identify dialog. TheMovieDb is a brand name and is normally left untranslated + /// + /// In en, this message translates to: + /// **'TheMovieDb Box Set Id'** + String get adminLabelTmdbBoxSetId; + + /// Label for the TheTVDB collection identifier field in the Identify dialog. TheTVDB is a brand name and is normally left untranslated + /// + /// In en, this message translates to: + /// **'TheTVDB Box Set Id'** + String get adminLabelTvdbBoxSetId; + + /// Label for the numeric TheTVDB identifier field in the Identify dialog. TheTVDB is a brand name and is normally left untranslated + /// + /// In en, this message translates to: + /// **'TheTVDB Numerical Id'** + String get adminLabelTvdbId; + + /// Label for the TheTVDB slug identifier field in the Identify dialog. TheTVDB is a brand name and is normally left untranslated + /// + /// In en, this message translates to: + /// **'TheTVDB Slug Movie Id'** + String get adminLabelTvdbSlug; + + /// Checkbox label in the confirmation dialog shown before applying an identified metadata match + /// + /// In en, this message translates to: + /// **'Replace existing images'** + String get adminReplaceImages; + + /// Tooltip on the button that returns from the Identify results list to the search form + /// + /// In en, this message translates to: + /// **'Back to Search Criteria'** + String get adminBackToSearch; } class _AppLocalizationsDelegate diff --git a/lib/l10n/app_localizations_af.dart b/lib/l10n/app_localizations_af.dart index cea92de18..64afd6d95 100644 --- a/lib/l10n/app_localizations_af.dart +++ b/lib/l10n/app_localizations_af.dart @@ -10157,4 +10157,37 @@ class AppLocalizationsAf extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_ar.dart b/lib/l10n/app_localizations_ar.dart index 464402a91..3608235f2 100644 --- a/lib/l10n/app_localizations_ar.dart +++ b/lib/l10n/app_localizations_ar.dart @@ -10133,4 +10133,37 @@ class AppLocalizationsAr extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_be.dart b/lib/l10n/app_localizations_be.dart index fe5ae0f3c..57fdac86f 100644 --- a/lib/l10n/app_localizations_be.dart +++ b/lib/l10n/app_localizations_be.dart @@ -10222,4 +10222,37 @@ class AppLocalizationsBe extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_bg.dart b/lib/l10n/app_localizations_bg.dart index 877b27ab1..0032b386d 100644 --- a/lib/l10n/app_localizations_bg.dart +++ b/lib/l10n/app_localizations_bg.dart @@ -10260,4 +10260,37 @@ class AppLocalizationsBg extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_bn.dart b/lib/l10n/app_localizations_bn.dart index f072b34d0..7d45c0ec4 100644 --- a/lib/l10n/app_localizations_bn.dart +++ b/lib/l10n/app_localizations_bn.dart @@ -10131,4 +10131,37 @@ class AppLocalizationsBn extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_ca.dart b/lib/l10n/app_localizations_ca.dart index f5f97251f..d5493de59 100644 --- a/lib/l10n/app_localizations_ca.dart +++ b/lib/l10n/app_localizations_ca.dart @@ -10308,4 +10308,37 @@ class AppLocalizationsCa extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_cs.dart b/lib/l10n/app_localizations_cs.dart index 486d614b4..faa0e03f8 100644 --- a/lib/l10n/app_localizations_cs.dart +++ b/lib/l10n/app_localizations_cs.dart @@ -10195,4 +10195,37 @@ class AppLocalizationsCs extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_cy.dart b/lib/l10n/app_localizations_cy.dart index 21d2ff062..8cad6f022 100644 --- a/lib/l10n/app_localizations_cy.dart +++ b/lib/l10n/app_localizations_cy.dart @@ -10212,4 +10212,37 @@ class AppLocalizationsCy extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_da.dart b/lib/l10n/app_localizations_da.dart index c043173e8..e0c91c845 100644 --- a/lib/l10n/app_localizations_da.dart +++ b/lib/l10n/app_localizations_da.dart @@ -10148,4 +10148,37 @@ class AppLocalizationsDa extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_de.dart b/lib/l10n/app_localizations_de.dart index 41722688f..b6f33ed63 100644 --- a/lib/l10n/app_localizations_de.dart +++ b/lib/l10n/app_localizations_de.dart @@ -10306,4 +10306,37 @@ class AppLocalizationsDe extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_el.dart b/lib/l10n/app_localizations_el.dart index 04a3f6290..0de82327e 100644 --- a/lib/l10n/app_localizations_el.dart +++ b/lib/l10n/app_localizations_el.dart @@ -10315,4 +10315,37 @@ class AppLocalizationsEl extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_en.dart b/lib/l10n/app_localizations_en.dart index 85e81bf1d..c6971f9b3 100644 --- a/lib/l10n/app_localizations_en.dart +++ b/lib/l10n/app_localizations_en.dart @@ -10067,6 +10067,39 @@ class AppLocalizationsEn extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } /// The translations for English, as used in the United Kingdom (`en_GB`). diff --git a/lib/l10n/app_localizations_eo.dart b/lib/l10n/app_localizations_eo.dart index 34fc29747..33e4e1e8f 100644 --- a/lib/l10n/app_localizations_eo.dart +++ b/lib/l10n/app_localizations_eo.dart @@ -10143,4 +10143,37 @@ class AppLocalizationsEo extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_es.dart b/lib/l10n/app_localizations_es.dart index 75a95eebb..e8fe7d239 100644 --- a/lib/l10n/app_localizations_es.dart +++ b/lib/l10n/app_localizations_es.dart @@ -10272,6 +10272,39 @@ class AppLocalizationsEs extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } /// The translations for Spanish Castilian, as used in Latin America and the Caribbean (`es_419`). diff --git a/lib/l10n/app_localizations_et.dart b/lib/l10n/app_localizations_et.dart index dfd3c6ec3..2305332c0 100644 --- a/lib/l10n/app_localizations_et.dart +++ b/lib/l10n/app_localizations_et.dart @@ -10161,4 +10161,37 @@ class AppLocalizationsEt extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_fa.dart b/lib/l10n/app_localizations_fa.dart index 359112f03..ae4817aa4 100644 --- a/lib/l10n/app_localizations_fa.dart +++ b/lib/l10n/app_localizations_fa.dart @@ -10089,4 +10089,37 @@ class AppLocalizationsFa extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_fi.dart b/lib/l10n/app_localizations_fi.dart index f454dd7bb..acecdc208 100644 --- a/lib/l10n/app_localizations_fi.dart +++ b/lib/l10n/app_localizations_fi.dart @@ -10179,4 +10179,37 @@ class AppLocalizationsFi extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_fr.dart b/lib/l10n/app_localizations_fr.dart index e0469db91..acaf9bc44 100644 --- a/lib/l10n/app_localizations_fr.dart +++ b/lib/l10n/app_localizations_fr.dart @@ -10284,4 +10284,37 @@ class AppLocalizationsFr extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_gl.dart b/lib/l10n/app_localizations_gl.dart index 9c6978ec6..94e1b7cff 100644 --- a/lib/l10n/app_localizations_gl.dart +++ b/lib/l10n/app_localizations_gl.dart @@ -10298,4 +10298,37 @@ class AppLocalizationsGl extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_he.dart b/lib/l10n/app_localizations_he.dart index 6e830d01c..702ab22e9 100644 --- a/lib/l10n/app_localizations_he.dart +++ b/lib/l10n/app_localizations_he.dart @@ -10010,4 +10010,37 @@ class AppLocalizationsHe extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_hi.dart b/lib/l10n/app_localizations_hi.dart index 95ebca0a3..2e064e507 100644 --- a/lib/l10n/app_localizations_hi.dart +++ b/lib/l10n/app_localizations_hi.dart @@ -10124,4 +10124,37 @@ class AppLocalizationsHi extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_hr.dart b/lib/l10n/app_localizations_hr.dart index f4494d26f..1bdfb5b6d 100644 --- a/lib/l10n/app_localizations_hr.dart +++ b/lib/l10n/app_localizations_hr.dart @@ -10377,4 +10377,37 @@ class AppLocalizationsHr extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_hu.dart b/lib/l10n/app_localizations_hu.dart index 551ab8eb2..4a1d45a61 100644 --- a/lib/l10n/app_localizations_hu.dart +++ b/lib/l10n/app_localizations_hu.dart @@ -10249,4 +10249,37 @@ class AppLocalizationsHu extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_id.dart b/lib/l10n/app_localizations_id.dart index 81708da00..49e4149c1 100644 --- a/lib/l10n/app_localizations_id.dart +++ b/lib/l10n/app_localizations_id.dart @@ -10153,4 +10153,37 @@ class AppLocalizationsId extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_it.dart b/lib/l10n/app_localizations_it.dart index a257d9bcb..487141dfd 100644 --- a/lib/l10n/app_localizations_it.dart +++ b/lib/l10n/app_localizations_it.dart @@ -10238,4 +10238,37 @@ class AppLocalizationsIt extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_ja.dart b/lib/l10n/app_localizations_ja.dart index 18f68442b..2ca9576e9 100644 --- a/lib/l10n/app_localizations_ja.dart +++ b/lib/l10n/app_localizations_ja.dart @@ -9837,4 +9837,37 @@ class AppLocalizationsJa extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_kk.dart b/lib/l10n/app_localizations_kk.dart index 7418bef61..2ac1cd121 100644 --- a/lib/l10n/app_localizations_kk.dart +++ b/lib/l10n/app_localizations_kk.dart @@ -10199,4 +10199,37 @@ class AppLocalizationsKk extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_kn.dart b/lib/l10n/app_localizations_kn.dart index 89d4d6905..a2666436c 100644 --- a/lib/l10n/app_localizations_kn.dart +++ b/lib/l10n/app_localizations_kn.dart @@ -10223,4 +10223,37 @@ class AppLocalizationsKn extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_ko.dart b/lib/l10n/app_localizations_ko.dart index fb863366e..3b86fedd1 100644 --- a/lib/l10n/app_localizations_ko.dart +++ b/lib/l10n/app_localizations_ko.dart @@ -9830,4 +9830,37 @@ class AppLocalizationsKo extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_lt.dart b/lib/l10n/app_localizations_lt.dart index 04ece38c6..f6b0bb5f5 100644 --- a/lib/l10n/app_localizations_lt.dart +++ b/lib/l10n/app_localizations_lt.dart @@ -10219,4 +10219,37 @@ class AppLocalizationsLt extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_lv.dart b/lib/l10n/app_localizations_lv.dart index a33032901..7901a55d0 100644 --- a/lib/l10n/app_localizations_lv.dart +++ b/lib/l10n/app_localizations_lv.dart @@ -10216,4 +10216,37 @@ class AppLocalizationsLv extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_mk.dart b/lib/l10n/app_localizations_mk.dart index b1e53470c..3b49af12e 100644 --- a/lib/l10n/app_localizations_mk.dart +++ b/lib/l10n/app_localizations_mk.dart @@ -10231,4 +10231,37 @@ class AppLocalizationsMk extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_ml.dart b/lib/l10n/app_localizations_ml.dart index 7aa8898e6..edad1a00e 100644 --- a/lib/l10n/app_localizations_ml.dart +++ b/lib/l10n/app_localizations_ml.dart @@ -10276,4 +10276,37 @@ class AppLocalizationsMl extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_mn.dart b/lib/l10n/app_localizations_mn.dart index 1b91edf99..595186831 100644 --- a/lib/l10n/app_localizations_mn.dart +++ b/lib/l10n/app_localizations_mn.dart @@ -10173,4 +10173,37 @@ class AppLocalizationsMn extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_nb.dart b/lib/l10n/app_localizations_nb.dart index 3e0dd05a6..5f851cc49 100644 --- a/lib/l10n/app_localizations_nb.dart +++ b/lib/l10n/app_localizations_nb.dart @@ -10146,4 +10146,37 @@ class AppLocalizationsNb extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_nl.dart b/lib/l10n/app_localizations_nl.dart index 730a6f1c5..77e488121 100644 --- a/lib/l10n/app_localizations_nl.dart +++ b/lib/l10n/app_localizations_nl.dart @@ -10205,4 +10205,37 @@ class AppLocalizationsNl extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_pa.dart b/lib/l10n/app_localizations_pa.dart index 7dfe370cb..3b50ec6ee 100644 --- a/lib/l10n/app_localizations_pa.dart +++ b/lib/l10n/app_localizations_pa.dart @@ -10112,4 +10112,37 @@ class AppLocalizationsPa extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_pl.dart b/lib/l10n/app_localizations_pl.dart index f8b2a6007..389cae952 100644 --- a/lib/l10n/app_localizations_pl.dart +++ b/lib/l10n/app_localizations_pl.dart @@ -10215,4 +10215,37 @@ class AppLocalizationsPl extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_pt.dart b/lib/l10n/app_localizations_pt.dart index 2d12d203b..968905b90 100644 --- a/lib/l10n/app_localizations_pt.dart +++ b/lib/l10n/app_localizations_pt.dart @@ -10238,6 +10238,39 @@ class AppLocalizationsPt extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } /// The translations for Portuguese, as used in Brazil (`pt_BR`). diff --git a/lib/l10n/app_localizations_ro.dart b/lib/l10n/app_localizations_ro.dart index 650f02300..68c987880 100644 --- a/lib/l10n/app_localizations_ro.dart +++ b/lib/l10n/app_localizations_ro.dart @@ -10242,4 +10242,37 @@ class AppLocalizationsRo extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_ru.dart b/lib/l10n/app_localizations_ru.dart index 4acf9b05c..bfbda2aa6 100644 --- a/lib/l10n/app_localizations_ru.dart +++ b/lib/l10n/app_localizations_ru.dart @@ -10232,4 +10232,37 @@ class AppLocalizationsRu extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_si.dart b/lib/l10n/app_localizations_si.dart index 0479944a5..229b6ff15 100644 --- a/lib/l10n/app_localizations_si.dart +++ b/lib/l10n/app_localizations_si.dart @@ -10144,4 +10144,37 @@ class AppLocalizationsSi extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_sk.dart b/lib/l10n/app_localizations_sk.dart index afedf6f12..28dbaa94c 100644 --- a/lib/l10n/app_localizations_sk.dart +++ b/lib/l10n/app_localizations_sk.dart @@ -10224,4 +10224,37 @@ class AppLocalizationsSk extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_sl.dart b/lib/l10n/app_localizations_sl.dart index 9b9a32e99..024b0066c 100644 --- a/lib/l10n/app_localizations_sl.dart +++ b/lib/l10n/app_localizations_sl.dart @@ -10218,4 +10218,37 @@ class AppLocalizationsSl extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_sq.dart b/lib/l10n/app_localizations_sq.dart index a8bef402e..aba3ae80b 100644 --- a/lib/l10n/app_localizations_sq.dart +++ b/lib/l10n/app_localizations_sq.dart @@ -10253,4 +10253,37 @@ class AppLocalizationsSq extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_sr.dart b/lib/l10n/app_localizations_sr.dart index 9842d88b2..42e3ad10c 100644 --- a/lib/l10n/app_localizations_sr.dart +++ b/lib/l10n/app_localizations_sr.dart @@ -10365,4 +10365,37 @@ class AppLocalizationsSr extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_sv.dart b/lib/l10n/app_localizations_sv.dart index 51b0393ad..23eb5ee15 100644 --- a/lib/l10n/app_localizations_sv.dart +++ b/lib/l10n/app_localizations_sv.dart @@ -10162,4 +10162,37 @@ class AppLocalizationsSv extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_sw.dart b/lib/l10n/app_localizations_sw.dart index 9487465ae..8dcf587ca 100644 --- a/lib/l10n/app_localizations_sw.dart +++ b/lib/l10n/app_localizations_sw.dart @@ -10237,4 +10237,37 @@ class AppLocalizationsSw extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_ta.dart b/lib/l10n/app_localizations_ta.dart index c522ce5d7..47fb9f8aa 100644 --- a/lib/l10n/app_localizations_ta.dart +++ b/lib/l10n/app_localizations_ta.dart @@ -10238,4 +10238,37 @@ class AppLocalizationsTa extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_te.dart b/lib/l10n/app_localizations_te.dart index 568aef1cd..9399fc8fe 100644 --- a/lib/l10n/app_localizations_te.dart +++ b/lib/l10n/app_localizations_te.dart @@ -10224,4 +10224,37 @@ class AppLocalizationsTe extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_th.dart b/lib/l10n/app_localizations_th.dart index c6114384a..236c3011b 100644 --- a/lib/l10n/app_localizations_th.dart +++ b/lib/l10n/app_localizations_th.dart @@ -10075,4 +10075,37 @@ class AppLocalizationsTh extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_tl.dart b/lib/l10n/app_localizations_tl.dart index 3771164ac..02d4d2b2a 100644 --- a/lib/l10n/app_localizations_tl.dart +++ b/lib/l10n/app_localizations_tl.dart @@ -10268,4 +10268,37 @@ class AppLocalizationsTl extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_tr.dart b/lib/l10n/app_localizations_tr.dart index d29ec263d..976624c71 100644 --- a/lib/l10n/app_localizations_tr.dart +++ b/lib/l10n/app_localizations_tr.dart @@ -10168,4 +10168,37 @@ class AppLocalizationsTr extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_ug.dart b/lib/l10n/app_localizations_ug.dart index fd65eb396..ddd20d894 100644 --- a/lib/l10n/app_localizations_ug.dart +++ b/lib/l10n/app_localizations_ug.dart @@ -10189,4 +10189,37 @@ class AppLocalizationsUg extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_uk.dart b/lib/l10n/app_localizations_uk.dart index fff5f7456..993866bd5 100644 --- a/lib/l10n/app_localizations_uk.dart +++ b/lib/l10n/app_localizations_uk.dart @@ -10243,4 +10243,37 @@ class AppLocalizationsUk extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_vi.dart b/lib/l10n/app_localizations_vi.dart index 8520d6749..959d4fc01 100644 --- a/lib/l10n/app_localizations_vi.dart +++ b/lib/l10n/app_localizations_vi.dart @@ -10156,4 +10156,37 @@ class AppLocalizationsVi extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } diff --git a/lib/l10n/app_localizations_yue.dart b/lib/l10n/app_localizations_yue.dart index b3eeca030..669621924 100644 --- a/lib/l10n/app_localizations_yue.dart +++ b/lib/l10n/app_localizations_yue.dart @@ -9749,6 +9749,39 @@ class AppLocalizationsYue extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } /// The translations for Yue Chinese Cantonese, as used in China (`yue_CN`). diff --git a/lib/l10n/app_localizations_zh.dart b/lib/l10n/app_localizations_zh.dart index 50c2f9961..aa0de0214 100644 --- a/lib/l10n/app_localizations_zh.dart +++ b/lib/l10n/app_localizations_zh.dart @@ -9745,6 +9745,39 @@ class AppLocalizationsZh extends AppLocalizations { @override String get watchlistUpdateFailed => 'Couldn\'t update watchlist'; + + @override + String get adminSearchParameters => 'Search Parameters'; + + @override + String get adminCurrentMetadata => 'Current Metadata'; + + @override + String get adminLabelYear => 'Year'; + + @override + String get adminLabelImdbId => 'IMDb Id'; + + @override + String get adminLabelTmdbMovieId => 'TheMovieDb Movie Id'; + + @override + String get adminLabelTmdbBoxSetId => 'TheMovieDb Box Set Id'; + + @override + String get adminLabelTvdbBoxSetId => 'TheTVDB Box Set Id'; + + @override + String get adminLabelTvdbId => 'TheTVDB Numerical Id'; + + @override + String get adminLabelTvdbSlug => 'TheTVDB Slug Movie Id'; + + @override + String get adminReplaceImages => 'Replace existing images'; + + @override + String get adminBackToSearch => 'Back to Search Criteria'; } /// The translations for Chinese, using the Han script (`zh_Hant`). diff --git a/lib/ui/screens/admin/metadata/admin_metadata_edit_screen.dart b/lib/ui/screens/admin/metadata/admin_metadata_edit_screen.dart index 7ce492891..74e82d19a 100644 --- a/lib/ui/screens/admin/metadata/admin_metadata_edit_screen.dart +++ b/lib/ui/screens/admin/metadata/admin_metadata_edit_screen.dart @@ -10,6 +10,7 @@ import '../../../../l10n/app_localizations.dart'; import '../../../../util/image_mime.dart'; import '../../../../util/platform_detection.dart'; import '../../../widgets/adaptive/adaptive_dialog.dart'; +import '../../../widgets/identify_dialog.dart'; import '../../detail/modern/widgets/details_tab_bar.dart'; import '../widgets/admin_form_styles.dart'; @@ -524,128 +525,28 @@ class _AdminMetadataEditScreenState extends State { } Future _searchAndApplyRemote() async { - final l10n = AppLocalizations.of(context); final searchType = (_raw['Type'] ?? '').toString(); - if (searchType.isEmpty) return; - - final queryController = - TextEditingController(text: (_raw['Name'] ?? '').toString()); - final query = await showDialog( - context: context, - builder: (ctx) => AlertDialog.adaptive( - title: Text(l10n.adminMetadataIdentify), - content: TextField( - controller: queryController, - autofocus: true, - decoration: adminInputDecoration(label: l10n.name), - onSubmitted: (v) => Navigator.pop(ctx, v.trim()), - ), - actions: [ - adaptiveDialogAction( - onPressed: () => Navigator.pop(ctx), - child: Text(l10n.cancel), - ), - FilledButton( - onPressed: () => Navigator.pop(ctx, queryController.text.trim()), - child: Text(l10n.search), - ), - ], - ), - ); - queryController.dispose(); - - if (query == null || query.isEmpty || !mounted) return; - - final searchInfo = {'Name': query}; + final name = (_raw['Name'] ?? '').toString(); final year = int.tryParse((_raw['ProductionYear'] ?? '').toString()); - if (year != null) searchInfo['Year'] = year; - final providerIds = _raw['ProviderIds']; - if (providerIds is Map && providerIds.isNotEmpty) { - searchInfo['ProviderIds'] = Map.from(providerIds); - } - - try { - final results = await _api.searchRemote(searchType, { - 'SearchInfo': searchInfo, - 'ItemId': widget.itemId, - }); - - if (!mounted) return; - if (results.isEmpty) { - ScaffoldMessenger.of(context).showSnackBar( - SnackBar(content: Text(l10n.adminNoRemoteMatches)), - ); - return; - } + final path = _raw['Path']?.toString(); + final providerIds = _raw['ProviderIds'] is Map + ? Map.from(_raw['ProviderIds']) + : null; - final selected = await showDialog>( - context: context, - builder: (ctx) => AlertDialog.adaptive( - title: Text(l10n.adminRemoteResults), - content: SizedBox( - width: (MediaQuery.sizeOf(ctx).width - 32).clamp(280.0, 560.0), - height: (MediaQuery.sizeOf(ctx).height * 0.6).clamp(240.0, 520.0), - child: ListView.builder( - shrinkWrap: true, - itemCount: results.length, - itemBuilder: (context, index) { - final item = results[index]; - final name = (item['Name'] ?? l10n.unknown).toString(); - final resultYear = item['ProductionYear']?.toString(); - final overview = (item['Overview'] ?? '').toString(); - final provider = - (item['SearchProviderName'] ?? item['ProviderName'] ?? '') - .toString(); - final imageUrl = item['ImageUrl']?.toString(); - final subtitle = overview.isNotEmpty ? overview : provider; - return ListTile( - leading: imageUrl != null && imageUrl.isNotEmpty - ? ClipRRect( - borderRadius: BorderRadius.circular(4), - child: Image.network( - imageUrl, - width: 40, - height: 60, - fit: BoxFit.cover, - errorBuilder: (_, _, _) => - const Icon(Icons.movie_outlined), - ), - ) - : const Icon(Icons.movie_outlined), - title: Text(resultYear != null && resultYear.isNotEmpty - ? '$name ($resultYear)' - : name), - subtitle: subtitle.isEmpty - ? null - : Text(subtitle, - maxLines: 2, overflow: TextOverflow.ellipsis), - isThreeLine: overview.isNotEmpty, - onTap: () => Navigator.pop(ctx, item), - ); - }, - ), - ), - actions: [ - adaptiveDialogAction( - onPressed: () => Navigator.pop(ctx), - child: Text(l10n.cancel), - ), - ], - ), - ); + final applied = await IdentifyDialog.show( + context, + itemId: widget.itemId, + // An empty type means unknown, so let the dialog resolve it from the item + // rather than posting to /Items/RemoteSearch/ with no type at all. + itemType: searchType.isEmpty ? null : searchType, + itemName: name, + itemYear: year, + itemPath: path, + providerIds: providerIds, + ); - if (selected == null || !mounted) return; - await _api.applyRemoteSearchResult(widget.itemId, selected); - if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(l10n.adminRemoteMetadataApplied))); + if (applied == true && mounted) { await _load(); - } catch (e) { - if (!mounted) return; - ScaffoldMessenger.of( - context, - ).showSnackBar(SnackBar(content: Text(l10n.adminRemoteSearchFailed(e.toString())))); } } diff --git a/lib/ui/screens/detail/item_detail_screen.dart b/lib/ui/screens/detail/item_detail_screen.dart index ec682bff6..fa429f7ac 100644 --- a/lib/ui/screens/detail/item_detail_screen.dart +++ b/lib/ui/screens/detail/item_detail_screen.dart @@ -2,6 +2,8 @@ import 'dart:async'; import 'dart:ui'; import '../../widgets/offline_aware_image.dart'; +import '../../widgets/identify_dialog.dart'; +import '../../widgets/focus/context_action.dart' show canIdentifyItemType; import 'package:dio/dio.dart'; import 'package:flutter/foundation.dart'; import 'package:flutter/material.dart'; @@ -6568,6 +6570,114 @@ class DetailActionButtonsState extends State { content: Column( mainAxisSize: MainAxisSize.min, children: [ + if (canIdentifyItemType(item.type)) + Focus( + onKeyEvent: (_, event) { + if (isActivateKey(event)) { + Navigator.of(dialogCtx).pop(); + IdentifyDialog.show( + context, + itemId: item.id, + itemType: item.type, + itemName: item.name, + itemYear: item.productionYear, + ).then((applied) { + if (applied == true) { + viewModel.load(); + } + }); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: Builder( + builder: (buttonCtx) { + final hasFocus = Focus.of(buttonCtx).hasFocus; + return InkWell( + onTap: () async { + Navigator.of(dialogCtx).pop(); + final applied = await IdentifyDialog.show( + context, + itemId: item.id, + itemType: item.type, + itemName: item.name, + itemYear: item.productionYear, + ); + if (applied == true) { + viewModel.load(); + } + }, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), + decoration: BoxDecoration( + color: hasFocus ? Colors.white12 : Colors.transparent, + borderRadius: AppRadius.circular(8), + ), + child: Row( + children: [ + const Icon(Icons.search, color: Colors.white70), + const SizedBox(width: 12), + Text( + l10n.adminMetadataIdentify, + style: const TextStyle(color: Colors.white), + ), + ], + ), + ), + ); + }, + ), + ), + if (!PlatformDetection.isTV) + Focus( + onKeyEvent: (_, event) { + if (isActivateKey(event)) { + Navigator.of(dialogCtx).pop(); + context.push(Destinations.adminMetadata(item.id)); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: Builder( + builder: (buttonCtx) { + final hasFocus = Focus.of(buttonCtx).hasFocus; + return InkWell( + onTap: () { + Navigator.of(dialogCtx).pop(); + context.push(Destinations.adminMetadata(item.id)); + }, + child: Container( + padding: const EdgeInsets.symmetric( + vertical: 12, + horizontal: 16, + ), + decoration: BoxDecoration( + color: hasFocus + ? Colors.white12 + : Colors.transparent, + borderRadius: AppRadius.circular(8), + ), + child: Row( + children: [ + const Icon( + Icons.edit_note, + color: Colors.white70, + ), + const SizedBox(width: 12), + Text( + l10n.editMetadata, + style: const TextStyle(color: Colors.white), + ), + ], + ), + ), + ); + }, + ), + ), Focus( onKeyEvent: (_, event) { if (isActivateKey(event)) { @@ -6621,53 +6731,6 @@ class DetailActionButtonsState extends State { }, ), ), - if (!PlatformDetection.isTV) - Focus( - onKeyEvent: (_, event) { - if (isActivateKey(event)) { - Navigator.of(dialogCtx).pop(); - context.push(Destinations.adminMetadata(item.id)); - return KeyEventResult.handled; - } - return KeyEventResult.ignored; - }, - child: Builder( - builder: (buttonCtx) { - final hasFocus = Focus.of(buttonCtx).hasFocus; - return InkWell( - onTap: () { - Navigator.of(dialogCtx).pop(); - context.push(Destinations.adminMetadata(item.id)); - }, - child: Container( - padding: const EdgeInsets.symmetric( - vertical: 12, - horizontal: 16, - ), - decoration: BoxDecoration( - color: hasFocus - ? Colors.white12 - : Colors.transparent, - borderRadius: AppRadius.circular(8), - ), - child: Row( - children: [ - const Icon( - Icons.edit_note, - color: Colors.white70, - ), - const SizedBox(width: 12), - Text( - l10n.editMetadata, - style: const TextStyle(color: Colors.white), - ), - ], - ), - ), - ); - }, - ), - ), if (item.canDelete) Focus( onKeyEvent: (_, event) { diff --git a/lib/ui/widgets/focus/context_action.dart b/lib/ui/widgets/focus/context_action.dart index ab8f06fb3..d768469a8 100644 --- a/lib/ui/widgets/focus/context_action.dart +++ b/lib/ui/widgets/focus/context_action.dart @@ -13,6 +13,7 @@ import '../../navigation/destinations.dart'; import '../add_to_collection_dialog.dart'; import '../add_to_playlist_dialog.dart'; import '../change_artwork_dialog.dart'; +import '../identify_dialog.dart'; class ItemContextAction { final IconData icon; @@ -26,6 +27,25 @@ class ItemContextAction { }); } +/// Item types the server exposes a `/Items/RemoteSearch/{type}` endpoint for. +/// Episodes and Seasons qualify because Identify redirects them to their +/// parent series. Anything else would just 404 with a raw error toast. +const _identifiableTypes = { + 'Movie', + 'Series', + 'Season', + 'Episode', + 'BoxSet', + 'Person', + 'MusicAlbum', + 'MusicArtist', + 'Book', + 'Trailer', + 'MusicVideo', +}; + +bool canIdentifyItemType(String? type) => _identifiableTypes.contains(type); + List contextActionsFor( BuildContext context, AggregatedItem item, { @@ -202,6 +222,37 @@ List contextActionsFor( } }, )); + + if (canIdentifyItemType(type)) { + actions.add(ItemContextAction( + icon: Icons.search_outlined, + label: l10n.adminMetadataIdentify, + onSelect: () async { + if (!context.mounted) return; + final isTVChild = item.type == 'Episode' || item.type == 'Season'; + final hasSeriesId = + item.seriesId != null && item.seriesId!.isNotEmpty; + final targetItemId = + (isTVChild && hasSeriesId) ? item.seriesId! : item.id; + final targetItemType = + (isTVChild && hasSeriesId) ? 'Series' : item.type; + final targetItemName = (isTVChild && hasSeriesId) + ? (item.seriesName ?? item.name) + : item.name; + + final applied = await IdentifyDialog.show( + context, + itemId: targetItemId, + itemType: targetItemType, + itemName: targetItemName, + itemYear: isTVChild ? null : item.productionYear, + ); + if (applied == true) { + onChanged?.call(); + } + }, + )); + } } } diff --git a/lib/ui/widgets/identify_dialog.dart b/lib/ui/widgets/identify_dialog.dart new file mode 100644 index 000000000..c0bfc9ca7 --- /dev/null +++ b/lib/ui/widgets/identify_dialog.dart @@ -0,0 +1,1264 @@ +import 'package:custom_tv_text_field/custom_tv_text_field.dart'; +import 'package:flutter/material.dart'; +import 'package:get_it/get_it.dart'; +import 'package:moonfin_design/moonfin_design.dart'; +import 'package:server_core/server_core.dart'; + +import '../../l10n/app_localizations.dart'; +import '../../preference/user_preferences.dart'; +import '../../util/focus/dpad_keys.dart'; +import '../../util/platform_detection.dart'; +import 'adaptive/adaptive_dialog.dart'; +import 'focus/focusable_button.dart'; +import 'overlay_sheet.dart'; + +class IdentifyDialog extends StatefulWidget { + final String itemId; + final String? itemType; + final String? itemName; + final int? itemYear; + final String? itemPath; + final Map? providerIds; + + const IdentifyDialog({ + super.key, + required this.itemId, + this.itemType, + this.itemName, + this.itemYear, + this.itemPath, + this.providerIds, + }); + + static Future show( + BuildContext context, { + required String itemId, + String? itemType, + String? itemName, + int? itemYear, + String? itemPath, + Map? providerIds, + }) { + return showFocusRestoringDialog( + context: context, + builder: (ctx) => IdentifyDialog( + itemId: itemId, + itemType: itemType, + itemName: itemName, + itemYear: itemYear, + itemPath: itemPath, + providerIds: providerIds, + ), + ); + } + + @override + State createState() => _IdentifyDialogState(); +} + +class _IdentifyDialogState extends State { + /// Every focus node list and the d-pad wrap-around logic is sized off this. + static const int _fieldCount = 8; + static const int _lastFieldIndex = _fieldCount - 1; + + late final AdminItemsApi _adminApi; + late final ItemsApi _itemsApi; + late final UserPreferences _prefs; + + bool _loadingItem = false; + String? _loadError; + bool _searching = false; + bool _applying = false; + + late String _targetItemId; + String? _path; + String _searchType = 'Movie'; + + List>? _searchResults; + + // What the item currently has, shown next to each search field. + String? _refName; + String? _refYear; + String? _refImdb; + String? _refTmdbMovie; + String? _refTmdbBoxSet; + String? _refTvdbBoxSet; + String? _refTvdbId; + String? _refTvdbSlug; + + // The search fields start blank so a search only uses what the admin types. + final TextEditingController _nameController = TextEditingController(); + final TextEditingController _yearController = TextEditingController(); + final TextEditingController _imdbController = TextEditingController(); + final TextEditingController _tmdbMovieController = TextEditingController(); + final TextEditingController _tmdbBoxSetController = TextEditingController(); + final TextEditingController _tvdbBoxSetController = TextEditingController(); + final TextEditingController _tvdbIdController = TextEditingController(); + final TextEditingController _tvdbSlugController = TextEditingController(); + + final ScrollController _scrollController = ScrollController(); + + final FocusNode _closeButtonFocusNode = FocusNode(debugLabel: 'identify-close-btn'); + final FocusNode _cancelButtonFocusNode = FocusNode(debugLabel: 'identify-cancel-btn'); + final FocusNode _searchButtonFocusNode = FocusNode(debugLabel: 'identify-search-btn'); + + final List _fieldFocusNodes = List.generate( + _fieldCount, (i) => FocusNode(debugLabel: 'identify-field-$i')); + final List _arrowFocusNodes = List.generate( + _fieldCount, (i) => FocusNode(debugLabel: 'identify-arrow-$i')); + final List> _tvFieldKeys = + List.generate(_fieldCount, (_) => GlobalKey()); + + List? _resultFocusNodes; + + bool get _isTV => PlatformDetection.isTV; + + @override + void initState() { + super.initState(); + final client = GetIt.instance(); + _adminApi = client.adminItemsApi; + _itemsApi = client.itemsApi; + _prefs = GetIt.instance(); + + _targetItemId = widget.itemId; + final itemType = widget.itemType?.trim(); + final isTVChild = itemType == 'Episode' || itemType == 'Season'; + _searchType = isTVChild + ? 'Series' + : (itemType == null || itemType.isEmpty ? 'Movie' : itemType); + _path = widget.itemPath; + + final initialProviders = widget.providerIds ?? const {}; + + if (!isTVChild) { + _refName = widget.itemName; + _refYear = widget.itemYear?.toString(); + _refImdb = initialProviders['Imdb']?.toString(); + _refTmdbMovie = initialProviders['Tmdb']?.toString(); + _refTmdbBoxSet = initialProviders['TmdbBoxSet']?.toString(); + _refTvdbBoxSet = initialProviders['TvdbBoxSet']?.toString(); + _refTvdbId = initialProviders['Tvdb']?.toString(); + _refTvdbSlug = initialProviders['TvdbSlug']?.toString(); + } + + // Keep the focused row on screen while the d-pad walks down the form. + for (int i = 0; i < _fieldCount; i++) { + final idx = i; + _fieldFocusNodes[idx].addListener(() { + if (_fieldFocusNodes[idx].hasFocus) { + _scrollToIndex(idx); + } + }); + _arrowFocusNodes[idx].addListener(() { + if (_arrowFocusNodes[idx].hasFocus) { + _scrollToIndex(idx); + } + }); + } + + _loadItemDetails(); + } + + void _closeDialog([bool? result]) { + if (!mounted) return; + FocusScope.of(context).unfocus(); + Navigator.of(context).pop(result); + } + + @override + void dispose() { + _nameController.dispose(); + _yearController.dispose(); + _imdbController.dispose(); + _tmdbMovieController.dispose(); + _tmdbBoxSetController.dispose(); + _tvdbBoxSetController.dispose(); + _tvdbIdController.dispose(); + _tvdbSlugController.dispose(); + + _scrollController.dispose(); + + _closeButtonFocusNode.unfocus(); + _closeButtonFocusNode.dispose(); + + _cancelButtonFocusNode.unfocus(); + _cancelButtonFocusNode.dispose(); + + _searchButtonFocusNode.unfocus(); + _searchButtonFocusNode.dispose(); + + for (final fn in _fieldFocusNodes) { + fn.unfocus(); + fn.dispose(); + } + for (final fn in _arrowFocusNodes) { + fn.unfocus(); + fn.dispose(); + } + _disposeNodes(_resultFocusNodes); + _resultFocusNodes = null; + super.dispose(); + } + + static void _disposeNodes(List? nodes) { + if (nodes == null) return; + for (final fn in nodes) { + fn.unfocus(); + fn.dispose(); + } + } + + /// Disposes the result focus nodes only after the frame that takes the result + /// cards off screen. Disposing them inline would leave the still mounted + /// [Focus] widgets holding dead nodes, which throws on the next detach. + void _releaseResultFocusNodes() { + final stale = _resultFocusNodes; + _resultFocusNodes = null; + if (stale == null) return; + WidgetsBinding.instance.addPostFrameCallback((_) => _disposeNodes(stale)); + } + + void _backToSearchForm() { + setState(() => _searchResults = null); + _releaseResultFocusNodes(); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && _isTV) { + _fieldFocusNodes.first.requestFocus(); + } + }); + } + + void _setupResultFocusNodes(int count) { + _releaseResultFocusNodes(); + _resultFocusNodes = + List.generate(count, (i) => FocusNode(debugLabel: 'identify-result-$i')); + for (int i = 0; i < count; i++) { + final idx = i; + _resultFocusNodes![idx].addListener(() { + if (_resultFocusNodes != null && + idx < _resultFocusNodes!.length && + _resultFocusNodes![idx].hasFocus) { + _scrollToResultIndex(idx); + } + }); + } + } + + void _scrollToIndex(int index) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + final fn = _fieldFocusNodes[index]; + final fnCtx = fn.context ?? _arrowFocusNodes[index].context; + if (fnCtx != null && fnCtx.mounted) { + Scrollable.ensureVisible( + fnCtx, + alignment: 0.35, + duration: const Duration(milliseconds: 150), + curve: Curves.easeOutCubic, + ); + } + }); + } + + void _scrollToResultIndex(int index) { + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted || _resultFocusNodes == null || index >= _resultFocusNodes!.length) return; + final fnCtx = _resultFocusNodes![index].context; + if (fnCtx != null && fnCtx.mounted) { + Scrollable.ensureVisible( + fnCtx, + alignment: 0.35, + duration: const Duration(milliseconds: 150), + curve: Curves.easeOutCubic, + ); + } + }); + } + + Future _loadItemDetails() async { + setState(() { + _loadingItem = true; + _loadError = null; + }); + try { + var raw = await _itemsApi.getItem(widget.itemId); + + if ((raw['Type'] == 'Episode' || raw['Type'] == 'Season') && + raw['SeriesId'] != null && + raw['SeriesId'].toString().isNotEmpty) { + final seriesId = raw['SeriesId'].toString(); + // Episodes and seasons are identified through their series, but only + // switch targets once that series has actually loaded, otherwise the + // series id would end up paired with episode metadata. + final series = await _itemsApi.getItem(seriesId); + _targetItemId = seriesId; + raw = series; + } + + if (!mounted) return; + setState(() { + _refName = raw['Name']?.toString(); + _refYear = raw['ProductionYear']?.toString(); + if (raw['Path'] != null && raw['Path'].toString().isNotEmpty) { + _path = raw['Path'].toString(); + } + if (raw['Type'] != null && raw['Type'].toString().isNotEmpty) { + _searchType = raw['Type'].toString(); + } + final pIds = raw['ProviderIds']; + if (pIds is Map) { + _refImdb = pIds['Imdb']?.toString(); + _refTmdbMovie = pIds['Tmdb']?.toString(); + _refTmdbBoxSet = pIds['TmdbBoxSet']?.toString(); + _refTvdbBoxSet = pIds['TvdbBoxSet']?.toString(); + _refTvdbId = pIds['Tvdb']?.toString(); + _refTvdbSlug = pIds['TvdbSlug']?.toString(); + } + }); + } catch (e) { + // Searching by hand still works without reference values, so say what + // went wrong instead of leaving the current metadata column blank. + if (mounted) { + setState(() => _loadError = '$e'); + } + } finally { + if (mounted) { + setState(() => _loadingItem = false); + } + } + } + + Future>> _searchRemote( + Map searchInfo, { + String? providerName, + }) { + return _adminApi.searchRemote(_searchType, { + 'SearchInfo': searchInfo, + 'ItemId': _targetItemId, + 'IncludeDisabledProviders': false, + 'SearchProviderName': ?providerName, + }); + } + + Future _performSearch() async { + final l10n = AppLocalizations.of(context); + setState(() { + _searching = true; + _searchResults = null; + }); + + final nameVal = _nameController.text.trim(); + final yearVal = int.tryParse(_yearController.text.trim()); + + // Provider ids are matched case insensitively, so one casing each is enough. + final providerIds = {}; + + var imdbVal = _imdbController.text.trim(); + if (imdbVal.isNotEmpty) { + if (RegExp(r'^\d+$').hasMatch(imdbVal)) { + imdbVal = 'tt$imdbVal'; + } + providerIds['Imdb'] = imdbVal; + } + + final tmdbVal = _tmdbMovieController.text.trim(); + if (tmdbVal.isNotEmpty) { + providerIds['Tmdb'] = tmdbVal; + } + + final tmdbBoxSetVal = _tmdbBoxSetController.text.trim(); + if (tmdbBoxSetVal.isNotEmpty) { + providerIds['TmdbBoxSet'] = tmdbBoxSetVal; + } + + final tvdbBoxSetVal = _tvdbBoxSetController.text.trim(); + if (tvdbBoxSetVal.isNotEmpty) { + providerIds['TvdbBoxSet'] = tvdbBoxSetVal; + } + + final tvdbVal = _tvdbIdController.text.trim(); + if (tvdbVal.isNotEmpty) { + providerIds['Tvdb'] = tvdbVal; + } + + final tvdbSlugVal = _tvdbSlugController.text.trim(); + if (tvdbSlugVal.isNotEmpty) { + providerIds['TvdbSlug'] = tvdbSlugVal; + } + + final searchInfo = { + 'Name': nameVal, + 'Year': ?yearVal, + if (providerIds.isNotEmpty) 'ProviderIds': providerIds, + }; + + try { + List> results = await _searchRemote(searchInfo); + + // Pin the search to TheMovieDb when a TMDB or IMDb id was given. Note + // that SearchProviderName belongs on the query, not inside SearchInfo. + if (results.isEmpty && + (providerIds.containsKey('Tmdb') || providerIds.containsKey('Imdb'))) { + results = await _searchRemote(searchInfo, providerName: 'TheMovieDb'); + } + + // Retry on the ids alone, in case the typed name blocked the match. + if (results.isEmpty && providerIds.isNotEmpty && nameVal.isNotEmpty) { + results = await _searchRemote({ + 'Name': '', + 'Year': ?yearVal, + 'ProviderIds': providerIds, + }); + } + + // Last resort, drop the ids and search on name and year only. + if (results.isEmpty && nameVal.isNotEmpty) { + results = await _searchRemote({ + 'Name': nameVal, + 'Year': ?yearVal, + }); + } + + if (!mounted) return; + + if (results.isEmpty) { + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.adminNoRemoteMatches)), + ); + setState(() { + _searching = false; + }); + } else { + _setupResultFocusNodes(results.length); + setState(() { + _searchResults = results; + _searching = false; + }); + WidgetsBinding.instance.addPostFrameCallback((_) { + if (mounted && _resultFocusNodes != null && _resultFocusNodes!.isNotEmpty) { + _resultFocusNodes![0].requestFocus(); + } + }); + } + } catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.adminRemoteSearchFailed('$e'))), + ); + setState(() => _searching = false); + } + } + + Future _applyResult(Map result) async { + if (_applying) return; + final l10n = AppLocalizations.of(context); + bool replaceAllImages = true; + + final confirmed = await showFocusRestoringDialog( + context: context, + builder: (ctx) => StatefulBuilder( + builder: (dialogCtx, setDialogState) => AlertDialog.adaptive( + title: Text(l10n.adminRemoteResults), + content: Column( + mainAxisSize: MainAxisSize.min, + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + (result['Name'] ?? l10n.unknown).toString(), + style: Theme.of(ctx).textTheme.titleMedium, + ), + const SizedBox(height: 16), + FocusableButton( + onPressed: () { + setDialogState(() { + replaceAllImages = !replaceAllImages; + }); + }, + borderRadius: 8, + padding: const EdgeInsets.symmetric(vertical: 4, horizontal: 4), + child: Row( + children: [ + Checkbox( + value: replaceAllImages, + onChanged: (val) { + setDialogState(() { + replaceAllImages = val ?? true; + }); + }, + ), + const SizedBox(width: 8), + Expanded( + child: Text( + l10n.adminReplaceImages, + style: Theme.of(ctx).textTheme.bodyMedium, + ), + ), + ], + ), + ), + ], + ), + actions: [ + adaptiveDialogAction( + onPressed: () => Navigator.pop(ctx, false), + child: Text(l10n.cancel), + ), + FilledButton( + onPressed: () => Navigator.pop(ctx, true), + child: Text(l10n.adminApply), + ), + ], + ), + ), + ); + + if (confirmed != true || !mounted || _applying) return; + + setState(() => _applying = true); + try { + await _adminApi.applyRemoteSearchResult( + _targetItemId, + result, + replaceAllImages: replaceAllImages, + ); + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.adminRemoteMetadataApplied)), + ); + _closeDialog(true); + } catch (e) { + if (!mounted) return; + ScaffoldMessenger.of(context).showSnackBar( + SnackBar(content: Text(l10n.adminRemoteSearchFailed('$e'))), + ); + } finally { + if (mounted) { + setState(() => _applying = false); + } + } + } + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final l10n = AppLocalizations.of(context); + final width = (MediaQuery.sizeOf(context).width - 32).clamp(340.0, 780.0); + final height = (MediaQuery.sizeOf(context).height * 0.92).clamp(420.0, 840.0); + + return Dialog( + backgroundColor: theme.colorScheme.surface, + shape: RoundedRectangleBorder( + borderRadius: AppRadius.circular(16), + ), + child: SizedBox( + width: width, + height: height, + child: Padding( + padding: const EdgeInsets.fromLTRB(20, 14, 20, 10), + child: Column( + crossAxisAlignment: CrossAxisAlignment.stretch, + children: [ + Row( + children: [ + if (_searchResults != null) + IconButton( + icon: const Icon(Icons.arrow_back), + onPressed: _backToSearchForm, + tooltip: l10n.adminBackToSearch, + ), + Expanded( + child: Text( + _searchResults == null + ? l10n.adminMetadataIdentify + : l10n.adminRemoteResults, + style: theme.textTheme.headlineSmall, + ), + ), + Focus( + focusNode: _closeButtonFocusNode, + onKeyEvent: (_, event) { + if (!event.isActionable) return KeyEventResult.ignored; + final key = event.logicalKey; + if (key.isSelectKey) { + _closeDialog(false); + return KeyEventResult.handled; + } + if (_isTV && key.isDownKey) { + if (_searchResults != null && + _resultFocusNodes != null && + _resultFocusNodes!.isNotEmpty) { + _resultFocusNodes![0].requestFocus(); + } else { + _fieldFocusNodes[0].requestFocus(); + } + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: Builder( + builder: (btnCtx) { + final hasFocus = Focus.of(btnCtx).hasFocus; + return InkWell( + onTap: () => _closeDialog(false), + borderRadius: AppRadius.circular(20), + child: AnimatedContainer( + duration: const Duration(milliseconds: 150), + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: hasFocus + ? theme.colorScheme.primary.withValues(alpha: 0.25) + : Colors.transparent, + border: Border.all( + color: hasFocus + ? theme.colorScheme.primary + : Colors.transparent, + width: 2.5, + ), + boxShadow: hasFocus + ? [ + BoxShadow( + color: theme.colorScheme.primary + .withValues(alpha: 0.6), + blurRadius: 10, + spreadRadius: 2, + ) + ] + : null, + ), + child: Icon( + Icons.close, + color: hasFocus + ? theme.colorScheme.primary + : theme.colorScheme.onSurface, + size: 20, + ), + ), + ); + }, + ), + ), + ], + ), + const Divider(height: 12), + + Expanded( + child: _loadingItem || _applying + ? const Center(child: CircularProgressIndicator()) + : _searchResults != null + ? _buildResultsList(context) + : _buildSearchForm(context), + ), + + if (_searchResults == null) ...[ + Padding( + padding: const EdgeInsets.only(top: 4, bottom: 2), + child: Row( + mainAxisAlignment: MainAxisAlignment.end, + children: [ + Focus( + focusNode: _cancelButtonFocusNode, + onKeyEvent: (_, event) { + if (!event.isActionable) { + return KeyEventResult.ignored; + } + final key = event.logicalKey; + if (key.isSelectKey) { + _closeDialog(false); + return KeyEventResult.handled; + } + if (!_isTV) return KeyEventResult.ignored; + if (key.isUpKey) { + _fieldFocusNodes[_lastFieldIndex].requestFocus(); + return KeyEventResult.handled; + } + if (key.isRightKey) { + _searchButtonFocusNode.requestFocus(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: Builder( + builder: (btnCtx) { + final hasFocus = Focus.of(btnCtx).hasFocus; + return TextButton( + onPressed: () => _closeDialog(false), + style: TextButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4), + minimumSize: const Size(60, 30), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + backgroundColor: hasFocus + ? theme.colorScheme.primary.withValues(alpha: 0.15) + : null, + side: hasFocus + ? BorderSide(color: theme.colorScheme.primary, width: 1.5) + : null, + ), + child: Text(l10n.cancel), + ); + }, + ), + ), + const SizedBox(width: 10), + Focus( + focusNode: _searchButtonFocusNode, + onKeyEvent: (_, event) { + if (!event.isActionable) { + return KeyEventResult.ignored; + } + final key = event.logicalKey; + if (key.isSelectKey) { + if (!_searching) _performSearch(); + return KeyEventResult.handled; + } + if (!_isTV) return KeyEventResult.ignored; + if (key.isUpKey) { + _arrowFocusNodes[_lastFieldIndex].requestFocus(); + return KeyEventResult.handled; + } + if (key.isLeftKey) { + _cancelButtonFocusNode.requestFocus(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: Builder( + builder: (btnCtx) { + final hasFocus = Focus.of(btnCtx).hasFocus; + return FilledButton.icon( + onPressed: _searching ? null : _performSearch, + style: FilledButton.styleFrom( + padding: const EdgeInsets.symmetric(horizontal: 14, vertical: 4), + minimumSize: const Size(70, 30), + tapTargetSize: MaterialTapTargetSize.shrinkWrap, + side: hasFocus + ? const BorderSide(color: Colors.white, width: 2.0) + : null, + ), + icon: _searching + ? const SizedBox( + width: 14, + height: 14, + child: CircularProgressIndicator( + strokeWidth: 2, + color: Colors.white, + ), + ) + : const Icon(Icons.search, size: 16), + label: Text(l10n.search), + ); + }, + ), + ), + ], + ), + ), + ], + ], + ), + ), + ), + ); + } + + /// The form rows in d-pad order. Position matters because the focus nodes are + /// keyed off it. + List<_IdentifyField> _searchFields(AppLocalizations l10n) => [ + _IdentifyField(l10n.name, _nameController, _refName), + _IdentifyField(l10n.adminLabelYear, _yearController, _refYear, + keyboardType: TextInputType.number), + _IdentifyField(l10n.adminLabelImdbId, _imdbController, _refImdb), + _IdentifyField( + l10n.adminLabelTmdbMovieId, _tmdbMovieController, _refTmdbMovie), + _IdentifyField( + l10n.adminLabelTmdbBoxSetId, _tmdbBoxSetController, _refTmdbBoxSet), + _IdentifyField( + l10n.adminLabelTvdbBoxSetId, _tvdbBoxSetController, _refTvdbBoxSet), + _IdentifyField(l10n.adminLabelTvdbId, _tvdbIdController, _refTvdbId), + _IdentifyField( + l10n.adminLabelTvdbSlug, _tvdbSlugController, _refTvdbSlug), + ]; + + Widget _buildSearchForm(BuildContext context) { + final theme = Theme.of(context); + final l10n = AppLocalizations.of(context); + final fields = _searchFields(l10n); + assert(fields.length == _fieldCount); + return SingleChildScrollView( + controller: _scrollController, + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + if (_loadError != null) ...[ + Row( + children: [ + Icon(Icons.warning_amber_rounded, + size: 18, color: theme.colorScheme.error), + const SizedBox(width: 8), + Expanded( + child: Text( + l10n.adminMetadataLoadFailed(_loadError!), + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.error, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ), + ], + ), + const SizedBox(height: 12), + ], + if (_path != null && _path!.isNotEmpty) ...[ + Container( + width: double.infinity, + padding: const EdgeInsets.all(10), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.5), + borderRadius: AppRadius.circular(8), + ), + child: SelectableText( + '${l10n.path}: $_path', + style: theme.textTheme.bodySmall?.copyWith( + fontFamily: 'monospace', + color: theme.colorScheme.onSurfaceVariant, + ), + ), + ), + const SizedBox(height: 12), + ], + + Padding( + padding: const EdgeInsets.only(bottom: 10), + child: Row( + children: [ + Expanded( + flex: 3, + child: Text( + l10n.adminSearchParameters, + style: theme.textTheme.titleSmall?.copyWith( + color: theme.colorScheme.primary, + fontWeight: FontWeight.bold, + ), + ), + ), + // Matches the arrow column: 6px gap + 30px button + 6px gap. + const SizedBox(width: 42), + Expanded( + flex: 2, + child: Text( + l10n.adminCurrentMetadata, + style: theme.textTheme.titleSmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + fontWeight: FontWeight.bold, + ), + ), + ), + ], + ), + ), + + for (var i = 0; i < fields.length; i++) _buildFieldRow(i, fields[i]), + ], + ), + ); + } + + Widget _buildFieldRow(int index, _IdentifyField field) { + final theme = Theme.of(context); + final label = field.label; + final controller = field.controller; + final keyboardType = field.keyboardType; + final reference = field.reference?.trim(); + final hasReference = reference != null && reference.isNotEmpty; + final fieldFocusNode = _fieldFocusNodes[index]; + final arrowFocusNode = _arrowFocusNodes[index]; + final tvFieldKey = _tvFieldKeys[index]; + void pullReference() { + if (hasReference) setState(() => controller.text = reference); + } + + return Padding( + padding: const EdgeInsets.only(bottom: 10.0), + child: Row( + crossAxisAlignment: CrossAxisAlignment.center, + children: [ + // Only TV gets the d-pad wrapper. Anywhere else a handler that + // swallows space and the arrows would stop them reaching the text + // input, so the field would refuse spaces and the caret couldn't move. + Expanded( + flex: 3, + child: _isTV + ? Focus( + focusNode: fieldFocusNode, + onKeyEvent: (_, event) { + if (!event.isActionable) return KeyEventResult.ignored; + final key = event.logicalKey; + if (key.isSelectKey) { + if (!fieldFocusNode.hasFocus) { + fieldFocusNode.requestFocus(); + } + tvFieldKey.currentState?.openKeyboard(); + return KeyEventResult.handled; + } + if (key.isRightKey) { + arrowFocusNode.requestFocus(); + return KeyEventResult.handled; + } + if (key.isDownKey) { + if (index < _lastFieldIndex) { + _fieldFocusNodes[index + 1].requestFocus(); + } else { + _cancelButtonFocusNode.requestFocus(); + } + return KeyEventResult.handled; + } + if (key.isUpKey) { + if (index > 0) { + _fieldFocusNodes[index - 1].requestFocus(); + } else { + _closeButtonFocusNode.requestFocus(); + } + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: ListenableBuilder( + listenable: fieldFocusNode, + builder: (ctx, _) { + final preferSystemIme = + _prefs.get(UserPreferences.preferSystemImeKeyboard); + return GestureDetector( + onTap: () { + if (!fieldFocusNode.hasFocus) { + fieldFocusNode.requestFocus(); + } + tvFieldKey.currentState?.openKeyboard(); + }, + child: CustomTVTextField( + key: tvFieldKey, + controller: controller, + isFocused: fieldFocusNode.hasFocus, + inputPurpose: InputPurpose.text, + keyboardType: keyboardType == TextInputType.number + ? KeyboardType.numeric + : KeyboardType.alphabetic, + preferSystemIme: preferSystemIme, + hint: label, + filled: true, + fillColor: theme.colorScheme.surfaceContainerHighest + .withValues(alpha: 0.4), + borderColor: theme.colorScheme.outlineVariant + .withValues(alpha: 0.4), + focusedBorderColor: theme.colorScheme.primary, + hintStyle: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurfaceVariant + .withValues(alpha: 0.6), + ), + textStyle: theme.textTheme.bodyMedium?.copyWith( + color: theme.colorScheme.onSurface, + ), + popParentOnKeyboardClose: false, + ), + ); + }, + ), + ) + : TextField( + controller: controller, + focusNode: fieldFocusNode, + keyboardType: keyboardType, + textInputAction: TextInputAction.search, + onSubmitted: (_) { + if (!_searching) _performSearch(); + }, + style: theme.textTheme.bodyMedium, + decoration: InputDecoration( + labelText: label, + border: const OutlineInputBorder(), + isDense: true, + ), + ), + ), + const SizedBox(width: 6), + + // Copies the current value on the right into the field on the left. + Focus( + focusNode: arrowFocusNode, + onKeyEvent: (_, event) { + if (!event.isActionable) return KeyEventResult.ignored; + final key = event.logicalKey; + if (key.isSelectKey) { + pullReference(); + return KeyEventResult.handled; + } + // Normal traversal already reaches this button off TV, so the + // arrow keys are left alone there. + if (!_isTV) return KeyEventResult.ignored; + if (key.isUpKey) { + if (index == 0) { + _closeButtonFocusNode.requestFocus(); + } else { + _arrowFocusNodes[index - 1].requestFocus(); + } + return KeyEventResult.handled; + } + if (key.isDownKey) { + if (index < _lastFieldIndex) { + _arrowFocusNodes[index + 1].requestFocus(); + } else { + _searchButtonFocusNode.requestFocus(); + } + return KeyEventResult.handled; + } + if (key.isRightKey) { + _searchButtonFocusNode.requestFocus(); + return KeyEventResult.handled; + } + if (key.isLeftKey) { + fieldFocusNode.requestFocus(); + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: Builder( + builder: (btnCtx) { + final hasFocus = Focus.of(btnCtx).hasFocus; + return InkWell( + onTap: hasReference ? pullReference : null, + borderRadius: AppRadius.circular(20), + child: Container( + padding: const EdgeInsets.all(6), + decoration: BoxDecoration( + shape: BoxShape.circle, + color: hasFocus + ? theme.colorScheme.primary.withValues(alpha: 0.25) + : Colors.transparent, + border: Border.all( + color: hasFocus + ? theme.colorScheme.primary + : Colors.transparent, + width: 2, + ), + ), + child: Icon( + Icons.west, + size: 18, + color: hasFocus + ? theme.colorScheme.primary + : (hasReference + ? theme.colorScheme.primary.withValues(alpha: 0.8) + : theme.colorScheme.onSurfaceVariant + .withValues(alpha: 0.3)), + ), + ), + ); + }, + ), + ), + + const SizedBox(width: 6), + + Expanded( + flex: 2, + child: Container( + padding: const EdgeInsets.symmetric(horizontal: 10, vertical: 10), + decoration: BoxDecoration( + color: theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.18), + borderRadius: AppRadius.circular(6), + border: Border( + left: BorderSide( + color: hasReference + ? theme.colorScheme.primary.withValues(alpha: 0.4) + : Colors.transparent, + width: 2.5, + ), + ), + ), + child: Text( + hasReference ? reference : '-', + style: theme.textTheme.bodyMedium?.copyWith( + color: hasReference + ? theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.85) + : theme.colorScheme.onSurfaceVariant.withValues(alpha: 0.3), + fontStyle: hasReference ? FontStyle.normal : FontStyle.italic, + ), + maxLines: 1, + overflow: TextOverflow.ellipsis, + ), + ), + ), + ], + ), + ); + } + + Widget _buildResultsList(BuildContext context) { + final theme = Theme.of(context); + final results = _searchResults!; + + return ListView.builder( + controller: _scrollController, + itemCount: results.length, + itemBuilder: (context, index) { + final item = results[index]; + final name = (item['Name'] ?? '').toString(); + final year = item['ProductionYear']?.toString(); + final overview = (item['Overview'] ?? '').toString(); + final provider = + (item['SearchProviderName'] ?? item['ProviderName'] ?? '').toString(); + final imageUrl = item['ImageUrl']?.toString(); + final resultFocusNode = _resultFocusNodes != null && index < _resultFocusNodes!.length + ? _resultFocusNodes![index] + : null; + + return Padding( + padding: const EdgeInsets.symmetric(vertical: 4), + child: Focus( + focusNode: resultFocusNode, + onKeyEvent: (_, event) { + if (!event.isActionable) return KeyEventResult.ignored; + final key = event.logicalKey; + if (key.isSelectKey) { + _applyResult(item); + return KeyEventResult.handled; + } + if (!_isTV) return KeyEventResult.ignored; + if (key.isUpKey) { + if (index == 0) { + _closeButtonFocusNode.requestFocus(); + } else if (_resultFocusNodes != null && index > 0) { + _resultFocusNodes![index - 1].requestFocus(); + } + return KeyEventResult.handled; + } + if (key.isDownKey) { + if (_resultFocusNodes != null && + index < _resultFocusNodes!.length - 1) { + _resultFocusNodes![index + 1].requestFocus(); + } + return KeyEventResult.handled; + } + return KeyEventResult.ignored; + }, + child: Builder( + builder: (cardCtx) { + final hasFocus = Focus.of(cardCtx).hasFocus; + return Card( + margin: EdgeInsets.zero, + color: hasFocus + ? theme.colorScheme.primaryContainer.withValues(alpha: 0.4) + : theme.colorScheme.surfaceContainerHighest.withValues(alpha: 0.3), + shape: RoundedRectangleBorder( + borderRadius: AppRadius.circular(10), + side: BorderSide( + color: hasFocus + ? theme.colorScheme.primary + : Colors.transparent, + width: 2, + ), + ), + child: InkWell( + onTap: () => _applyResult(item), + borderRadius: AppRadius.circular(10), + child: Padding( + padding: const EdgeInsets.all(12), + child: Row( + children: [ + ClipRRect( + borderRadius: AppRadius.circular(6), + child: imageUrl != null && imageUrl.isNotEmpty + ? Image.network( + imageUrl, + width: 50, + height: 75, + fit: BoxFit.cover, + errorBuilder: (_, _, _) => Container( + width: 50, + height: 75, + color: theme.colorScheme.surfaceContainerHighest, + child: const Icon(Icons.movie_outlined), + ), + ) + : Container( + width: 50, + height: 75, + color: theme.colorScheme.surfaceContainerHighest, + child: const Icon(Icons.movie_outlined), + ), + ), + const SizedBox(width: 16), + Expanded( + child: Column( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + Text( + year != null && year.isNotEmpty ? '$name ($year)' : name, + style: theme.textTheme.titleMedium?.copyWith( + fontWeight: FontWeight.bold, + color: hasFocus + ? theme.colorScheme.primary + : theme.colorScheme.onSurface, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + if (provider.isNotEmpty) ...[ + const SizedBox(height: 4), + Text( + provider, + style: theme.textTheme.labelMedium?.copyWith( + color: theme.colorScheme.primary, + ), + ), + ], + if (overview.isNotEmpty) ...[ + const SizedBox(height: 6), + Text( + overview, + style: theme.textTheme.bodySmall?.copyWith( + color: theme.colorScheme.onSurfaceVariant, + ), + maxLines: 2, + overflow: TextOverflow.ellipsis, + ), + ], + ], + ), + ), + Icon( + Icons.chevron_right, + color: hasFocus + ? theme.colorScheme.primary + : theme.colorScheme.onSurfaceVariant, + ), + ], + ), + ), + ), + ); + }, + ), + ), + ); + }, + ); + } +} + +/// One row of the search form, pairing an editable field with the value the +/// item already carries. +class _IdentifyField { + final String label; + final TextEditingController controller; + final String? reference; + final TextInputType? keyboardType; + + const _IdentifyField( + this.label, + this.controller, + this.reference, { + this.keyboardType, + }); +} diff --git a/packages/server_core/lib/src/api/admin_items_api.dart b/packages/server_core/lib/src/api/admin_items_api.dart index ccc176a84..9b667db3c 100644 --- a/packages/server_core/lib/src/api/admin_items_api.dart +++ b/packages/server_core/lib/src/api/admin_items_api.dart @@ -21,10 +21,14 @@ abstract class AdminItemsApi { Future>> searchRemotePerson( Map query, ); + /// Applies a result returned by [searchRemote] to [itemId]. When + /// [replaceAllImages] is false the item keeps its existing artwork and only + /// textual metadata is replaced. Future applyRemoteSearchResult( String itemId, - Map result, - ); + Map result, { + bool replaceAllImages = true, + }); Future> getRemoteImages( String itemId, { required ImageType imageType, diff --git a/packages/server_jellyfin/lib/src/api/jellyfin_admin_items_api.dart b/packages/server_jellyfin/lib/src/api/jellyfin_admin_items_api.dart index 8548e0494..23d8fca28 100644 --- a/packages/server_jellyfin/lib/src/api/jellyfin_admin_items_api.dart +++ b/packages/server_jellyfin/lib/src/api/jellyfin_admin_items_api.dart @@ -76,9 +76,18 @@ class JellyfinAdminItemsApi implements AdminItemsApi { String searchType, Map query, ) async { + final normalized = switch (searchType.toLowerCase()) { + 'movie' => 'Movie', + 'series' => 'Series', + 'boxset' => 'BoxSet', + 'person' => 'Person', + 'musicalbum' => 'MusicAlbum', + 'musicartist' => 'MusicArtist', + _ => searchType, + }; return _requestWithFallback>>( methods: const ['POST'], - paths: ['/Items/RemoteSearch/$searchType'], + paths: ['/Items/RemoteSearch/$normalized', '/Items/RemoteSearch/$searchType'], data: query, convert: _asList, ); @@ -93,14 +102,16 @@ class JellyfinAdminItemsApi implements AdminItemsApi { @override Future applyRemoteSearchResult( String itemId, - Map result, - ) async { + Map result, { + bool replaceAllImages = true, + }) async { await _requestWithFallback( methods: const ['POST'], paths: [ '/Items/RemoteSearch/Apply/$itemId', '/Items/$itemId/RemoteSearch/Apply', ], + queryParameters: {'replaceAllImages': replaceAllImages}, data: result, convert: _ignoreResponse, );