From 3221a0cb1a75e60d0ec767ff4467e91fc3535d58 Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Wed, 10 Jun 2026 11:42:06 +0200 Subject: [PATCH] feat: add continue watching details navigation close #1295 --- lib/i18n/bg.i18n.json | 6 +- lib/i18n/da.i18n.json | 6 +- lib/i18n/de.i18n.json | 6 +- lib/i18n/en.i18n.json | 6 +- lib/i18n/es.i18n.json | 6 +- lib/i18n/fr.i18n.json | 6 +- lib/i18n/it.i18n.json | 6 +- lib/i18n/ja.i18n.json | 6 +- lib/i18n/ko.i18n.json | 6 +- lib/i18n/nb.i18n.json | 6 +- lib/i18n/nl.i18n.json | 6 +- lib/i18n/pl.i18n.json | 6 +- lib/i18n/pt.i18n.json | 6 +- lib/i18n/ru.i18n.json | 6 +- lib/i18n/strings.g.dart | 2 +- lib/i18n/strings_bg.g.dart | 20 +- lib/i18n/strings_da.g.dart | 20 +- lib/i18n/strings_de.g.dart | 20 +- lib/i18n/strings_en.g.dart | 32 ++-- lib/i18n/strings_es.g.dart | 20 +- lib/i18n/strings_fr.g.dart | 20 +- lib/i18n/strings_it.g.dart | 20 +- lib/i18n/strings_ja.g.dart | 20 +- lib/i18n/strings_ko.g.dart | 20 +- lib/i18n/strings_nb.g.dart | 20 +- lib/i18n/strings_nl.g.dart | 20 +- lib/i18n/strings_pl.g.dart | 20 +- lib/i18n/strings_pt.g.dart | 20 +- lib/i18n/strings_ru.g.dart | 20 +- lib/i18n/strings_sv.g.dart | 20 +- lib/i18n/strings_zh.g.dart | 20 +- lib/i18n/sv.i18n.json | 6 +- lib/i18n/zh.i18n.json | 6 +- lib/media/episode_collection.dart | 12 +- lib/screens/discover_screen.dart | 7 +- lib/screens/media_detail_screen.dart | 179 ++++++++++++++++-- .../settings/appearance_settings_screen.dart | 13 ++ lib/services/settings_service.dart | 8 + lib/utils/media_navigation_helper.dart | 155 +++++++++++---- lib/widgets/media_card.dart | 88 +-------- lib/widgets/media_context_menu.dart | 53 +++--- test/screens/media_detail_screen_test.dart | 97 +++++++++- test/utils/episode_collection_test.dart | 1 + test/utils/media_navigation_helper_test.dart | 60 ++++++ 44 files changed, 772 insertions(+), 331 deletions(-) create mode 100644 test/utils/media_navigation_helper_test.dart diff --git a/lib/i18n/bg.i18n.json b/lib/i18n/bg.i18n.json index b299b7a2..2211e1a5 100644 --- a/lib/i18n/bg.i18n.json +++ b/lib/i18n/bg.i18n.json @@ -116,6 +116,9 @@ "gridView": "Мрежа", "listView": "Списък", "showHeroSection": "Показвай водеща секция", + "continueWatchingAction": "Действие за продължаване на гледането", + "continueWatchingPlay": "Пусни", + "continueWatchingDetails": "Отвори подробности", "useGlobalHubs": "Използвай начално оформление", "useGlobalHubsDescription": "Показвай обединени начални хъбове. В противен случай използвай препоръките на библиотеката.", "showServerNameOnHubs": "Показвай името на сървъра в хъбовете", @@ -361,8 +364,8 @@ "markAsWatched": "Маркирай като гледано", "markAsUnwatched": "Маркирай като негледано", "removeFromContinueWatching": "Премахни от продължаване на гледането", + "viewDetails": "Виж подробности", "goToSeries": "Към сериала", - "goToSeason": "Към сезона", "shufflePlay": "Разбъркано възпроизвеждане", "shuffleNotAvailableOffline": "Разбърканото възпроизвеждане не е налично офлайн", "fileInfo": "Информация за файла", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Информацията за файла не е налична", "errorLoadingFileInfo": "Грешка при зареждане на информация за файла: ${error}", "errorLoadingSeries": "Грешка при зареждане на сериала", - "errorLoadingSeason": "Грешка при зареждане на сезона", "musicNotSupported": "Възпроизвеждането на музика все още не се поддържа", "noDescriptionAvailable": "Няма налично описание", "noProfilesAvailable": "Няма налични профили", diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index ee06ed60..a23b3bb0 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -116,6 +116,9 @@ "gridView": "Gitter", "listView": "Liste", "showHeroSection": "Vis hero-sektion", + "continueWatchingAction": "Handling for Fortsæt med at se", + "continueWatchingPlay": "Afspil", + "continueWatchingDetails": "Åbn detaljer", "useGlobalHubs": "Brug startlayout", "useGlobalHubsDescription": "Vis samlet startsideindhold. Brug ellers biblioteksanbefalinger.", "showServerNameOnHubs": "Vis servernavn på hubbe", @@ -361,8 +364,8 @@ "markAsWatched": "Markér som set", "markAsUnwatched": "Markér som uset", "removeFromContinueWatching": "Fjern fra Fortsæt med at se", + "viewDetails": "Vis detaljer", "goToSeries": "Gå til serie", - "goToSeason": "Gå til sæson", "shufflePlay": "Afspil tilfældigt", "shuffleNotAvailableOffline": "Bland afspilning er ikke tilgængelig offline", "fileInfo": "Filinfo", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Filinfo ikke tilgængelig", "errorLoadingFileInfo": "Fejl ved indlæsning af filinfo: ${error}", "errorLoadingSeries": "Fejl ved indlæsning af serie", - "errorLoadingSeason": "Fejl ved indlæsning af sæson", "musicNotSupported": "Musikafspilning understøttes endnu ikke", "noDescriptionAvailable": "Ingen beskrivelse tilgængelig", "noProfilesAvailable": "Ingen profiler tilgængelige", diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index 1a73f03e..33a8b6fb 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -116,6 +116,9 @@ "gridView": "Raster", "listView": "Liste", "showHeroSection": "Hero-Bereich anzeigen", + "continueWatchingAction": "Aktion für Weiterschauen", + "continueWatchingPlay": "Abspielen", + "continueWatchingDetails": "Details öffnen", "useGlobalHubs": "Startlayout verwenden", "useGlobalHubsDescription": "Einheitliche Start-Hubs anzeigen. Sonst Bibliotheksempfehlungen verwenden.", "showServerNameOnHubs": "Servername bei Hubs anzeigen", @@ -361,8 +364,8 @@ "markAsWatched": "Als gesehen markieren", "markAsUnwatched": "Als ungesehen markieren", "removeFromContinueWatching": "Aus ‚Weiterschauen‘ entfernen", + "viewDetails": "Details anzeigen", "goToSeries": "Zur Serie", - "goToSeason": "Zur Staffel", "shufflePlay": "Zufallswiedergabe", "shuffleNotAvailableOffline": "Zufallswiedergabe ist offline nicht verfügbar", "fileInfo": "Dateiinfo", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Dateiinfo nicht verfügbar", "errorLoadingFileInfo": "Fehler beim Laden der Dateiinfo: ${error}", "errorLoadingSeries": "Fehler beim Laden der Serie", - "errorLoadingSeason": "Fehler beim Laden der Staffel", "musicNotSupported": "Musikwiedergabe wird noch nicht unterstützt", "noDescriptionAvailable": "Keine Beschreibung verfügbar", "noProfilesAvailable": "Keine Profile verfügbar", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 5559e231..07ff5dbe 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -116,6 +116,9 @@ "gridView": "Grid", "listView": "List", "showHeroSection": "Show Hero Section", + "continueWatchingAction": "Continue Watching Action", + "continueWatchingPlay": "Play", + "continueWatchingDetails": "Open Details", "useGlobalHubs": "Use Home Layout", "useGlobalHubsDescription": "Show unified home hubs. Otherwise use library recommendations.", "showServerNameOnHubs": "Show Server Name on Hubs", @@ -361,8 +364,8 @@ "markAsWatched": "Mark as Watched", "markAsUnwatched": "Mark as Unwatched", "removeFromContinueWatching": "Remove from Continue Watching", + "viewDetails": "View details", "goToSeries": "Go to series", - "goToSeason": "Go to season", "shufflePlay": "Shuffle Play", "shuffleNotAvailableOffline": "Shuffle not available offline", "fileInfo": "File Info", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "File information not available", "errorLoadingFileInfo": "Error loading file info: ${error}", "errorLoadingSeries": "Error loading series", - "errorLoadingSeason": "Error loading season", "musicNotSupported": "Music playback is not yet supported", "noDescriptionAvailable": "No description available", "noProfilesAvailable": "No profiles available", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index 04cf1177..10e027c6 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -116,6 +116,9 @@ "gridView": "Cuadrícula", "listView": "Lista", "showHeroSection": "Mostrar Sección Destacada", + "continueWatchingAction": "Acción de Seguir viendo", + "continueWatchingPlay": "Reproducir", + "continueWatchingDetails": "Abrir detalles", "useGlobalHubs": "Usar diseño de inicio", "useGlobalHubsDescription": "Mostrar hubs de inicio unificados. Si no, usar recomendaciones de biblioteca.", "showServerNameOnHubs": "Mostrar Nombre del Servidor en los Hubs", @@ -361,8 +364,8 @@ "markAsWatched": "Marcar como Visto", "markAsUnwatched": "Marcar como No Visto", "removeFromContinueWatching": "Eliminar de Seguir Viendo", + "viewDetails": "Ver detalles", "goToSeries": "Ir a la serie", - "goToSeason": "Ir a la temporada", "shufflePlay": "Reproducción Aleatoria", "shuffleNotAvailableOffline": "La reproducción aleatoria no está disponible sin conexión", "fileInfo": "Información del Archivo", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Información de archivo no disponible", "errorLoadingFileInfo": "Error al cargar info de archivo: ${error}", "errorLoadingSeries": "Error al cargar la serie", - "errorLoadingSeason": "Error al cargar la temporada", "musicNotSupported": "La reproducción de música aún no está soportada", "noDescriptionAvailable": "No hay descripción disponible", "noProfilesAvailable": "No hay perfiles disponibles", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index 5988b47e..9b9c50f6 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -116,6 +116,9 @@ "gridView": "Grille", "listView": "Liste", "showHeroSection": "Afficher la section Hero", + "continueWatchingAction": "Action Continuer à regarder", + "continueWatchingPlay": "Lire", + "continueWatchingDetails": "Ouvrir les détails", "useGlobalHubs": "Utiliser la mise en page d'accueil", "useGlobalHubsDescription": "Afficher des hubs d'accueil unifiés. Sinon, utiliser les recommandations de bibliothèque.", "showServerNameOnHubs": "Afficher le nom du serveur sur les hubs", @@ -361,8 +364,8 @@ "markAsWatched": "Marquer comme vu", "markAsUnwatched": "Marquer comme non visionné", "removeFromContinueWatching": "Supprimer de la liste \"Continuer à regarder\"", + "viewDetails": "Voir les détails", "goToSeries": "Aller à la série", - "goToSeason": "Aller à la saison", "shufflePlay": "Lecture aléatoire", "shuffleNotAvailableOffline": "La lecture aléatoire n’est pas disponible hors ligne", "fileInfo": "Informations sur le fichier", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Informations sur le fichier non disponibles", "errorLoadingFileInfo": "Erreur lors du chargement des informations sur le fichier: ${error}", "errorLoadingSeries": "Erreur lors du chargement de la série", - "errorLoadingSeason": "Erreur lors du chargement de la saison", "musicNotSupported": "La lecture de musique n'est pas encore prise en charge", "noDescriptionAvailable": "Aucune description disponible", "noProfilesAvailable": "Aucun profil disponible", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 3d1378b2..1946eb2e 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -116,6 +116,9 @@ "gridView": "Griglia", "listView": "Elenco", "showHeroSection": "Mostra sezione principale", + "continueWatchingAction": "Azione Continua a guardare", + "continueWatchingPlay": "Riproduci", + "continueWatchingDetails": "Apri dettagli", "useGlobalHubs": "Usa layout home", "useGlobalHubsDescription": "Mostra hub Home unificati. Altrimenti usa i consigli della libreria.", "showServerNameOnHubs": "Mostra nome server sugli hub", @@ -361,8 +364,8 @@ "markAsWatched": "Segna come visto", "markAsUnwatched": "Segna come non visto", "removeFromContinueWatching": "Rimuovi da Continua a guardare", + "viewDetails": "Vedi dettagli", "goToSeries": "Vai alle serie", - "goToSeason": "Vai alla stagione", "shufflePlay": "Riproduzione casuale", "shuffleNotAvailableOffline": "Riproduzione casuale non disponibile offline", "fileInfo": "Info sul file", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Informazioni sul file non disponibili", "errorLoadingFileInfo": "Errore caricamento informazioni sul file: ${error}", "errorLoadingSeries": "Errore caricamento serie", - "errorLoadingSeason": "Errore caricamento stagione", "musicNotSupported": "La riproduzione musicale non è ancora supportata", "noDescriptionAvailable": "Nessuna descrizione disponibile", "noProfilesAvailable": "Nessun profilo disponibile", diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index 19a8af99..ec8a0b01 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -116,6 +116,9 @@ "gridView": "グリッド", "listView": "リスト", "showHeroSection": "ヒーローセクションを表示", + "continueWatchingAction": "視聴中の操作", + "continueWatchingPlay": "再生", + "continueWatchingDetails": "詳細を開く", "useGlobalHubs": "ホームレイアウトを使用", "useGlobalHubsDescription": "統合ホームハブを表示します。オフの場合はライブラリのおすすめを使用します。", "showServerNameOnHubs": "ハブにサーバー名を表示", @@ -361,8 +364,8 @@ "markAsWatched": "視聴済みにする", "markAsUnwatched": "未視聴にする", "removeFromContinueWatching": "視聴中から削除", + "viewDetails": "詳細を表示", "goToSeries": "シリーズへ移動", - "goToSeason": "シーズンへ移動", "shufflePlay": "シャッフル再生", "shuffleNotAvailableOffline": "オフラインではシャッフルを利用できません", "fileInfo": "ファイル情報", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "ファイル情報が利用できません", "errorLoadingFileInfo": "ファイル情報の読み込みエラー: ${error}", "errorLoadingSeries": "シリーズの読み込みエラー", - "errorLoadingSeason": "シーズンの読み込みエラー", "musicNotSupported": "音楽の再生はまだサポートされていません", "noDescriptionAvailable": "説明はありません", "noProfilesAvailable": "利用可能なプロフィールがありません", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 09092385..daaa567c 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -116,6 +116,9 @@ "gridView": "그리드 보기", "listView": "목록 보기", "showHeroSection": "주요 추천 영역 표시", + "continueWatchingAction": "계속 보기 동작", + "continueWatchingPlay": "재생", + "continueWatchingDetails": "상세 정보 열기", "useGlobalHubs": "홈 레이아웃 사용", "useGlobalHubsDescription": "통합 홈 허브를 표시합니다. 끄면 라이브러리 추천을 사용합니다.", "showServerNameOnHubs": "허브에 서버 이름 표시", @@ -361,8 +364,8 @@ "markAsWatched": "시청 완료로 표시", "markAsUnwatched": "시청 안 함으로 표시", "removeFromContinueWatching": "계속 보기에서 제거", + "viewDetails": "상세 정보 보기", "goToSeries": "시리즈로 이동", - "goToSeason": "시즌으로 이동", "shufflePlay": "무작위 재생", "shuffleNotAvailableOffline": "오프라인에서는 셔플을 사용할 수 없습니다", "fileInfo": "파일 정보", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "파일 정보가 없습니다", "errorLoadingFileInfo": "파일 정보 로딩 중 오류: ${error}", "errorLoadingSeries": "시리즈 로딩 중 오류", - "errorLoadingSeason": "시즌 로딩 중 오류", "musicNotSupported": "음악 재생 미지원", "noDescriptionAvailable": "설명이 없습니다", "noProfilesAvailable": "사용 가능한 프로필이 없습니다", diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index e59ca841..6e484fe2 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -116,6 +116,9 @@ "gridView": "Rutenett", "listView": "Liste", "showHeroSection": "Vis fremhevet seksjon", + "continueWatchingAction": "Handling for Fortsett å se", + "continueWatchingPlay": "Spill av", + "continueWatchingDetails": "Åpne detaljer", "useGlobalHubs": "Bruk startoppsett", "useGlobalHubsDescription": "Vis samlet startinnhold. Ellers brukes bibliotekanbefalinger.", "showServerNameOnHubs": "Vis servernavn på huber", @@ -361,8 +364,8 @@ "markAsWatched": "Merk som sett", "markAsUnwatched": "Merk som usett", "removeFromContinueWatching": "Fjern fra Fortsett å se", + "viewDetails": "Vis detaljer", "goToSeries": "Gå til serie", - "goToSeason": "Gå til sesong", "shufflePlay": "Tilfeldig avspilling", "shuffleNotAvailableOffline": "Tilfeldig avspilling er ikke tilgjengelig offline", "fileInfo": "Filinformasjon", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Filinformasjon ikke tilgjengelig", "errorLoadingFileInfo": "Feil ved lasting av filinformasjon: ${error}", "errorLoadingSeries": "Feil ved lasting av serie", - "errorLoadingSeason": "Feil ved lasting av sesong", "musicNotSupported": "Musikkavspilling støttes ikke ennå", "noDescriptionAvailable": "Ingen beskrivelse tilgjengelig", "noProfilesAvailable": "Ingen profiler tilgjengelige", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 6541f95f..fe55ae05 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -116,6 +116,9 @@ "gridView": "Raster", "listView": "Lijst", "showHeroSection": "Toon hoofdsectie", + "continueWatchingAction": "Actie voor Doorgaan met kijken", + "continueWatchingPlay": "Afspelen", + "continueWatchingDetails": "Details openen", "useGlobalHubs": "Startlayout gebruiken", "useGlobalHubsDescription": "Toon gecombineerde home-hubs. Anders bibliotheekaanbevelingen gebruiken.", "showServerNameOnHubs": "Servernaam tonen bij hubs", @@ -361,8 +364,8 @@ "markAsWatched": "Markeer als gekeken", "markAsUnwatched": "Markeer als ongekeken", "removeFromContinueWatching": "Verwijder uit Doorgaan met kijken", + "viewDetails": "Details bekijken", "goToSeries": "Ga naar serie", - "goToSeason": "Ga naar seizoen", "shufflePlay": "Willekeurig afspelen", "shuffleNotAvailableOffline": "Shuffle is offline niet beschikbaar", "fileInfo": "Bestand info", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Bestand informatie niet beschikbaar", "errorLoadingFileInfo": "Fout bij laden bestand info: ${error}", "errorLoadingSeries": "Fout bij laden serie", - "errorLoadingSeason": "Fout bij laden seizoen", "musicNotSupported": "Muziek afspelen wordt nog niet ondersteund", "noDescriptionAvailable": "Geen beschrijving beschikbaar", "noProfilesAvailable": "Geen profielen beschikbaar", diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index da1d398c..cccb482a 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -116,6 +116,9 @@ "gridView": "Siatka", "listView": "Lista", "showHeroSection": "Pokaż sekcję wyróżnioną", + "continueWatchingAction": "Akcja Kontynuuj oglądanie", + "continueWatchingPlay": "Odtwórz", + "continueWatchingDetails": "Otwórz szczegóły", "useGlobalHubs": "Użyj układu strony głównej", "useGlobalHubsDescription": "Pokaż ujednolicone huby ekranu głównego. W przeciwnym razie użyj rekomendacji bibliotek.", "showServerNameOnHubs": "Pokaż nazwę serwera w hubach", @@ -361,8 +364,8 @@ "markAsWatched": "Oznacz jako obejrzane", "markAsUnwatched": "Oznacz jako nieobejrzane", "removeFromContinueWatching": "Usuń z kontynuowania oglądania", + "viewDetails": "Pokaż szczegóły", "goToSeries": "Przejdź do serialu", - "goToSeason": "Przejdź do sezonu", "shufflePlay": "Odtwarzanie losowe", "shuffleNotAvailableOffline": "Odtwarzanie losowe nie jest dostępne offline", "fileInfo": "Informacje o pliku", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Informacje o pliku niedostępne", "errorLoadingFileInfo": "Błąd ładowania informacji o pliku: ${error}", "errorLoadingSeries": "Błąd ładowania serialu", - "errorLoadingSeason": "Błąd ładowania sezonu", "musicNotSupported": "Odtwarzanie muzyki nie jest jeszcze obsługiwane", "noDescriptionAvailable": "Brak dostępnego opisu", "noProfilesAvailable": "Brak dostępnych profili", diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index 1b3a3fa8..39c7b5f5 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -116,6 +116,9 @@ "gridView": "Grade", "listView": "Lista", "showHeroSection": "Mostrar Seção de Destaque", + "continueWatchingAction": "Ação de Continuar Assistindo", + "continueWatchingPlay": "Reproduzir", + "continueWatchingDetails": "Abrir detalhes", "useGlobalHubs": "Usar layout inicial", "useGlobalHubsDescription": "Mostrar hubs iniciais unificados. Caso contrário, usar recomendações da biblioteca.", "showServerNameOnHubs": "Mostrar Nome do Servidor nos Hubs", @@ -361,8 +364,8 @@ "markAsWatched": "Marcar como Assistido", "markAsUnwatched": "Marcar como Não Assistido", "removeFromContinueWatching": "Remover de Continuar Assistindo", + "viewDetails": "Ver detalhes", "goToSeries": "Ir para a série", - "goToSeason": "Ir para a temporada", "shufflePlay": "Reprodução Aleatória", "shuffleNotAvailableOffline": "Reprodução aleatória indisponível offline", "fileInfo": "Info do Arquivo", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Informações do arquivo não disponíveis", "errorLoadingFileInfo": "Erro ao carregar info do arquivo: ${error}", "errorLoadingSeries": "Erro ao carregar série", - "errorLoadingSeason": "Erro ao carregar temporada", "musicNotSupported": "Reprodução de música ainda não é suportada", "noDescriptionAvailable": "Nenhuma descrição disponível", "noProfilesAvailable": "Nenhum perfil disponível", diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index fe2a0042..6b005d7a 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -116,6 +116,9 @@ "gridView": "Сетка", "listView": "Список", "showHeroSection": "Показать раздел избранного", + "continueWatchingAction": "Действие для «Продолжить просмотр»", + "continueWatchingPlay": "Воспроизвести", + "continueWatchingDetails": "Открыть сведения", "useGlobalHubs": "Использовать макет главной", "useGlobalHubsDescription": "Показывать единые разделы главной. Иначе использовать рекомендации библиотек.", "showServerNameOnHubs": "Показывать имя сервера в хабах", @@ -361,8 +364,8 @@ "markAsWatched": "Отметить как просмотренное", "markAsUnwatched": "Отметить как непросмотренное", "removeFromContinueWatching": "Удалить из «Продолжить просмотр»", + "viewDetails": "Показать сведения", "goToSeries": "Перейти к сериалу", - "goToSeason": "Перейти к сезону", "shufflePlay": "Случайное воспроизведение", "shuffleNotAvailableOffline": "Перемешивание недоступно офлайн", "fileInfo": "Информация о файле", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Информация о файле недоступна", "errorLoadingFileInfo": "Ошибка загрузки информации о файле: ${error}", "errorLoadingSeries": "Ошибка загрузки сериала", - "errorLoadingSeason": "Ошибка загрузки сезона", "musicNotSupported": "Воспроизведение музыки пока не поддерживается", "noDescriptionAvailable": "Описание недоступно", "noProfilesAvailable": "Профили недоступны", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index 3a805a9f..7c49418e 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,7 +4,7 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 16 -/// Strings: 20352 (1272 per locale) +/// Strings: 20384 (1274 per locale) // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_bg.g.dart b/lib/i18n/strings_bg.g.dart index 7c3dfd9d..78066151 100644 --- a/lib/i18n/strings_bg.g.dart +++ b/lib/i18n/strings_bg.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsBg extends TranslationsSettingsEn { @override String get gridView => 'Мрежа'; @override String get listView => 'Списък'; @override String get showHeroSection => 'Показвай водеща секция'; + @override String get continueWatchingAction => 'Действие за продължаване на гледането'; + @override String get continueWatchingPlay => 'Пусни'; + @override String get continueWatchingDetails => 'Отвори подробности'; @override String get useGlobalHubs => 'Използвай начално оформление'; @override String get useGlobalHubsDescription => 'Показвай обединени начални хъбове. В противен случай използвай препоръките на библиотеката.'; @override String get showServerNameOnHubs => 'Показвай името на сървъра в хъбовете'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuBg extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Маркирай като гледано'; @override String get markAsUnwatched => 'Маркирай като негледано'; @override String get removeFromContinueWatching => 'Премахни от продължаване на гледането'; + @override String get viewDetails => 'Виж подробности'; @override String get goToSeries => 'Към сериала'; - @override String get goToSeason => 'Към сезона'; @override String get shufflePlay => 'Разбъркано възпроизвеждане'; @override String get shuffleNotAvailableOffline => 'Разбърканото възпроизвеждане не е налично офлайн'; @override String get fileInfo => 'Информация за файла'; @@ -673,7 +676,6 @@ class _TranslationsMessagesBg extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Информацията за файла не е налична'; @override String errorLoadingFileInfo({required Object error}) => 'Грешка при зареждане на информация за файла: ${error}'; @override String get errorLoadingSeries => 'Грешка при зареждане на сериала'; - @override String get errorLoadingSeason => 'Грешка при зареждане на сезона'; @override String get musicNotSupported => 'Възпроизвеждането на музика все още не се поддържа'; @override String get noDescriptionAvailable => 'Няма налично описание'; @override String get noProfilesAvailable => 'Няма налични профили'; @@ -2027,6 +2029,9 @@ extension on TranslationsBg { 'settings.gridView' => 'Мрежа', 'settings.listView' => 'Списък', 'settings.showHeroSection' => 'Показвай водеща секция', + 'settings.continueWatchingAction' => 'Действие за продължаване на гледането', + 'settings.continueWatchingPlay' => 'Пусни', + 'settings.continueWatchingDetails' => 'Отвори подробности', 'settings.useGlobalHubs' => 'Използвай начално оформление', 'settings.useGlobalHubsDescription' => 'Показвай обединени начални хъбове. В противен случай използвай препоръките на библиотеката.', 'settings.showServerNameOnHubs' => 'Показвай името на сървъра в хъбовете', @@ -2262,8 +2267,8 @@ extension on TranslationsBg { 'mediaMenu.markAsWatched' => 'Маркирай като гледано', 'mediaMenu.markAsUnwatched' => 'Маркирай като негледано', 'mediaMenu.removeFromContinueWatching' => 'Премахни от продължаване на гледането', + 'mediaMenu.viewDetails' => 'Виж подробности', 'mediaMenu.goToSeries' => 'Към сериала', - 'mediaMenu.goToSeason' => 'Към сезона', 'mediaMenu.shufflePlay' => 'Разбъркано възпроизвеждане', 'mediaMenu.shuffleNotAvailableOffline' => 'Разбърканото възпроизвеждане не е налично офлайн', 'mediaMenu.fileInfo' => 'Информация за файла', @@ -2395,7 +2400,6 @@ extension on TranslationsBg { 'messages.fileInfoNotAvailable' => 'Информацията за файла не е налична', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Грешка при зареждане на информация за файла: ${error}', 'messages.errorLoadingSeries' => 'Грешка при зареждане на сериала', - 'messages.errorLoadingSeason' => 'Грешка при зареждане на сезона', 'messages.musicNotSupported' => 'Възпроизвеждането на музика все още не се поддържа', 'messages.noDescriptionAvailable' => 'Няма налично описание', 'messages.noProfilesAvailable' => 'Няма налични профили', @@ -2431,10 +2435,10 @@ extension on TranslationsBg { 'subtitlingStyling.border' => 'Рамка', 'subtitlingStyling.background' => 'Фон', 'subtitlingStyling.fontSize' => 'Размер на шрифта', - 'subtitlingStyling.textColor' => 'Цвят на текста', - 'subtitlingStyling.borderSize' => 'Размер на рамката', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Цвят на текста', + 'subtitlingStyling.borderSize' => 'Размер на рамката', 'subtitlingStyling.borderColor' => 'Цвят на рамката', 'subtitlingStyling.backgroundOpacity' => 'Прозрачност на фона', 'subtitlingStyling.backgroundColor' => 'Цвят на фона', @@ -2945,10 +2949,10 @@ extension on TranslationsBg { 'companionRemote.remote.menu' => 'Меню', 'companionRemote.remote.tabNavigation' => 'Навигация с Tab', 'companionRemote.remote.tabDiscover' => 'Открий', - 'companionRemote.remote.tabLibraries' => 'Библиотеки', - 'companionRemote.remote.tabSearch' => 'Търсене', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Библиотеки', + 'companionRemote.remote.tabSearch' => 'Търсене', 'companionRemote.remote.tabDownloads' => 'Изтегляния', 'companionRemote.remote.tabSettings' => 'Настройки', 'companionRemote.remote.previous' => 'Предишен', diff --git a/lib/i18n/strings_da.g.dart b/lib/i18n/strings_da.g.dart index a2f1cd7f..34e3f68b 100644 --- a/lib/i18n/strings_da.g.dart +++ b/lib/i18n/strings_da.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsDa extends TranslationsSettingsEn { @override String get gridView => 'Gitter'; @override String get listView => 'Liste'; @override String get showHeroSection => 'Vis hero-sektion'; + @override String get continueWatchingAction => 'Handling for Fortsæt med at se'; + @override String get continueWatchingPlay => 'Afspil'; + @override String get continueWatchingDetails => 'Åbn detaljer'; @override String get useGlobalHubs => 'Brug startlayout'; @override String get useGlobalHubsDescription => 'Vis samlet startsideindhold. Brug ellers biblioteksanbefalinger.'; @override String get showServerNameOnHubs => 'Vis servernavn på hubbe'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuDa extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Markér som set'; @override String get markAsUnwatched => 'Markér som uset'; @override String get removeFromContinueWatching => 'Fjern fra Fortsæt med at se'; + @override String get viewDetails => 'Vis detaljer'; @override String get goToSeries => 'Gå til serie'; - @override String get goToSeason => 'Gå til sæson'; @override String get shufflePlay => 'Afspil tilfældigt'; @override String get shuffleNotAvailableOffline => 'Bland afspilning er ikke tilgængelig offline'; @override String get fileInfo => 'Filinfo'; @@ -673,7 +676,6 @@ class _TranslationsMessagesDa extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Filinfo ikke tilgængelig'; @override String errorLoadingFileInfo({required Object error}) => 'Fejl ved indlæsning af filinfo: ${error}'; @override String get errorLoadingSeries => 'Fejl ved indlæsning af serie'; - @override String get errorLoadingSeason => 'Fejl ved indlæsning af sæson'; @override String get musicNotSupported => 'Musikafspilning understøttes endnu ikke'; @override String get noDescriptionAvailable => 'Ingen beskrivelse tilgængelig'; @override String get noProfilesAvailable => 'Ingen profiler tilgængelige'; @@ -2027,6 +2029,9 @@ extension on TranslationsDa { 'settings.gridView' => 'Gitter', 'settings.listView' => 'Liste', 'settings.showHeroSection' => 'Vis hero-sektion', + 'settings.continueWatchingAction' => 'Handling for Fortsæt med at se', + 'settings.continueWatchingPlay' => 'Afspil', + 'settings.continueWatchingDetails' => 'Åbn detaljer', 'settings.useGlobalHubs' => 'Brug startlayout', 'settings.useGlobalHubsDescription' => 'Vis samlet startsideindhold. Brug ellers biblioteksanbefalinger.', 'settings.showServerNameOnHubs' => 'Vis servernavn på hubbe', @@ -2262,8 +2267,8 @@ extension on TranslationsDa { 'mediaMenu.markAsWatched' => 'Markér som set', 'mediaMenu.markAsUnwatched' => 'Markér som uset', 'mediaMenu.removeFromContinueWatching' => 'Fjern fra Fortsæt med at se', + 'mediaMenu.viewDetails' => 'Vis detaljer', 'mediaMenu.goToSeries' => 'Gå til serie', - 'mediaMenu.goToSeason' => 'Gå til sæson', 'mediaMenu.shufflePlay' => 'Afspil tilfældigt', 'mediaMenu.shuffleNotAvailableOffline' => 'Bland afspilning er ikke tilgængelig offline', 'mediaMenu.fileInfo' => 'Filinfo', @@ -2395,7 +2400,6 @@ extension on TranslationsDa { 'messages.fileInfoNotAvailable' => 'Filinfo ikke tilgængelig', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Fejl ved indlæsning af filinfo: ${error}', 'messages.errorLoadingSeries' => 'Fejl ved indlæsning af serie', - 'messages.errorLoadingSeason' => 'Fejl ved indlæsning af sæson', 'messages.musicNotSupported' => 'Musikafspilning understøttes endnu ikke', 'messages.noDescriptionAvailable' => 'Ingen beskrivelse tilgængelig', 'messages.noProfilesAvailable' => 'Ingen profiler tilgængelige', @@ -2431,10 +2435,10 @@ extension on TranslationsDa { 'subtitlingStyling.border' => 'Kant', 'subtitlingStyling.background' => 'Baggrund', 'subtitlingStyling.fontSize' => 'Skriftstørrelse', - 'subtitlingStyling.textColor' => 'Tekstfarve', - 'subtitlingStyling.borderSize' => 'Kantstørrelse', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Tekstfarve', + 'subtitlingStyling.borderSize' => 'Kantstørrelse', 'subtitlingStyling.borderColor' => 'Kantfarve', 'subtitlingStyling.backgroundOpacity' => 'Baggrundsgennemsigtighed', 'subtitlingStyling.backgroundColor' => 'Baggrundsfarve', @@ -2945,10 +2949,10 @@ extension on TranslationsDa { 'companionRemote.remote.menu' => 'Menu', 'companionRemote.remote.tabNavigation' => 'Fanenavigation', 'companionRemote.remote.tabDiscover' => 'Opdag', - 'companionRemote.remote.tabLibraries' => 'Biblioteker', - 'companionRemote.remote.tabSearch' => 'Søg', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Biblioteker', + 'companionRemote.remote.tabSearch' => 'Søg', 'companionRemote.remote.tabDownloads' => 'Downloads', 'companionRemote.remote.tabSettings' => 'Indstillinger', 'companionRemote.remote.previous' => 'Forrige', diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index 60735d8d..e409b5cc 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsDe extends TranslationsSettingsEn { @override String get gridView => 'Raster'; @override String get listView => 'Liste'; @override String get showHeroSection => 'Hero-Bereich anzeigen'; + @override String get continueWatchingAction => 'Aktion für Weiterschauen'; + @override String get continueWatchingPlay => 'Abspielen'; + @override String get continueWatchingDetails => 'Details öffnen'; @override String get useGlobalHubs => 'Startlayout verwenden'; @override String get useGlobalHubsDescription => 'Einheitliche Start-Hubs anzeigen. Sonst Bibliotheksempfehlungen verwenden.'; @override String get showServerNameOnHubs => 'Servername bei Hubs anzeigen'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuDe extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Als gesehen markieren'; @override String get markAsUnwatched => 'Als ungesehen markieren'; @override String get removeFromContinueWatching => 'Aus ‚Weiterschauen‘ entfernen'; + @override String get viewDetails => 'Details anzeigen'; @override String get goToSeries => 'Zur Serie'; - @override String get goToSeason => 'Zur Staffel'; @override String get shufflePlay => 'Zufallswiedergabe'; @override String get shuffleNotAvailableOffline => 'Zufallswiedergabe ist offline nicht verfügbar'; @override String get fileInfo => 'Dateiinfo'; @@ -673,7 +676,6 @@ class _TranslationsMessagesDe extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Dateiinfo nicht verfügbar'; @override String errorLoadingFileInfo({required Object error}) => 'Fehler beim Laden der Dateiinfo: ${error}'; @override String get errorLoadingSeries => 'Fehler beim Laden der Serie'; - @override String get errorLoadingSeason => 'Fehler beim Laden der Staffel'; @override String get musicNotSupported => 'Musikwiedergabe wird noch nicht unterstützt'; @override String get noDescriptionAvailable => 'Keine Beschreibung verfügbar'; @override String get noProfilesAvailable => 'Keine Profile verfügbar'; @@ -2027,6 +2029,9 @@ extension on TranslationsDe { 'settings.gridView' => 'Raster', 'settings.listView' => 'Liste', 'settings.showHeroSection' => 'Hero-Bereich anzeigen', + 'settings.continueWatchingAction' => 'Aktion für Weiterschauen', + 'settings.continueWatchingPlay' => 'Abspielen', + 'settings.continueWatchingDetails' => 'Details öffnen', 'settings.useGlobalHubs' => 'Startlayout verwenden', 'settings.useGlobalHubsDescription' => 'Einheitliche Start-Hubs anzeigen. Sonst Bibliotheksempfehlungen verwenden.', 'settings.showServerNameOnHubs' => 'Servername bei Hubs anzeigen', @@ -2262,8 +2267,8 @@ extension on TranslationsDe { 'mediaMenu.markAsWatched' => 'Als gesehen markieren', 'mediaMenu.markAsUnwatched' => 'Als ungesehen markieren', 'mediaMenu.removeFromContinueWatching' => 'Aus ‚Weiterschauen‘ entfernen', + 'mediaMenu.viewDetails' => 'Details anzeigen', 'mediaMenu.goToSeries' => 'Zur Serie', - 'mediaMenu.goToSeason' => 'Zur Staffel', 'mediaMenu.shufflePlay' => 'Zufallswiedergabe', 'mediaMenu.shuffleNotAvailableOffline' => 'Zufallswiedergabe ist offline nicht verfügbar', 'mediaMenu.fileInfo' => 'Dateiinfo', @@ -2395,7 +2400,6 @@ extension on TranslationsDe { 'messages.fileInfoNotAvailable' => 'Dateiinfo nicht verfügbar', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Fehler beim Laden der Dateiinfo: ${error}', 'messages.errorLoadingSeries' => 'Fehler beim Laden der Serie', - 'messages.errorLoadingSeason' => 'Fehler beim Laden der Staffel', 'messages.musicNotSupported' => 'Musikwiedergabe wird noch nicht unterstützt', 'messages.noDescriptionAvailable' => 'Keine Beschreibung verfügbar', 'messages.noProfilesAvailable' => 'Keine Profile verfügbar', @@ -2431,10 +2435,10 @@ extension on TranslationsDe { 'subtitlingStyling.border' => 'Rahmen', 'subtitlingStyling.background' => 'Hintergrund', 'subtitlingStyling.fontSize' => 'Schriftgröße', - 'subtitlingStyling.textColor' => 'Textfarbe', - 'subtitlingStyling.borderSize' => 'Rahmengröße', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Textfarbe', + 'subtitlingStyling.borderSize' => 'Rahmengröße', 'subtitlingStyling.borderColor' => 'Rahmenfarbe', 'subtitlingStyling.backgroundOpacity' => 'Hintergrunddeckkraft', 'subtitlingStyling.backgroundColor' => 'Hintergrundfarbe', @@ -2945,10 +2949,10 @@ extension on TranslationsDe { 'companionRemote.remote.menu' => 'Menü', 'companionRemote.remote.tabNavigation' => 'Tab-Navigation', 'companionRemote.remote.tabDiscover' => 'Entdecken', - 'companionRemote.remote.tabLibraries' => 'Mediatheken', - 'companionRemote.remote.tabSearch' => 'Suche', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Mediatheken', + 'companionRemote.remote.tabSearch' => 'Suche', 'companionRemote.remote.tabDownloads' => 'Downloads', 'companionRemote.remote.tabSettings' => 'Einstellungen', 'companionRemote.remote.previous' => 'Zurück', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index aa8a8415..a4ae6e90 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -459,6 +459,15 @@ class TranslationsSettingsEn { /// en: 'Show Hero Section' String get showHeroSection => 'Show Hero Section'; + /// en: 'Continue Watching Action' + String get continueWatchingAction => 'Continue Watching Action'; + + /// en: 'Play' + String get continueWatchingPlay => 'Play'; + + /// en: 'Open Details' + String get continueWatchingDetails => 'Open Details'; + /// en: 'Use Home Layout' String get useGlobalHubs => 'Use Home Layout'; @@ -1127,12 +1136,12 @@ class TranslationsMediaMenuEn { /// en: 'Remove from Continue Watching' String get removeFromContinueWatching => 'Remove from Continue Watching'; + /// en: 'View details' + String get viewDetails => 'View details'; + /// en: 'Go to series' String get goToSeries => 'Go to series'; - /// en: 'Go to season' - String get goToSeason => 'Go to season'; - /// en: 'Shuffle Play' String get shufflePlay => 'Shuffle Play'; @@ -1561,9 +1570,6 @@ class TranslationsMessagesEn { /// en: 'Error loading series' String get errorLoadingSeries => 'Error loading series'; - /// en: 'Error loading season' - String get errorLoadingSeason => 'Error loading season'; - /// en: 'Music playback is not yet supported' String get musicNotSupported => 'Music playback is not yet supported'; @@ -4577,6 +4583,9 @@ extension on Translations { 'settings.gridView' => 'Grid', 'settings.listView' => 'List', 'settings.showHeroSection' => 'Show Hero Section', + 'settings.continueWatchingAction' => 'Continue Watching Action', + 'settings.continueWatchingPlay' => 'Play', + 'settings.continueWatchingDetails' => 'Open Details', 'settings.useGlobalHubs' => 'Use Home Layout', 'settings.useGlobalHubsDescription' => 'Show unified home hubs. Otherwise use library recommendations.', 'settings.showServerNameOnHubs' => 'Show Server Name on Hubs', @@ -4812,8 +4821,8 @@ extension on Translations { 'mediaMenu.markAsWatched' => 'Mark as Watched', 'mediaMenu.markAsUnwatched' => 'Mark as Unwatched', 'mediaMenu.removeFromContinueWatching' => 'Remove from Continue Watching', + 'mediaMenu.viewDetails' => 'View details', 'mediaMenu.goToSeries' => 'Go to series', - 'mediaMenu.goToSeason' => 'Go to season', 'mediaMenu.shufflePlay' => 'Shuffle Play', 'mediaMenu.shuffleNotAvailableOffline' => 'Shuffle not available offline', 'mediaMenu.fileInfo' => 'File Info', @@ -4945,7 +4954,6 @@ extension on Translations { 'messages.fileInfoNotAvailable' => 'File information not available', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Error loading file info: ${error}', 'messages.errorLoadingSeries' => 'Error loading series', - 'messages.errorLoadingSeason' => 'Error loading season', 'messages.musicNotSupported' => 'Music playback is not yet supported', 'messages.noDescriptionAvailable' => 'No description available', 'messages.noProfilesAvailable' => 'No profiles available', @@ -4981,10 +4989,10 @@ extension on Translations { 'subtitlingStyling.border' => 'Border', 'subtitlingStyling.background' => 'Background', 'subtitlingStyling.fontSize' => 'Font Size', - 'subtitlingStyling.textColor' => 'Text Color', - 'subtitlingStyling.borderSize' => 'Border Size', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Text Color', + 'subtitlingStyling.borderSize' => 'Border Size', 'subtitlingStyling.borderColor' => 'Border Color', 'subtitlingStyling.backgroundOpacity' => 'Background Opacity', 'subtitlingStyling.backgroundColor' => 'Background Color', @@ -5495,10 +5503,10 @@ extension on Translations { 'companionRemote.remote.menu' => 'Menu', 'companionRemote.remote.tabNavigation' => 'Tab Navigation', 'companionRemote.remote.tabDiscover' => 'Discover', - 'companionRemote.remote.tabLibraries' => 'Libraries', - 'companionRemote.remote.tabSearch' => 'Search', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Libraries', + 'companionRemote.remote.tabSearch' => 'Search', 'companionRemote.remote.tabDownloads' => 'Downloads', 'companionRemote.remote.tabSettings' => 'Settings', 'companionRemote.remote.previous' => 'Previous', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index 97b1336a..23cd18dd 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsEs extends TranslationsSettingsEn { @override String get gridView => 'Cuadrícula'; @override String get listView => 'Lista'; @override String get showHeroSection => 'Mostrar Sección Destacada'; + @override String get continueWatchingAction => 'Acción de Seguir viendo'; + @override String get continueWatchingPlay => 'Reproducir'; + @override String get continueWatchingDetails => 'Abrir detalles'; @override String get useGlobalHubs => 'Usar diseño de inicio'; @override String get useGlobalHubsDescription => 'Mostrar hubs de inicio unificados. Si no, usar recomendaciones de biblioteca.'; @override String get showServerNameOnHubs => 'Mostrar Nombre del Servidor en los Hubs'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuEs extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Marcar como Visto'; @override String get markAsUnwatched => 'Marcar como No Visto'; @override String get removeFromContinueWatching => 'Eliminar de Seguir Viendo'; + @override String get viewDetails => 'Ver detalles'; @override String get goToSeries => 'Ir a la serie'; - @override String get goToSeason => 'Ir a la temporada'; @override String get shufflePlay => 'Reproducción Aleatoria'; @override String get shuffleNotAvailableOffline => 'La reproducción aleatoria no está disponible sin conexión'; @override String get fileInfo => 'Información del Archivo'; @@ -673,7 +676,6 @@ class _TranslationsMessagesEs extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Información de archivo no disponible'; @override String errorLoadingFileInfo({required Object error}) => 'Error al cargar info de archivo: ${error}'; @override String get errorLoadingSeries => 'Error al cargar la serie'; - @override String get errorLoadingSeason => 'Error al cargar la temporada'; @override String get musicNotSupported => 'La reproducción de música aún no está soportada'; @override String get noDescriptionAvailable => 'No hay descripción disponible'; @override String get noProfilesAvailable => 'No hay perfiles disponibles'; @@ -2027,6 +2029,9 @@ extension on TranslationsEs { 'settings.gridView' => 'Cuadrícula', 'settings.listView' => 'Lista', 'settings.showHeroSection' => 'Mostrar Sección Destacada', + 'settings.continueWatchingAction' => 'Acción de Seguir viendo', + 'settings.continueWatchingPlay' => 'Reproducir', + 'settings.continueWatchingDetails' => 'Abrir detalles', 'settings.useGlobalHubs' => 'Usar diseño de inicio', 'settings.useGlobalHubsDescription' => 'Mostrar hubs de inicio unificados. Si no, usar recomendaciones de biblioteca.', 'settings.showServerNameOnHubs' => 'Mostrar Nombre del Servidor en los Hubs', @@ -2262,8 +2267,8 @@ extension on TranslationsEs { 'mediaMenu.markAsWatched' => 'Marcar como Visto', 'mediaMenu.markAsUnwatched' => 'Marcar como No Visto', 'mediaMenu.removeFromContinueWatching' => 'Eliminar de Seguir Viendo', + 'mediaMenu.viewDetails' => 'Ver detalles', 'mediaMenu.goToSeries' => 'Ir a la serie', - 'mediaMenu.goToSeason' => 'Ir a la temporada', 'mediaMenu.shufflePlay' => 'Reproducción Aleatoria', 'mediaMenu.shuffleNotAvailableOffline' => 'La reproducción aleatoria no está disponible sin conexión', 'mediaMenu.fileInfo' => 'Información del Archivo', @@ -2395,7 +2400,6 @@ extension on TranslationsEs { 'messages.fileInfoNotAvailable' => 'Información de archivo no disponible', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Error al cargar info de archivo: ${error}', 'messages.errorLoadingSeries' => 'Error al cargar la serie', - 'messages.errorLoadingSeason' => 'Error al cargar la temporada', 'messages.musicNotSupported' => 'La reproducción de música aún no está soportada', 'messages.noDescriptionAvailable' => 'No hay descripción disponible', 'messages.noProfilesAvailable' => 'No hay perfiles disponibles', @@ -2431,10 +2435,10 @@ extension on TranslationsEs { 'subtitlingStyling.border' => 'Borde', 'subtitlingStyling.background' => 'Fondo', 'subtitlingStyling.fontSize' => 'Tamaño de Fuente', - 'subtitlingStyling.textColor' => 'Color de Texto', - 'subtitlingStyling.borderSize' => 'Tamaño de Borde', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Color de Texto', + 'subtitlingStyling.borderSize' => 'Tamaño de Borde', 'subtitlingStyling.borderColor' => 'Color de Borde', 'subtitlingStyling.backgroundOpacity' => 'Opacidad de Fondo', 'subtitlingStyling.backgroundColor' => 'Color de Fondo', @@ -2945,10 +2949,10 @@ extension on TranslationsEs { 'companionRemote.remote.menu' => 'Menú', 'companionRemote.remote.tabNavigation' => 'Navegación por pestañas', 'companionRemote.remote.tabDiscover' => 'Descubrir', - 'companionRemote.remote.tabLibraries' => 'Bibliotecas', - 'companionRemote.remote.tabSearch' => 'Buscar', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Bibliotecas', + 'companionRemote.remote.tabSearch' => 'Buscar', 'companionRemote.remote.tabDownloads' => 'Descargas', 'companionRemote.remote.tabSettings' => 'Configuración', 'companionRemote.remote.previous' => 'Anterior', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index 03a04f19..9f95b20e 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsFr extends TranslationsSettingsEn { @override String get gridView => 'Grille'; @override String get listView => 'Liste'; @override String get showHeroSection => 'Afficher la section Hero'; + @override String get continueWatchingAction => 'Action Continuer à regarder'; + @override String get continueWatchingPlay => 'Lire'; + @override String get continueWatchingDetails => 'Ouvrir les détails'; @override String get useGlobalHubs => 'Utiliser la mise en page d\'accueil'; @override String get useGlobalHubsDescription => 'Afficher des hubs d\'accueil unifiés. Sinon, utiliser les recommandations de bibliothèque.'; @override String get showServerNameOnHubs => 'Afficher le nom du serveur sur les hubs'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuFr extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Marquer comme vu'; @override String get markAsUnwatched => 'Marquer comme non visionné'; @override String get removeFromContinueWatching => 'Supprimer de la liste "Continuer à regarder"'; + @override String get viewDetails => 'Voir les détails'; @override String get goToSeries => 'Aller à la série'; - @override String get goToSeason => 'Aller à la saison'; @override String get shufflePlay => 'Lecture aléatoire'; @override String get shuffleNotAvailableOffline => 'La lecture aléatoire n’est pas disponible hors ligne'; @override String get fileInfo => 'Informations sur le fichier'; @@ -673,7 +676,6 @@ class _TranslationsMessagesFr extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Informations sur le fichier non disponibles'; @override String errorLoadingFileInfo({required Object error}) => 'Erreur lors du chargement des informations sur le fichier: ${error}'; @override String get errorLoadingSeries => 'Erreur lors du chargement de la série'; - @override String get errorLoadingSeason => 'Erreur lors du chargement de la saison'; @override String get musicNotSupported => 'La lecture de musique n\'est pas encore prise en charge'; @override String get noDescriptionAvailable => 'Aucune description disponible'; @override String get noProfilesAvailable => 'Aucun profil disponible'; @@ -2027,6 +2029,9 @@ extension on TranslationsFr { 'settings.gridView' => 'Grille', 'settings.listView' => 'Liste', 'settings.showHeroSection' => 'Afficher la section Hero', + 'settings.continueWatchingAction' => 'Action Continuer à regarder', + 'settings.continueWatchingPlay' => 'Lire', + 'settings.continueWatchingDetails' => 'Ouvrir les détails', 'settings.useGlobalHubs' => 'Utiliser la mise en page d\'accueil', 'settings.useGlobalHubsDescription' => 'Afficher des hubs d\'accueil unifiés. Sinon, utiliser les recommandations de bibliothèque.', 'settings.showServerNameOnHubs' => 'Afficher le nom du serveur sur les hubs', @@ -2262,8 +2267,8 @@ extension on TranslationsFr { 'mediaMenu.markAsWatched' => 'Marquer comme vu', 'mediaMenu.markAsUnwatched' => 'Marquer comme non visionné', 'mediaMenu.removeFromContinueWatching' => 'Supprimer de la liste "Continuer à regarder"', + 'mediaMenu.viewDetails' => 'Voir les détails', 'mediaMenu.goToSeries' => 'Aller à la série', - 'mediaMenu.goToSeason' => 'Aller à la saison', 'mediaMenu.shufflePlay' => 'Lecture aléatoire', 'mediaMenu.shuffleNotAvailableOffline' => 'La lecture aléatoire n’est pas disponible hors ligne', 'mediaMenu.fileInfo' => 'Informations sur le fichier', @@ -2395,7 +2400,6 @@ extension on TranslationsFr { 'messages.fileInfoNotAvailable' => 'Informations sur le fichier non disponibles', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Erreur lors du chargement des informations sur le fichier: ${error}', 'messages.errorLoadingSeries' => 'Erreur lors du chargement de la série', - 'messages.errorLoadingSeason' => 'Erreur lors du chargement de la saison', 'messages.musicNotSupported' => 'La lecture de musique n\'est pas encore prise en charge', 'messages.noDescriptionAvailable' => 'Aucune description disponible', 'messages.noProfilesAvailable' => 'Aucun profil disponible', @@ -2431,10 +2435,10 @@ extension on TranslationsFr { 'subtitlingStyling.border' => 'Bordure', 'subtitlingStyling.background' => 'Arrière-plan', 'subtitlingStyling.fontSize' => 'Taille de la police', - 'subtitlingStyling.textColor' => 'Couleur du texte', - 'subtitlingStyling.borderSize' => 'Taille de la bordure', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Couleur du texte', + 'subtitlingStyling.borderSize' => 'Taille de la bordure', 'subtitlingStyling.borderColor' => 'Couleur de la bordure', 'subtitlingStyling.backgroundOpacity' => 'Opacité d\'arrière-plan', 'subtitlingStyling.backgroundColor' => 'Couleur d\'arrière-plan', @@ -2945,10 +2949,10 @@ extension on TranslationsFr { 'companionRemote.remote.menu' => 'Menu', 'companionRemote.remote.tabNavigation' => 'Navigation par onglets', 'companionRemote.remote.tabDiscover' => 'Découvrir', - 'companionRemote.remote.tabLibraries' => 'Bibliothèques', - 'companionRemote.remote.tabSearch' => 'Rechercher', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Bibliothèques', + 'companionRemote.remote.tabSearch' => 'Rechercher', 'companionRemote.remote.tabDownloads' => 'Téléchargements', 'companionRemote.remote.tabSettings' => 'Paramètres', 'companionRemote.remote.previous' => 'Précédent', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index 71eb285c..d725bc86 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsIt extends TranslationsSettingsEn { @override String get gridView => 'Griglia'; @override String get listView => 'Elenco'; @override String get showHeroSection => 'Mostra sezione principale'; + @override String get continueWatchingAction => 'Azione Continua a guardare'; + @override String get continueWatchingPlay => 'Riproduci'; + @override String get continueWatchingDetails => 'Apri dettagli'; @override String get useGlobalHubs => 'Usa layout home'; @override String get useGlobalHubsDescription => 'Mostra hub Home unificati. Altrimenti usa i consigli della libreria.'; @override String get showServerNameOnHubs => 'Mostra nome server sugli hub'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuIt extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Segna come visto'; @override String get markAsUnwatched => 'Segna come non visto'; @override String get removeFromContinueWatching => 'Rimuovi da Continua a guardare'; + @override String get viewDetails => 'Vedi dettagli'; @override String get goToSeries => 'Vai alle serie'; - @override String get goToSeason => 'Vai alla stagione'; @override String get shufflePlay => 'Riproduzione casuale'; @override String get shuffleNotAvailableOffline => 'Riproduzione casuale non disponibile offline'; @override String get fileInfo => 'Info sul file'; @@ -673,7 +676,6 @@ class _TranslationsMessagesIt extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Informazioni sul file non disponibili'; @override String errorLoadingFileInfo({required Object error}) => 'Errore caricamento informazioni sul file: ${error}'; @override String get errorLoadingSeries => 'Errore caricamento serie'; - @override String get errorLoadingSeason => 'Errore caricamento stagione'; @override String get musicNotSupported => 'La riproduzione musicale non è ancora supportata'; @override String get noDescriptionAvailable => 'Nessuna descrizione disponibile'; @override String get noProfilesAvailable => 'Nessun profilo disponibile'; @@ -2027,6 +2029,9 @@ extension on TranslationsIt { 'settings.gridView' => 'Griglia', 'settings.listView' => 'Elenco', 'settings.showHeroSection' => 'Mostra sezione principale', + 'settings.continueWatchingAction' => 'Azione Continua a guardare', + 'settings.continueWatchingPlay' => 'Riproduci', + 'settings.continueWatchingDetails' => 'Apri dettagli', 'settings.useGlobalHubs' => 'Usa layout home', 'settings.useGlobalHubsDescription' => 'Mostra hub Home unificati. Altrimenti usa i consigli della libreria.', 'settings.showServerNameOnHubs' => 'Mostra nome server sugli hub', @@ -2262,8 +2267,8 @@ extension on TranslationsIt { 'mediaMenu.markAsWatched' => 'Segna come visto', 'mediaMenu.markAsUnwatched' => 'Segna come non visto', 'mediaMenu.removeFromContinueWatching' => 'Rimuovi da Continua a guardare', + 'mediaMenu.viewDetails' => 'Vedi dettagli', 'mediaMenu.goToSeries' => 'Vai alle serie', - 'mediaMenu.goToSeason' => 'Vai alla stagione', 'mediaMenu.shufflePlay' => 'Riproduzione casuale', 'mediaMenu.shuffleNotAvailableOffline' => 'Riproduzione casuale non disponibile offline', 'mediaMenu.fileInfo' => 'Info sul file', @@ -2395,7 +2400,6 @@ extension on TranslationsIt { 'messages.fileInfoNotAvailable' => 'Informazioni sul file non disponibili', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Errore caricamento informazioni sul file: ${error}', 'messages.errorLoadingSeries' => 'Errore caricamento serie', - 'messages.errorLoadingSeason' => 'Errore caricamento stagione', 'messages.musicNotSupported' => 'La riproduzione musicale non è ancora supportata', 'messages.noDescriptionAvailable' => 'Nessuna descrizione disponibile', 'messages.noProfilesAvailable' => 'Nessun profilo disponibile', @@ -2431,10 +2435,10 @@ extension on TranslationsIt { 'subtitlingStyling.border' => 'Bordo', 'subtitlingStyling.background' => 'Sfondo', 'subtitlingStyling.fontSize' => 'Dimensione', - 'subtitlingStyling.textColor' => 'Colore testo', - 'subtitlingStyling.borderSize' => 'Dimensione bordo', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Colore testo', + 'subtitlingStyling.borderSize' => 'Dimensione bordo', 'subtitlingStyling.borderColor' => 'Colore bordo', 'subtitlingStyling.backgroundOpacity' => 'Opacità sfondo', 'subtitlingStyling.backgroundColor' => 'Colore sfondo', @@ -2945,10 +2949,10 @@ extension on TranslationsIt { 'companionRemote.remote.menu' => 'Menu', 'companionRemote.remote.tabNavigation' => 'Navigazione schede', 'companionRemote.remote.tabDiscover' => 'Esplora', - 'companionRemote.remote.tabLibraries' => 'Librerie', - 'companionRemote.remote.tabSearch' => 'Cerca', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Librerie', + 'companionRemote.remote.tabSearch' => 'Cerca', 'companionRemote.remote.tabDownloads' => 'Download', 'companionRemote.remote.tabSettings' => 'Impostazioni', 'companionRemote.remote.previous' => 'Precedente', diff --git a/lib/i18n/strings_ja.g.dart b/lib/i18n/strings_ja.g.dart index ee8119c9..b1b2dc55 100644 --- a/lib/i18n/strings_ja.g.dart +++ b/lib/i18n/strings_ja.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsJa extends TranslationsSettingsEn { @override String get gridView => 'グリッド'; @override String get listView => 'リスト'; @override String get showHeroSection => 'ヒーローセクションを表示'; + @override String get continueWatchingAction => '視聴中の操作'; + @override String get continueWatchingPlay => '再生'; + @override String get continueWatchingDetails => '詳細を開く'; @override String get useGlobalHubs => 'ホームレイアウトを使用'; @override String get useGlobalHubsDescription => '統合ホームハブを表示します。オフの場合はライブラリのおすすめを使用します。'; @override String get showServerNameOnHubs => 'ハブにサーバー名を表示'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuJa extends TranslationsMediaMenuEn { @override String get markAsWatched => '視聴済みにする'; @override String get markAsUnwatched => '未視聴にする'; @override String get removeFromContinueWatching => '視聴中から削除'; + @override String get viewDetails => '詳細を表示'; @override String get goToSeries => 'シリーズへ移動'; - @override String get goToSeason => 'シーズンへ移動'; @override String get shufflePlay => 'シャッフル再生'; @override String get shuffleNotAvailableOffline => 'オフラインではシャッフルを利用できません'; @override String get fileInfo => 'ファイル情報'; @@ -673,7 +676,6 @@ class _TranslationsMessagesJa extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'ファイル情報が利用できません'; @override String errorLoadingFileInfo({required Object error}) => 'ファイル情報の読み込みエラー: ${error}'; @override String get errorLoadingSeries => 'シリーズの読み込みエラー'; - @override String get errorLoadingSeason => 'シーズンの読み込みエラー'; @override String get musicNotSupported => '音楽の再生はまだサポートされていません'; @override String get noDescriptionAvailable => '説明はありません'; @override String get noProfilesAvailable => '利用可能なプロフィールがありません'; @@ -2027,6 +2029,9 @@ extension on TranslationsJa { 'settings.gridView' => 'グリッド', 'settings.listView' => 'リスト', 'settings.showHeroSection' => 'ヒーローセクションを表示', + 'settings.continueWatchingAction' => '視聴中の操作', + 'settings.continueWatchingPlay' => '再生', + 'settings.continueWatchingDetails' => '詳細を開く', 'settings.useGlobalHubs' => 'ホームレイアウトを使用', 'settings.useGlobalHubsDescription' => '統合ホームハブを表示します。オフの場合はライブラリのおすすめを使用します。', 'settings.showServerNameOnHubs' => 'ハブにサーバー名を表示', @@ -2262,8 +2267,8 @@ extension on TranslationsJa { 'mediaMenu.markAsWatched' => '視聴済みにする', 'mediaMenu.markAsUnwatched' => '未視聴にする', 'mediaMenu.removeFromContinueWatching' => '視聴中から削除', + 'mediaMenu.viewDetails' => '詳細を表示', 'mediaMenu.goToSeries' => 'シリーズへ移動', - 'mediaMenu.goToSeason' => 'シーズンへ移動', 'mediaMenu.shufflePlay' => 'シャッフル再生', 'mediaMenu.shuffleNotAvailableOffline' => 'オフラインではシャッフルを利用できません', 'mediaMenu.fileInfo' => 'ファイル情報', @@ -2395,7 +2400,6 @@ extension on TranslationsJa { 'messages.fileInfoNotAvailable' => 'ファイル情報が利用できません', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'ファイル情報の読み込みエラー: ${error}', 'messages.errorLoadingSeries' => 'シリーズの読み込みエラー', - 'messages.errorLoadingSeason' => 'シーズンの読み込みエラー', 'messages.musicNotSupported' => '音楽の再生はまだサポートされていません', 'messages.noDescriptionAvailable' => '説明はありません', 'messages.noProfilesAvailable' => '利用可能なプロフィールがありません', @@ -2431,10 +2435,10 @@ extension on TranslationsJa { 'subtitlingStyling.border' => '枠線', 'subtitlingStyling.background' => '背景', 'subtitlingStyling.fontSize' => 'フォントサイズ', - 'subtitlingStyling.textColor' => 'テキストの色', - 'subtitlingStyling.borderSize' => '枠線サイズ', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'テキストの色', + 'subtitlingStyling.borderSize' => '枠線サイズ', 'subtitlingStyling.borderColor' => '枠線の色', 'subtitlingStyling.backgroundOpacity' => '背景の不透明度', 'subtitlingStyling.backgroundColor' => '背景色', @@ -2945,10 +2949,10 @@ extension on TranslationsJa { 'companionRemote.remote.menu' => 'メニュー', 'companionRemote.remote.tabNavigation' => 'タブナビゲーション', 'companionRemote.remote.tabDiscover' => '探す', - 'companionRemote.remote.tabLibraries' => 'ライブラリ', - 'companionRemote.remote.tabSearch' => '検索', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'ライブラリ', + 'companionRemote.remote.tabSearch' => '検索', 'companionRemote.remote.tabDownloads' => 'ダウンロード', 'companionRemote.remote.tabSettings' => '設定', 'companionRemote.remote.previous' => '前へ', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index af258469..815350b7 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsKo extends TranslationsSettingsEn { @override String get gridView => '그리드 보기'; @override String get listView => '목록 보기'; @override String get showHeroSection => '주요 추천 영역 표시'; + @override String get continueWatchingAction => '계속 보기 동작'; + @override String get continueWatchingPlay => '재생'; + @override String get continueWatchingDetails => '상세 정보 열기'; @override String get useGlobalHubs => '홈 레이아웃 사용'; @override String get useGlobalHubsDescription => '통합 홈 허브를 표시합니다. 끄면 라이브러리 추천을 사용합니다.'; @override String get showServerNameOnHubs => '허브에 서버 이름 표시'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuKo extends TranslationsMediaMenuEn { @override String get markAsWatched => '시청 완료로 표시'; @override String get markAsUnwatched => '시청 안 함으로 표시'; @override String get removeFromContinueWatching => '계속 보기에서 제거'; + @override String get viewDetails => '상세 정보 보기'; @override String get goToSeries => '시리즈로 이동'; - @override String get goToSeason => '시즌으로 이동'; @override String get shufflePlay => '무작위 재생'; @override String get shuffleNotAvailableOffline => '오프라인에서는 셔플을 사용할 수 없습니다'; @override String get fileInfo => '파일 정보'; @@ -673,7 +676,6 @@ class _TranslationsMessagesKo extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => '파일 정보가 없습니다'; @override String errorLoadingFileInfo({required Object error}) => '파일 정보 로딩 중 오류: ${error}'; @override String get errorLoadingSeries => '시리즈 로딩 중 오류'; - @override String get errorLoadingSeason => '시즌 로딩 중 오류'; @override String get musicNotSupported => '음악 재생 미지원'; @override String get noDescriptionAvailable => '설명이 없습니다'; @override String get noProfilesAvailable => '사용 가능한 프로필이 없습니다'; @@ -2027,6 +2029,9 @@ extension on TranslationsKo { 'settings.gridView' => '그리드 보기', 'settings.listView' => '목록 보기', 'settings.showHeroSection' => '주요 추천 영역 표시', + 'settings.continueWatchingAction' => '계속 보기 동작', + 'settings.continueWatchingPlay' => '재생', + 'settings.continueWatchingDetails' => '상세 정보 열기', 'settings.useGlobalHubs' => '홈 레이아웃 사용', 'settings.useGlobalHubsDescription' => '통합 홈 허브를 표시합니다. 끄면 라이브러리 추천을 사용합니다.', 'settings.showServerNameOnHubs' => '허브에 서버 이름 표시', @@ -2262,8 +2267,8 @@ extension on TranslationsKo { 'mediaMenu.markAsWatched' => '시청 완료로 표시', 'mediaMenu.markAsUnwatched' => '시청 안 함으로 표시', 'mediaMenu.removeFromContinueWatching' => '계속 보기에서 제거', + 'mediaMenu.viewDetails' => '상세 정보 보기', 'mediaMenu.goToSeries' => '시리즈로 이동', - 'mediaMenu.goToSeason' => '시즌으로 이동', 'mediaMenu.shufflePlay' => '무작위 재생', 'mediaMenu.shuffleNotAvailableOffline' => '오프라인에서는 셔플을 사용할 수 없습니다', 'mediaMenu.fileInfo' => '파일 정보', @@ -2395,7 +2400,6 @@ extension on TranslationsKo { 'messages.fileInfoNotAvailable' => '파일 정보가 없습니다', 'messages.errorLoadingFileInfo' => ({required Object error}) => '파일 정보 로딩 중 오류: ${error}', 'messages.errorLoadingSeries' => '시리즈 로딩 중 오류', - 'messages.errorLoadingSeason' => '시즌 로딩 중 오류', 'messages.musicNotSupported' => '음악 재생 미지원', 'messages.noDescriptionAvailable' => '설명이 없습니다', 'messages.noProfilesAvailable' => '사용 가능한 프로필이 없습니다', @@ -2431,10 +2435,10 @@ extension on TranslationsKo { 'subtitlingStyling.border' => '테두리', 'subtitlingStyling.background' => '배경', 'subtitlingStyling.fontSize' => '글자 크기', - 'subtitlingStyling.textColor' => '텍스트 색상', - 'subtitlingStyling.borderSize' => '테두리 크기', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => '텍스트 색상', + 'subtitlingStyling.borderSize' => '테두리 크기', 'subtitlingStyling.borderColor' => '테두리 색상', 'subtitlingStyling.backgroundOpacity' => '배경 불투명도', 'subtitlingStyling.backgroundColor' => '배경색', @@ -2945,10 +2949,10 @@ extension on TranslationsKo { 'companionRemote.remote.menu' => '메뉴', 'companionRemote.remote.tabNavigation' => '탭 탐색', 'companionRemote.remote.tabDiscover' => '발견', - 'companionRemote.remote.tabLibraries' => '미디어 라이브러리', - 'companionRemote.remote.tabSearch' => '검색', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => '미디어 라이브러리', + 'companionRemote.remote.tabSearch' => '검색', 'companionRemote.remote.tabDownloads' => '다운로드', 'companionRemote.remote.tabSettings' => '설정', 'companionRemote.remote.previous' => '이전', diff --git a/lib/i18n/strings_nb.g.dart b/lib/i18n/strings_nb.g.dart index 493538d9..6c947ea8 100644 --- a/lib/i18n/strings_nb.g.dart +++ b/lib/i18n/strings_nb.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsNb extends TranslationsSettingsEn { @override String get gridView => 'Rutenett'; @override String get listView => 'Liste'; @override String get showHeroSection => 'Vis fremhevet seksjon'; + @override String get continueWatchingAction => 'Handling for Fortsett å se'; + @override String get continueWatchingPlay => 'Spill av'; + @override String get continueWatchingDetails => 'Åpne detaljer'; @override String get useGlobalHubs => 'Bruk startoppsett'; @override String get useGlobalHubsDescription => 'Vis samlet startinnhold. Ellers brukes bibliotekanbefalinger.'; @override String get showServerNameOnHubs => 'Vis servernavn på huber'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuNb extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Merk som sett'; @override String get markAsUnwatched => 'Merk som usett'; @override String get removeFromContinueWatching => 'Fjern fra Fortsett å se'; + @override String get viewDetails => 'Vis detaljer'; @override String get goToSeries => 'Gå til serie'; - @override String get goToSeason => 'Gå til sesong'; @override String get shufflePlay => 'Tilfeldig avspilling'; @override String get shuffleNotAvailableOffline => 'Tilfeldig avspilling er ikke tilgjengelig offline'; @override String get fileInfo => 'Filinformasjon'; @@ -673,7 +676,6 @@ class _TranslationsMessagesNb extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Filinformasjon ikke tilgjengelig'; @override String errorLoadingFileInfo({required Object error}) => 'Feil ved lasting av filinformasjon: ${error}'; @override String get errorLoadingSeries => 'Feil ved lasting av serie'; - @override String get errorLoadingSeason => 'Feil ved lasting av sesong'; @override String get musicNotSupported => 'Musikkavspilling støttes ikke ennå'; @override String get noDescriptionAvailable => 'Ingen beskrivelse tilgjengelig'; @override String get noProfilesAvailable => 'Ingen profiler tilgjengelige'; @@ -2027,6 +2029,9 @@ extension on TranslationsNb { 'settings.gridView' => 'Rutenett', 'settings.listView' => 'Liste', 'settings.showHeroSection' => 'Vis fremhevet seksjon', + 'settings.continueWatchingAction' => 'Handling for Fortsett å se', + 'settings.continueWatchingPlay' => 'Spill av', + 'settings.continueWatchingDetails' => 'Åpne detaljer', 'settings.useGlobalHubs' => 'Bruk startoppsett', 'settings.useGlobalHubsDescription' => 'Vis samlet startinnhold. Ellers brukes bibliotekanbefalinger.', 'settings.showServerNameOnHubs' => 'Vis servernavn på huber', @@ -2262,8 +2267,8 @@ extension on TranslationsNb { 'mediaMenu.markAsWatched' => 'Merk som sett', 'mediaMenu.markAsUnwatched' => 'Merk som usett', 'mediaMenu.removeFromContinueWatching' => 'Fjern fra Fortsett å se', + 'mediaMenu.viewDetails' => 'Vis detaljer', 'mediaMenu.goToSeries' => 'Gå til serie', - 'mediaMenu.goToSeason' => 'Gå til sesong', 'mediaMenu.shufflePlay' => 'Tilfeldig avspilling', 'mediaMenu.shuffleNotAvailableOffline' => 'Tilfeldig avspilling er ikke tilgjengelig offline', 'mediaMenu.fileInfo' => 'Filinformasjon', @@ -2395,7 +2400,6 @@ extension on TranslationsNb { 'messages.fileInfoNotAvailable' => 'Filinformasjon ikke tilgjengelig', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Feil ved lasting av filinformasjon: ${error}', 'messages.errorLoadingSeries' => 'Feil ved lasting av serie', - 'messages.errorLoadingSeason' => 'Feil ved lasting av sesong', 'messages.musicNotSupported' => 'Musikkavspilling støttes ikke ennå', 'messages.noDescriptionAvailable' => 'Ingen beskrivelse tilgjengelig', 'messages.noProfilesAvailable' => 'Ingen profiler tilgjengelige', @@ -2431,10 +2435,10 @@ extension on TranslationsNb { 'subtitlingStyling.border' => 'Kantlinje', 'subtitlingStyling.background' => 'Bakgrunn', 'subtitlingStyling.fontSize' => 'Skriftstørrelse', - 'subtitlingStyling.textColor' => 'Tekstfarge', - 'subtitlingStyling.borderSize' => 'Kantstørrelse', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Tekstfarge', + 'subtitlingStyling.borderSize' => 'Kantstørrelse', 'subtitlingStyling.borderColor' => 'Kantfarge', 'subtitlingStyling.backgroundOpacity' => 'Bakgrunnsopasitet', 'subtitlingStyling.backgroundColor' => 'Bakgrunnsfarge', @@ -2945,10 +2949,10 @@ extension on TranslationsNb { 'companionRemote.remote.menu' => 'Meny', 'companionRemote.remote.tabNavigation' => 'Fanenavigering', 'companionRemote.remote.tabDiscover' => 'Oppdag', - 'companionRemote.remote.tabLibraries' => 'Biblioteker', - 'companionRemote.remote.tabSearch' => 'Søk', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Biblioteker', + 'companionRemote.remote.tabSearch' => 'Søk', 'companionRemote.remote.tabDownloads' => 'Nedlastinger', 'companionRemote.remote.tabSettings' => 'Innstillinger', 'companionRemote.remote.previous' => 'Forrige', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index 7e38f42d..8736a7cc 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsNl extends TranslationsSettingsEn { @override String get gridView => 'Raster'; @override String get listView => 'Lijst'; @override String get showHeroSection => 'Toon hoofdsectie'; + @override String get continueWatchingAction => 'Actie voor Doorgaan met kijken'; + @override String get continueWatchingPlay => 'Afspelen'; + @override String get continueWatchingDetails => 'Details openen'; @override String get useGlobalHubs => 'Startlayout gebruiken'; @override String get useGlobalHubsDescription => 'Toon gecombineerde home-hubs. Anders bibliotheekaanbevelingen gebruiken.'; @override String get showServerNameOnHubs => 'Servernaam tonen bij hubs'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuNl extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Markeer als gekeken'; @override String get markAsUnwatched => 'Markeer als ongekeken'; @override String get removeFromContinueWatching => 'Verwijder uit Doorgaan met kijken'; + @override String get viewDetails => 'Details bekijken'; @override String get goToSeries => 'Ga naar serie'; - @override String get goToSeason => 'Ga naar seizoen'; @override String get shufflePlay => 'Willekeurig afspelen'; @override String get shuffleNotAvailableOffline => 'Shuffle is offline niet beschikbaar'; @override String get fileInfo => 'Bestand info'; @@ -673,7 +676,6 @@ class _TranslationsMessagesNl extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Bestand informatie niet beschikbaar'; @override String errorLoadingFileInfo({required Object error}) => 'Fout bij laden bestand info: ${error}'; @override String get errorLoadingSeries => 'Fout bij laden serie'; - @override String get errorLoadingSeason => 'Fout bij laden seizoen'; @override String get musicNotSupported => 'Muziek afspelen wordt nog niet ondersteund'; @override String get noDescriptionAvailable => 'Geen beschrijving beschikbaar'; @override String get noProfilesAvailable => 'Geen profielen beschikbaar'; @@ -2027,6 +2029,9 @@ extension on TranslationsNl { 'settings.gridView' => 'Raster', 'settings.listView' => 'Lijst', 'settings.showHeroSection' => 'Toon hoofdsectie', + 'settings.continueWatchingAction' => 'Actie voor Doorgaan met kijken', + 'settings.continueWatchingPlay' => 'Afspelen', + 'settings.continueWatchingDetails' => 'Details openen', 'settings.useGlobalHubs' => 'Startlayout gebruiken', 'settings.useGlobalHubsDescription' => 'Toon gecombineerde home-hubs. Anders bibliotheekaanbevelingen gebruiken.', 'settings.showServerNameOnHubs' => 'Servernaam tonen bij hubs', @@ -2262,8 +2267,8 @@ extension on TranslationsNl { 'mediaMenu.markAsWatched' => 'Markeer als gekeken', 'mediaMenu.markAsUnwatched' => 'Markeer als ongekeken', 'mediaMenu.removeFromContinueWatching' => 'Verwijder uit Doorgaan met kijken', + 'mediaMenu.viewDetails' => 'Details bekijken', 'mediaMenu.goToSeries' => 'Ga naar serie', - 'mediaMenu.goToSeason' => 'Ga naar seizoen', 'mediaMenu.shufflePlay' => 'Willekeurig afspelen', 'mediaMenu.shuffleNotAvailableOffline' => 'Shuffle is offline niet beschikbaar', 'mediaMenu.fileInfo' => 'Bestand info', @@ -2395,7 +2400,6 @@ extension on TranslationsNl { 'messages.fileInfoNotAvailable' => 'Bestand informatie niet beschikbaar', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Fout bij laden bestand info: ${error}', 'messages.errorLoadingSeries' => 'Fout bij laden serie', - 'messages.errorLoadingSeason' => 'Fout bij laden seizoen', 'messages.musicNotSupported' => 'Muziek afspelen wordt nog niet ondersteund', 'messages.noDescriptionAvailable' => 'Geen beschrijving beschikbaar', 'messages.noProfilesAvailable' => 'Geen profielen beschikbaar', @@ -2431,10 +2435,10 @@ extension on TranslationsNl { 'subtitlingStyling.border' => 'Rand', 'subtitlingStyling.background' => 'Achtergrond', 'subtitlingStyling.fontSize' => 'Lettergrootte', - 'subtitlingStyling.textColor' => 'Tekstkleur', - 'subtitlingStyling.borderSize' => 'Rand grootte', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Tekstkleur', + 'subtitlingStyling.borderSize' => 'Rand grootte', 'subtitlingStyling.borderColor' => 'Randkleur', 'subtitlingStyling.backgroundOpacity' => 'Achtergrond transparantie', 'subtitlingStyling.backgroundColor' => 'Achtergrondkleur', @@ -2945,10 +2949,10 @@ extension on TranslationsNl { 'companionRemote.remote.menu' => 'Menu', 'companionRemote.remote.tabNavigation' => 'Tabnavigatie', 'companionRemote.remote.tabDiscover' => 'Ontdekken', - 'companionRemote.remote.tabLibraries' => 'Bibliotheken', - 'companionRemote.remote.tabSearch' => 'Zoeken', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Bibliotheken', + 'companionRemote.remote.tabSearch' => 'Zoeken', 'companionRemote.remote.tabDownloads' => 'Downloads', 'companionRemote.remote.tabSettings' => 'Instellingen', 'companionRemote.remote.previous' => 'Vorige', diff --git a/lib/i18n/strings_pl.g.dart b/lib/i18n/strings_pl.g.dart index 4362cf04..4d29e529 100644 --- a/lib/i18n/strings_pl.g.dart +++ b/lib/i18n/strings_pl.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsPl extends TranslationsSettingsEn { @override String get gridView => 'Siatka'; @override String get listView => 'Lista'; @override String get showHeroSection => 'Pokaż sekcję wyróżnioną'; + @override String get continueWatchingAction => 'Akcja Kontynuuj oglądanie'; + @override String get continueWatchingPlay => 'Odtwórz'; + @override String get continueWatchingDetails => 'Otwórz szczegóły'; @override String get useGlobalHubs => 'Użyj układu strony głównej'; @override String get useGlobalHubsDescription => 'Pokaż ujednolicone huby ekranu głównego. W przeciwnym razie użyj rekomendacji bibliotek.'; @override String get showServerNameOnHubs => 'Pokaż nazwę serwera w hubach'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuPl extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Oznacz jako obejrzane'; @override String get markAsUnwatched => 'Oznacz jako nieobejrzane'; @override String get removeFromContinueWatching => 'Usuń z kontynuowania oglądania'; + @override String get viewDetails => 'Pokaż szczegóły'; @override String get goToSeries => 'Przejdź do serialu'; - @override String get goToSeason => 'Przejdź do sezonu'; @override String get shufflePlay => 'Odtwarzanie losowe'; @override String get shuffleNotAvailableOffline => 'Odtwarzanie losowe nie jest dostępne offline'; @override String get fileInfo => 'Informacje o pliku'; @@ -673,7 +676,6 @@ class _TranslationsMessagesPl extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Informacje o pliku niedostępne'; @override String errorLoadingFileInfo({required Object error}) => 'Błąd ładowania informacji o pliku: ${error}'; @override String get errorLoadingSeries => 'Błąd ładowania serialu'; - @override String get errorLoadingSeason => 'Błąd ładowania sezonu'; @override String get musicNotSupported => 'Odtwarzanie muzyki nie jest jeszcze obsługiwane'; @override String get noDescriptionAvailable => 'Brak dostępnego opisu'; @override String get noProfilesAvailable => 'Brak dostępnych profili'; @@ -2027,6 +2029,9 @@ extension on TranslationsPl { 'settings.gridView' => 'Siatka', 'settings.listView' => 'Lista', 'settings.showHeroSection' => 'Pokaż sekcję wyróżnioną', + 'settings.continueWatchingAction' => 'Akcja Kontynuuj oglądanie', + 'settings.continueWatchingPlay' => 'Odtwórz', + 'settings.continueWatchingDetails' => 'Otwórz szczegóły', 'settings.useGlobalHubs' => 'Użyj układu strony głównej', 'settings.useGlobalHubsDescription' => 'Pokaż ujednolicone huby ekranu głównego. W przeciwnym razie użyj rekomendacji bibliotek.', 'settings.showServerNameOnHubs' => 'Pokaż nazwę serwera w hubach', @@ -2262,8 +2267,8 @@ extension on TranslationsPl { 'mediaMenu.markAsWatched' => 'Oznacz jako obejrzane', 'mediaMenu.markAsUnwatched' => 'Oznacz jako nieobejrzane', 'mediaMenu.removeFromContinueWatching' => 'Usuń z kontynuowania oglądania', + 'mediaMenu.viewDetails' => 'Pokaż szczegóły', 'mediaMenu.goToSeries' => 'Przejdź do serialu', - 'mediaMenu.goToSeason' => 'Przejdź do sezonu', 'mediaMenu.shufflePlay' => 'Odtwarzanie losowe', 'mediaMenu.shuffleNotAvailableOffline' => 'Odtwarzanie losowe nie jest dostępne offline', 'mediaMenu.fileInfo' => 'Informacje o pliku', @@ -2395,7 +2400,6 @@ extension on TranslationsPl { 'messages.fileInfoNotAvailable' => 'Informacje o pliku niedostępne', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Błąd ładowania informacji o pliku: ${error}', 'messages.errorLoadingSeries' => 'Błąd ładowania serialu', - 'messages.errorLoadingSeason' => 'Błąd ładowania sezonu', 'messages.musicNotSupported' => 'Odtwarzanie muzyki nie jest jeszcze obsługiwane', 'messages.noDescriptionAvailable' => 'Brak dostępnego opisu', 'messages.noProfilesAvailable' => 'Brak dostępnych profili', @@ -2431,10 +2435,10 @@ extension on TranslationsPl { 'subtitlingStyling.border' => 'Obramowanie', 'subtitlingStyling.background' => 'Tło', 'subtitlingStyling.fontSize' => 'Rozmiar czcionki', - 'subtitlingStyling.textColor' => 'Kolor tekstu', - 'subtitlingStyling.borderSize' => 'Rozmiar obramowania', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Kolor tekstu', + 'subtitlingStyling.borderSize' => 'Rozmiar obramowania', 'subtitlingStyling.borderColor' => 'Kolor obramowania', 'subtitlingStyling.backgroundOpacity' => 'Przezroczystość tła', 'subtitlingStyling.backgroundColor' => 'Kolor tła', @@ -2945,10 +2949,10 @@ extension on TranslationsPl { 'companionRemote.remote.menu' => 'Menu', 'companionRemote.remote.tabNavigation' => 'Nawigacja', 'companionRemote.remote.tabDiscover' => 'Odkryj', - 'companionRemote.remote.tabLibraries' => 'Biblioteki', - 'companionRemote.remote.tabSearch' => 'Szukaj', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Biblioteki', + 'companionRemote.remote.tabSearch' => 'Szukaj', 'companionRemote.remote.tabDownloads' => 'Pobrania', 'companionRemote.remote.tabSettings' => 'Ustawienia', 'companionRemote.remote.previous' => 'Poprzedni', diff --git a/lib/i18n/strings_pt.g.dart b/lib/i18n/strings_pt.g.dart index 63a6cbca..8a617077 100644 --- a/lib/i18n/strings_pt.g.dart +++ b/lib/i18n/strings_pt.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsPt extends TranslationsSettingsEn { @override String get gridView => 'Grade'; @override String get listView => 'Lista'; @override String get showHeroSection => 'Mostrar Seção de Destaque'; + @override String get continueWatchingAction => 'Ação de Continuar Assistindo'; + @override String get continueWatchingPlay => 'Reproduzir'; + @override String get continueWatchingDetails => 'Abrir detalhes'; @override String get useGlobalHubs => 'Usar layout inicial'; @override String get useGlobalHubsDescription => 'Mostrar hubs iniciais unificados. Caso contrário, usar recomendações da biblioteca.'; @override String get showServerNameOnHubs => 'Mostrar Nome do Servidor nos Hubs'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuPt extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Marcar como Assistido'; @override String get markAsUnwatched => 'Marcar como Não Assistido'; @override String get removeFromContinueWatching => 'Remover de Continuar Assistindo'; + @override String get viewDetails => 'Ver detalhes'; @override String get goToSeries => 'Ir para a série'; - @override String get goToSeason => 'Ir para a temporada'; @override String get shufflePlay => 'Reprodução Aleatória'; @override String get shuffleNotAvailableOffline => 'Reprodução aleatória indisponível offline'; @override String get fileInfo => 'Info do Arquivo'; @@ -673,7 +676,6 @@ class _TranslationsMessagesPt extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Informações do arquivo não disponíveis'; @override String errorLoadingFileInfo({required Object error}) => 'Erro ao carregar info do arquivo: ${error}'; @override String get errorLoadingSeries => 'Erro ao carregar série'; - @override String get errorLoadingSeason => 'Erro ao carregar temporada'; @override String get musicNotSupported => 'Reprodução de música ainda não é suportada'; @override String get noDescriptionAvailable => 'Nenhuma descrição disponível'; @override String get noProfilesAvailable => 'Nenhum perfil disponível'; @@ -2027,6 +2029,9 @@ extension on TranslationsPt { 'settings.gridView' => 'Grade', 'settings.listView' => 'Lista', 'settings.showHeroSection' => 'Mostrar Seção de Destaque', + 'settings.continueWatchingAction' => 'Ação de Continuar Assistindo', + 'settings.continueWatchingPlay' => 'Reproduzir', + 'settings.continueWatchingDetails' => 'Abrir detalhes', 'settings.useGlobalHubs' => 'Usar layout inicial', 'settings.useGlobalHubsDescription' => 'Mostrar hubs iniciais unificados. Caso contrário, usar recomendações da biblioteca.', 'settings.showServerNameOnHubs' => 'Mostrar Nome do Servidor nos Hubs', @@ -2262,8 +2267,8 @@ extension on TranslationsPt { 'mediaMenu.markAsWatched' => 'Marcar como Assistido', 'mediaMenu.markAsUnwatched' => 'Marcar como Não Assistido', 'mediaMenu.removeFromContinueWatching' => 'Remover de Continuar Assistindo', + 'mediaMenu.viewDetails' => 'Ver detalhes', 'mediaMenu.goToSeries' => 'Ir para a série', - 'mediaMenu.goToSeason' => 'Ir para a temporada', 'mediaMenu.shufflePlay' => 'Reprodução Aleatória', 'mediaMenu.shuffleNotAvailableOffline' => 'Reprodução aleatória indisponível offline', 'mediaMenu.fileInfo' => 'Info do Arquivo', @@ -2395,7 +2400,6 @@ extension on TranslationsPt { 'messages.fileInfoNotAvailable' => 'Informações do arquivo não disponíveis', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Erro ao carregar info do arquivo: ${error}', 'messages.errorLoadingSeries' => 'Erro ao carregar série', - 'messages.errorLoadingSeason' => 'Erro ao carregar temporada', 'messages.musicNotSupported' => 'Reprodução de música ainda não é suportada', 'messages.noDescriptionAvailable' => 'Nenhuma descrição disponível', 'messages.noProfilesAvailable' => 'Nenhum perfil disponível', @@ -2431,10 +2435,10 @@ extension on TranslationsPt { 'subtitlingStyling.border' => 'Borda', 'subtitlingStyling.background' => 'Fundo', 'subtitlingStyling.fontSize' => 'Tamanho da Fonte', - 'subtitlingStyling.textColor' => 'Cor do Texto', - 'subtitlingStyling.borderSize' => 'Tamanho da Borda', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Cor do Texto', + 'subtitlingStyling.borderSize' => 'Tamanho da Borda', 'subtitlingStyling.borderColor' => 'Cor da Borda', 'subtitlingStyling.backgroundOpacity' => 'Opacidade do Fundo', 'subtitlingStyling.backgroundColor' => 'Cor de Fundo', @@ -2945,10 +2949,10 @@ extension on TranslationsPt { 'companionRemote.remote.menu' => 'Menu', 'companionRemote.remote.tabNavigation' => 'Navegação', 'companionRemote.remote.tabDiscover' => 'Descobrir', - 'companionRemote.remote.tabLibraries' => 'Bibliotecas', - 'companionRemote.remote.tabSearch' => 'Buscar', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Bibliotecas', + 'companionRemote.remote.tabSearch' => 'Buscar', 'companionRemote.remote.tabDownloads' => 'Downloads', 'companionRemote.remote.tabSettings' => 'Configurações', 'companionRemote.remote.previous' => 'Anterior', diff --git a/lib/i18n/strings_ru.g.dart b/lib/i18n/strings_ru.g.dart index d2f40fa8..2d9532c2 100644 --- a/lib/i18n/strings_ru.g.dart +++ b/lib/i18n/strings_ru.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsRu extends TranslationsSettingsEn { @override String get gridView => 'Сетка'; @override String get listView => 'Список'; @override String get showHeroSection => 'Показать раздел избранного'; + @override String get continueWatchingAction => 'Действие для «Продолжить просмотр»'; + @override String get continueWatchingPlay => 'Воспроизвести'; + @override String get continueWatchingDetails => 'Открыть сведения'; @override String get useGlobalHubs => 'Использовать макет главной'; @override String get useGlobalHubsDescription => 'Показывать единые разделы главной. Иначе использовать рекомендации библиотек.'; @override String get showServerNameOnHubs => 'Показывать имя сервера в хабах'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuRu extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Отметить как просмотренное'; @override String get markAsUnwatched => 'Отметить как непросмотренное'; @override String get removeFromContinueWatching => 'Удалить из «Продолжить просмотр»'; + @override String get viewDetails => 'Показать сведения'; @override String get goToSeries => 'Перейти к сериалу'; - @override String get goToSeason => 'Перейти к сезону'; @override String get shufflePlay => 'Случайное воспроизведение'; @override String get shuffleNotAvailableOffline => 'Перемешивание недоступно офлайн'; @override String get fileInfo => 'Информация о файле'; @@ -673,7 +676,6 @@ class _TranslationsMessagesRu extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Информация о файле недоступна'; @override String errorLoadingFileInfo({required Object error}) => 'Ошибка загрузки информации о файле: ${error}'; @override String get errorLoadingSeries => 'Ошибка загрузки сериала'; - @override String get errorLoadingSeason => 'Ошибка загрузки сезона'; @override String get musicNotSupported => 'Воспроизведение музыки пока не поддерживается'; @override String get noDescriptionAvailable => 'Описание недоступно'; @override String get noProfilesAvailable => 'Профили недоступны'; @@ -2027,6 +2029,9 @@ extension on TranslationsRu { 'settings.gridView' => 'Сетка', 'settings.listView' => 'Список', 'settings.showHeroSection' => 'Показать раздел избранного', + 'settings.continueWatchingAction' => 'Действие для «Продолжить просмотр»', + 'settings.continueWatchingPlay' => 'Воспроизвести', + 'settings.continueWatchingDetails' => 'Открыть сведения', 'settings.useGlobalHubs' => 'Использовать макет главной', 'settings.useGlobalHubsDescription' => 'Показывать единые разделы главной. Иначе использовать рекомендации библиотек.', 'settings.showServerNameOnHubs' => 'Показывать имя сервера в хабах', @@ -2262,8 +2267,8 @@ extension on TranslationsRu { 'mediaMenu.markAsWatched' => 'Отметить как просмотренное', 'mediaMenu.markAsUnwatched' => 'Отметить как непросмотренное', 'mediaMenu.removeFromContinueWatching' => 'Удалить из «Продолжить просмотр»', + 'mediaMenu.viewDetails' => 'Показать сведения', 'mediaMenu.goToSeries' => 'Перейти к сериалу', - 'mediaMenu.goToSeason' => 'Перейти к сезону', 'mediaMenu.shufflePlay' => 'Случайное воспроизведение', 'mediaMenu.shuffleNotAvailableOffline' => 'Перемешивание недоступно офлайн', 'mediaMenu.fileInfo' => 'Информация о файле', @@ -2395,7 +2400,6 @@ extension on TranslationsRu { 'messages.fileInfoNotAvailable' => 'Информация о файле недоступна', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Ошибка загрузки информации о файле: ${error}', 'messages.errorLoadingSeries' => 'Ошибка загрузки сериала', - 'messages.errorLoadingSeason' => 'Ошибка загрузки сезона', 'messages.musicNotSupported' => 'Воспроизведение музыки пока не поддерживается', 'messages.noDescriptionAvailable' => 'Описание недоступно', 'messages.noProfilesAvailable' => 'Профили недоступны', @@ -2431,10 +2435,10 @@ extension on TranslationsRu { 'subtitlingStyling.border' => 'Обводка', 'subtitlingStyling.background' => 'Фон', 'subtitlingStyling.fontSize' => 'Размер шрифта', - 'subtitlingStyling.textColor' => 'Цвет текста', - 'subtitlingStyling.borderSize' => 'Размер обводки', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Цвет текста', + 'subtitlingStyling.borderSize' => 'Размер обводки', 'subtitlingStyling.borderColor' => 'Цвет обводки', 'subtitlingStyling.backgroundOpacity' => 'Прозрачность фона', 'subtitlingStyling.backgroundColor' => 'Цвет фона', @@ -2945,10 +2949,10 @@ extension on TranslationsRu { 'companionRemote.remote.menu' => 'Меню', 'companionRemote.remote.tabNavigation' => 'Навигация', 'companionRemote.remote.tabDiscover' => 'Обзор', - 'companionRemote.remote.tabLibraries' => 'Библиотеки', - 'companionRemote.remote.tabSearch' => 'Поиск', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Библиотеки', + 'companionRemote.remote.tabSearch' => 'Поиск', 'companionRemote.remote.tabDownloads' => 'Загрузки', 'companionRemote.remote.tabSettings' => 'Настройки', 'companionRemote.remote.previous' => 'Предыдущий', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index 2c32fcd8..f3523bcb 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsSv extends TranslationsSettingsEn { @override String get gridView => 'Rutnät'; @override String get listView => 'Lista'; @override String get showHeroSection => 'Visa hjältesektion'; + @override String get continueWatchingAction => 'Åtgärd för Fortsätt titta'; + @override String get continueWatchingPlay => 'Spela'; + @override String get continueWatchingDetails => 'Öppna detaljer'; @override String get useGlobalHubs => 'Använd startlayout'; @override String get useGlobalHubsDescription => 'Visa enhetliga startsideshubbar. Annars används biblioteksrekommendationer.'; @override String get showServerNameOnHubs => 'Visa servernamn på hubbar'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuSv extends TranslationsMediaMenuEn { @override String get markAsWatched => 'Markera som sedd'; @override String get markAsUnwatched => 'Markera som osedd'; @override String get removeFromContinueWatching => 'Ta bort från Fortsätt titta'; + @override String get viewDetails => 'Visa detaljer'; @override String get goToSeries => 'Gå till serie'; - @override String get goToSeason => 'Gå till säsong'; @override String get shufflePlay => 'Blanda uppspelning'; @override String get shuffleNotAvailableOffline => 'Blandad uppspelning är inte tillgänglig offline'; @override String get fileInfo => 'Filinformation'; @@ -673,7 +676,6 @@ class _TranslationsMessagesSv extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => 'Filinformation inte tillgänglig'; @override String errorLoadingFileInfo({required Object error}) => 'Fel vid laddning av filinformation: ${error}'; @override String get errorLoadingSeries => 'Fel vid laddning av serie'; - @override String get errorLoadingSeason => 'Fel vid laddning av säsong'; @override String get musicNotSupported => 'Musikuppspelning stöds inte ännu'; @override String get noDescriptionAvailable => 'Ingen beskrivning tillgänglig'; @override String get noProfilesAvailable => 'Inga profiler tillgängliga'; @@ -2027,6 +2029,9 @@ extension on TranslationsSv { 'settings.gridView' => 'Rutnät', 'settings.listView' => 'Lista', 'settings.showHeroSection' => 'Visa hjältesektion', + 'settings.continueWatchingAction' => 'Åtgärd för Fortsätt titta', + 'settings.continueWatchingPlay' => 'Spela', + 'settings.continueWatchingDetails' => 'Öppna detaljer', 'settings.useGlobalHubs' => 'Använd startlayout', 'settings.useGlobalHubsDescription' => 'Visa enhetliga startsideshubbar. Annars används biblioteksrekommendationer.', 'settings.showServerNameOnHubs' => 'Visa servernamn på hubbar', @@ -2262,8 +2267,8 @@ extension on TranslationsSv { 'mediaMenu.markAsWatched' => 'Markera som sedd', 'mediaMenu.markAsUnwatched' => 'Markera som osedd', 'mediaMenu.removeFromContinueWatching' => 'Ta bort från Fortsätt titta', + 'mediaMenu.viewDetails' => 'Visa detaljer', 'mediaMenu.goToSeries' => 'Gå till serie', - 'mediaMenu.goToSeason' => 'Gå till säsong', 'mediaMenu.shufflePlay' => 'Blanda uppspelning', 'mediaMenu.shuffleNotAvailableOffline' => 'Blandad uppspelning är inte tillgänglig offline', 'mediaMenu.fileInfo' => 'Filinformation', @@ -2395,7 +2400,6 @@ extension on TranslationsSv { 'messages.fileInfoNotAvailable' => 'Filinformation inte tillgänglig', 'messages.errorLoadingFileInfo' => ({required Object error}) => 'Fel vid laddning av filinformation: ${error}', 'messages.errorLoadingSeries' => 'Fel vid laddning av serie', - 'messages.errorLoadingSeason' => 'Fel vid laddning av säsong', 'messages.musicNotSupported' => 'Musikuppspelning stöds inte ännu', 'messages.noDescriptionAvailable' => 'Ingen beskrivning tillgänglig', 'messages.noProfilesAvailable' => 'Inga profiler tillgängliga', @@ -2431,10 +2435,10 @@ extension on TranslationsSv { 'subtitlingStyling.border' => 'Kantlinje', 'subtitlingStyling.background' => 'Bakgrund', 'subtitlingStyling.fontSize' => 'Teckenstorlek', - 'subtitlingStyling.textColor' => 'Textfärg', - 'subtitlingStyling.borderSize' => 'Kantstorlek', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => 'Textfärg', + 'subtitlingStyling.borderSize' => 'Kantstorlek', 'subtitlingStyling.borderColor' => 'Kantfärg', 'subtitlingStyling.backgroundOpacity' => 'Bakgrundsopacitet', 'subtitlingStyling.backgroundColor' => 'Bakgrundsfärg', @@ -2945,10 +2949,10 @@ extension on TranslationsSv { 'companionRemote.remote.menu' => 'Meny', 'companionRemote.remote.tabNavigation' => 'Fliknavigering', 'companionRemote.remote.tabDiscover' => 'Upptäck', - 'companionRemote.remote.tabLibraries' => 'Bibliotek', - 'companionRemote.remote.tabSearch' => 'Sök', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => 'Bibliotek', + 'companionRemote.remote.tabSearch' => 'Sök', 'companionRemote.remote.tabDownloads' => 'Nedladdningar', 'companionRemote.remote.tabSettings' => 'Inställningar', 'companionRemote.remote.previous' => 'Föregående', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index 5b24f72a..df019311 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -245,6 +245,9 @@ class _TranslationsSettingsZh extends TranslationsSettingsEn { @override String get gridView => '网格视图'; @override String get listView => '列表视图'; @override String get showHeroSection => '显示主要精选区'; + @override String get continueWatchingAction => '继续观看操作'; + @override String get continueWatchingPlay => '播放'; + @override String get continueWatchingDetails => '打开详情'; @override String get useGlobalHubs => '使用主页布局'; @override String get useGlobalHubsDescription => '显示统一主页中心。否则使用资料库推荐。'; @override String get showServerNameOnHubs => '在推荐栏显示服务器名称'; @@ -492,8 +495,8 @@ class _TranslationsMediaMenuZh extends TranslationsMediaMenuEn { @override String get markAsWatched => '标记为已观看'; @override String get markAsUnwatched => '标记为未观看'; @override String get removeFromContinueWatching => '从继续观看中移除'; + @override String get viewDetails => '查看详情'; @override String get goToSeries => '转到系列'; - @override String get goToSeason => '转到季'; @override String get shufflePlay => '随机播放'; @override String get shuffleNotAvailableOffline => '离线时无法随机播放'; @override String get fileInfo => '文件信息'; @@ -673,7 +676,6 @@ class _TranslationsMessagesZh extends TranslationsMessagesEn { @override String get fileInfoNotAvailable => '文件信息不可用'; @override String errorLoadingFileInfo({required Object error}) => '加载文件信息时出错: ${error}'; @override String get errorLoadingSeries => '加载系列时出错'; - @override String get errorLoadingSeason => '加载季时出错'; @override String get musicNotSupported => '尚不支持播放音乐'; @override String get noDescriptionAvailable => '暂无描述'; @override String get noProfilesAvailable => '没有可用的用户'; @@ -2027,6 +2029,9 @@ extension on TranslationsZh { 'settings.gridView' => '网格视图', 'settings.listView' => '列表视图', 'settings.showHeroSection' => '显示主要精选区', + 'settings.continueWatchingAction' => '继续观看操作', + 'settings.continueWatchingPlay' => '播放', + 'settings.continueWatchingDetails' => '打开详情', 'settings.useGlobalHubs' => '使用主页布局', 'settings.useGlobalHubsDescription' => '显示统一主页中心。否则使用资料库推荐。', 'settings.showServerNameOnHubs' => '在推荐栏显示服务器名称', @@ -2262,8 +2267,8 @@ extension on TranslationsZh { 'mediaMenu.markAsWatched' => '标记为已观看', 'mediaMenu.markAsUnwatched' => '标记为未观看', 'mediaMenu.removeFromContinueWatching' => '从继续观看中移除', + 'mediaMenu.viewDetails' => '查看详情', 'mediaMenu.goToSeries' => '转到系列', - 'mediaMenu.goToSeason' => '转到季', 'mediaMenu.shufflePlay' => '随机播放', 'mediaMenu.shuffleNotAvailableOffline' => '离线时无法随机播放', 'mediaMenu.fileInfo' => '文件信息', @@ -2395,7 +2400,6 @@ extension on TranslationsZh { 'messages.fileInfoNotAvailable' => '文件信息不可用', 'messages.errorLoadingFileInfo' => ({required Object error}) => '加载文件信息时出错: ${error}', 'messages.errorLoadingSeries' => '加载系列时出错', - 'messages.errorLoadingSeason' => '加载季时出错', 'messages.musicNotSupported' => '尚不支持播放音乐', 'messages.noDescriptionAvailable' => '暂无描述', 'messages.noProfilesAvailable' => '没有可用的用户', @@ -2431,10 +2435,10 @@ extension on TranslationsZh { 'subtitlingStyling.border' => '边框', 'subtitlingStyling.background' => '背景', 'subtitlingStyling.fontSize' => '字号', - 'subtitlingStyling.textColor' => '文本颜色', - 'subtitlingStyling.borderSize' => '边框大小', _ => null, } ?? switch (path) { + 'subtitlingStyling.textColor' => '文本颜色', + 'subtitlingStyling.borderSize' => '边框大小', 'subtitlingStyling.borderColor' => '边框颜色', 'subtitlingStyling.backgroundOpacity' => '背景不透明度', 'subtitlingStyling.backgroundColor' => '背景颜色', @@ -2945,10 +2949,10 @@ extension on TranslationsZh { 'companionRemote.remote.menu' => '菜单', 'companionRemote.remote.tabNavigation' => '标签导航', 'companionRemote.remote.tabDiscover' => '发现', - 'companionRemote.remote.tabLibraries' => '媒体库', - 'companionRemote.remote.tabSearch' => '搜索', _ => null, } ?? switch (path) { + 'companionRemote.remote.tabLibraries' => '媒体库', + 'companionRemote.remote.tabSearch' => '搜索', 'companionRemote.remote.tabDownloads' => '下载', 'companionRemote.remote.tabSettings' => '设置', 'companionRemote.remote.previous' => '上一个', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index 6a026a1c..4c892852 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -116,6 +116,9 @@ "gridView": "Rutnät", "listView": "Lista", "showHeroSection": "Visa hjältesektion", + "continueWatchingAction": "Åtgärd för Fortsätt titta", + "continueWatchingPlay": "Spela", + "continueWatchingDetails": "Öppna detaljer", "useGlobalHubs": "Använd startlayout", "useGlobalHubsDescription": "Visa enhetliga startsideshubbar. Annars används biblioteksrekommendationer.", "showServerNameOnHubs": "Visa servernamn på hubbar", @@ -361,8 +364,8 @@ "markAsWatched": "Markera som sedd", "markAsUnwatched": "Markera som osedd", "removeFromContinueWatching": "Ta bort från Fortsätt titta", + "viewDetails": "Visa detaljer", "goToSeries": "Gå till serie", - "goToSeason": "Gå till säsong", "shufflePlay": "Blanda uppspelning", "shuffleNotAvailableOffline": "Blandad uppspelning är inte tillgänglig offline", "fileInfo": "Filinformation", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "Filinformation inte tillgänglig", "errorLoadingFileInfo": "Fel vid laddning av filinformation: ${error}", "errorLoadingSeries": "Fel vid laddning av serie", - "errorLoadingSeason": "Fel vid laddning av säsong", "musicNotSupported": "Musikuppspelning stöds inte ännu", "noDescriptionAvailable": "Ingen beskrivning tillgänglig", "noProfilesAvailable": "Inga profiler tillgängliga", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 9ed091ee..a9ba5705 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -116,6 +116,9 @@ "gridView": "网格视图", "listView": "列表视图", "showHeroSection": "显示主要精选区", + "continueWatchingAction": "继续观看操作", + "continueWatchingPlay": "播放", + "continueWatchingDetails": "打开详情", "useGlobalHubs": "使用主页布局", "useGlobalHubsDescription": "显示统一主页中心。否则使用资料库推荐。", "showServerNameOnHubs": "在推荐栏显示服务器名称", @@ -361,8 +364,8 @@ "markAsWatched": "标记为已观看", "markAsUnwatched": "标记为未观看", "removeFromContinueWatching": "从继续观看中移除", + "viewDetails": "查看详情", "goToSeries": "转到系列", - "goToSeason": "转到季", "shufflePlay": "随机播放", "shuffleNotAvailableOffline": "离线时无法随机播放", "fileInfo": "文件信息", @@ -508,7 +511,6 @@ "fileInfoNotAvailable": "文件信息不可用", "errorLoadingFileInfo": "加载文件信息时出错: ${error}", "errorLoadingSeries": "加载系列时出错", - "errorLoadingSeason": "加载季时出错", "musicNotSupported": "尚不支持播放音乐", "noDescriptionAvailable": "暂无描述", "noProfilesAvailable": "没有可用的用户", diff --git a/lib/media/episode_collection.dart b/lib/media/episode_collection.dart index e3c8566b..c138842d 100644 --- a/lib/media/episode_collection.dart +++ b/lib/media/episode_collection.dart @@ -99,8 +99,18 @@ MediaItem? firstUnwatchedEpisode(List episodes) { /// episode. With neither, fall back to the first season that still has /// unwatched episodes (so a partially-watched show removed from Continue /// Watching still opens on the right season), then [defaultPlaybackSeasonIndex]. -int preferredSeasonIndex(List seasons, {int? initialSeasonIndex, MediaItem? onDeckEpisode}) { +int preferredSeasonIndex( + List seasons, { + String? initialSeasonId, + int? initialSeasonIndex, + MediaItem? onDeckEpisode, +}) { if (seasons.isEmpty) return 0; + if (initialSeasonId != null) { + final idx = seasons.indexWhere((season) => season.kind == MediaKind.season && season.id == initialSeasonId); + if (idx != -1) return idx; + } + if (initialSeasonIndex != null) { final idx = seasons.indexWhere((season) => season.kind == MediaKind.season && season.index == initialSeasonIndex); if (idx != -1) return idx; diff --git a/lib/screens/discover_screen.dart b/lib/screens/discover_screen.dart index a6c23dd6..0adcb417 100644 --- a/lib/screens/discover_screen.dart +++ b/lib/screens/discover_screen.dart @@ -59,6 +59,7 @@ import '../utils/app_logger.dart'; import '../utils/dialogs.dart'; import '../utils/formatters.dart'; import '../utils/media_hub_ordering.dart'; +import '../utils/media_navigation_helper.dart'; import '../utils/provider_extensions.dart'; import '../utils/video_player_navigation.dart'; import '../utils/layout_constants.dart'; @@ -538,7 +539,7 @@ class _DiscoverScreenState extends State }, onSelect: () { if (_onDeck.isNotEmpty && _currentHeroIndex < _onDeck.length) { - navigateToVideoPlayer(context, metadata: _onDeck[_currentHeroIndex]); + navigateToMediaItem(context, _onDeck[_currentHeroIndex], playDirectly: true); } }, )(node, event); @@ -1819,8 +1820,8 @@ class _DiscoverScreenState extends State child: ClickableCursor( child: GestureDetector( onTap: () { - appLogger.d('Navigating to VideoPlayerScreen for: ${heroItem.title}'); - navigateToVideoPlayer(context, metadata: heroItem); + appLogger.d('Activating hero item: ${heroItem.title}'); + navigateToMediaItem(context, heroItem, playDirectly: true); }, child: Stack( fit: StackFit.expand, diff --git a/lib/screens/media_detail_screen.dart b/lib/screens/media_detail_screen.dart index 280f6868..ff58f702 100644 --- a/lib/screens/media_detail_screen.dart +++ b/lib/screens/media_detail_screen.dart @@ -191,15 +191,36 @@ class MediaDetailScreen extends StatefulWidget { /// If provided, auto-selects this season index when the screen loads. /// Used when navigating to a show from a season context. final int? initialSeasonIndex; + final String? initialSeasonId; + final String? initialEpisodeId; - const MediaDetailScreen({super.key, required this.metadata, this.isOffline = false, this.initialSeasonIndex}); + const MediaDetailScreen({ + super.key, + required this.metadata, + this.isOffline = false, + this.initialSeasonIndex, + this.initialSeasonId, + this.initialEpisodeId, + }); @override State createState() => _MediaDetailScreenState(); } -PageRoute mediaDetailRoute({required MediaItem metadata, bool isOffline = false, int? initialSeasonIndex}) { - final page = MediaDetailScreen(metadata: metadata, isOffline: isOffline, initialSeasonIndex: initialSeasonIndex); +PageRoute mediaDetailRoute({ + required MediaItem metadata, + bool isOffline = false, + int? initialSeasonIndex, + String? initialSeasonId, + String? initialEpisodeId, +}) { + final page = MediaDetailScreen( + metadata: metadata, + isOffline: isOffline, + initialSeasonIndex: initialSeasonIndex, + initialSeasonId: initialSeasonId, + initialEpisodeId: initialEpisodeId, + ); if (!PlatformDetector.isTV()) return MaterialPageRoute(builder: (_) => page); return PageRouteBuilder( @@ -285,8 +306,12 @@ class _MediaDetailScreenState extends State final ScrollController _seasonTabsScrollController = ScrollController(); final FocusNode _firstEpisodeFocusNode = FocusNode(debugLabel: 'first_episode'); final FocusNode _lastEpisodeFocusNode = FocusNode(debugLabel: 'last_episode'); + final FocusNode _initialEpisodeFocusNode = FocusNode(debugLabel: 'initial_episode'); String? _lastEpisodeFocusPinnedKey; bool _suppressNextLastEpisodeFocusLoad = false; + bool _initialDetailFocusApplied = false; + bool _initialEpisodePagingInFlight = false; + bool _initialEpisodePagingDone = false; static const int _episodesPageSize = 200; // Sentinel at the end of the inline episode list; its viewport position drives // lazy paging (the list is shrink-wrapped, so it can't self-detect near-end). @@ -765,6 +790,7 @@ class _MediaDetailScreenState extends State _firstEpisodeFocusNode.dispose(); _lastEpisodeFocusNode.removeListener(_onLastEpisodeFocusChanged); _lastEpisodeFocusNode.dispose(); + _initialEpisodeFocusNode.dispose(); super.dispose(); } @@ -1534,7 +1560,12 @@ class _MediaDetailScreenState extends State /// Find the season index matching the initial selection or on-deck episode, /// then fall back to the same season the Play button would use. int _findOnDeckSeasonIndex(List seasons) { - return preferredSeasonIndex(seasons, initialSeasonIndex: widget.initialSeasonIndex, onDeckEpisode: _onDeckEpisode); + return preferredSeasonIndex( + seasons, + initialSeasonId: widget.initialSeasonId, + initialSeasonIndex: widget.initialSeasonIndex, + onDeckEpisode: _onDeckEpisode, + ); } /// Fetch episodes for a specific season by index, using cache when available @@ -1917,6 +1948,93 @@ class _MediaDetailScreenState extends State scrollContextToCenter(key.currentContext); } + bool get _hasInitialDetailFocusTarget => + widget.initialEpisodeId != null || widget.initialSeasonId != null || widget.initialSeasonIndex != null; + + bool get _episodesContainInitialTarget { + final targetEpisodeId = widget.initialEpisodeId; + return targetEpisodeId != null && _episodes.any((episode) => episode.id == targetEpisodeId); + } + + void _scheduleInitialMobileDetailFocus(MediaItem metadata) { + if (_initialDetailFocusApplied || PlatformDetector.isTV() || !_hasInitialDetailFocusTarget) return; + + if (widget.initialEpisodeId != null) { + if (_episodesContainInitialTarget) { + _applyInitialMobileFocus(_initialTargetEpisodeFocusNode(), _seasonsSectionKey); + return; + } + _maybeLoadMoreForInitialEpisode(); + // Target never materialized (stale/deleted id) — fall back to the + // season anchors below instead of retrying forever. + if (!_initialEpisodePagingDone) return; + } + + if (metadata.isShow && + !_showEpisodesDirectly && + _seasons.isNotEmpty && + _seasonTabFocusNodes.length > _selectedSeasonIndex) { + _applyInitialMobileFocus(_seasonTabFocusNodes[_selectedSeasonIndex], _seasonsSectionKey); + return; + } + + if (((metadata.isShow && _showEpisodesDirectly) || metadata.isSeason) && _episodes.isNotEmpty) { + _applyInitialMobileFocus(_firstEpisodeFocusNode, _seasonsSectionKey); + } + } + + void _applyInitialMobileFocus(FocusNode node, GlobalKey sectionKey) { + _initialDetailFocusApplied = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted) return; + node.requestFocus(); + _scrollSectionIntoView(sectionKey); + }); + } + + void _maybeLoadMoreForInitialEpisode() { + if (_initialEpisodePagingDone || _initialEpisodePagingInFlight) return; + if (_episodesContainInitialTarget) { + _initialEpisodePagingDone = true; + return; + } + + final hasMore = _isFlattenEpisodeList ? _allEpisodes.hasMore : _selectedSeasonHasMore; + final pageError = _isFlattenEpisodeList ? _allEpisodesPageError : _seasonEpisodesPageError; + if (!hasMore || pageError) { + // All pages loaded (or paging failed) without the target appearing. + _initialEpisodePagingDone = true; + return; + } + if (_isFlattenEpisodeList ? _isLoadingAllEpisodes : _isLoadingMoreSeasonEpisodes) return; + + _initialEpisodePagingInFlight = true; + WidgetsBinding.instance.addPostFrameCallback((_) { + if (!mounted || _episodesContainInitialTarget) { + _initialEpisodePagingInFlight = false; + return; + } + + Future? load; + if (_isFlattenEpisodeList) { + if (_allEpisodes.hasMore && !_isLoadingAllEpisodes && !_allEpisodesPageError) { + load = _loadMoreAllEpisodes(); + } + } else if (_selectedSeasonHasMore && !_isLoadingMoreSeasonEpisodes && !_seasonEpisodesPageError) { + load = _loadMoreSeasonEpisodes(); + } + + if (load == null) { + _initialEpisodePagingInFlight = false; + return; + } + + // The loaders setState on success and failure, so the resulting rebuild + // re-enters this method for the next page (or marks paging done). + unawaited(load.whenComplete(() => _initialEpisodePagingInFlight = false)); + }); + } + /// Focus the first available section above the primary action row. void _focusAboveActionRow() { if (PlatformDetector.isTV()) return; @@ -2431,9 +2549,7 @@ class _MediaDetailScreenState extends State /// Build episode list directly when the library hides seasons for single-season shows Widget _buildEpisodesList() { final client = _getMediaClientForMetadata(context); - final pinnedLastEpisodeKey = _lastEpisodeFocusPinnedKey; - final hasPinnedLastEpisode = - pinnedLastEpisodeKey != null && _episodes.any((episode) => episode.globalKey == pinnedLastEpisodeKey); + final hasPinnedLastEpisode = _hasPinnedLastEpisodeInList; return ListView.builder( shrinkWrap: true, physics: const NeverScrollableScrollPhysics(), @@ -2454,15 +2570,7 @@ class _MediaDetailScreenState extends State client: client, isOffline: widget.isOffline, autofocus: false, - focusNode: index == 0 - ? _firstEpisodeFocusNode - : _shouldUseLastEpisodeFocusNode( - episode: episode, - index: index, - hasPinnedLastEpisode: hasPinnedLastEpisode, - ) - ? _lastEpisodeFocusNode - : null, + focusNode: _episodeListFocusNode(episode: episode, index: index, hasPinnedLastEpisode: hasPinnedLastEpisode), onNavigateUp: index == 0 ? () { if (!_showEpisodesDirectly) { @@ -2510,6 +2618,39 @@ class _MediaDetailScreenState extends State return index == _episodes.length - 1; } + bool get _hasPinnedLastEpisodeInList { + final pinnedKey = _lastEpisodeFocusPinnedKey; + return pinnedKey != null && _episodes.any((episode) => episode.globalKey == pinnedKey); + } + + /// Resolve the focus node for an inline episode list item. Role nodes + /// (first/last) take priority so their consumers always find them attached; + /// the initial navigation target only claims a node when no role applies. + FocusNode? _episodeListFocusNode({ + required MediaItem episode, + required int index, + required bool hasPinnedLastEpisode, + }) { + if (index == 0) return _firstEpisodeFocusNode; + if (_shouldUseLastEpisodeFocusNode(episode: episode, index: index, hasPinnedLastEpisode: hasPinnedLastEpisode)) { + return _lastEpisodeFocusNode; + } + if (widget.initialEpisodeId == episode.id) return _initialEpisodeFocusNode; + return null; + } + + /// The node the initial target episode's card actually holds. + FocusNode _initialTargetEpisodeFocusNode() { + final index = _episodes.indexWhere((episode) => episode.id == widget.initialEpisodeId); + if (index == -1) return _initialEpisodeFocusNode; + return _episodeListFocusNode( + episode: _episodes[index], + index: index, + hasPinnedLastEpisode: _hasPinnedLastEpisodeInList, + ) ?? + _initialEpisodeFocusNode; + } + /// Tail of the inline episode list: a retry tile when a page failed, otherwise /// a spinner. Carries [_episodeTailKey] so the scroll listener can detect when /// it nears the viewport and page the next batch in. @@ -2878,6 +3019,8 @@ class _MediaDetailScreenState extends State return _buildTvDetailScreen(context, metadata, _handleMediaDetailBackKey); } + _scheduleInitialMobileDetailFocus(metadata); + final blockSystemBack = Platform.isAndroid && InputModeTracker.isKeyboardMode(context); final content = PrimaryScrollController( controller: _scrollController, @@ -3115,6 +3258,9 @@ class _MediaDetailScreenState extends State ) { final size = MediaQuery.sizeOf(context); final detailHubs = _tvDetailHubs(metadata); + if (widget.initialEpisodeId != null && !_initialEpisodePagingDone) { + _maybeLoadMoreForInitialEpisode(); + } final hideSpoilers = SettingsService.instance.read(SettingsService.hideSpoilers); final detailScale = TvLayoutConstants.scaleForSize(size); final spotlightTop = (size.height * 0.08).clamp(44.0 * detailScale, 110.0 * detailScale).toDouble(); @@ -3601,6 +3747,7 @@ class _MediaDetailScreenState extends State } String? _tvDetailInitialItemId(MediaItem metadata) { + if (widget.initialEpisodeId != null) return widget.initialEpisodeId; if (!metadata.isShow) return null; return _onDeckEpisode?.id; } diff --git a/lib/screens/settings/appearance_settings_screen.dart b/lib/screens/settings/appearance_settings_screen.dart index cded048b..4302c139 100644 --- a/lib/screens/settings/appearance_settings_screen.dart +++ b/lib/screens/settings/appearance_settings_screen.dart @@ -63,6 +63,7 @@ class AppearanceSettingsScreen extends StatelessWidget { title: t.settings.showHeroSection, subtitle: t.settings.showHeroSectionDescription, ), + _continueWatchingActionSelector(), SettingSwitchTile( pref: SettingsService.useGlobalHubs, icon: Symbols.home_rounded, @@ -252,6 +253,18 @@ class AppearanceSettingsScreen extends StatelessWidget { encode: (v) => v, ); + Widget _continueWatchingActionSelector() => SettingSegmentedTile( + pref: SettingsService.continueWatchingAction, + icon: Symbols.play_circle_rounded, + title: t.settings.continueWatchingAction, + segments: [ + ButtonSegment(value: ContinueWatchingAction.play, label: Text(t.settings.continueWatchingPlay)), + ButtonSegment(value: ContinueWatchingAction.details, label: Text(t.settings.continueWatchingDetails)), + ], + decode: (v) => v, + encode: (v) => v, + ); + // Sections offered as a startup destination, in display order. Live TV is // always listed; if no server provides it, startup falls back to Home. static const _startupSectionOptions = [ diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index 8964b65a..3a084133 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -35,6 +35,8 @@ enum ViewMode { grid, list } enum EpisodePosterMode { seriesPoster, seasonPoster, episodeThumbnail } +enum ContinueWatchingAction { play, details } + enum SubAssOverride { no, yes, scale, force, strip } enum DvConversionModePreference { auto, disabled, dv81, hevcStrip } @@ -405,6 +407,11 @@ class SettingsService extends BaseSharedPreferencesService { static const bufferSize = _BufferSizePref(); static const libraryDensity = _LibraryDensityPref(); static const episodePosterMode = _EpisodePosterModePref(); + static const continueWatchingAction = EnumPref( + 'continue_watching_action', + values: ContinueWatchingAction.values, + defaultValue: ContinueWatchingAction.play, + ); static const mpvConfigText = _MpvConfigTextPref(); static final keyboardShortcuts = JsonPref>( @@ -701,6 +708,7 @@ class SettingsService extends BaseSharedPreferencesService { preferredAudioCodec, viewMode, showHeroSection, + continueWatchingAction, seekTimeSmall, seekTimeLarge, sleepTimerDuration, diff --git a/lib/utils/media_navigation_helper.dart b/lib/utils/media_navigation_helper.dart index ba35b3d1..588275a4 100644 --- a/lib/utils/media_navigation_helper.dart +++ b/lib/utils/media_navigation_helper.dart @@ -1,12 +1,15 @@ import 'package:flutter/material.dart'; +import '../i18n/strings.g.dart'; import '../media/ids.dart'; import '../media/media_item.dart'; +import '../media/media_item_types.dart'; import '../media/media_kind.dart'; import '../media/media_playlist.dart'; import '../screens/collection_detail_screen.dart'; import '../screens/main_screen.dart'; import '../screens/media_detail_screen.dart'; import '../screens/playlist/playlist_detail_screen.dart'; +import '../services/settings_service.dart'; import '../utils/global_key_utils.dart'; import 'plex_library_section_helpers.dart'; import 'video_player_navigation.dart'; @@ -26,14 +29,98 @@ enum MediaNavigationResult { librarySelected, } +class MediaDetailNavigationTarget { + final MediaItem metadata; + final int? initialSeasonIndex; + final String? initialSeasonId; + final String? initialEpisodeId; + + const MediaDetailNavigationTarget({ + required this.metadata, + this.initialSeasonIndex, + this.initialSeasonId, + this.initialEpisodeId, + }); +} + +MediaDetailNavigationTarget mediaDetailNavigationTargetFor(MediaItem item, {MediaItem? metadataOverride}) { + if (item.isEpisode && item.grandparentId != null) { + return MediaDetailNavigationTarget( + metadata: + metadataOverride ?? + MediaItem( + id: item.grandparentId!, + backend: item.backend, + kind: MediaKind.show, + title: item.grandparentTitle ?? item.displayTitle, + thumbPath: item.grandparentThumbPath, + artPath: item.grandparentArtPath, + libraryId: item.libraryId, + libraryTitle: item.libraryTitle, + serverId: item.serverId, + serverName: item.serverName, + ), + initialSeasonIndex: item.parentIndex, + initialSeasonId: item.parentId, + initialEpisodeId: item.id, + ); + } + + if (item.isEpisode && item.parentId != null) { + return MediaDetailNavigationTarget( + metadata: + metadataOverride ?? + MediaItem( + id: item.parentId!, + backend: item.backend, + kind: MediaKind.season, + title: item.parentTitle ?? t.common.seasonNumber(number: item.parentIndex ?? ''), + index: item.parentIndex, + thumbPath: item.parentThumbPath, + parentId: item.grandparentId, + libraryId: item.libraryId, + libraryTitle: item.libraryTitle, + serverId: item.serverId, + serverName: item.serverName, + ), + initialEpisodeId: item.id, + ); + } + + if (item.isSeason && item.parentId != null) { + return MediaDetailNavigationTarget( + metadata: + metadataOverride ?? + MediaItem( + id: item.parentId!, + backend: item.backend, + kind: MediaKind.show, + title: item.grandparentTitle ?? item.parentTitle ?? item.displayTitle, + thumbPath: item.grandparentThumbPath ?? item.parentThumbPath, + artPath: item.grandparentArtPath, + libraryId: item.libraryId, + libraryTitle: item.libraryTitle, + serverId: item.serverId, + serverName: item.serverName, + ), + initialSeasonIndex: item.index, + initialSeasonId: item.id, + ); + } + + return MediaDetailNavigationTarget(metadata: metadataOverride ?? item); +} + /// Navigates to the appropriate screen based on the item type. /// /// Accepts a [MediaItem] or a [MediaPlaylist] (typed as [Object] because Dart /// has no nominal union type). /// -/// For episodes, starts playback directly via video player. -/// For movies, starts playback directly if [playDirectly] is true, otherwise -/// navigates to media detail screen. +/// For episodes, starts playback directly via video player unless [playDirectly] +/// is paired with the Continue Watching details setting. +/// For movies, starts playback directly if [playDirectly] is true and the +/// Continue Watching details setting is disabled; otherwise navigates to media +/// detail screen. /// For seasons, navigates to season detail screen. /// For playlists, navigates to playlist detail screen. /// For collections, navigates to collection detail screen. @@ -45,7 +132,9 @@ enum MediaNavigationResult { /// /// Set [isOffline] to true for downloaded content without server access. /// -/// Set [playDirectly] to true to play movies immediately (e.g., from continue watching). +/// Set [playDirectly] to true for Continue Watching activation; the user's +/// Continue Watching action setting decides whether resumable movies/episodes +/// play immediately or open details first. /// /// Returns a [MediaNavigationResult] indicating what action was taken: /// - [MediaNavigationResult.navigated]: Navigation completed, item refresh handled @@ -67,6 +156,10 @@ Future navigateToMediaItem( return MediaNavigationResult.unsupported; } final mi = item; + final shouldOpenContinueWatchingDetails = + playDirectly && + (SettingsService.instanceOrNull?.read(SettingsService.continueWatchingAction) ?? ContinueWatchingAction.play) == + ContinueWatchingAction.details; // Handle library section items (shared whole-library entries) — Plex-only; // [PlexLibrarySection.isLibrarySection] reads the stashed `key` from `raw`. @@ -100,6 +193,9 @@ Future navigateToMediaItem( case MediaKind.clip: case MediaKind.episode: + if (shouldOpenContinueWatchingDetails && mi.kind == MediaKind.episode) { + return navigateToMediaItemDetails(context, mi, onRefresh: onRefresh, isOffline: isOffline); + } final result = await navigateToVideoPlayer(context, metadata: mi, isOffline: isOffline); if (result == true && context.mounted) { onRefresh?.call(mi.id); @@ -107,52 +203,41 @@ Future navigateToMediaItem( return MediaNavigationResult.navigated; case MediaKind.movie: - if (playDirectly) { + if (playDirectly && !shouldOpenContinueWatchingDetails) { final result = await navigateToVideoPlayer(context, metadata: mi, isOffline: isOffline); if (result == true && context.mounted) { onRefresh?.call(mi.id); } return MediaNavigationResult.navigated; } - return _showDetail(context, mi, isOffline, onRefresh); + return navigateToMediaItemDetails(context, mi, isOffline: isOffline, onRefresh: onRefresh); case MediaKind.season: - if (mi.parentId != null) { - final showStub = MediaItem( - id: mi.parentId!, - backend: mi.backend, - kind: MediaKind.show, - title: mi.grandparentTitle ?? mi.parentTitle ?? mi.displayTitle, - thumbPath: mi.grandparentThumbPath ?? mi.parentThumbPath, - artPath: mi.grandparentArtPath, - libraryId: mi.libraryId, - libraryTitle: mi.libraryTitle, - serverId: mi.serverId, - serverName: mi.serverName, - ); - final result = await Navigator.push( - context, - mediaDetailRoute(metadata: showStub, isOffline: isOffline, initialSeasonIndex: mi.index), - ); - if (result == true && context.mounted) { - onRefresh?.call(mi.id); - } - return MediaNavigationResult.navigated; - } - return _showDetail(context, mi, isOffline, onRefresh); + return navigateToMediaItemDetails(context, mi, isOffline: isOffline, onRefresh: onRefresh); default: - return _showDetail(context, mi, isOffline, onRefresh); + return navigateToMediaItemDetails(context, mi, isOffline: isOffline, onRefresh: onRefresh); } } -Future _showDetail( +Future navigateToMediaItemDetails( BuildContext context, - MediaItem mi, - bool isOffline, + MediaItem mi, { + bool isOffline = false, void Function(String)? onRefresh, -) async { - final result = await Navigator.push(context, mediaDetailRoute(metadata: mi, isOffline: isOffline)); + MediaItem? metadataOverride, +}) async { + final target = mediaDetailNavigationTargetFor(mi, metadataOverride: metadataOverride); + final result = await Navigator.push( + context, + mediaDetailRoute( + metadata: target.metadata, + isOffline: isOffline, + initialSeasonIndex: target.initialSeasonIndex, + initialSeasonId: target.initialSeasonId, + initialEpisodeId: target.initialEpisodeId, + ), + ); if (result == true && context.mounted) { onRefresh?.call(mi.id); } diff --git a/lib/widgets/media_card.dart b/lib/widgets/media_card.dart index 705831a7..45d23a67 100644 --- a/lib/widgets/media_card.dart +++ b/lib/widgets/media_card.dart @@ -17,7 +17,6 @@ import '../providers/watch_state_store.dart'; import '../services/download_storage_service.dart'; import '../services/settings_service.dart'; import 'settings_builder.dart'; -import '../screens/media_detail_screen.dart'; import '../utils/content_utils.dart'; import '../utils/provider_extensions.dart'; import '../utils/formatters.dart'; @@ -382,7 +381,7 @@ class MediaCardState extends State with ContextMenuTapMixin _navigateToDetail(context, item, isOffline: widget.isOffline), + onTap: () => _navigateToFocusedDetail(context, item, isOffline: widget.isOffline), ) else Text( @@ -566,7 +565,7 @@ class _MediaCardList extends StatelessWidget { _ClickableText( text: 'S${mi.parentIndex}', style: style, - onTap: () => _navigateToSeason(context, mi, isOffline: isOffline), + onTap: () => _navigateToFocusedDetail(context, mi, isOffline: isOffline), ), Text('$episodeNum · ', style: style), Expanded( @@ -624,7 +623,7 @@ class _MediaCardList extends StatelessWidget { _ClickableText( text: (item as MediaItem).displayTitle, style: TextStyle(fontWeight: .w600, fontSize: _titleFontSize, height: 1.2), - onTap: () => _navigateToDetail(context, item as MediaItem, isOffline: isOffline), + onTap: () => _navigateToFocusedDetail(context, item as MediaItem, isOffline: isOffline), ) else Text( @@ -859,7 +858,7 @@ class _MediaCardHelpers { _ClickableText( text: 'S${mi.parentIndex}', style: subtitleStyle, - onTap: () => _navigateToSeason(context, mi, isOffline: isOffline), + onTap: () => _navigateToFocusedDetail(context, mi, isOffline: isOffline), ), Text('$episodeSuffix · ', style: subtitleStyle), Expanded( @@ -981,83 +980,8 @@ bool _hasClickableTitle(MediaItem mi) { return false; } -void _navigateToSeason(BuildContext context, MediaItem episode, {bool isOffline = false}) { - if (episode.grandparentId != null) { - final showStub = MediaItem( - id: episode.grandparentId!, - backend: episode.backend, - kind: MediaKind.show, - title: episode.grandparentTitle ?? episode.displayTitle, - thumbPath: episode.grandparentThumbPath, - artPath: episode.grandparentArtPath, - libraryId: episode.libraryId, - libraryTitle: episode.libraryTitle, - serverId: episode.serverId, - serverName: episode.serverName, - ); - Navigator.push( - context, - mediaDetailRoute(metadata: showStub, isOffline: isOffline, initialSeasonIndex: episode.parentIndex), - ); - } else if (episode.parentId != null) { - // Fallback: navigate to season directly if no grandparent - final seasonStub = MediaItem( - id: episode.parentId!, - backend: episode.backend, - kind: MediaKind.season, - title: episode.parentTitle ?? t.common.seasonNumber(number: episode.parentIndex ?? ''), - index: episode.parentIndex, - parentId: episode.grandparentId, - thumbPath: episode.parentThumbPath, - libraryId: episode.libraryId, - libraryTitle: episode.libraryTitle, - serverId: episode.serverId, - serverName: episode.serverName, - ); - Navigator.push(context, mediaDetailRoute(metadata: seasonStub, isOffline: isOffline)); - } -} - -/// Navigate to the detail screen for a media item. -/// For episodes/seasons: navigates to the parent show with season pre-selected. -/// For movies and other types: navigates to the item's own detail page. -void _navigateToDetail(BuildContext context, MediaItem mi, {bool isOffline = false}) { - MediaItem target = mi; - int? initialSeasonIndex; - - if (mi.isEpisode && mi.grandparentId != null) { - target = MediaItem( - id: mi.grandparentId!, - backend: mi.backend, - kind: MediaKind.show, - title: mi.grandparentTitle ?? mi.displayTitle, - thumbPath: mi.grandparentThumbPath, - artPath: mi.grandparentArtPath, - libraryId: mi.libraryId, - libraryTitle: mi.libraryTitle, - serverId: mi.serverId, - serverName: mi.serverName, - ); - } else if (mi.isSeason && mi.parentId != null) { - initialSeasonIndex = mi.index; - target = MediaItem( - id: mi.parentId!, - backend: mi.backend, - kind: MediaKind.show, - title: mi.grandparentTitle ?? mi.parentTitle ?? mi.displayTitle, - thumbPath: mi.grandparentThumbPath ?? mi.parentThumbPath, - artPath: mi.grandparentArtPath, - libraryId: mi.libraryId, - libraryTitle: mi.libraryTitle, - serverId: mi.serverId, - serverName: mi.serverName, - ); - } - - Navigator.push( - context, - mediaDetailRoute(metadata: target, isOffline: isOffline, initialSeasonIndex: initialSeasonIndex), - ); +void _navigateToFocusedDetail(BuildContext context, MediaItem item, {bool isOffline = false}) { + navigateToMediaItemDetails(context, item, isOffline: isOffline); } /// Text widget that shows hover underline + pointer cursor only in pointer mode. diff --git a/lib/widgets/media_context_menu.dart b/lib/widgets/media_context_menu.dart index 86c7a657..3e5eca24 100644 --- a/lib/widgets/media_context_menu.dart +++ b/lib/widgets/media_context_menu.dart @@ -9,6 +9,7 @@ import 'package:provider/provider.dart'; import '../providers/watch_state_store.dart'; import '../media/media_backend.dart'; import '../media/media_item.dart'; +import '../media/media_item_types.dart'; import '../media/media_kind.dart'; import '../media/media_playlist.dart'; import '../media/media_server_client.dart'; @@ -35,6 +36,7 @@ import '../profiles/profile.dart'; import '../utils/provider_extensions.dart'; import '../utils/app_logger.dart'; import '../utils/library_refresh_notifier.dart'; +import '../utils/media_navigation_helper.dart'; import '../utils/media_server_http_client.dart'; import '../utils/platform_detector.dart'; import '../utils/snackbar_helper.dart'; @@ -311,10 +313,13 @@ class MediaContextMenuState extends State { ); } - if (mediaKind == MediaKind.movie || - mediaKind == MediaKind.show || - mediaKind == MediaKind.season || - mediaKind == MediaKind.episode) { + final isVideoKind = mediaItem.isVideoContent; + + if (widget.isInContinueWatching && isVideoKind) { + menuActions.add(_MenuAction(value: 'details', icon: Symbols.info_rounded, label: t.mediaMenu.viewDetails)); + } + + if (isVideoKind) { menuActions.add(_MenuAction(value: 'rate', icon: Symbols.star_rounded, label: t.mediaMenu.rate)); } @@ -363,19 +368,11 @@ class MediaContextMenuState extends State { final itemSeriesKey = mediaKind == MediaKind.episode ? mediaItem.grandparentId : mediaItem.parentId; if ((mediaKind == MediaKind.episode || mediaKind == MediaKind.season) && itemSeriesKey != null && + !widget.isInContinueWatching && ancestorSeriesKey != itemSeriesKey) { menuActions.add(_MenuAction(value: 'series', icon: Symbols.tv_rounded, label: t.mediaMenu.goToSeries)); } - // Go to Season (for episodes) — hide if already viewing that season's MediaDetailScreen - if (mediaKind == MediaKind.episode && - mediaItem.parentTitle != null && - !(ancestorMeta != null && ancestorMeta.kind == MediaKind.season && ancestorMeta.id == mediaItem.parentId)) { - menuActions.add( - _MenuAction(value: 'season', icon: Symbols.playlist_play_rounded, label: t.mediaMenu.goToSeason), - ); - } - if (mediaKind == MediaKind.show || mediaKind == MediaKind.season) { menuActions.add( _MenuAction(value: 'shuffle_play', icon: Symbols.shuffle_rounded, label: t.mediaMenu.shufflePlay), @@ -615,6 +612,13 @@ class MediaContextMenuState extends State { } break; + case 'details': + didNavigate = true; + if (context.mounted) { + await navigateToMediaItemDetails(context, mediaItem!, onRefresh: _notifyRefresh); + } + break; + case 'rate': if (context.mounted) { try { @@ -659,24 +663,19 @@ class MediaContextMenuState extends State { await _navigateToRelated( context, mediaItem!.kind == MediaKind.season ? mediaItem.parentId : mediaItem.grandparentId, - (item) => mediaDetailRoute(metadata: item), + (item) { + final target = mediaDetailNavigationTargetFor(mediaItem, metadataOverride: item); + return mediaDetailRoute( + metadata: target.metadata, + initialSeasonIndex: target.initialSeasonIndex, + initialSeasonId: target.initialSeasonId, + initialEpisodeId: target.initialEpisodeId, + ); + }, t.messages.errorLoadingSeries, ); break; - case 'season': - didNavigate = true; - // Navigate to the show with the season tab pre-selected - final seasonParentKey = mediaItem!.kind == MediaKind.episode ? mediaItem.grandparentId : mediaItem.parentId; - final seasonIndex = mediaItem.parentIndex; - await _navigateToRelated( - context, - seasonParentKey, - (show) => mediaDetailRoute(metadata: show, initialSeasonIndex: seasonIndex), - t.messages.errorLoadingSeason, - ); - break; - case 'play_version': didNavigate = await _handlePlayVersion(context); break; diff --git a/test/screens/media_detail_screen_test.dart b/test/screens/media_detail_screen_test.dart index 972ddcac..c6dbfe01 100644 --- a/test/screens/media_detail_screen_test.dart +++ b/test/screens/media_detail_screen_test.dart @@ -566,7 +566,14 @@ void main() { serverName: show.serverName, ); - Future pumpPhoneDetail(WidgetTester tester, _FakeMediaServerClient client, MediaItem show) async { + Future pumpPhoneDetail( + WidgetTester tester, + _FakeMediaServerClient client, + MediaItem show, { + String? initialSeasonId, + int? initialSeasonIndex, + String? initialEpisodeId, + }) async { TvDetectionService.debugSetAppleTVOverride(false); await SettingsService.getInstance(); tester.view.physicalSize = const Size(1100, 2400); @@ -604,7 +611,12 @@ void main() { ], child: MaterialApp( theme: monoTheme(dark: true), - home: MediaDetailScreen(metadata: show), + home: MediaDetailScreen( + metadata: show, + initialSeasonId: initialSeasonId, + initialSeasonIndex: initialSeasonIndex, + initialEpisodeId: initialEpisodeId, + ), ), ), ), @@ -637,6 +649,87 @@ void main() { await tester.pump(const Duration(milliseconds: 100)); } + testWidgets('phone detail focuses requested season tab', (tester) async { + final show = buildShow(); + final season1 = buildSeason(show, 1); + final season2 = buildSeason(show, 2); + final client = _FakeMediaServerClient( + show: show, + childrenByParent: { + show.id: [season1, season2], + season1.id: [buildEpisode(show, season1, 1)], + season2.id: [buildEpisode(show, season2, 1)], + }, + ); + + await pumpPhoneDetail(tester, client, show, initialSeasonId: season2.id); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 200)); + + expect(find.text('Episode S2E1'), findsOneWidget); + expect(FocusManager.instance.primaryFocus?.debugLabel, 'season_tab_1'); + }); + + testWidgets('phone detail focuses requested episode row', (tester) async { + final show = buildShow(); + final season1 = buildSeason(show, 1); + final season2 = buildSeason(show, 2); + final episode2 = buildEpisode(show, season2, 2); + final client = _FakeMediaServerClient( + show: show, + childrenByParent: { + show.id: [season1, season2], + season1.id: [buildEpisode(show, season1, 1)], + season2.id: [buildEpisode(show, season2, 1), episode2, buildEpisode(show, season2, 3)], + }, + ); + + await pumpPhoneDetail( + tester, + client, + show, + initialSeasonId: season2.id, + initialSeasonIndex: season2.index, + initialEpisodeId: episode2.id, + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 200)); + + expect(find.text('Episode S2E2'), findsOneWidget); + expect(FocusManager.instance.primaryFocus?.debugLabel, 'initial_episode'); + }); + + testWidgets('phone detail keeps the first-episode role node when it is the target', (tester) async { + final show = buildShow(); + final season1 = buildSeason(show, 1); + final season2 = buildSeason(show, 2); + final episode1 = buildEpisode(show, season2, 1); + final client = _FakeMediaServerClient( + show: show, + childrenByParent: { + show.id: [season1, season2], + season1.id: [buildEpisode(show, season1, 1)], + season2.id: [episode1, buildEpisode(show, season2, 2), buildEpisode(show, season2, 3)], + }, + ); + + await pumpPhoneDetail( + tester, + client, + show, + initialSeasonId: season2.id, + initialSeasonIndex: season2.index, + initialEpisodeId: episode1.id, + ); + await tester.pump(); + await tester.pump(const Duration(milliseconds: 200)); + + // The first row keeps _firstEpisodeFocusNode (so season-tab DOWN keeps + // working) and the initial focus lands on that node instead. + expect(find.text('Episode S2E1'), findsOneWidget); + expect(FocusManager.instance.primaryFocus?.debugLabel, 'first_episode'); + }); + testWidgets('marking the show watched flips every visible episode row', (tester) async { final show = buildShow(); final season1 = buildSeason(show, 1); diff --git a/test/utils/episode_collection_test.dart b/test/utils/episode_collection_test.dart index 6a9888cf..f079bf0f 100644 --- a/test/utils/episode_collection_test.dart +++ b/test/utils/episode_collection_test.dart @@ -92,6 +92,7 @@ void main() { final season1 = _season('season-1'); final season2 = _season('season-2', index: 2); + expect(preferredSeasonIndex([special, season1, season2], initialSeasonId: season2.id, initialSeasonIndex: 1), 2); expect(preferredSeasonIndex([special, season1, season2], initialSeasonIndex: 2), 2); expect( preferredSeasonIndex([special, season1, season2], onDeckEpisode: _episode('episode-2', parentId: season2.id)), diff --git a/test/utils/media_navigation_helper_test.dart b/test/utils/media_navigation_helper_test.dart new file mode 100644 index 00000000..9ad51578 --- /dev/null +++ b/test/utils/media_navigation_helper_test.dart @@ -0,0 +1,60 @@ +import 'package:flutter_test/flutter_test.dart'; +import 'package:plezy/media/media_backend.dart'; +import 'package:plezy/media/media_item.dart'; +import 'package:plezy/media/media_kind.dart'; +import 'package:plezy/utils/media_navigation_helper.dart'; + +void main() { + test('episode detail target opens parent show and focuses season episode', () { + final episode = MediaItem( + id: 'episode-1', + backend: MediaBackend.plex, + kind: MediaKind.episode, + title: 'Episode 1', + parentId: 'season-2', + parentIndex: 2, + grandparentId: 'show-1', + grandparentTitle: 'The Show', + serverId: 'server-1', + ); + + final target = mediaDetailNavigationTargetFor(episode); + + expect(target.metadata.id, 'show-1'); + expect(target.metadata.kind, MediaKind.show); + expect(target.initialSeasonId, 'season-2'); + expect(target.initialSeasonIndex, 2); + expect(target.initialEpisodeId, 'episode-1'); + }); + + test('season detail target opens parent show and focuses season', () { + final season = MediaItem( + id: 'season-3', + backend: MediaBackend.jellyfin, + kind: MediaKind.season, + title: 'Season 3', + index: 3, + parentId: 'show-1', + parentTitle: 'The Show', + serverId: 'server-1', + ); + + final target = mediaDetailNavigationTargetFor(season); + + expect(target.metadata.id, 'show-1'); + expect(target.metadata.kind, MediaKind.show); + expect(target.initialSeasonId, 'season-3'); + expect(target.initialSeasonIndex, 3); + expect(target.initialEpisodeId, isNull); + }); + + test('movie detail target keeps the movie itself', () { + final movie = MediaItem(id: 'movie-1', backend: MediaBackend.plex, kind: MediaKind.movie, title: 'Movie'); + + final target = mediaDetailNavigationTargetFor(movie); + + expect(target.metadata, same(movie)); + expect(target.initialSeasonId, isNull); + expect(target.initialEpisodeId, isNull); + }); +}