From 91d191b3d8a04ff8a1c92e791c35dd704ae0d4ac Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Sat, 11 Apr 2026 08:03:58 +0200 Subject: [PATCH] fix: file info bitrate units, multi-track display, tv scrolling close #840, close #841 --- lib/i18n/da.i18n.json | 2 + lib/i18n/de.i18n.json | 2 + lib/i18n/en.i18n.json | 2 + lib/i18n/es.i18n.json | 2 + lib/i18n/fr.i18n.json | 2 + lib/i18n/it.i18n.json | 2 + lib/i18n/ja.i18n.json | 2 + lib/i18n/ko.i18n.json | 2 + lib/i18n/nb.i18n.json | 2 + lib/i18n/nl.i18n.json | 2 + lib/i18n/pl.i18n.json | 2 + lib/i18n/pt.i18n.json | 2 + lib/i18n/ru.i18n.json | 2 + lib/i18n/strings.g.dart | 4 +- lib/i18n/strings_da.g.dart | 8 +- lib/i18n/strings_de.g.dart | 8 +- lib/i18n/strings_en.g.dart | 12 ++- lib/i18n/strings_es.g.dart | 8 +- lib/i18n/strings_fr.g.dart | 8 +- lib/i18n/strings_it.g.dart | 8 +- lib/i18n/strings_ja.g.dart | 8 +- lib/i18n/strings_ko.g.dart | 8 +- lib/i18n/strings_nb.g.dart | 8 +- lib/i18n/strings_nl.g.dart | 8 +- lib/i18n/strings_pl.g.dart | 8 +- lib/i18n/strings_pt.g.dart | 8 +- lib/i18n/strings_ru.g.dart | 8 +- lib/i18n/strings_sv.g.dart | 8 +- lib/i18n/strings_zh.g.dart | 8 +- lib/i18n/sv.i18n.json | 2 + lib/i18n/zh.i18n.json | 2 + lib/models/plex_file_info.dart | 19 ++++- lib/services/plex_client.dart | 8 +- lib/widgets/file_info_bottom_sheet.dart | 100 +++++++++++++++++++----- 34 files changed, 230 insertions(+), 55 deletions(-) diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index b1357c97..15e16042 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Farveprimærer", "chromaSubsampling": "Chroma-subsampling", "channels": "Kanaler", + "subtitles": "Undertekster", + "overallBitrate": "Samlet bitrate", "path": "Sti", "size": "Størrelse", "container": "Container", diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index d9e50d8d..f5b0177f 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Primärfarben", "chromaSubsampling": "Chroma-Subsampling", "channels": "Kanäle", + "subtitles": "Untertitel", + "overallBitrate": "Gesamt-Bitrate", "path": "Pfad", "size": "Größe", "container": "Container", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 4d9d5129..977ddd34 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Color Primaries", "chromaSubsampling": "Chroma Subsampling", "channels": "Channels", + "subtitles": "Subtitles", + "overallBitrate": "Overall Bitrate", "path": "Path", "size": "Size", "container": "Container", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index 0a6bccb2..e88db486 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Primarias de color", "chromaSubsampling": "Submuestreo de croma", "channels": "Canales", + "subtitles": "Subtítulos", + "overallBitrate": "Bitrate total", "path": "Ruta", "size": "Tamaño", "container": "Contenedor", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index 17927bae..ada017e4 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Couleurs primaires", "chromaSubsampling": "Sous-échantillonnage chromatique", "channels": "Canaux", + "subtitles": "Sous-titres", + "overallBitrate": "Débit global", "path": "Chemin", "size": "Taille", "container": "Conteneur", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 000acfec..1bf2a543 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Colori primari", "chromaSubsampling": "Sottocampionamento cromatico", "channels": "Canali", + "subtitles": "Sottotitoli", + "overallBitrate": "Bitrate complessivo", "path": "Percorso", "size": "Dimensione", "container": "Contenitore", diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index 21ad325c..204d3b73 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "色原色", "chromaSubsampling": "クロマサブサンプリング", "channels": "チャンネル", + "subtitles": "字幕", + "overallBitrate": "全体ビットレート", "path": "パス", "size": "サイズ", "container": "コンテナ", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 17d28ccd..9f025a25 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "색상 원색", "chromaSubsampling": "채도 서브샘플링", "channels": "채널", + "subtitles": "자막", + "overallBitrate": "전체 비트레이트", "path": "경로", "size": "크기", "container": "컨테이너", diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index 18b27c47..0f9c02ab 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Fargeprimærer", "chromaSubsampling": "Krominansnedsampling", "channels": "Kanaler", + "subtitles": "Undertekster", + "overallBitrate": "Total bitrate", "path": "Sti", "size": "Størrelse", "container": "Beholder", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index d3dfe30b..2f543930 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Kleurprimaires", "chromaSubsampling": "Chroma subsampling", "channels": "Kanalen", + "subtitles": "Ondertitels", + "overallBitrate": "Totale bitrate", "path": "Pad", "size": "Grootte", "container": "Container", diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index 3e33add9..9bb5f327 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Kolory podstawowe", "chromaSubsampling": "Subsampling chrominancji", "channels": "Kanały", + "subtitles": "Napisy", + "overallBitrate": "Całkowity bitrate", "path": "Ścieżka", "size": "Rozmiar", "container": "Kontener", diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index f3d45e49..16fb2c6c 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Primárias de Cor", "chromaSubsampling": "Subamostragem de Croma", "channels": "Canais", + "subtitles": "Legendas", + "overallBitrate": "Taxa de bits total", "path": "Caminho", "size": "Tamanho", "container": "Container", diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index 1413afdc..d9f855ae 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Цветовые первичные", "chromaSubsampling": "Субдискретизация цветности", "channels": "Каналы", + "subtitles": "Субтитры", + "overallBitrate": "Общий битрейт", "path": "Путь", "size": "Размер", "container": "Контейнер", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index fbf3e640..f2cc1776 100644 --- a/lib/i18n/strings.g.dart +++ b/lib/i18n/strings.g.dart @@ -4,9 +4,9 @@ /// To regenerate, run: `dart run slang` /// /// Locales: 15 -/// Strings: 12945 (863 per locale) +/// Strings: 12975 (865 per locale) /// -/// Built on 2026-04-10 at 16:32 UTC +/// Built on 2026-04-11 at 05:59 UTC // coverage:ignore-file // ignore_for_file: type=lint, unused_import diff --git a/lib/i18n/strings_da.g.dart b/lib/i18n/strings_da.g.dart index d83fe0b4..7a27ae08 100644 --- a/lib/i18n/strings_da.g.dart +++ b/lib/i18n/strings_da.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoDa implements TranslationsFileInfoEn { @override String get colorPrimaries => 'Farveprimærer'; @override String get chromaSubsampling => 'Chroma-subsampling'; @override String get channels => 'Kanaler'; + @override String get subtitles => 'Undertekster'; + @override String get overallBitrate => 'Samlet bitrate'; @override String get path => 'Sti'; @override String get size => 'Størrelse'; @override String get container => 'Container'; @@ -1629,6 +1631,8 @@ extension on TranslationsDa { 'fileInfo.colorPrimaries' => 'Farveprimærer', 'fileInfo.chromaSubsampling' => 'Chroma-subsampling', 'fileInfo.channels' => 'Kanaler', + 'fileInfo.subtitles' => 'Undertekster', + 'fileInfo.overallBitrate' => 'Samlet bitrate', 'fileInfo.path' => 'Sti', 'fileInfo.size' => 'Størrelse', 'fileInfo.container' => 'Container', @@ -1870,10 +1874,10 @@ extension on TranslationsDa { 'about.versionLabel' => ({required Object version}) => 'Version ${version}', 'about.appDescription' => 'En smuk Plex-klient til Flutter', 'about.viewLicensesDescription' => 'Se licenser for tredjepartsbiblioteker', - 'serverSelection.allServerConnectionsFailed' => 'Kunne ikke forbinde til nogen servere. Tjek dit netværk og prøv igen.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Ingen servere fundet for ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'Kunne ikke forbinde til nogen servere. Tjek dit netværk og prøv igen.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Ingen servere fundet for ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Kunne ikke indlæse servere: ${error}', 'hubDetail.title' => 'Titel', 'hubDetail.releaseYear' => 'Udgivelsesår', diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index b6e628ef..71126208 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoDe implements TranslationsFileInfoEn { @override String get colorPrimaries => 'Primärfarben'; @override String get chromaSubsampling => 'Chroma-Subsampling'; @override String get channels => 'Kanäle'; + @override String get subtitles => 'Untertitel'; + @override String get overallBitrate => 'Gesamt-Bitrate'; @override String get path => 'Pfad'; @override String get size => 'Größe'; @override String get container => 'Container'; @@ -1629,6 +1631,8 @@ extension on TranslationsDe { 'fileInfo.colorPrimaries' => 'Primärfarben', 'fileInfo.chromaSubsampling' => 'Chroma-Subsampling', 'fileInfo.channels' => 'Kanäle', + 'fileInfo.subtitles' => 'Untertitel', + 'fileInfo.overallBitrate' => 'Gesamt-Bitrate', 'fileInfo.path' => 'Pfad', 'fileInfo.size' => 'Größe', 'fileInfo.container' => 'Container', @@ -1870,10 +1874,10 @@ extension on TranslationsDe { 'about.versionLabel' => ({required Object version}) => 'Version ${version}', 'about.appDescription' => 'Ein schöner Plex-Client für Flutter', 'about.viewLicensesDescription' => 'Lizenzen von Drittanbieter-Bibliotheken anzeigen', - 'serverSelection.allServerConnectionsFailed' => 'Verbindung zu allen Servern fehlgeschlagen. Bitte Netzwerk prüfen und erneut versuchen.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Keine Server gefunden für ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'Verbindung zu allen Servern fehlgeschlagen. Bitte Netzwerk prüfen und erneut versuchen.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Keine Server gefunden für ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Server konnten nicht geladen werden: ${error}', 'hubDetail.title' => 'Titel', 'hubDetail.releaseYear' => 'Erscheinungsjahr', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index 4f9e88ac..4b08b0a5 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -912,6 +912,12 @@ class TranslationsFileInfoEn { /// en: 'Channels' String get channels => 'Channels'; + /// en: 'Subtitles' + String get subtitles => 'Subtitles'; + + /// en: 'Overall Bitrate' + String get overallBitrate => 'Overall Bitrate'; + /// en: 'Path' String get path => 'Path'; @@ -3362,6 +3368,8 @@ extension on Translations { 'fileInfo.colorPrimaries' => 'Color Primaries', 'fileInfo.chromaSubsampling' => 'Chroma Subsampling', 'fileInfo.channels' => 'Channels', + 'fileInfo.subtitles' => 'Subtitles', + 'fileInfo.overallBitrate' => 'Overall Bitrate', 'fileInfo.path' => 'Path', 'fileInfo.size' => 'Size', 'fileInfo.container' => 'Container', @@ -3603,10 +3611,10 @@ extension on Translations { 'about.versionLabel' => ({required Object version}) => 'Version ${version}', 'about.appDescription' => 'A beautiful Plex client for Flutter', 'about.viewLicensesDescription' => 'View licenses of third-party libraries', - 'serverSelection.allServerConnectionsFailed' => 'Failed to connect to any servers. Please check your network and try again.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'No servers found for ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'Failed to connect to any servers. Please check your network and try again.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'No servers found for ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Failed to load servers: ${error}', 'hubDetail.title' => 'Title', 'hubDetail.releaseYear' => 'Release Year', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index 9635a758..9dfc8bf6 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoEs implements TranslationsFileInfoEn { @override String get colorPrimaries => 'Primarias de color'; @override String get chromaSubsampling => 'Submuestreo de croma'; @override String get channels => 'Canales'; + @override String get subtitles => 'Subtítulos'; + @override String get overallBitrate => 'Bitrate total'; @override String get path => 'Ruta'; @override String get size => 'Tamaño'; @override String get container => 'Contenedor'; @@ -1629,6 +1631,8 @@ extension on TranslationsEs { 'fileInfo.colorPrimaries' => 'Primarias de color', 'fileInfo.chromaSubsampling' => 'Submuestreo de croma', 'fileInfo.channels' => 'Canales', + 'fileInfo.subtitles' => 'Subtítulos', + 'fileInfo.overallBitrate' => 'Bitrate total', 'fileInfo.path' => 'Ruta', 'fileInfo.size' => 'Tamaño', 'fileInfo.container' => 'Contenedor', @@ -1870,10 +1874,10 @@ extension on TranslationsEs { 'about.versionLabel' => ({required Object version}) => 'Versión ${version}', 'about.appDescription' => 'Un cliente de Plex para Flutter', 'about.viewLicensesDescription' => 'Ver licencias de librerías de terceros', - 'serverSelection.allServerConnectionsFailed' => 'No se pudo conectar con ningún servidor. Por favor, comprueba tu conexión e inténtalo de nuevo.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'No se encontraron servidores para ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'No se pudo conectar con ningún servidor. Por favor, comprueba tu conexión e inténtalo de nuevo.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'No se encontraron servidores para ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Error al cargar servidores: ${error}', 'hubDetail.title' => 'Título', 'hubDetail.releaseYear' => 'Año de lanzamiento', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index 856b7edc..dd4a0314 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoFr implements TranslationsFileInfoEn { @override String get colorPrimaries => 'Couleurs primaires'; @override String get chromaSubsampling => 'Sous-échantillonnage chromatique'; @override String get channels => 'Canaux'; + @override String get subtitles => 'Sous-titres'; + @override String get overallBitrate => 'Débit global'; @override String get path => 'Chemin'; @override String get size => 'Taille'; @override String get container => 'Conteneur'; @@ -1629,6 +1631,8 @@ extension on TranslationsFr { 'fileInfo.colorPrimaries' => 'Couleurs primaires', 'fileInfo.chromaSubsampling' => 'Sous-échantillonnage chromatique', 'fileInfo.channels' => 'Canaux', + 'fileInfo.subtitles' => 'Sous-titres', + 'fileInfo.overallBitrate' => 'Débit global', 'fileInfo.path' => 'Chemin', 'fileInfo.size' => 'Taille', 'fileInfo.container' => 'Conteneur', @@ -1870,10 +1874,10 @@ extension on TranslationsFr { 'about.versionLabel' => ({required Object version}) => 'Version ${version}', 'about.appDescription' => 'Un magnifique client Plex pour Flutter', 'about.viewLicensesDescription' => 'Afficher les licences des bibliothèques tierces', - 'serverSelection.allServerConnectionsFailed' => 'Impossible de se connecter à un serveur. Veuillez vérifier votre connexion réseau et réessayer.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Aucun serveur trouvé pour ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'Impossible de se connecter à un serveur. Veuillez vérifier votre connexion réseau et réessayer.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Aucun serveur trouvé pour ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Échec du chargement des serveurs: ${error}', 'hubDetail.title' => 'Titre', 'hubDetail.releaseYear' => 'Année de sortie', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index 78ae0df7..2f1353cd 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoIt implements TranslationsFileInfoEn { @override String get colorPrimaries => 'Colori primari'; @override String get chromaSubsampling => 'Sottocampionamento cromatico'; @override String get channels => 'Canali'; + @override String get subtitles => 'Sottotitoli'; + @override String get overallBitrate => 'Bitrate complessivo'; @override String get path => 'Percorso'; @override String get size => 'Dimensione'; @override String get container => 'Contenitore'; @@ -1629,6 +1631,8 @@ extension on TranslationsIt { 'fileInfo.colorPrimaries' => 'Colori primari', 'fileInfo.chromaSubsampling' => 'Sottocampionamento cromatico', 'fileInfo.channels' => 'Canali', + 'fileInfo.subtitles' => 'Sottotitoli', + 'fileInfo.overallBitrate' => 'Bitrate complessivo', 'fileInfo.path' => 'Percorso', 'fileInfo.size' => 'Dimensione', 'fileInfo.container' => 'Contenitore', @@ -1870,10 +1874,10 @@ extension on TranslationsIt { 'about.versionLabel' => ({required Object version}) => 'Versione ${version}', 'about.appDescription' => 'Un bellissimo client Plex per Flutter', 'about.viewLicensesDescription' => 'Visualizza le licenze delle librerie di terze parti', - 'serverSelection.allServerConnectionsFailed' => 'Impossibile connettersi a nessun server. Controlla la tua rete e riprova.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nessun server trovato per ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'Impossibile connettersi a nessun server. Controlla la tua rete e riprova.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nessun server trovato per ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Impossibile caricare i server: ${error}', 'hubDetail.title' => 'Titolo', 'hubDetail.releaseYear' => 'Anno rilascio', diff --git a/lib/i18n/strings_ja.g.dart b/lib/i18n/strings_ja.g.dart index 968cb577..db6acca7 100644 --- a/lib/i18n/strings_ja.g.dart +++ b/lib/i18n/strings_ja.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoJa implements TranslationsFileInfoEn { @override String get colorPrimaries => '色原色'; @override String get chromaSubsampling => 'クロマサブサンプリング'; @override String get channels => 'チャンネル'; + @override String get subtitles => '字幕'; + @override String get overallBitrate => '全体ビットレート'; @override String get path => 'パス'; @override String get size => 'サイズ'; @override String get container => 'コンテナ'; @@ -1629,6 +1631,8 @@ extension on TranslationsJa { 'fileInfo.colorPrimaries' => '色原色', 'fileInfo.chromaSubsampling' => 'クロマサブサンプリング', 'fileInfo.channels' => 'チャンネル', + 'fileInfo.subtitles' => '字幕', + 'fileInfo.overallBitrate' => '全体ビットレート', 'fileInfo.path' => 'パス', 'fileInfo.size' => 'サイズ', 'fileInfo.container' => 'コンテナ', @@ -1870,10 +1874,10 @@ extension on TranslationsJa { 'about.versionLabel' => ({required Object version}) => 'バージョン ${version}', 'about.appDescription' => 'Flutter製の美しいPlexクライアント', 'about.viewLicensesDescription' => 'サードパーティライブラリのライセンスを表示', - 'serverSelection.allServerConnectionsFailed' => 'どのサーバーにも接続できませんでした。ネットワークを確認してもう一度お試しください。', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '${username} (${email})のサーバーが見つかりません', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'どのサーバーにも接続できませんでした。ネットワークを確認してもう一度お試しください。', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '${username} (${email})のサーバーが見つかりません', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'サーバーの読み込みに失敗しました: ${error}', 'hubDetail.title' => 'タイトル', 'hubDetail.releaseYear' => '公開年', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index 9346bdb3..54d56973 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoKo implements TranslationsFileInfoEn { @override String get colorPrimaries => '색상 원색'; @override String get chromaSubsampling => '채도 서브샘플링'; @override String get channels => '채널'; + @override String get subtitles => '자막'; + @override String get overallBitrate => '전체 비트레이트'; @override String get path => '경로'; @override String get size => '크기'; @override String get container => '컨테이너'; @@ -1629,6 +1631,8 @@ extension on TranslationsKo { 'fileInfo.colorPrimaries' => '색상 원색', 'fileInfo.chromaSubsampling' => '채도 서브샘플링', 'fileInfo.channels' => '채널', + 'fileInfo.subtitles' => '자막', + 'fileInfo.overallBitrate' => '전체 비트레이트', 'fileInfo.path' => '경로', 'fileInfo.size' => '크기', 'fileInfo.container' => '컨테이너', @@ -1870,10 +1874,10 @@ extension on TranslationsKo { 'about.versionLabel' => ({required Object version}) => '버전 ${version}', 'about.appDescription' => '아름다운 Flutter Plex 클라이언트', 'about.viewLicensesDescription' => '타사 라이브러리 라이선스 보기', - 'serverSelection.allServerConnectionsFailed' => '어떤 서버에도 연결할 수 없습니다. 네트워크를 확인하고 다시 시도하세요.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '${username} (${email})의 서버를 찾을 수 없습니다.', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => '어떤 서버에도 연결할 수 없습니다. 네트워크를 확인하고 다시 시도하세요.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '${username} (${email})의 서버를 찾을 수 없습니다.', 'serverSelection.failedToLoadServers' => ({required Object error}) => '서버를 로드할 수 없습니다: ${error}', 'hubDetail.title' => '제목', 'hubDetail.releaseYear' => '출시 연도', diff --git a/lib/i18n/strings_nb.g.dart b/lib/i18n/strings_nb.g.dart index a62c8df7..0582b5d3 100644 --- a/lib/i18n/strings_nb.g.dart +++ b/lib/i18n/strings_nb.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoNb implements TranslationsFileInfoEn { @override String get colorPrimaries => 'Fargeprimærer'; @override String get chromaSubsampling => 'Krominansnedsampling'; @override String get channels => 'Kanaler'; + @override String get subtitles => 'Undertekster'; + @override String get overallBitrate => 'Total bitrate'; @override String get path => 'Sti'; @override String get size => 'Størrelse'; @override String get container => 'Beholder'; @@ -1629,6 +1631,8 @@ extension on TranslationsNb { 'fileInfo.colorPrimaries' => 'Fargeprimærer', 'fileInfo.chromaSubsampling' => 'Krominansnedsampling', 'fileInfo.channels' => 'Kanaler', + 'fileInfo.subtitles' => 'Undertekster', + 'fileInfo.overallBitrate' => 'Total bitrate', 'fileInfo.path' => 'Sti', 'fileInfo.size' => 'Størrelse', 'fileInfo.container' => 'Beholder', @@ -1870,10 +1874,10 @@ extension on TranslationsNb { 'about.versionLabel' => ({required Object version}) => 'Versjon ${version}', 'about.appDescription' => 'En vakker Plex-klient for Flutter', 'about.viewLicensesDescription' => 'Vis lisenser for tredjepartsbiblioteker', - 'serverSelection.allServerConnectionsFailed' => 'Kunne ikke koble til noen servere. Sjekk nettverket ditt og prøv igjen.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Ingen servere funnet for ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'Kunne ikke koble til noen servere. Sjekk nettverket ditt og prøv igjen.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Ingen servere funnet for ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Kunne ikke laste servere: ${error}', 'hubDetail.title' => 'Tittel', 'hubDetail.releaseYear' => 'Utgivelsesår', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index 29fea521..ab765178 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoNl implements TranslationsFileInfoEn { @override String get colorPrimaries => 'Kleurprimaires'; @override String get chromaSubsampling => 'Chroma subsampling'; @override String get channels => 'Kanalen'; + @override String get subtitles => 'Ondertitels'; + @override String get overallBitrate => 'Totale bitrate'; @override String get path => 'Pad'; @override String get size => 'Grootte'; @override String get container => 'Container'; @@ -1629,6 +1631,8 @@ extension on TranslationsNl { 'fileInfo.colorPrimaries' => 'Kleurprimaires', 'fileInfo.chromaSubsampling' => 'Chroma subsampling', 'fileInfo.channels' => 'Kanalen', + 'fileInfo.subtitles' => 'Ondertitels', + 'fileInfo.overallBitrate' => 'Totale bitrate', 'fileInfo.path' => 'Pad', 'fileInfo.size' => 'Grootte', 'fileInfo.container' => 'Container', @@ -1870,10 +1874,10 @@ extension on TranslationsNl { 'about.versionLabel' => ({required Object version}) => 'Versie ${version}', 'about.appDescription' => 'Een mooie Plex client voor Flutter', 'about.viewLicensesDescription' => 'Bekijk licenties van third-party bibliotheken', - 'serverSelection.allServerConnectionsFailed' => 'Kon niet verbinden met servers. Controleer je netwerk en probeer opnieuw.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Geen servers gevonden voor ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'Kon niet verbinden met servers. Controleer je netwerk en probeer opnieuw.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Geen servers gevonden voor ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Kon servers niet laden: ${error}', 'hubDetail.title' => 'Titel', 'hubDetail.releaseYear' => 'Uitgavejaar', diff --git a/lib/i18n/strings_pl.g.dart b/lib/i18n/strings_pl.g.dart index 700d85d3..c8abf668 100644 --- a/lib/i18n/strings_pl.g.dart +++ b/lib/i18n/strings_pl.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoPl implements TranslationsFileInfoEn { @override String get colorPrimaries => 'Kolory podstawowe'; @override String get chromaSubsampling => 'Subsampling chrominancji'; @override String get channels => 'Kanały'; + @override String get subtitles => 'Napisy'; + @override String get overallBitrate => 'Całkowity bitrate'; @override String get path => 'Ścieżka'; @override String get size => 'Rozmiar'; @override String get container => 'Kontener'; @@ -1629,6 +1631,8 @@ extension on TranslationsPl { 'fileInfo.colorPrimaries' => 'Kolory podstawowe', 'fileInfo.chromaSubsampling' => 'Subsampling chrominancji', 'fileInfo.channels' => 'Kanały', + 'fileInfo.subtitles' => 'Napisy', + 'fileInfo.overallBitrate' => 'Całkowity bitrate', 'fileInfo.path' => 'Ścieżka', 'fileInfo.size' => 'Rozmiar', 'fileInfo.container' => 'Kontener', @@ -1870,10 +1874,10 @@ extension on TranslationsPl { 'about.versionLabel' => ({required Object version}) => 'Wersja ${version}', 'about.appDescription' => 'Piękny klient Plex na Flutter', 'about.viewLicensesDescription' => 'Zobacz licencje bibliotek zewnętrznych', - 'serverSelection.allServerConnectionsFailed' => 'Nie udało się połączyć z żadnym serwerem. Sprawdź sieć i spróbuj ponownie.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nie znaleziono serwerów dla ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'Nie udało się połączyć z żadnym serwerem. Sprawdź sieć i spróbuj ponownie.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nie znaleziono serwerów dla ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Nie udało się załadować serwerów: ${error}', 'hubDetail.title' => 'Tytuł', 'hubDetail.releaseYear' => 'Rok premiery', diff --git a/lib/i18n/strings_pt.g.dart b/lib/i18n/strings_pt.g.dart index e732712f..995b6cd8 100644 --- a/lib/i18n/strings_pt.g.dart +++ b/lib/i18n/strings_pt.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoPt implements TranslationsFileInfoEn { @override String get colorPrimaries => 'Primárias de Cor'; @override String get chromaSubsampling => 'Subamostragem de Croma'; @override String get channels => 'Canais'; + @override String get subtitles => 'Legendas'; + @override String get overallBitrate => 'Taxa de bits total'; @override String get path => 'Caminho'; @override String get size => 'Tamanho'; @override String get container => 'Container'; @@ -1629,6 +1631,8 @@ extension on TranslationsPt { 'fileInfo.colorPrimaries' => 'Primárias de Cor', 'fileInfo.chromaSubsampling' => 'Subamostragem de Croma', 'fileInfo.channels' => 'Canais', + 'fileInfo.subtitles' => 'Legendas', + 'fileInfo.overallBitrate' => 'Taxa de bits total', 'fileInfo.path' => 'Caminho', 'fileInfo.size' => 'Tamanho', 'fileInfo.container' => 'Container', @@ -1870,10 +1874,10 @@ extension on TranslationsPt { 'about.versionLabel' => ({required Object version}) => 'Versão ${version}', 'about.appDescription' => 'Um belo cliente Plex para Flutter', 'about.viewLicensesDescription' => 'Ver licenças de bibliotecas de terceiros', - 'serverSelection.allServerConnectionsFailed' => 'Falha ao conectar a qualquer servidor. Verifique sua rede e tente novamente.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nenhum servidor encontrado para ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'Falha ao conectar a qualquer servidor. Verifique sua rede e tente novamente.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Nenhum servidor encontrado para ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Falha ao carregar servidores: ${error}', 'hubDetail.title' => 'Título', 'hubDetail.releaseYear' => 'Ano de Lançamento', diff --git a/lib/i18n/strings_ru.g.dart b/lib/i18n/strings_ru.g.dart index 1a2f3e54..fe43810f 100644 --- a/lib/i18n/strings_ru.g.dart +++ b/lib/i18n/strings_ru.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoRu implements TranslationsFileInfoEn { @override String get colorPrimaries => 'Цветовые первичные'; @override String get chromaSubsampling => 'Субдискретизация цветности'; @override String get channels => 'Каналы'; + @override String get subtitles => 'Субтитры'; + @override String get overallBitrate => 'Общий битрейт'; @override String get path => 'Путь'; @override String get size => 'Размер'; @override String get container => 'Контейнер'; @@ -1629,6 +1631,8 @@ extension on TranslationsRu { 'fileInfo.colorPrimaries' => 'Цветовые первичные', 'fileInfo.chromaSubsampling' => 'Субдискретизация цветности', 'fileInfo.channels' => 'Каналы', + 'fileInfo.subtitles' => 'Субтитры', + 'fileInfo.overallBitrate' => 'Общий битрейт', 'fileInfo.path' => 'Путь', 'fileInfo.size' => 'Размер', 'fileInfo.container' => 'Контейнер', @@ -1870,10 +1874,10 @@ extension on TranslationsRu { 'about.versionLabel' => ({required Object version}) => 'Версия ${version}', 'about.appDescription' => 'Красивый клиент Plex на Flutter', 'about.viewLicensesDescription' => 'Просмотр лицензий сторонних библиотек', - 'serverSelection.allServerConnectionsFailed' => 'Не удалось подключиться ни к одному серверу. Проверьте сеть и попробуйте снова.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Серверы не найдены для ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'Не удалось подключиться ни к одному серверу. Проверьте сеть и попробуйте снова.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Серверы не найдены для ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Не удалось загрузить серверы: ${error}', 'hubDetail.title' => 'Название', 'hubDetail.releaseYear' => 'Год выпуска', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index 6e2b60a7..1fb15a09 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoSv implements TranslationsFileInfoEn { @override String get colorPrimaries => 'Färggrunder'; @override String get chromaSubsampling => 'Kroma-undersampling'; @override String get channels => 'Kanaler'; + @override String get subtitles => 'Undertexter'; + @override String get overallBitrate => 'Total bithastighet'; @override String get path => 'Sökväg'; @override String get size => 'Storlek'; @override String get container => 'Container'; @@ -1629,6 +1631,8 @@ extension on TranslationsSv { 'fileInfo.colorPrimaries' => 'Färggrunder', 'fileInfo.chromaSubsampling' => 'Kroma-undersampling', 'fileInfo.channels' => 'Kanaler', + 'fileInfo.subtitles' => 'Undertexter', + 'fileInfo.overallBitrate' => 'Total bithastighet', 'fileInfo.path' => 'Sökväg', 'fileInfo.size' => 'Storlek', 'fileInfo.container' => 'Container', @@ -1870,10 +1874,10 @@ extension on TranslationsSv { 'about.versionLabel' => ({required Object version}) => 'Version ${version}', 'about.appDescription' => 'En vacker Plex-klient för Flutter', 'about.viewLicensesDescription' => 'Visa licenser för tredjepartsbibliotek', - 'serverSelection.allServerConnectionsFailed' => 'Misslyckades att ansluta till servrar. Kontrollera ditt nätverk och försök igen.', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Inga servrar hittades för ${username} (${email})', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => 'Misslyckades att ansluta till servrar. Kontrollera ditt nätverk och försök igen.', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => 'Inga servrar hittades för ${username} (${email})', 'serverSelection.failedToLoadServers' => ({required Object error}) => 'Misslyckades att ladda servrar: ${error}', 'hubDetail.title' => 'Titel', 'hubDetail.releaseYear' => 'Utgivningsår', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index 7135975c..bf7c5763 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -407,6 +407,8 @@ class _TranslationsFileInfoZh implements TranslationsFileInfoEn { @override String get colorPrimaries => '颜色原色'; @override String get chromaSubsampling => '色度子采样'; @override String get channels => '声道'; + @override String get subtitles => '字幕'; + @override String get overallBitrate => '总比特率'; @override String get path => '路径'; @override String get size => '大小'; @override String get container => '容器'; @@ -1629,6 +1631,8 @@ extension on TranslationsZh { 'fileInfo.colorPrimaries' => '颜色原色', 'fileInfo.chromaSubsampling' => '色度子采样', 'fileInfo.channels' => '声道', + 'fileInfo.subtitles' => '字幕', + 'fileInfo.overallBitrate' => '总比特率', 'fileInfo.path' => '路径', 'fileInfo.size' => '大小', 'fileInfo.container' => '容器', @@ -1870,10 +1874,10 @@ extension on TranslationsZh { 'about.versionLabel' => ({required Object version}) => '版本 ${version}', 'about.appDescription' => '一款精美的 Flutter Plex 客户端', 'about.viewLicensesDescription' => '查看第三方库的许可证', - 'serverSelection.allServerConnectionsFailed' => '无法连接到任何服务器。请检查你的网络并重试。', - 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '未找到 ${username} (${email}) 的服务器', _ => null, } ?? switch (path) { + 'serverSelection.allServerConnectionsFailed' => '无法连接到任何服务器。请检查你的网络并重试。', + 'serverSelection.noServersFoundForAccount' => ({required Object username, required Object email}) => '未找到 ${username} (${email}) 的服务器', 'serverSelection.failedToLoadServers' => ({required Object error}) => '无法加载服务器: ${error}', 'hubDetail.title' => '标题', 'hubDetail.releaseYear' => '发行年份', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index 5b18310a..fcbd5a33 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "Färggrunder", "chromaSubsampling": "Kroma-undersampling", "channels": "Kanaler", + "subtitles": "Undertexter", + "overallBitrate": "Total bithastighet", "path": "Sökväg", "size": "Storlek", "container": "Container", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 773cf7e0..76255eed 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -287,6 +287,8 @@ "colorPrimaries": "颜色原色", "chromaSubsampling": "色度子采样", "channels": "声道", + "subtitles": "字幕", + "overallBitrate": "总比特率", "path": "路径", "size": "大小", "container": "容器", diff --git a/lib/models/plex_file_info.dart b/lib/models/plex_file_info.dart index 54676308..cc68593a 100644 --- a/lib/models/plex_file_info.dart +++ b/lib/models/plex_file_info.dart @@ -1,4 +1,5 @@ import '../utils/formatters.dart'; +import 'plex_media_info.dart'; class PlexFileInfo { // Media level properties @@ -30,8 +31,13 @@ class PlexFileInfo { final String? chromaSubsampling; final double? frameRate; final int? bitDepth; + final int? videoBitrate; final String? audioChannelLayout; + // Multi-track support + final List audioTracks; + final List subtitleTracks; + PlexFileInfo({ this.container, this.videoCodec, @@ -57,7 +63,10 @@ class PlexFileInfo { this.chromaSubsampling, this.frameRate, this.bitDepth, + this.videoBitrate, this.audioChannelLayout, + this.audioTracks = const [], + this.subtitleTracks = const [], }); /// Format file size in human-readable format (GB, MB, KB, bytes) @@ -78,10 +87,16 @@ class PlexFileInfo { return hours > 0 ? '${hours}h ${minutes}m ${secs}s' : '${minutes}m ${secs}s'; } - /// Format bitrate in Mbps or Kbps + /// Format overall bitrate in Mbps or kbps (Plex API returns kbps) String get bitrateFormatted { if (bitrate == null) return 'Unknown'; - return ByteFormatter.formatBitrateBps(bitrate!); + return ByteFormatter.formatBitrate(bitrate!); + } + + /// Format video stream bitrate in Mbps or kbps + String get videoBitrateFormatted { + if (videoBitrate == null) return 'Unknown'; + return ByteFormatter.formatBitrate(videoBitrate!); } /// Format resolution as widthxheight diff --git a/lib/services/plex_client.dart b/lib/services/plex_client.dart index 94b9118a..02b826a2 100644 --- a/lib/services/plex_client.dart +++ b/lib/services/plex_client.dart @@ -1266,7 +1266,7 @@ class PlexClient { final media = metadataJson['Media'][0]; final part = media['Part'] != null && (media['Part'] as List).isNotEmpty ? media['Part'][0] : null; - // Extract video stream details + // Extract video stream details and all audio/subtitle tracks final streams = part?['Stream'] as List? ?? []; Map? videoStream; Map? audioStream; @@ -1280,6 +1280,8 @@ class PlexClient { } } + final parsedTracks = _parseStreams(streams); + return PlexFileInfo( // Media level properties container: media['container'] as String?, @@ -1308,8 +1310,12 @@ class PlexClient { chromaSubsampling: videoStream?['chromaSubsampling'] as String?, frameRate: (videoStream?['frameRate'] as num?)?.toDouble(), bitDepth: videoStream?['bitDepth'] as int?, + videoBitrate: videoStream?['bitrate'] as int?, // Audio stream details audioChannelLayout: audioStream?['audioChannelLayout'] as String?, + // All audio and subtitle tracks + audioTracks: parsedTracks.audio, + subtitleTracks: parsedTracks.subtitles, ); } diff --git a/lib/widgets/file_info_bottom_sheet.dart b/lib/widgets/file_info_bottom_sheet.dart index bff491c6..58b5ff00 100644 --- a/lib/widgets/file_info_bottom_sheet.dart +++ b/lib/widgets/file_info_bottom_sheet.dart @@ -2,6 +2,7 @@ import 'package:flutter/material.dart'; import 'package:material_symbols_icons/symbols.dart'; import '../models/plex_file_info.dart'; import '../i18n/strings.g.dart'; +import '../utils/scroll_utils.dart'; import 'bottom_sheet_header.dart'; class FileInfoBottomSheet extends StatefulWidget { @@ -58,7 +59,8 @@ class _FileInfoBottomSheetState extends State { const SizedBox(height: 8), _buildInfoRow(t.fileInfo.codec, widget.fileInfo.videoCodec ?? t.common.unknown), _buildInfoRow(t.fileInfo.resolution, widget.fileInfo.resolutionFormatted), - _buildInfoRow(t.fileInfo.bitrate, widget.fileInfo.bitrateFormatted), + if (widget.fileInfo.videoBitrate != null) + _buildInfoRow(t.fileInfo.bitrate, widget.fileInfo.videoBitrateFormatted), _buildInfoRow(t.fileInfo.frameRate, widget.fileInfo.frameRateFormatted), _buildInfoRow(t.fileInfo.aspectRatio, widget.fileInfo.aspectRatioFormatted), if (widget.fileInfo.videoProfile != null) @@ -76,12 +78,26 @@ class _FileInfoBottomSheetState extends State { // Audio Section _buildSectionHeader(t.fileInfo.audio), const SizedBox(height: 8), - _buildInfoRow(t.fileInfo.codec, widget.fileInfo.audioCodec ?? t.common.unknown), - _buildInfoRow(t.fileInfo.channels, widget.fileInfo.audioChannelsFormatted), - if (widget.fileInfo.audioProfile != null) - _buildInfoRow(t.fileInfo.profile, widget.fileInfo.audioProfile!), + if (widget.fileInfo.audioTracks.isNotEmpty) + for (int i = 0; i < widget.fileInfo.audioTracks.length; i++) + _buildInfoRow('${i + 1}', widget.fileInfo.audioTracks[i].label), + if (widget.fileInfo.audioTracks.isEmpty) ...[ + _buildInfoRow(t.fileInfo.codec, widget.fileInfo.audioCodec ?? t.common.unknown), + _buildInfoRow(t.fileInfo.channels, widget.fileInfo.audioChannelsFormatted), + if (widget.fileInfo.audioProfile != null) + _buildInfoRow(t.fileInfo.profile, widget.fileInfo.audioProfile!), + ], const SizedBox(height: 20), + // Subtitles Section + if (widget.fileInfo.subtitleTracks.isNotEmpty) ...[ + _buildSectionHeader(t.fileInfo.subtitles), + const SizedBox(height: 8), + for (int i = 0; i < widget.fileInfo.subtitleTracks.length; i++) + _buildInfoRow('${i + 1}', widget.fileInfo.subtitleTracks[i].label), + const SizedBox(height: 20), + ], + // File Section _buildSectionHeader(t.fileInfo.file), const SizedBox(height: 8), @@ -90,6 +106,7 @@ class _FileInfoBottomSheetState extends State { _buildInfoRow(t.fileInfo.size, widget.fileInfo.fileSizeFormatted), _buildInfoRow(t.fileInfo.container, widget.fileInfo.container ?? t.common.unknown), _buildInfoRow(t.fileInfo.duration, widget.fileInfo.durationFormatted), + _buildInfoRow(t.fileInfo.overallBitrate, widget.fileInfo.bitrateFormatted), const SizedBox(height: 20), // Advanced Section @@ -118,22 +135,65 @@ class _FileInfoBottomSheetState extends State { } Widget _buildInfoRow(String label, String value, {bool isMonospace = false}) { - return Padding( - padding: const EdgeInsets.symmetric(vertical: 6), - child: Row( - crossAxisAlignment: CrossAxisAlignment.start, - children: [ - SizedBox( - width: 140, - child: Text(label, style: TextStyle(color: Theme.of(context).textTheme.bodySmall?.color, fontSize: 14)), - ), - Expanded( - child: Text( - value, - style: TextStyle(fontSize: 14, fontFamily: isMonospace ? 'monospace' : null), + return _FocusableInfoRow(label: label, value: value, isMonospace: isMonospace); + } +} + +class _FocusableInfoRow extends StatefulWidget { + final String label; + final String value; + final bool isMonospace; + + const _FocusableInfoRow({required this.label, required this.value, this.isMonospace = false}); + + @override + State<_FocusableInfoRow> createState() => _FocusableInfoRowState(); +} + +class _FocusableInfoRowState extends State<_FocusableInfoRow> { + late final FocusNode _focusNode; + + @override + void initState() { + super.initState(); + _focusNode = FocusNode(); + _focusNode.addListener(_onFocusChange); + } + + @override + void dispose() { + _focusNode.removeListener(_onFocusChange); + _focusNode.dispose(); + super.dispose(); + } + + void _onFocusChange() { + if (_focusNode.hasFocus) { + scrollContextToCenter(context); + } + } + + @override + Widget build(BuildContext context) { + return Focus( + focusNode: _focusNode, + child: Padding( + padding: const EdgeInsets.symmetric(vertical: 6), + child: Row( + crossAxisAlignment: CrossAxisAlignment.start, + children: [ + SizedBox( + width: 140, + child: Text(widget.label, style: TextStyle(color: Theme.of(context).textTheme.bodySmall?.color, fontSize: 14)), ), - ), - ], + Expanded( + child: Text( + widget.value, + style: TextStyle(fontSize: 14, fontFamily: widget.isMonospace ? 'monospace' : null), + ), + ), + ], + ), ), ); }