From b81f6eaa9618d5701cae9e545a63617b63ffaa4e Mon Sep 17 00:00:00 2001 From: edde746 <86283021+edde746@users.noreply.github.com> Date: Mon, 13 Apr 2026 14:02:07 +0200 Subject: [PATCH] feat: watch together recent rooms, plex usernames, action toasts, shorter codes close #855 --- lib/i18n/da.i18n.json | 12 +- lib/i18n/de.i18n.json | 12 +- lib/i18n/en.i18n.json | 12 +- lib/i18n/es.i18n.json | 12 +- lib/i18n/fr.i18n.json | 12 +- lib/i18n/it.i18n.json | 12 +- lib/i18n/ja.i18n.json | 12 +- lib/i18n/ko.i18n.json | 12 +- lib/i18n/nb.i18n.json | 12 +- lib/i18n/nl.i18n.json | 12 +- lib/i18n/pl.i18n.json | 12 +- lib/i18n/pt.i18n.json | 12 +- lib/i18n/ru.i18n.json | 12 +- lib/i18n/strings.g.dart | 4 +- lib/i18n/strings_da.g.dart | 20 +- lib/i18n/strings_de.g.dart | 20 +- lib/i18n/strings_en.g.dart | 36 +++- 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 | 12 +- lib/i18n/zh.i18n.json | 12 +- lib/services/settings_service.dart | 10 + .../providers/watch_together_provider.dart | 78 +++++++- .../screens/watch_together_screen.dart | 175 +++++++++++++++++- .../services/recent_rooms_service.dart | 78 ++++++++ .../services/watch_together_peer_service.dart | 28 ++- .../widgets/join_session_dialog.dart | 8 +- .../widgets/watch_together_overlay.dart | 10 +- 38 files changed, 748 insertions(+), 139 deletions(-) create mode 100644 lib/watch_together/services/recent_rooms_service.dart diff --git a/lib/i18n/da.i18n.json b/lib/i18n/da.i18n.json index 15e16042..cd966c65 100644 --- a/lib/i18n/da.i18n.json +++ b/lib/i18n/da.i18n.json @@ -702,10 +702,10 @@ "leave": "Forlad", "syncing": "Synkroniserer...", "joinWatchSession": "Deltag i se-session", - "enterCodeHint": "Indtast 8-tegns kode", + "enterCodeHint": "Indtast 5-tegns kode", "pasteFromClipboard": "Indsæt fra udklipsholder", "pleaseEnterCode": "Indtast en sessionskode", - "codeMustBe8Chars": "Sessionskode skal være 8 tegn", + "codeMustBe5Chars": "Sessionskode skal være 5 tegn", "joinInstructions": "Indtast sessionskoden delt af værten for at deltage i se-sessionen.", "failedToCreate": "Kunne ikke oprette session", "failedToJoin": "Kunne ikke deltage i session", @@ -717,7 +717,13 @@ "joinCurrentPlaybackDescription": "Hop tilbage til det værten ser nu", "failedToOpenCurrentPlayback": "Kunne ikke åbne nuværende afspilning", "participantJoined": "${name} deltog", - "participantLeft": "${name} forlod" + "participantLeft": "${name} forlod", + "participantPaused": "${name} satte på pause", + "participantResumed": "${name} genoptog", + "participantSeeked": "${name} spoled", + "recentRooms": "Seneste rum", + "renameRoom": "Omdøb rum", + "removeRoom": "Fjern" }, "downloads": { "title": "Downloads", diff --git a/lib/i18n/de.i18n.json b/lib/i18n/de.i18n.json index f5b0177f..c904a357 100644 --- a/lib/i18n/de.i18n.json +++ b/lib/i18n/de.i18n.json @@ -729,10 +729,10 @@ "leave": "Verlassen", "syncing": "Synchronisieren...", "joinWatchSession": "Schausitzung Beitreten", - "enterCodeHint": "8-stelligen Code eingeben", + "enterCodeHint": "5-stelligen Code eingeben", "pasteFromClipboard": "Aus Zwischenablage einfügen", "pleaseEnterCode": "Bitte gib einen Sitzungscode ein", - "codeMustBe8Chars": "Sitzungscode muss 8 Zeichen haben", + "codeMustBe5Chars": "Sitzungscode muss 5 Zeichen haben", "joinInstructions": "Gib den vom Host geteilten Sitzungscode ein, um seiner Schausitzung beizutreten.", "failedToCreate": "Sitzung konnte nicht erstellt werden", "failedToJoin": "Sitzung konnte nicht beigetreten werden", @@ -744,7 +744,13 @@ "joinCurrentPlaybackDescription": "Springe zurück zu dem, was der Host gerade ansieht", "failedToOpenCurrentPlayback": "Aktuelle Wiedergabe konnte nicht geöffnet werden", "participantJoined": "${name} ist beigetreten", - "participantLeft": "${name} hat die Sitzung verlassen" + "participantLeft": "${name} hat die Sitzung verlassen", + "participantPaused": "${name} hat pausiert", + "participantResumed": "${name} hat fortgesetzt", + "participantSeeked": "${name} hat gespult", + "recentRooms": "Letzte Räume", + "renameRoom": "Raum umbenennen", + "removeRoom": "Entfernen" }, "shaders": { "title": "Shader", diff --git a/lib/i18n/en.i18n.json b/lib/i18n/en.i18n.json index 977ddd34..d557ee76 100644 --- a/lib/i18n/en.i18n.json +++ b/lib/i18n/en.i18n.json @@ -702,10 +702,10 @@ "leave": "Leave", "syncing": "Syncing...", "joinWatchSession": "Join Watch Session", - "enterCodeHint": "Enter 8-character code", + "enterCodeHint": "Enter 5-character code", "pasteFromClipboard": "Paste from clipboard", "pleaseEnterCode": "Please enter a session code", - "codeMustBe8Chars": "Session code must be 8 characters", + "codeMustBe5Chars": "Session code must be 5 characters", "joinInstructions": "Enter the session code shared by the host to join their watch session.", "failedToCreate": "Failed to create session", "failedToJoin": "Failed to join session", @@ -717,7 +717,13 @@ "joinCurrentPlaybackDescription": "Jump back into what the host is currently watching", "failedToOpenCurrentPlayback": "Failed to open current playback", "participantJoined": "${name} joined", - "participantLeft": "${name} left" + "participantLeft": "${name} left", + "participantPaused": "${name} paused", + "participantResumed": "${name} resumed", + "participantSeeked": "${name} seeked", + "recentRooms": "Recent Rooms", + "renameRoom": "Rename Room", + "removeRoom": "Remove" }, "downloads": { "title": "Downloads", diff --git a/lib/i18n/es.i18n.json b/lib/i18n/es.i18n.json index e88db486..4ef8cdd5 100644 --- a/lib/i18n/es.i18n.json +++ b/lib/i18n/es.i18n.json @@ -702,10 +702,10 @@ "leave": "Salir", "syncing": "Sincronizando...", "joinWatchSession": "Unirse a Sesión de Visualización", - "enterCodeHint": "Introduce el código de 8 caracteres", + "enterCodeHint": "Introduce el código de 5 caracteres", "pasteFromClipboard": "Pegar desde el portapapeles", "pleaseEnterCode": "Por favor, introduce un código de sesión", - "codeMustBe8Chars": "El código de sesión debe tener 8 caracteres", + "codeMustBe5Chars": "El código de sesión debe tener 5 caracteres", "joinInstructions": "Introduce el código de sesión compartido por el anfitrión para unirte a su sesión.", "failedToCreate": "Error al crear la sesión", "failedToJoin": "Error al unirse a la sesión", @@ -717,7 +717,13 @@ "joinCurrentPlaybackDescription": "Vuelve a lo que el anfitrión está viendo ahora mismo", "failedToOpenCurrentPlayback": "No se pudo abrir la reproducción actual", "participantJoined": "${name} se unió", - "participantLeft": "${name} se fue" + "participantLeft": "${name} se fue", + "participantPaused": "${name} pausó", + "participantResumed": "${name} reanudó", + "participantSeeked": "${name} avanzó", + "recentRooms": "Salas recientes", + "renameRoom": "Renombrar sala", + "removeRoom": "Eliminar" }, "downloads": { "title": "Descargas", diff --git a/lib/i18n/fr.i18n.json b/lib/i18n/fr.i18n.json index ada017e4..6e9ceccb 100644 --- a/lib/i18n/fr.i18n.json +++ b/lib/i18n/fr.i18n.json @@ -702,10 +702,10 @@ "leave": "Fin", "syncing": "Synchronisation...", "joinWatchSession": "Rejoindre la session de visionnage", - "enterCodeHint": "Entrez le code à 8 caractères", + "enterCodeHint": "Entrez le code à 5 caractères", "pasteFromClipboard": "Coller depuis le presse-papiers", "pleaseEnterCode": "Veuillez saisir un code de session", - "codeMustBe8Chars": "Le code de session doit comporter 8 caractères", + "codeMustBe5Chars": "Le code de session doit comporter 5 caractères", "joinInstructions": "Entrez le code de session partagé par l'hôte pour rejoindre sa session de visionnage.", "failedToCreate": "Échec de la création de la session", "failedToJoin": "Échec de la connexion à la session", @@ -717,7 +717,13 @@ "joinCurrentPlaybackDescription": "Revenez à ce que l'hôte regarde actuellement", "failedToOpenCurrentPlayback": "Impossible d'ouvrir la lecture en cours", "participantJoined": "${name} a rejoint", - "participantLeft": "${name} est parti" + "participantLeft": "${name} est parti", + "participantPaused": "${name} a mis en pause", + "participantResumed": "${name} a repris", + "participantSeeked": "${name} a avancé", + "recentRooms": "Salons récents", + "renameRoom": "Renommer le salon", + "removeRoom": "Supprimer" }, "downloads": { "title": "Téléchargements", diff --git a/lib/i18n/it.i18n.json b/lib/i18n/it.i18n.json index 1bf2a543..47a3bc1e 100644 --- a/lib/i18n/it.i18n.json +++ b/lib/i18n/it.i18n.json @@ -729,10 +729,10 @@ "leave": "Lascia", "syncing": "Sincronizzazione...", "joinWatchSession": "Unisciti alla Sessione di Visione", - "enterCodeHint": "Inserisci codice di 8 caratteri", + "enterCodeHint": "Inserisci codice di 5 caratteri", "pasteFromClipboard": "Incolla dagli appunti", "pleaseEnterCode": "Inserisci un codice sessione", - "codeMustBe8Chars": "Il codice sessione deve essere di 8 caratteri", + "codeMustBe5Chars": "Il codice sessione deve essere di 5 caratteri", "joinInstructions": "Inserisci il codice sessione condiviso dall'host per unirti alla loro sessione di visione.", "failedToCreate": "Impossibile creare la sessione", "failedToJoin": "Impossibile unirsi alla sessione", @@ -744,7 +744,13 @@ "joinCurrentPlaybackDescription": "Torna a ciò che l'host sta guardando in questo momento", "failedToOpenCurrentPlayback": "Impossibile aprire la riproduzione corrente", "participantJoined": "${name} si è unito", - "participantLeft": "${name} se ne è andato" + "participantLeft": "${name} se ne è andato", + "participantPaused": "${name} ha messo in pausa", + "participantResumed": "${name} ha ripreso", + "participantSeeked": "${name} ha cercato", + "recentRooms": "Stanze recenti", + "renameRoom": "Rinomina stanza", + "removeRoom": "Rimuovi" }, "shaders": { "title": "Shader", diff --git a/lib/i18n/ja.i18n.json b/lib/i18n/ja.i18n.json index 204d3b73..1a1f69b4 100644 --- a/lib/i18n/ja.i18n.json +++ b/lib/i18n/ja.i18n.json @@ -702,10 +702,10 @@ "leave": "退出", "syncing": "同期中...", "joinWatchSession": "視聴セッションに参加", - "enterCodeHint": "8文字のコードを入力", + "enterCodeHint": "5文字のコードを入力", "pasteFromClipboard": "クリップボードから貼り付け", "pleaseEnterCode": "セッションコードを入力してください", - "codeMustBe8Chars": "セッションコードは8文字である必要があります", + "codeMustBe5Chars": "セッションコードは5文字である必要があります", "joinInstructions": "ホストが共有したセッションコードを入力して視聴セッションに参加してください。", "failedToCreate": "セッションの作成に失敗しました", "failedToJoin": "セッションへの参加に失敗しました", @@ -717,7 +717,13 @@ "joinCurrentPlaybackDescription": "ホストが現在視聴中のコンテンツに戻る", "failedToOpenCurrentPlayback": "現在の再生を開けませんでした", "participantJoined": "${name}が参加しました", - "participantLeft": "${name}が退出しました" + "participantLeft": "${name}が退出しました", + "participantPaused": "${name}が一時停止しました", + "participantResumed": "${name}が再開しました", + "participantSeeked": "${name}がシークしました", + "recentRooms": "最近のルーム", + "renameRoom": "ルーム名を変更", + "removeRoom": "削除" }, "downloads": { "title": "ダウンロード", diff --git a/lib/i18n/ko.i18n.json b/lib/i18n/ko.i18n.json index 9f025a25..8ef72239 100644 --- a/lib/i18n/ko.i18n.json +++ b/lib/i18n/ko.i18n.json @@ -702,10 +702,10 @@ "leave": "이탈", "syncing": "동기화 중...", "joinWatchSession": "시청 세션에 참여", - "enterCodeHint": "8자리 코드 입력", + "enterCodeHint": "5자리 코드 입력", "pasteFromClipboard": "클립보드에서 붙여넣기", "pleaseEnterCode": "세션 코드를 입력하세요", - "codeMustBe8Chars": "세션 코드는 반드시 8자리여야 합니다", + "codeMustBe5Chars": "세션 코드는 반드시 5자리여야 합니다", "joinInstructions": "호스트가 공유한 세션 코드를 입력하여 시청 세션에 참여하세요.", "failedToCreate": "세션 생성 실패", "failedToJoin": "세션 참여 실패", @@ -717,7 +717,13 @@ "joinCurrentPlaybackDescription": "호스트가 현재 보고 있는 항목으로 돌아갑니다", "failedToOpenCurrentPlayback": "현재 재생을 열 수 없습니다", "participantJoined": "${name}님이 참여했습니다", - "participantLeft": "${name}님이 나갔습니다" + "participantLeft": "${name}님이 나갔습니다", + "participantPaused": "${name}님이 일시정지했습니다", + "participantResumed": "${name}님이 재생했습니다", + "participantSeeked": "${name}님이 탐색했습니다", + "recentRooms": "최근 방", + "renameRoom": "방 이름 변경", + "removeRoom": "제거" }, "downloads": { "title": "다운로드", diff --git a/lib/i18n/nb.i18n.json b/lib/i18n/nb.i18n.json index 0f9c02ab..fcc9c6c3 100644 --- a/lib/i18n/nb.i18n.json +++ b/lib/i18n/nb.i18n.json @@ -702,10 +702,10 @@ "leave": "Forlat", "syncing": "Synkroniserer...", "joinWatchSession": "Bli med i se sammen-økt", - "enterCodeHint": "Skriv inn 8-tegns kode", + "enterCodeHint": "Skriv inn 5-tegns kode", "pasteFromClipboard": "Lim inn fra utklippstavle", "pleaseEnterCode": "Vennligst skriv inn en øktkode", - "codeMustBe8Chars": "Øktkoden må være 8 tegn", + "codeMustBe5Chars": "Øktkoden må være 5 tegn", "joinInstructions": "Skriv inn øktkoden delt av verten for å bli med i se sammen-økten.", "failedToCreate": "Kunne ikke opprette økt", "failedToJoin": "Kunne ikke bli med i økt", @@ -717,7 +717,13 @@ "joinCurrentPlaybackDescription": "Hopp tilbake til det verten ser på nå", "failedToOpenCurrentPlayback": "Kunne ikke åpne gjeldende avspilling", "participantJoined": "${name} ble med", - "participantLeft": "${name} forlot" + "participantLeft": "${name} forlot", + "participantPaused": "${name} pauset", + "participantResumed": "${name} gjenopptok", + "participantSeeked": "${name} spolet", + "recentRooms": "Nylige rom", + "renameRoom": "Gi nytt navn til rom", + "removeRoom": "Fjern" }, "downloads": { "title": "Nedlastinger", diff --git a/lib/i18n/nl.i18n.json b/lib/i18n/nl.i18n.json index 2f543930..f02a6a0f 100644 --- a/lib/i18n/nl.i18n.json +++ b/lib/i18n/nl.i18n.json @@ -729,10 +729,10 @@ "leave": "Verlaten", "syncing": "Synchroniseren...", "joinWatchSession": "Kijksessie Deelnemen", - "enterCodeHint": "Voer 8-teken code in", + "enterCodeHint": "Voer 5-teken code in", "pasteFromClipboard": "Plakken van klembord", "pleaseEnterCode": "Voer een sessiecode in", - "codeMustBe8Chars": "Sessiecode moet 8 tekens zijn", + "codeMustBe5Chars": "Sessiecode moet 5 tekens zijn", "joinInstructions": "Voer de sessiecode in die door de host is gedeeld om deel te nemen aan hun kijksessie.", "failedToCreate": "Sessie maken mislukt", "failedToJoin": "Sessie deelnemen mislukt", @@ -744,7 +744,13 @@ "joinCurrentPlaybackDescription": "Ga terug naar wat de host nu kijkt", "failedToOpenCurrentPlayback": "Huidige weergave kon niet worden geopend", "participantJoined": "${name} is toegetreden", - "participantLeft": "${name} heeft de sessie verlaten" + "participantLeft": "${name} heeft de sessie verlaten", + "participantPaused": "${name} heeft gepauzeerd", + "participantResumed": "${name} heeft hervat", + "participantSeeked": "${name} heeft gespoeld", + "recentRooms": "Recente kamers", + "renameRoom": "Kamer hernoemen", + "removeRoom": "Verwijderen" }, "shaders": { "title": "Shaders", diff --git a/lib/i18n/pl.i18n.json b/lib/i18n/pl.i18n.json index 9bb5f327..313c64ca 100644 --- a/lib/i18n/pl.i18n.json +++ b/lib/i18n/pl.i18n.json @@ -702,10 +702,10 @@ "leave": "Opuść", "syncing": "Synchronizacja...", "joinWatchSession": "Dołącz do sesji oglądania", - "enterCodeHint": "Wprowadź 8-znakowy kod", + "enterCodeHint": "Wprowadź 5-znakowy kod", "pasteFromClipboard": "Wklej ze schowka", "pleaseEnterCode": "Wprowadź kod sesji", - "codeMustBe8Chars": "Kod sesji musi mieć 8 znaków", + "codeMustBe5Chars": "Kod sesji musi mieć 5 znaków", "joinInstructions": "Wprowadź kod sesji udostępniony przez hosta, aby dołączyć do sesji oglądania.", "failedToCreate": "Nie udało się utworzyć sesji", "failedToJoin": "Nie udało się dołączyć do sesji", @@ -717,7 +717,13 @@ "joinCurrentPlaybackDescription": "Wróć do tego, co host aktualnie ogląda", "failedToOpenCurrentPlayback": "Nie udało się otworzyć bieżącego odtwarzania", "participantJoined": "${name} dołączył", - "participantLeft": "${name} opuścił" + "participantLeft": "${name} opuścił", + "participantPaused": "${name} wstrzymał", + "participantResumed": "${name} wznowił", + "participantSeeked": "${name} przewinął", + "recentRooms": "Ostatnie pokoje", + "renameRoom": "Zmień nazwę pokoju", + "removeRoom": "Usuń" }, "downloads": { "title": "Pobrania", diff --git a/lib/i18n/pt.i18n.json b/lib/i18n/pt.i18n.json index 16fb2c6c..8fcd3b4c 100644 --- a/lib/i18n/pt.i18n.json +++ b/lib/i18n/pt.i18n.json @@ -702,10 +702,10 @@ "leave": "Sair", "syncing": "Sincronizando...", "joinWatchSession": "Entrar na Sessão", - "enterCodeHint": "Insira o código de 8 caracteres", + "enterCodeHint": "Insira o código de 5 caracteres", "pasteFromClipboard": "Colar da área de transferência", "pleaseEnterCode": "Insira um código de sessão", - "codeMustBe8Chars": "O código da sessão deve ter 8 caracteres", + "codeMustBe5Chars": "O código da sessão deve ter 5 caracteres", "joinInstructions": "Insira o código da sessão compartilhado pelo anfitrião para entrar na sessão.", "failedToCreate": "Falha ao criar sessão", "failedToJoin": "Falha ao entrar na sessão", @@ -717,7 +717,13 @@ "joinCurrentPlaybackDescription": "Voltar ao que o anfitrião está assistindo agora", "failedToOpenCurrentPlayback": "Falha ao abrir reprodução atual", "participantJoined": "${name} entrou", - "participantLeft": "${name} saiu" + "participantLeft": "${name} saiu", + "participantPaused": "${name} pausou", + "participantResumed": "${name} retomou", + "participantSeeked": "${name} avançou", + "recentRooms": "Salas recentes", + "renameRoom": "Renomear sala", + "removeRoom": "Remover" }, "downloads": { "title": "Downloads", diff --git a/lib/i18n/ru.i18n.json b/lib/i18n/ru.i18n.json index d9f855ae..d9e9c434 100644 --- a/lib/i18n/ru.i18n.json +++ b/lib/i18n/ru.i18n.json @@ -702,10 +702,10 @@ "leave": "Покинуть", "syncing": "Синхронизация...", "joinWatchSession": "Присоединиться к просмотру", - "enterCodeHint": "Введите 8-символьный код", + "enterCodeHint": "Введите 5-символьный код", "pasteFromClipboard": "Вставить из буфера обмена", "pleaseEnterCode": "Введите код сессии", - "codeMustBe8Chars": "Код сессии должен содержать 8 символов", + "codeMustBe5Chars": "Код сессии должен содержать 5 символов", "joinInstructions": "Введите код сессии, предоставленный хостом, чтобы присоединиться к просмотру.", "failedToCreate": "Не удалось создать сессию", "failedToJoin": "Не удалось присоединиться к сессии", @@ -717,7 +717,13 @@ "joinCurrentPlaybackDescription": "Вернуться к тому, что сейчас смотрит хост", "failedToOpenCurrentPlayback": "Не удалось открыть текущее воспроизведение", "participantJoined": "${name} присоединился", - "participantLeft": "${name} вышел" + "participantLeft": "${name} вышел", + "participantPaused": "${name} поставил на паузу", + "participantResumed": "${name} возобновил", + "participantSeeked": "${name} перемотал", + "recentRooms": "Недавние комнаты", + "renameRoom": "Переименовать комнату", + "removeRoom": "Удалить" }, "downloads": { "title": "Загрузки", diff --git a/lib/i18n/strings.g.dart b/lib/i18n/strings.g.dart index f2cc1776..964b878a 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: 12975 (865 per locale) +/// Strings: 13065 (871 per locale) /// -/// Built on 2026-04-11 at 05:59 UTC +/// Built on 2026-04-13 at 10:11 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 7a27ae08..658fb224 100644 --- a/lib/i18n/strings_da.g.dart +++ b/lib/i18n/strings_da.g.dart @@ -955,10 +955,10 @@ class _TranslationsWatchTogetherDa implements TranslationsWatchTogetherEn { @override String get leave => 'Forlad'; @override String get syncing => 'Synkroniserer...'; @override String get joinWatchSession => 'Deltag i se-session'; - @override String get enterCodeHint => 'Indtast 8-tegns kode'; + @override String get enterCodeHint => 'Indtast 5-tegns kode'; @override String get pasteFromClipboard => 'Indsæt fra udklipsholder'; @override String get pleaseEnterCode => 'Indtast en sessionskode'; - @override String get codeMustBe8Chars => 'Sessionskode skal være 8 tegn'; + @override String get codeMustBe5Chars => 'Sessionskode skal være 5 tegn'; @override String get joinInstructions => 'Indtast sessionskoden delt af værten for at deltage i se-sessionen.'; @override String get failedToCreate => 'Kunne ikke oprette session'; @override String get failedToJoin => 'Kunne ikke deltage i session'; @@ -971,6 +971,12 @@ class _TranslationsWatchTogetherDa implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => 'Kunne ikke åbne nuværende afspilning'; @override String participantJoined({required Object name}) => '${name} deltog'; @override String participantLeft({required Object name}) => '${name} forlod'; + @override String participantPaused({required Object name}) => '${name} satte på pause'; + @override String participantResumed({required Object name}) => '${name} genoptog'; + @override String participantSeeked({required Object name}) => '${name} spoled'; + @override String get recentRooms => 'Seneste rum'; + @override String get renameRoom => 'Omdøb rum'; + @override String get removeRoom => 'Fjern'; } // Path: downloads @@ -1998,10 +2004,10 @@ extension on TranslationsDa { 'watchTogether.leave' => 'Forlad', 'watchTogether.syncing' => 'Synkroniserer...', 'watchTogether.joinWatchSession' => 'Deltag i se-session', - 'watchTogether.enterCodeHint' => 'Indtast 8-tegns kode', + 'watchTogether.enterCodeHint' => 'Indtast 5-tegns kode', 'watchTogether.pasteFromClipboard' => 'Indsæt fra udklipsholder', 'watchTogether.pleaseEnterCode' => 'Indtast en sessionskode', - 'watchTogether.codeMustBe8Chars' => 'Sessionskode skal være 8 tegn', + 'watchTogether.codeMustBe5Chars' => 'Sessionskode skal være 5 tegn', 'watchTogether.joinInstructions' => 'Indtast sessionskoden delt af værten for at deltage i se-sessionen.', 'watchTogether.failedToCreate' => 'Kunne ikke oprette session', 'watchTogether.failedToJoin' => 'Kunne ikke deltage i session', @@ -2014,6 +2020,12 @@ extension on TranslationsDa { 'watchTogether.failedToOpenCurrentPlayback' => 'Kunne ikke åbne nuværende afspilning', 'watchTogether.participantJoined' => ({required Object name}) => '${name} deltog', 'watchTogether.participantLeft' => ({required Object name}) => '${name} forlod', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} satte på pause', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} genoptog', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} spoled', + 'watchTogether.recentRooms' => 'Seneste rum', + 'watchTogether.renameRoom' => 'Omdøb rum', + 'watchTogether.removeRoom' => 'Fjern', 'downloads.title' => 'Downloads', 'downloads.manage' => 'Administrer', 'downloads.tvShows' => 'TV-serier', diff --git a/lib/i18n/strings_de.g.dart b/lib/i18n/strings_de.g.dart index 71126208..fcefbfbd 100644 --- a/lib/i18n/strings_de.g.dart +++ b/lib/i18n/strings_de.g.dart @@ -989,10 +989,10 @@ class _TranslationsWatchTogetherDe implements TranslationsWatchTogetherEn { @override String get leave => 'Verlassen'; @override String get syncing => 'Synchronisieren...'; @override String get joinWatchSession => 'Schausitzung Beitreten'; - @override String get enterCodeHint => '8-stelligen Code eingeben'; + @override String get enterCodeHint => '5-stelligen Code eingeben'; @override String get pasteFromClipboard => 'Aus Zwischenablage einfügen'; @override String get pleaseEnterCode => 'Bitte gib einen Sitzungscode ein'; - @override String get codeMustBe8Chars => 'Sitzungscode muss 8 Zeichen haben'; + @override String get codeMustBe5Chars => 'Sitzungscode muss 5 Zeichen haben'; @override String get joinInstructions => 'Gib den vom Host geteilten Sitzungscode ein, um seiner Schausitzung beizutreten.'; @override String get failedToCreate => 'Sitzung konnte nicht erstellt werden'; @override String get failedToJoin => 'Sitzung konnte nicht beigetreten werden'; @@ -1005,6 +1005,12 @@ class _TranslationsWatchTogetherDe implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => 'Aktuelle Wiedergabe konnte nicht geöffnet werden'; @override String participantJoined({required Object name}) => '${name} ist beigetreten'; @override String participantLeft({required Object name}) => '${name} hat die Sitzung verlassen'; + @override String participantPaused({required Object name}) => '${name} hat pausiert'; + @override String participantResumed({required Object name}) => '${name} hat fortgesetzt'; + @override String participantSeeked({required Object name}) => '${name} hat gespult'; + @override String get recentRooms => 'Letzte Räume'; + @override String get renameRoom => 'Raum umbenennen'; + @override String get removeRoom => 'Entfernen'; } // Path: shaders @@ -2023,10 +2029,10 @@ extension on TranslationsDe { 'watchTogether.leave' => 'Verlassen', 'watchTogether.syncing' => 'Synchronisieren...', 'watchTogether.joinWatchSession' => 'Schausitzung Beitreten', - 'watchTogether.enterCodeHint' => '8-stelligen Code eingeben', + 'watchTogether.enterCodeHint' => '5-stelligen Code eingeben', 'watchTogether.pasteFromClipboard' => 'Aus Zwischenablage einfügen', 'watchTogether.pleaseEnterCode' => 'Bitte gib einen Sitzungscode ein', - 'watchTogether.codeMustBe8Chars' => 'Sitzungscode muss 8 Zeichen haben', + 'watchTogether.codeMustBe5Chars' => 'Sitzungscode muss 5 Zeichen haben', 'watchTogether.joinInstructions' => 'Gib den vom Host geteilten Sitzungscode ein, um seiner Schausitzung beizutreten.', 'watchTogether.failedToCreate' => 'Sitzung konnte nicht erstellt werden', 'watchTogether.failedToJoin' => 'Sitzung konnte nicht beigetreten werden', @@ -2039,6 +2045,12 @@ extension on TranslationsDe { 'watchTogether.failedToOpenCurrentPlayback' => 'Aktuelle Wiedergabe konnte nicht geöffnet werden', 'watchTogether.participantJoined' => ({required Object name}) => '${name} ist beigetreten', 'watchTogether.participantLeft' => ({required Object name}) => '${name} hat die Sitzung verlassen', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} hat pausiert', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} hat fortgesetzt', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} hat gespult', + 'watchTogether.recentRooms' => 'Letzte Räume', + 'watchTogether.renameRoom' => 'Raum umbenennen', + 'watchTogether.removeRoom' => 'Entfernen', 'shaders.title' => 'Shader', 'shaders.noShaderDescription' => 'Keine Videoverbesserung', 'shaders.nvscalerDescription' => 'NVIDIA-Bildskalierung für schärferes Video', diff --git a/lib/i18n/strings_en.g.dart b/lib/i18n/strings_en.g.dart index 4b08b0a5..e8c91153 100644 --- a/lib/i18n/strings_en.g.dart +++ b/lib/i18n/strings_en.g.dart @@ -2156,8 +2156,8 @@ class TranslationsWatchTogetherEn { /// en: 'Join Watch Session' String get joinWatchSession => 'Join Watch Session'; - /// en: 'Enter 8-character code' - String get enterCodeHint => 'Enter 8-character code'; + /// en: 'Enter 5-character code' + String get enterCodeHint => 'Enter 5-character code'; /// en: 'Paste from clipboard' String get pasteFromClipboard => 'Paste from clipboard'; @@ -2165,8 +2165,8 @@ class TranslationsWatchTogetherEn { /// en: 'Please enter a session code' String get pleaseEnterCode => 'Please enter a session code'; - /// en: 'Session code must be 8 characters' - String get codeMustBe8Chars => 'Session code must be 8 characters'; + /// en: 'Session code must be 5 characters' + String get codeMustBe5Chars => 'Session code must be 5 characters'; /// en: 'Enter the session code shared by the host to join their watch session.' String get joinInstructions => 'Enter the session code shared by the host to join their watch session.'; @@ -2203,6 +2203,24 @@ class TranslationsWatchTogetherEn { /// en: '${name} left' String participantLeft({required Object name}) => '${name} left'; + + /// en: '${name} paused' + String participantPaused({required Object name}) => '${name} paused'; + + /// en: '${name} resumed' + String participantResumed({required Object name}) => '${name} resumed'; + + /// en: '${name} seeked' + String participantSeeked({required Object name}) => '${name} seeked'; + + /// en: 'Recent Rooms' + String get recentRooms => 'Recent Rooms'; + + /// en: 'Rename Room' + String get renameRoom => 'Rename Room'; + + /// en: 'Remove' + String get removeRoom => 'Remove'; } // Path: downloads @@ -3735,10 +3753,10 @@ extension on Translations { 'watchTogether.leave' => 'Leave', 'watchTogether.syncing' => 'Syncing...', 'watchTogether.joinWatchSession' => 'Join Watch Session', - 'watchTogether.enterCodeHint' => 'Enter 8-character code', + 'watchTogether.enterCodeHint' => 'Enter 5-character code', 'watchTogether.pasteFromClipboard' => 'Paste from clipboard', 'watchTogether.pleaseEnterCode' => 'Please enter a session code', - 'watchTogether.codeMustBe8Chars' => 'Session code must be 8 characters', + 'watchTogether.codeMustBe5Chars' => 'Session code must be 5 characters', 'watchTogether.joinInstructions' => 'Enter the session code shared by the host to join their watch session.', 'watchTogether.failedToCreate' => 'Failed to create session', 'watchTogether.failedToJoin' => 'Failed to join session', @@ -3751,6 +3769,12 @@ extension on Translations { 'watchTogether.failedToOpenCurrentPlayback' => 'Failed to open current playback', 'watchTogether.participantJoined' => ({required Object name}) => '${name} joined', 'watchTogether.participantLeft' => ({required Object name}) => '${name} left', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} paused', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} resumed', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} seeked', + 'watchTogether.recentRooms' => 'Recent Rooms', + 'watchTogether.renameRoom' => 'Rename Room', + 'watchTogether.removeRoom' => 'Remove', 'downloads.title' => 'Downloads', 'downloads.manage' => 'Manage', 'downloads.tvShows' => 'TV Shows', diff --git a/lib/i18n/strings_es.g.dart b/lib/i18n/strings_es.g.dart index 9dfc8bf6..b3d9bce6 100644 --- a/lib/i18n/strings_es.g.dart +++ b/lib/i18n/strings_es.g.dart @@ -955,10 +955,10 @@ class _TranslationsWatchTogetherEs implements TranslationsWatchTogetherEn { @override String get leave => 'Salir'; @override String get syncing => 'Sincronizando...'; @override String get joinWatchSession => 'Unirse a Sesión de Visualización'; - @override String get enterCodeHint => 'Introduce el código de 8 caracteres'; + @override String get enterCodeHint => 'Introduce el código de 5 caracteres'; @override String get pasteFromClipboard => 'Pegar desde el portapapeles'; @override String get pleaseEnterCode => 'Por favor, introduce un código de sesión'; - @override String get codeMustBe8Chars => 'El código de sesión debe tener 8 caracteres'; + @override String get codeMustBe5Chars => 'El código de sesión debe tener 5 caracteres'; @override String get joinInstructions => 'Introduce el código de sesión compartido por el anfitrión para unirte a su sesión.'; @override String get failedToCreate => 'Error al crear la sesión'; @override String get failedToJoin => 'Error al unirse a la sesión'; @@ -971,6 +971,12 @@ class _TranslationsWatchTogetherEs implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => 'No se pudo abrir la reproducción actual'; @override String participantJoined({required Object name}) => '${name} se unió'; @override String participantLeft({required Object name}) => '${name} se fue'; + @override String participantPaused({required Object name}) => '${name} pausó'; + @override String participantResumed({required Object name}) => '${name} reanudó'; + @override String participantSeeked({required Object name}) => '${name} avanzó'; + @override String get recentRooms => 'Salas recientes'; + @override String get renameRoom => 'Renombrar sala'; + @override String get removeRoom => 'Eliminar'; } // Path: downloads @@ -1998,10 +2004,10 @@ extension on TranslationsEs { 'watchTogether.leave' => 'Salir', 'watchTogether.syncing' => 'Sincronizando...', 'watchTogether.joinWatchSession' => 'Unirse a Sesión de Visualización', - 'watchTogether.enterCodeHint' => 'Introduce el código de 8 caracteres', + 'watchTogether.enterCodeHint' => 'Introduce el código de 5 caracteres', 'watchTogether.pasteFromClipboard' => 'Pegar desde el portapapeles', 'watchTogether.pleaseEnterCode' => 'Por favor, introduce un código de sesión', - 'watchTogether.codeMustBe8Chars' => 'El código de sesión debe tener 8 caracteres', + 'watchTogether.codeMustBe5Chars' => 'El código de sesión debe tener 5 caracteres', 'watchTogether.joinInstructions' => 'Introduce el código de sesión compartido por el anfitrión para unirte a su sesión.', 'watchTogether.failedToCreate' => 'Error al crear la sesión', 'watchTogether.failedToJoin' => 'Error al unirse a la sesión', @@ -2014,6 +2020,12 @@ extension on TranslationsEs { 'watchTogether.failedToOpenCurrentPlayback' => 'No se pudo abrir la reproducción actual', 'watchTogether.participantJoined' => ({required Object name}) => '${name} se unió', 'watchTogether.participantLeft' => ({required Object name}) => '${name} se fue', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} pausó', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} reanudó', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} avanzó', + 'watchTogether.recentRooms' => 'Salas recientes', + 'watchTogether.renameRoom' => 'Renombrar sala', + 'watchTogether.removeRoom' => 'Eliminar', 'downloads.title' => 'Descargas', 'downloads.manage' => 'Gestionar', 'downloads.tvShows' => 'Series de TV', diff --git a/lib/i18n/strings_fr.g.dart b/lib/i18n/strings_fr.g.dart index dd4a0314..44761354 100644 --- a/lib/i18n/strings_fr.g.dart +++ b/lib/i18n/strings_fr.g.dart @@ -955,10 +955,10 @@ class _TranslationsWatchTogetherFr implements TranslationsWatchTogetherEn { @override String get leave => 'Fin'; @override String get syncing => 'Synchronisation...'; @override String get joinWatchSession => 'Rejoindre la session de visionnage'; - @override String get enterCodeHint => 'Entrez le code à 8 caractères'; + @override String get enterCodeHint => 'Entrez le code à 5 caractères'; @override String get pasteFromClipboard => 'Coller depuis le presse-papiers'; @override String get pleaseEnterCode => 'Veuillez saisir un code de session'; - @override String get codeMustBe8Chars => 'Le code de session doit comporter 8 caractères'; + @override String get codeMustBe5Chars => 'Le code de session doit comporter 5 caractères'; @override String get joinInstructions => 'Entrez le code de session partagé par l\'hôte pour rejoindre sa session de visionnage.'; @override String get failedToCreate => 'Échec de la création de la session'; @override String get failedToJoin => 'Échec de la connexion à la session'; @@ -971,6 +971,12 @@ class _TranslationsWatchTogetherFr implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => 'Impossible d\'ouvrir la lecture en cours'; @override String participantJoined({required Object name}) => '${name} a rejoint'; @override String participantLeft({required Object name}) => '${name} est parti'; + @override String participantPaused({required Object name}) => '${name} a mis en pause'; + @override String participantResumed({required Object name}) => '${name} a repris'; + @override String participantSeeked({required Object name}) => '${name} a avancé'; + @override String get recentRooms => 'Salons récents'; + @override String get renameRoom => 'Renommer le salon'; + @override String get removeRoom => 'Supprimer'; } // Path: downloads @@ -1998,10 +2004,10 @@ extension on TranslationsFr { 'watchTogether.leave' => 'Fin', 'watchTogether.syncing' => 'Synchronisation...', 'watchTogether.joinWatchSession' => 'Rejoindre la session de visionnage', - 'watchTogether.enterCodeHint' => 'Entrez le code à 8 caractères', + 'watchTogether.enterCodeHint' => 'Entrez le code à 5 caractères', 'watchTogether.pasteFromClipboard' => 'Coller depuis le presse-papiers', 'watchTogether.pleaseEnterCode' => 'Veuillez saisir un code de session', - 'watchTogether.codeMustBe8Chars' => 'Le code de session doit comporter 8 caractères', + 'watchTogether.codeMustBe5Chars' => 'Le code de session doit comporter 5 caractères', 'watchTogether.joinInstructions' => 'Entrez le code de session partagé par l\'hôte pour rejoindre sa session de visionnage.', 'watchTogether.failedToCreate' => 'Échec de la création de la session', 'watchTogether.failedToJoin' => 'Échec de la connexion à la session', @@ -2014,6 +2020,12 @@ extension on TranslationsFr { 'watchTogether.failedToOpenCurrentPlayback' => 'Impossible d\'ouvrir la lecture en cours', 'watchTogether.participantJoined' => ({required Object name}) => '${name} a rejoint', 'watchTogether.participantLeft' => ({required Object name}) => '${name} est parti', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} a mis en pause', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} a repris', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} a avancé', + 'watchTogether.recentRooms' => 'Salons récents', + 'watchTogether.renameRoom' => 'Renommer le salon', + 'watchTogether.removeRoom' => 'Supprimer', 'downloads.title' => 'Téléchargements', 'downloads.manage' => 'Gérer', 'downloads.tvShows' => 'Show TV', diff --git a/lib/i18n/strings_it.g.dart b/lib/i18n/strings_it.g.dart index 2f1353cd..a1af6909 100644 --- a/lib/i18n/strings_it.g.dart +++ b/lib/i18n/strings_it.g.dart @@ -989,10 +989,10 @@ class _TranslationsWatchTogetherIt implements TranslationsWatchTogetherEn { @override String get leave => 'Lascia'; @override String get syncing => 'Sincronizzazione...'; @override String get joinWatchSession => 'Unisciti alla Sessione di Visione'; - @override String get enterCodeHint => 'Inserisci codice di 8 caratteri'; + @override String get enterCodeHint => 'Inserisci codice di 5 caratteri'; @override String get pasteFromClipboard => 'Incolla dagli appunti'; @override String get pleaseEnterCode => 'Inserisci un codice sessione'; - @override String get codeMustBe8Chars => 'Il codice sessione deve essere di 8 caratteri'; + @override String get codeMustBe5Chars => 'Il codice sessione deve essere di 5 caratteri'; @override String get joinInstructions => 'Inserisci il codice sessione condiviso dall\'host per unirti alla loro sessione di visione.'; @override String get failedToCreate => 'Impossibile creare la sessione'; @override String get failedToJoin => 'Impossibile unirsi alla sessione'; @@ -1005,6 +1005,12 @@ class _TranslationsWatchTogetherIt implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => 'Impossibile aprire la riproduzione corrente'; @override String participantJoined({required Object name}) => '${name} si è unito'; @override String participantLeft({required Object name}) => '${name} se ne è andato'; + @override String participantPaused({required Object name}) => '${name} ha messo in pausa'; + @override String participantResumed({required Object name}) => '${name} ha ripreso'; + @override String participantSeeked({required Object name}) => '${name} ha cercato'; + @override String get recentRooms => 'Stanze recenti'; + @override String get renameRoom => 'Rinomina stanza'; + @override String get removeRoom => 'Rimuovi'; } // Path: shaders @@ -2023,10 +2029,10 @@ extension on TranslationsIt { 'watchTogether.leave' => 'Lascia', 'watchTogether.syncing' => 'Sincronizzazione...', 'watchTogether.joinWatchSession' => 'Unisciti alla Sessione di Visione', - 'watchTogether.enterCodeHint' => 'Inserisci codice di 8 caratteri', + 'watchTogether.enterCodeHint' => 'Inserisci codice di 5 caratteri', 'watchTogether.pasteFromClipboard' => 'Incolla dagli appunti', 'watchTogether.pleaseEnterCode' => 'Inserisci un codice sessione', - 'watchTogether.codeMustBe8Chars' => 'Il codice sessione deve essere di 8 caratteri', + 'watchTogether.codeMustBe5Chars' => 'Il codice sessione deve essere di 5 caratteri', 'watchTogether.joinInstructions' => 'Inserisci il codice sessione condiviso dall\'host per unirti alla loro sessione di visione.', 'watchTogether.failedToCreate' => 'Impossibile creare la sessione', 'watchTogether.failedToJoin' => 'Impossibile unirsi alla sessione', @@ -2039,6 +2045,12 @@ extension on TranslationsIt { 'watchTogether.failedToOpenCurrentPlayback' => 'Impossibile aprire la riproduzione corrente', 'watchTogether.participantJoined' => ({required Object name}) => '${name} si è unito', 'watchTogether.participantLeft' => ({required Object name}) => '${name} se ne è andato', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} ha messo in pausa', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} ha ripreso', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} ha cercato', + 'watchTogether.recentRooms' => 'Stanze recenti', + 'watchTogether.renameRoom' => 'Rinomina stanza', + 'watchTogether.removeRoom' => 'Rimuovi', 'shaders.title' => 'Shader', 'shaders.noShaderDescription' => 'Nessun miglioramento video', 'shaders.nvscalerDescription' => 'Ridimensionamento NVIDIA per video più nitido', diff --git a/lib/i18n/strings_ja.g.dart b/lib/i18n/strings_ja.g.dart index db6acca7..c44e6de8 100644 --- a/lib/i18n/strings_ja.g.dart +++ b/lib/i18n/strings_ja.g.dart @@ -955,10 +955,10 @@ class _TranslationsWatchTogetherJa implements TranslationsWatchTogetherEn { @override String get leave => '退出'; @override String get syncing => '同期中...'; @override String get joinWatchSession => '視聴セッションに参加'; - @override String get enterCodeHint => '8文字のコードを入力'; + @override String get enterCodeHint => '5文字のコードを入力'; @override String get pasteFromClipboard => 'クリップボードから貼り付け'; @override String get pleaseEnterCode => 'セッションコードを入力してください'; - @override String get codeMustBe8Chars => 'セッションコードは8文字である必要があります'; + @override String get codeMustBe5Chars => 'セッションコードは5文字である必要があります'; @override String get joinInstructions => 'ホストが共有したセッションコードを入力して視聴セッションに参加してください。'; @override String get failedToCreate => 'セッションの作成に失敗しました'; @override String get failedToJoin => 'セッションへの参加に失敗しました'; @@ -971,6 +971,12 @@ class _TranslationsWatchTogetherJa implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => '現在の再生を開けませんでした'; @override String participantJoined({required Object name}) => '${name}が参加しました'; @override String participantLeft({required Object name}) => '${name}が退出しました'; + @override String participantPaused({required Object name}) => '${name}が一時停止しました'; + @override String participantResumed({required Object name}) => '${name}が再開しました'; + @override String participantSeeked({required Object name}) => '${name}がシークしました'; + @override String get recentRooms => '最近のルーム'; + @override String get renameRoom => 'ルーム名を変更'; + @override String get removeRoom => '削除'; } // Path: downloads @@ -1998,10 +2004,10 @@ extension on TranslationsJa { 'watchTogether.leave' => '退出', 'watchTogether.syncing' => '同期中...', 'watchTogether.joinWatchSession' => '視聴セッションに参加', - 'watchTogether.enterCodeHint' => '8文字のコードを入力', + 'watchTogether.enterCodeHint' => '5文字のコードを入力', 'watchTogether.pasteFromClipboard' => 'クリップボードから貼り付け', 'watchTogether.pleaseEnterCode' => 'セッションコードを入力してください', - 'watchTogether.codeMustBe8Chars' => 'セッションコードは8文字である必要があります', + 'watchTogether.codeMustBe5Chars' => 'セッションコードは5文字である必要があります', 'watchTogether.joinInstructions' => 'ホストが共有したセッションコードを入力して視聴セッションに参加してください。', 'watchTogether.failedToCreate' => 'セッションの作成に失敗しました', 'watchTogether.failedToJoin' => 'セッションへの参加に失敗しました', @@ -2014,6 +2020,12 @@ extension on TranslationsJa { 'watchTogether.failedToOpenCurrentPlayback' => '現在の再生を開けませんでした', 'watchTogether.participantJoined' => ({required Object name}) => '${name}が参加しました', 'watchTogether.participantLeft' => ({required Object name}) => '${name}が退出しました', + 'watchTogether.participantPaused' => ({required Object name}) => '${name}が一時停止しました', + 'watchTogether.participantResumed' => ({required Object name}) => '${name}が再開しました', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name}がシークしました', + 'watchTogether.recentRooms' => '最近のルーム', + 'watchTogether.renameRoom' => 'ルーム名を変更', + 'watchTogether.removeRoom' => '削除', 'downloads.title' => 'ダウンロード', 'downloads.manage' => '管理', 'downloads.tvShows' => 'テレビ番組', diff --git a/lib/i18n/strings_ko.g.dart b/lib/i18n/strings_ko.g.dart index 54d56973..d07e63ea 100644 --- a/lib/i18n/strings_ko.g.dart +++ b/lib/i18n/strings_ko.g.dart @@ -955,10 +955,10 @@ class _TranslationsWatchTogetherKo implements TranslationsWatchTogetherEn { @override String get leave => '이탈'; @override String get syncing => '동기화 중...'; @override String get joinWatchSession => '시청 세션에 참여'; - @override String get enterCodeHint => '8자리 코드 입력'; + @override String get enterCodeHint => '5자리 코드 입력'; @override String get pasteFromClipboard => '클립보드에서 붙여넣기'; @override String get pleaseEnterCode => '세션 코드를 입력하세요'; - @override String get codeMustBe8Chars => '세션 코드는 반드시 8자리여야 합니다'; + @override String get codeMustBe5Chars => '세션 코드는 반드시 5자리여야 합니다'; @override String get joinInstructions => '호스트가 공유한 세션 코드를 입력하여 시청 세션에 참여하세요.'; @override String get failedToCreate => '세션 생성 실패'; @override String get failedToJoin => '세션 참여 실패'; @@ -971,6 +971,12 @@ class _TranslationsWatchTogetherKo implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => '현재 재생을 열 수 없습니다'; @override String participantJoined({required Object name}) => '${name}님이 참여했습니다'; @override String participantLeft({required Object name}) => '${name}님이 나갔습니다'; + @override String participantPaused({required Object name}) => '${name}님이 일시정지했습니다'; + @override String participantResumed({required Object name}) => '${name}님이 재생했습니다'; + @override String participantSeeked({required Object name}) => '${name}님이 탐색했습니다'; + @override String get recentRooms => '최근 방'; + @override String get renameRoom => '방 이름 변경'; + @override String get removeRoom => '제거'; } // Path: downloads @@ -1998,10 +2004,10 @@ extension on TranslationsKo { 'watchTogether.leave' => '이탈', 'watchTogether.syncing' => '동기화 중...', 'watchTogether.joinWatchSession' => '시청 세션에 참여', - 'watchTogether.enterCodeHint' => '8자리 코드 입력', + 'watchTogether.enterCodeHint' => '5자리 코드 입력', 'watchTogether.pasteFromClipboard' => '클립보드에서 붙여넣기', 'watchTogether.pleaseEnterCode' => '세션 코드를 입력하세요', - 'watchTogether.codeMustBe8Chars' => '세션 코드는 반드시 8자리여야 합니다', + 'watchTogether.codeMustBe5Chars' => '세션 코드는 반드시 5자리여야 합니다', 'watchTogether.joinInstructions' => '호스트가 공유한 세션 코드를 입력하여 시청 세션에 참여하세요.', 'watchTogether.failedToCreate' => '세션 생성 실패', 'watchTogether.failedToJoin' => '세션 참여 실패', @@ -2014,6 +2020,12 @@ extension on TranslationsKo { 'watchTogether.failedToOpenCurrentPlayback' => '현재 재생을 열 수 없습니다', 'watchTogether.participantJoined' => ({required Object name}) => '${name}님이 참여했습니다', 'watchTogether.participantLeft' => ({required Object name}) => '${name}님이 나갔습니다', + 'watchTogether.participantPaused' => ({required Object name}) => '${name}님이 일시정지했습니다', + 'watchTogether.participantResumed' => ({required Object name}) => '${name}님이 재생했습니다', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name}님이 탐색했습니다', + 'watchTogether.recentRooms' => '최근 방', + 'watchTogether.renameRoom' => '방 이름 변경', + 'watchTogether.removeRoom' => '제거', 'downloads.title' => '다운로드', 'downloads.manage' => '관리', 'downloads.tvShows' => 'TV 프로그램', diff --git a/lib/i18n/strings_nb.g.dart b/lib/i18n/strings_nb.g.dart index 0582b5d3..291bd4e2 100644 --- a/lib/i18n/strings_nb.g.dart +++ b/lib/i18n/strings_nb.g.dart @@ -955,10 +955,10 @@ class _TranslationsWatchTogetherNb implements TranslationsWatchTogetherEn { @override String get leave => 'Forlat'; @override String get syncing => 'Synkroniserer...'; @override String get joinWatchSession => 'Bli med i se sammen-økt'; - @override String get enterCodeHint => 'Skriv inn 8-tegns kode'; + @override String get enterCodeHint => 'Skriv inn 5-tegns kode'; @override String get pasteFromClipboard => 'Lim inn fra utklippstavle'; @override String get pleaseEnterCode => 'Vennligst skriv inn en øktkode'; - @override String get codeMustBe8Chars => 'Øktkoden må være 8 tegn'; + @override String get codeMustBe5Chars => 'Øktkoden må være 5 tegn'; @override String get joinInstructions => 'Skriv inn øktkoden delt av verten for å bli med i se sammen-økten.'; @override String get failedToCreate => 'Kunne ikke opprette økt'; @override String get failedToJoin => 'Kunne ikke bli med i økt'; @@ -971,6 +971,12 @@ class _TranslationsWatchTogetherNb implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => 'Kunne ikke åpne gjeldende avspilling'; @override String participantJoined({required Object name}) => '${name} ble med'; @override String participantLeft({required Object name}) => '${name} forlot'; + @override String participantPaused({required Object name}) => '${name} pauset'; + @override String participantResumed({required Object name}) => '${name} gjenopptok'; + @override String participantSeeked({required Object name}) => '${name} spolet'; + @override String get recentRooms => 'Nylige rom'; + @override String get renameRoom => 'Gi nytt navn til rom'; + @override String get removeRoom => 'Fjern'; } // Path: downloads @@ -1998,10 +2004,10 @@ extension on TranslationsNb { 'watchTogether.leave' => 'Forlat', 'watchTogether.syncing' => 'Synkroniserer...', 'watchTogether.joinWatchSession' => 'Bli med i se sammen-økt', - 'watchTogether.enterCodeHint' => 'Skriv inn 8-tegns kode', + 'watchTogether.enterCodeHint' => 'Skriv inn 5-tegns kode', 'watchTogether.pasteFromClipboard' => 'Lim inn fra utklippstavle', 'watchTogether.pleaseEnterCode' => 'Vennligst skriv inn en øktkode', - 'watchTogether.codeMustBe8Chars' => 'Øktkoden må være 8 tegn', + 'watchTogether.codeMustBe5Chars' => 'Øktkoden må være 5 tegn', 'watchTogether.joinInstructions' => 'Skriv inn øktkoden delt av verten for å bli med i se sammen-økten.', 'watchTogether.failedToCreate' => 'Kunne ikke opprette økt', 'watchTogether.failedToJoin' => 'Kunne ikke bli med i økt', @@ -2014,6 +2020,12 @@ extension on TranslationsNb { 'watchTogether.failedToOpenCurrentPlayback' => 'Kunne ikke åpne gjeldende avspilling', 'watchTogether.participantJoined' => ({required Object name}) => '${name} ble med', 'watchTogether.participantLeft' => ({required Object name}) => '${name} forlot', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} pauset', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} gjenopptok', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} spolet', + 'watchTogether.recentRooms' => 'Nylige rom', + 'watchTogether.renameRoom' => 'Gi nytt navn til rom', + 'watchTogether.removeRoom' => 'Fjern', 'downloads.title' => 'Nedlastinger', 'downloads.manage' => 'Administrer', 'downloads.tvShows' => 'TV-serier', diff --git a/lib/i18n/strings_nl.g.dart b/lib/i18n/strings_nl.g.dart index ab765178..651ff5ff 100644 --- a/lib/i18n/strings_nl.g.dart +++ b/lib/i18n/strings_nl.g.dart @@ -989,10 +989,10 @@ class _TranslationsWatchTogetherNl implements TranslationsWatchTogetherEn { @override String get leave => 'Verlaten'; @override String get syncing => 'Synchroniseren...'; @override String get joinWatchSession => 'Kijksessie Deelnemen'; - @override String get enterCodeHint => 'Voer 8-teken code in'; + @override String get enterCodeHint => 'Voer 5-teken code in'; @override String get pasteFromClipboard => 'Plakken van klembord'; @override String get pleaseEnterCode => 'Voer een sessiecode in'; - @override String get codeMustBe8Chars => 'Sessiecode moet 8 tekens zijn'; + @override String get codeMustBe5Chars => 'Sessiecode moet 5 tekens zijn'; @override String get joinInstructions => 'Voer de sessiecode in die door de host is gedeeld om deel te nemen aan hun kijksessie.'; @override String get failedToCreate => 'Sessie maken mislukt'; @override String get failedToJoin => 'Sessie deelnemen mislukt'; @@ -1005,6 +1005,12 @@ class _TranslationsWatchTogetherNl implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => 'Huidige weergave kon niet worden geopend'; @override String participantJoined({required Object name}) => '${name} is toegetreden'; @override String participantLeft({required Object name}) => '${name} heeft de sessie verlaten'; + @override String participantPaused({required Object name}) => '${name} heeft gepauzeerd'; + @override String participantResumed({required Object name}) => '${name} heeft hervat'; + @override String participantSeeked({required Object name}) => '${name} heeft gespoeld'; + @override String get recentRooms => 'Recente kamers'; + @override String get renameRoom => 'Kamer hernoemen'; + @override String get removeRoom => 'Verwijderen'; } // Path: shaders @@ -2023,10 +2029,10 @@ extension on TranslationsNl { 'watchTogether.leave' => 'Verlaten', 'watchTogether.syncing' => 'Synchroniseren...', 'watchTogether.joinWatchSession' => 'Kijksessie Deelnemen', - 'watchTogether.enterCodeHint' => 'Voer 8-teken code in', + 'watchTogether.enterCodeHint' => 'Voer 5-teken code in', 'watchTogether.pasteFromClipboard' => 'Plakken van klembord', 'watchTogether.pleaseEnterCode' => 'Voer een sessiecode in', - 'watchTogether.codeMustBe8Chars' => 'Sessiecode moet 8 tekens zijn', + 'watchTogether.codeMustBe5Chars' => 'Sessiecode moet 5 tekens zijn', 'watchTogether.joinInstructions' => 'Voer de sessiecode in die door de host is gedeeld om deel te nemen aan hun kijksessie.', 'watchTogether.failedToCreate' => 'Sessie maken mislukt', 'watchTogether.failedToJoin' => 'Sessie deelnemen mislukt', @@ -2039,6 +2045,12 @@ extension on TranslationsNl { 'watchTogether.failedToOpenCurrentPlayback' => 'Huidige weergave kon niet worden geopend', 'watchTogether.participantJoined' => ({required Object name}) => '${name} is toegetreden', 'watchTogether.participantLeft' => ({required Object name}) => '${name} heeft de sessie verlaten', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} heeft gepauzeerd', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} heeft hervat', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} heeft gespoeld', + 'watchTogether.recentRooms' => 'Recente kamers', + 'watchTogether.renameRoom' => 'Kamer hernoemen', + 'watchTogether.removeRoom' => 'Verwijderen', 'shaders.title' => 'Shaders', 'shaders.noShaderDescription' => 'Geen videoverbetering', 'shaders.nvscalerDescription' => 'NVIDIA-beeldschaling voor scherpere video', diff --git a/lib/i18n/strings_pl.g.dart b/lib/i18n/strings_pl.g.dart index c8abf668..83991d6e 100644 --- a/lib/i18n/strings_pl.g.dart +++ b/lib/i18n/strings_pl.g.dart @@ -955,10 +955,10 @@ class _TranslationsWatchTogetherPl implements TranslationsWatchTogetherEn { @override String get leave => 'Opuść'; @override String get syncing => 'Synchronizacja...'; @override String get joinWatchSession => 'Dołącz do sesji oglądania'; - @override String get enterCodeHint => 'Wprowadź 8-znakowy kod'; + @override String get enterCodeHint => 'Wprowadź 5-znakowy kod'; @override String get pasteFromClipboard => 'Wklej ze schowka'; @override String get pleaseEnterCode => 'Wprowadź kod sesji'; - @override String get codeMustBe8Chars => 'Kod sesji musi mieć 8 znaków'; + @override String get codeMustBe5Chars => 'Kod sesji musi mieć 5 znaków'; @override String get joinInstructions => 'Wprowadź kod sesji udostępniony przez hosta, aby dołączyć do sesji oglądania.'; @override String get failedToCreate => 'Nie udało się utworzyć sesji'; @override String get failedToJoin => 'Nie udało się dołączyć do sesji'; @@ -971,6 +971,12 @@ class _TranslationsWatchTogetherPl implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => 'Nie udało się otworzyć bieżącego odtwarzania'; @override String participantJoined({required Object name}) => '${name} dołączył'; @override String participantLeft({required Object name}) => '${name} opuścił'; + @override String participantPaused({required Object name}) => '${name} wstrzymał'; + @override String participantResumed({required Object name}) => '${name} wznowił'; + @override String participantSeeked({required Object name}) => '${name} przewinął'; + @override String get recentRooms => 'Ostatnie pokoje'; + @override String get renameRoom => 'Zmień nazwę pokoju'; + @override String get removeRoom => 'Usuń'; } // Path: downloads @@ -1998,10 +2004,10 @@ extension on TranslationsPl { 'watchTogether.leave' => 'Opuść', 'watchTogether.syncing' => 'Synchronizacja...', 'watchTogether.joinWatchSession' => 'Dołącz do sesji oglądania', - 'watchTogether.enterCodeHint' => 'Wprowadź 8-znakowy kod', + 'watchTogether.enterCodeHint' => 'Wprowadź 5-znakowy kod', 'watchTogether.pasteFromClipboard' => 'Wklej ze schowka', 'watchTogether.pleaseEnterCode' => 'Wprowadź kod sesji', - 'watchTogether.codeMustBe8Chars' => 'Kod sesji musi mieć 8 znaków', + 'watchTogether.codeMustBe5Chars' => 'Kod sesji musi mieć 5 znaków', 'watchTogether.joinInstructions' => 'Wprowadź kod sesji udostępniony przez hosta, aby dołączyć do sesji oglądania.', 'watchTogether.failedToCreate' => 'Nie udało się utworzyć sesji', 'watchTogether.failedToJoin' => 'Nie udało się dołączyć do sesji', @@ -2014,6 +2020,12 @@ extension on TranslationsPl { 'watchTogether.failedToOpenCurrentPlayback' => 'Nie udało się otworzyć bieżącego odtwarzania', 'watchTogether.participantJoined' => ({required Object name}) => '${name} dołączył', 'watchTogether.participantLeft' => ({required Object name}) => '${name} opuścił', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} wstrzymał', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} wznowił', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} przewinął', + 'watchTogether.recentRooms' => 'Ostatnie pokoje', + 'watchTogether.renameRoom' => 'Zmień nazwę pokoju', + 'watchTogether.removeRoom' => 'Usuń', 'downloads.title' => 'Pobrania', 'downloads.manage' => 'Zarządzaj', 'downloads.tvShows' => 'Seriale TV', diff --git a/lib/i18n/strings_pt.g.dart b/lib/i18n/strings_pt.g.dart index 995b6cd8..80c4a27f 100644 --- a/lib/i18n/strings_pt.g.dart +++ b/lib/i18n/strings_pt.g.dart @@ -955,10 +955,10 @@ class _TranslationsWatchTogetherPt implements TranslationsWatchTogetherEn { @override String get leave => 'Sair'; @override String get syncing => 'Sincronizando...'; @override String get joinWatchSession => 'Entrar na Sessão'; - @override String get enterCodeHint => 'Insira o código de 8 caracteres'; + @override String get enterCodeHint => 'Insira o código de 5 caracteres'; @override String get pasteFromClipboard => 'Colar da área de transferência'; @override String get pleaseEnterCode => 'Insira um código de sessão'; - @override String get codeMustBe8Chars => 'O código da sessão deve ter 8 caracteres'; + @override String get codeMustBe5Chars => 'O código da sessão deve ter 5 caracteres'; @override String get joinInstructions => 'Insira o código da sessão compartilhado pelo anfitrião para entrar na sessão.'; @override String get failedToCreate => 'Falha ao criar sessão'; @override String get failedToJoin => 'Falha ao entrar na sessão'; @@ -971,6 +971,12 @@ class _TranslationsWatchTogetherPt implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => 'Falha ao abrir reprodução atual'; @override String participantJoined({required Object name}) => '${name} entrou'; @override String participantLeft({required Object name}) => '${name} saiu'; + @override String participantPaused({required Object name}) => '${name} pausou'; + @override String participantResumed({required Object name}) => '${name} retomou'; + @override String participantSeeked({required Object name}) => '${name} avançou'; + @override String get recentRooms => 'Salas recentes'; + @override String get renameRoom => 'Renomear sala'; + @override String get removeRoom => 'Remover'; } // Path: downloads @@ -1998,10 +2004,10 @@ extension on TranslationsPt { 'watchTogether.leave' => 'Sair', 'watchTogether.syncing' => 'Sincronizando...', 'watchTogether.joinWatchSession' => 'Entrar na Sessão', - 'watchTogether.enterCodeHint' => 'Insira o código de 8 caracteres', + 'watchTogether.enterCodeHint' => 'Insira o código de 5 caracteres', 'watchTogether.pasteFromClipboard' => 'Colar da área de transferência', 'watchTogether.pleaseEnterCode' => 'Insira um código de sessão', - 'watchTogether.codeMustBe8Chars' => 'O código da sessão deve ter 8 caracteres', + 'watchTogether.codeMustBe5Chars' => 'O código da sessão deve ter 5 caracteres', 'watchTogether.joinInstructions' => 'Insira o código da sessão compartilhado pelo anfitrião para entrar na sessão.', 'watchTogether.failedToCreate' => 'Falha ao criar sessão', 'watchTogether.failedToJoin' => 'Falha ao entrar na sessão', @@ -2014,6 +2020,12 @@ extension on TranslationsPt { 'watchTogether.failedToOpenCurrentPlayback' => 'Falha ao abrir reprodução atual', 'watchTogether.participantJoined' => ({required Object name}) => '${name} entrou', 'watchTogether.participantLeft' => ({required Object name}) => '${name} saiu', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} pausou', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} retomou', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} avançou', + 'watchTogether.recentRooms' => 'Salas recentes', + 'watchTogether.renameRoom' => 'Renomear sala', + 'watchTogether.removeRoom' => 'Remover', 'downloads.title' => 'Downloads', 'downloads.manage' => 'Gerenciar', 'downloads.tvShows' => 'Séries de TV', diff --git a/lib/i18n/strings_ru.g.dart b/lib/i18n/strings_ru.g.dart index fe43810f..29c7264d 100644 --- a/lib/i18n/strings_ru.g.dart +++ b/lib/i18n/strings_ru.g.dart @@ -955,10 +955,10 @@ class _TranslationsWatchTogetherRu implements TranslationsWatchTogetherEn { @override String get leave => 'Покинуть'; @override String get syncing => 'Синхронизация...'; @override String get joinWatchSession => 'Присоединиться к просмотру'; - @override String get enterCodeHint => 'Введите 8-символьный код'; + @override String get enterCodeHint => 'Введите 5-символьный код'; @override String get pasteFromClipboard => 'Вставить из буфера обмена'; @override String get pleaseEnterCode => 'Введите код сессии'; - @override String get codeMustBe8Chars => 'Код сессии должен содержать 8 символов'; + @override String get codeMustBe5Chars => 'Код сессии должен содержать 5 символов'; @override String get joinInstructions => 'Введите код сессии, предоставленный хостом, чтобы присоединиться к просмотру.'; @override String get failedToCreate => 'Не удалось создать сессию'; @override String get failedToJoin => 'Не удалось присоединиться к сессии'; @@ -971,6 +971,12 @@ class _TranslationsWatchTogetherRu implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => 'Не удалось открыть текущее воспроизведение'; @override String participantJoined({required Object name}) => '${name} присоединился'; @override String participantLeft({required Object name}) => '${name} вышел'; + @override String participantPaused({required Object name}) => '${name} поставил на паузу'; + @override String participantResumed({required Object name}) => '${name} возобновил'; + @override String participantSeeked({required Object name}) => '${name} перемотал'; + @override String get recentRooms => 'Недавние комнаты'; + @override String get renameRoom => 'Переименовать комнату'; + @override String get removeRoom => 'Удалить'; } // Path: downloads @@ -1998,10 +2004,10 @@ extension on TranslationsRu { 'watchTogether.leave' => 'Покинуть', 'watchTogether.syncing' => 'Синхронизация...', 'watchTogether.joinWatchSession' => 'Присоединиться к просмотру', - 'watchTogether.enterCodeHint' => 'Введите 8-символьный код', + 'watchTogether.enterCodeHint' => 'Введите 5-символьный код', 'watchTogether.pasteFromClipboard' => 'Вставить из буфера обмена', 'watchTogether.pleaseEnterCode' => 'Введите код сессии', - 'watchTogether.codeMustBe8Chars' => 'Код сессии должен содержать 8 символов', + 'watchTogether.codeMustBe5Chars' => 'Код сессии должен содержать 5 символов', 'watchTogether.joinInstructions' => 'Введите код сессии, предоставленный хостом, чтобы присоединиться к просмотру.', 'watchTogether.failedToCreate' => 'Не удалось создать сессию', 'watchTogether.failedToJoin' => 'Не удалось присоединиться к сессии', @@ -2014,6 +2020,12 @@ extension on TranslationsRu { 'watchTogether.failedToOpenCurrentPlayback' => 'Не удалось открыть текущее воспроизведение', 'watchTogether.participantJoined' => ({required Object name}) => '${name} присоединился', 'watchTogether.participantLeft' => ({required Object name}) => '${name} вышел', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} поставил на паузу', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} возобновил', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} перемотал', + 'watchTogether.recentRooms' => 'Недавние комнаты', + 'watchTogether.renameRoom' => 'Переименовать комнату', + 'watchTogether.removeRoom' => 'Удалить', 'downloads.title' => 'Загрузки', 'downloads.manage' => 'Управление', 'downloads.tvShows' => 'Сериалы', diff --git a/lib/i18n/strings_sv.g.dart b/lib/i18n/strings_sv.g.dart index 1fb15a09..54c91adb 100644 --- a/lib/i18n/strings_sv.g.dart +++ b/lib/i18n/strings_sv.g.dart @@ -989,10 +989,10 @@ class _TranslationsWatchTogetherSv implements TranslationsWatchTogetherEn { @override String get leave => 'Lämna'; @override String get syncing => 'Synkroniserar...'; @override String get joinWatchSession => 'Gå med i Tittarsession'; - @override String get enterCodeHint => 'Ange 8-teckens kod'; + @override String get enterCodeHint => 'Ange 5-teckens kod'; @override String get pasteFromClipboard => 'Klistra in från urklipp'; @override String get pleaseEnterCode => 'Vänligen ange en sessionskod'; - @override String get codeMustBe8Chars => 'Sessionskod måste vara 8 tecken'; + @override String get codeMustBe5Chars => 'Sessionskod måste vara 5 tecken'; @override String get joinInstructions => 'Ange sessionskoden som delats av värden för att gå med i deras tittarsession.'; @override String get failedToCreate => 'Det gick inte att skapa session'; @override String get failedToJoin => 'Det gick inte att gå med i session'; @@ -1005,6 +1005,12 @@ class _TranslationsWatchTogetherSv implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => 'Kunde inte öppna aktuell uppspelning'; @override String participantJoined({required Object name}) => '${name} gick med'; @override String participantLeft({required Object name}) => '${name} lämnade'; + @override String participantPaused({required Object name}) => '${name} pausade'; + @override String participantResumed({required Object name}) => '${name} återupptog'; + @override String participantSeeked({required Object name}) => '${name} spolade'; + @override String get recentRooms => 'Senaste rum'; + @override String get renameRoom => 'Byt namn på rum'; + @override String get removeRoom => 'Ta bort'; } // Path: shaders @@ -2023,10 +2029,10 @@ extension on TranslationsSv { 'watchTogether.leave' => 'Lämna', 'watchTogether.syncing' => 'Synkroniserar...', 'watchTogether.joinWatchSession' => 'Gå med i Tittarsession', - 'watchTogether.enterCodeHint' => 'Ange 8-teckens kod', + 'watchTogether.enterCodeHint' => 'Ange 5-teckens kod', 'watchTogether.pasteFromClipboard' => 'Klistra in från urklipp', 'watchTogether.pleaseEnterCode' => 'Vänligen ange en sessionskod', - 'watchTogether.codeMustBe8Chars' => 'Sessionskod måste vara 8 tecken', + 'watchTogether.codeMustBe5Chars' => 'Sessionskod måste vara 5 tecken', 'watchTogether.joinInstructions' => 'Ange sessionskoden som delats av värden för att gå med i deras tittarsession.', 'watchTogether.failedToCreate' => 'Det gick inte att skapa session', 'watchTogether.failedToJoin' => 'Det gick inte att gå med i session', @@ -2039,6 +2045,12 @@ extension on TranslationsSv { 'watchTogether.failedToOpenCurrentPlayback' => 'Kunde inte öppna aktuell uppspelning', 'watchTogether.participantJoined' => ({required Object name}) => '${name} gick med', 'watchTogether.participantLeft' => ({required Object name}) => '${name} lämnade', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} pausade', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} återupptog', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} spolade', + 'watchTogether.recentRooms' => 'Senaste rum', + 'watchTogether.renameRoom' => 'Byt namn på rum', + 'watchTogether.removeRoom' => 'Ta bort', 'shaders.title' => 'Shaders', 'shaders.noShaderDescription' => 'Ingen videoförbättring', 'shaders.nvscalerDescription' => 'NVIDIA-bildskalning för skarpare video', diff --git a/lib/i18n/strings_zh.g.dart b/lib/i18n/strings_zh.g.dart index bf7c5763..d75be0e9 100644 --- a/lib/i18n/strings_zh.g.dart +++ b/lib/i18n/strings_zh.g.dart @@ -989,10 +989,10 @@ class _TranslationsWatchTogetherZh implements TranslationsWatchTogetherEn { @override String get leave => '离开'; @override String get syncing => '同步中...'; @override String get joinWatchSession => '加入观看会话'; - @override String get enterCodeHint => '输入8位代码'; + @override String get enterCodeHint => '输入5位代码'; @override String get pasteFromClipboard => '从剪贴板粘贴'; @override String get pleaseEnterCode => '请输入会话代码'; - @override String get codeMustBe8Chars => '会话代码必须是8个字符'; + @override String get codeMustBe5Chars => '会话代码必须是5个字符'; @override String get joinInstructions => '输入主持人分享的会话代码以加入他们的观看会话。'; @override String get failedToCreate => '创建会话失败'; @override String get failedToJoin => '加入会话失败'; @@ -1005,6 +1005,12 @@ class _TranslationsWatchTogetherZh implements TranslationsWatchTogetherEn { @override String get failedToOpenCurrentPlayback => '无法打开当前播放'; @override String participantJoined({required Object name}) => '${name} 加入了'; @override String participantLeft({required Object name}) => '${name} 离开了'; + @override String participantPaused({required Object name}) => '${name} 暂停了'; + @override String participantResumed({required Object name}) => '${name} 继续播放了'; + @override String participantSeeked({required Object name}) => '${name} 跳转了'; + @override String get recentRooms => '最近的房间'; + @override String get renameRoom => '重命名房间'; + @override String get removeRoom => '移除'; } // Path: shaders @@ -2023,10 +2029,10 @@ extension on TranslationsZh { 'watchTogether.leave' => '离开', 'watchTogether.syncing' => '同步中...', 'watchTogether.joinWatchSession' => '加入观看会话', - 'watchTogether.enterCodeHint' => '输入8位代码', + 'watchTogether.enterCodeHint' => '输入5位代码', 'watchTogether.pasteFromClipboard' => '从剪贴板粘贴', 'watchTogether.pleaseEnterCode' => '请输入会话代码', - 'watchTogether.codeMustBe8Chars' => '会话代码必须是8个字符', + 'watchTogether.codeMustBe5Chars' => '会话代码必须是5个字符', 'watchTogether.joinInstructions' => '输入主持人分享的会话代码以加入他们的观看会话。', 'watchTogether.failedToCreate' => '创建会话失败', 'watchTogether.failedToJoin' => '加入会话失败', @@ -2039,6 +2045,12 @@ extension on TranslationsZh { 'watchTogether.failedToOpenCurrentPlayback' => '无法打开当前播放', 'watchTogether.participantJoined' => ({required Object name}) => '${name} 加入了', 'watchTogether.participantLeft' => ({required Object name}) => '${name} 离开了', + 'watchTogether.participantPaused' => ({required Object name}) => '${name} 暂停了', + 'watchTogether.participantResumed' => ({required Object name}) => '${name} 继续播放了', + 'watchTogether.participantSeeked' => ({required Object name}) => '${name} 跳转了', + 'watchTogether.recentRooms' => '最近的房间', + 'watchTogether.renameRoom' => '重命名房间', + 'watchTogether.removeRoom' => '移除', 'shaders.title' => '着色器', 'shaders.noShaderDescription' => '无视频增强', 'shaders.nvscalerDescription' => 'NVIDIA 图像缩放,使视频更清晰', diff --git a/lib/i18n/sv.i18n.json b/lib/i18n/sv.i18n.json index fcbd5a33..f2f30ec2 100644 --- a/lib/i18n/sv.i18n.json +++ b/lib/i18n/sv.i18n.json @@ -729,10 +729,10 @@ "leave": "Lämna", "syncing": "Synkroniserar...", "joinWatchSession": "Gå med i Tittarsession", - "enterCodeHint": "Ange 8-teckens kod", + "enterCodeHint": "Ange 5-teckens kod", "pasteFromClipboard": "Klistra in från urklipp", "pleaseEnterCode": "Vänligen ange en sessionskod", - "codeMustBe8Chars": "Sessionskod måste vara 8 tecken", + "codeMustBe5Chars": "Sessionskod måste vara 5 tecken", "joinInstructions": "Ange sessionskoden som delats av värden för att gå med i deras tittarsession.", "failedToCreate": "Det gick inte att skapa session", "failedToJoin": "Det gick inte att gå med i session", @@ -744,7 +744,13 @@ "joinCurrentPlaybackDescription": "Hoppa tillbaka till det värden tittar på just nu", "failedToOpenCurrentPlayback": "Kunde inte öppna aktuell uppspelning", "participantJoined": "${name} gick med", - "participantLeft": "${name} lämnade" + "participantLeft": "${name} lämnade", + "participantPaused": "${name} pausade", + "participantResumed": "${name} återupptog", + "participantSeeked": "${name} spolade", + "recentRooms": "Senaste rum", + "renameRoom": "Byt namn på rum", + "removeRoom": "Ta bort" }, "shaders": { "title": "Shaders", diff --git a/lib/i18n/zh.i18n.json b/lib/i18n/zh.i18n.json index 76255eed..4bd03512 100644 --- a/lib/i18n/zh.i18n.json +++ b/lib/i18n/zh.i18n.json @@ -729,10 +729,10 @@ "leave": "离开", "syncing": "同步中...", "joinWatchSession": "加入观看会话", - "enterCodeHint": "输入8位代码", + "enterCodeHint": "输入5位代码", "pasteFromClipboard": "从剪贴板粘贴", "pleaseEnterCode": "请输入会话代码", - "codeMustBe8Chars": "会话代码必须是8个字符", + "codeMustBe5Chars": "会话代码必须是5个字符", "joinInstructions": "输入主持人分享的会话代码以加入他们的观看会话。", "failedToCreate": "创建会话失败", "failedToJoin": "加入会话失败", @@ -744,7 +744,13 @@ "joinCurrentPlaybackDescription": "回到房主当前正在观看的内容", "failedToOpenCurrentPlayback": "无法打开当前播放", "participantJoined": "${name} 加入了", - "participantLeft": "${name} 离开了" + "participantLeft": "${name} 离开了", + "participantPaused": "${name} 暂停了", + "participantResumed": "${name} 继续播放了", + "participantSeeked": "${name} 跳转了", + "recentRooms": "最近的房间", + "renameRoom": "重命名房间", + "removeRoom": "移除" }, "shaders": { "title": "着色器", diff --git a/lib/services/settings_service.dart b/lib/services/settings_service.dart index f7312678..81464380 100644 --- a/lib/services/settings_service.dart +++ b/lib/services/settings_service.dart @@ -113,6 +113,7 @@ class SettingsService extends BaseSharedPreferencesService { static const String _keyCustomShaderPresets = 'custom_shader_presets'; static const String _keyLiveTvDefaultFavorites = 'live_tv_default_favorites'; static const String _keyCustomRelayUrl = 'custom_relay_url'; + static const String _keyRecentRooms = 'watch_together_recent_rooms'; static const String _keyMatchRefreshRate = 'match_refresh_rate'; static const String _keyMatchDynamicRange = 'match_dynamic_range'; static const String _keyDisplaySwitchDelay = 'display_switch_delay'; @@ -938,6 +939,15 @@ class SettingsService extends BaseSharedPreferencesService { return prefs.getString(_keyCustomRelayUrl); } + // Recent Watch Together rooms + Future setRecentRooms(String json) async { + await prefs.setString(_keyRecentRooms, json); + } + + String? getRecentRooms() { + return prefs.getString(_keyRecentRooms); + } + // Download on WiFi Only Future setDownloadOnWifiOnly(bool value) async { await prefs.setBool(_keyDownloadOnWifiOnly, value); diff --git a/lib/watch_together/providers/watch_together_provider.dart b/lib/watch_together/providers/watch_together_provider.dart index bd4634b4..8751d170 100644 --- a/lib/watch_together/providers/watch_together_provider.dart +++ b/lib/watch_together/providers/watch_together_provider.dart @@ -50,6 +50,9 @@ class WatchTogetherProvider with ChangeNotifier { Timer? _hostReconnectTimer; bool _isWaitingForHostReconnect = false; + // Debounce map for action events (peerId+type → last emission timestamp) + final Map _lastActionEventMs = {}; + /// Generate a random display name for this session static String _generateDisplayName() { const adjectives = ['Happy', 'Sleepy', 'Sunny', 'Cozy', 'Chill', 'Swift', 'Brave', 'Calm', 'Jolly', 'Lucky']; @@ -180,6 +183,8 @@ class WatchTogetherProvider with ChangeNotifier { /// Create a new watch together session as host Future createSession({ required ControlMode controlMode, + String? displayName, + String? sessionId, String? mediaRatingKey, String? mediaServerId, String? mediaTitle, @@ -195,10 +200,10 @@ class WatchTogetherProvider with ChangeNotifier { _setupPeerServiceListeners(); try { - final sessionId = await _peerService!.createSession(); + final createdSessionId = await _peerService!.createSession(sessionId: sessionId); _session = WatchSession.createAsHost( - sessionId: sessionId, + sessionId: createdSessionId, hostPeerId: _peerService!.myPeerId!, controlMode: controlMode, mediaRatingKey: mediaRatingKey, @@ -206,8 +211,7 @@ class WatchTogetherProvider with ChangeNotifier { mediaTitle: mediaTitle, ).copyWith(state: SessionState.connected); - // Generate a random display name and add self to participants - _displayName = _generateDisplayName(); + _displayName = displayName ?? _generateDisplayName(); _participants.add(Participant(peerId: _peerService!.myPeerId!, displayName: _displayName, isHost: true)); _syncManager = WatchTogetherSyncManager( @@ -219,9 +223,9 @@ class WatchTogetherProvider with ChangeNotifier { _wireSyncStateChanges(); notifyListeners(); - appLogger.d('WatchTogether: Session created: $sessionId'); + appLogger.d('WatchTogether: Session created: $createdSessionId'); - return sessionId; + return createdSessionId; } catch (e) { appLogger.e('WatchTogether: Failed to create session', error: e); _session = _session?.copyWith(state: SessionState.error, errorMessage: e.toString()); @@ -231,7 +235,7 @@ class WatchTogetherProvider with ChangeNotifier { } /// Join an existing session as guest - Future joinSession(String sessionId) async { + Future joinSession(String sessionId, {String? displayName}) async { // Clean up any existing session await leaveSession(); _lastHandledCurrentPlaybackKey = null; @@ -251,8 +255,7 @@ class WatchTogetherProvider with ChangeNotifier { // Session will be fully configured when we receive sessionConfig from host _session = _session!.copyWith(state: SessionState.connected, hostPeerId: 'wt-${sessionId.toUpperCase()}'); - // Generate a random display name for this session - _displayName = _generateDisplayName(); + _displayName = displayName ?? _generateDisplayName(); _syncManager = WatchTogetherSyncManager( peerService: _peerService!, @@ -285,6 +288,32 @@ class WatchTogetherProvider with ChangeNotifier { } } + /// Enter a room by code — joins if it exists, creates if empty. + /// + /// Returns `true` if the user became the host. + Future enterRoom(String sessionId, {ControlMode controlMode = ControlMode.anyone, String? displayName}) async { + // Probe the relay to determine if the room exists, then delegate to + // the existing createSession / joinSession which handle all setup. + final customRelayUrl = SettingsService.instanceOrNull?.getCustomRelayUrl(); + final probe = WatchTogetherPeerService(customBaseUrl: customRelayUrl); + try { + final becameHost = await probe.joinOrCreateSession(sessionId); + await probe.disconnect(); + probe.dispose(); + + if (becameHost) { + await createSession(controlMode: controlMode, displayName: displayName, sessionId: sessionId); + } else { + await joinSession(sessionId, displayName: displayName); + } + return becameHost; + } catch (e) { + await probe.disconnect(); + probe.dispose(); + rethrow; + } + } + /// Leave the current session Future leaveSession() async { if (_session == null) return; @@ -320,6 +349,7 @@ class WatchTogetherProvider with ChangeNotifier { _participants.clear(); _isSyncing = false; _lastHandledCurrentPlaybackKey = null; + _lastActionEventMs.clear(); notifyListeners(); appLogger.d('WatchTogether: Session left'); @@ -493,11 +523,39 @@ class WatchTogetherProvider with ChangeNotifier { // Handled at sync manager level (host responds with config) break; + case SyncMessageType.play: + _emitActionEvent(message.peerId, ParticipantEventType.resumed); + break; + + case SyncMessageType.pause: + _emitActionEvent(message.peerId, ParticipantEventType.paused); + break; + + case SyncMessageType.seek: + _emitActionEvent(message.peerId, ParticipantEventType.seeked); + break; + default: break; } } + /// Emit an action event for a remote peer (with 1s debounce per peer+type) + void _emitActionEvent(String? peerId, ParticipantEventType type) { + if (peerId == null || peerId == _peerService?.myPeerId) return; + + final key = '$peerId:${type.name}'; + final now = DateTime.now().millisecondsSinceEpoch; + final last = _lastActionEventMs[key] ?? 0; + if (now - last < 1000) return; + _lastActionEventMs[key] = now; + + final name = _participants.where((p) => p.peerId == peerId).map((p) => p.displayName).firstOrNull; + if (name != null) { + _participantEventController.add(ParticipantEvent(displayName: name, type: type)); + } + } + /// Handle session config from host (guest only) /// This is handled at provider level so it's processed even before player is attached void _handleSessionConfig(SyncMessage message) { @@ -679,7 +737,7 @@ class WatchTogetherProvider with ChangeNotifier { } /// Type of participant event -enum ParticipantEventType { joined, left } +enum ParticipantEventType { joined, left, paused, resumed, seeked } /// Event emitted when a participant joins or leaves class ParticipantEvent { diff --git a/lib/watch_together/screens/watch_together_screen.dart b/lib/watch_together/screens/watch_together_screen.dart index 894be2e9..09e9bcea 100644 --- a/lib/watch_together/screens/watch_together_screen.dart +++ b/lib/watch_together/screens/watch_together_screen.dart @@ -11,12 +11,14 @@ import '../../focus/focusable_button.dart'; import '../../focus/focusable_wrapper.dart'; import '../../services/settings_service.dart'; import '../../utils/app_logger.dart'; +import '../../utils/provider_extensions.dart'; import '../../utils/dialogs.dart'; import '../../utils/snackbar_helper.dart'; import '../../utils/video_player_navigation.dart'; import '../../widgets/focused_scroll_scaffold.dart'; import '../models/watch_session.dart'; import '../providers/watch_together_provider.dart'; +import '../services/recent_rooms_service.dart'; import '../services/watch_together_peer_service.dart'; import '../widgets/join_session_dialog.dart'; @@ -80,16 +82,23 @@ class _NotInSessionView extends StatefulWidget { class _NotInSessionViewState extends State<_NotInSessionView> { bool _isCreating = false; bool _isJoining = false; + String? _enteringRoomCode; bool? _healthOk; String? _customRelayUrl; + List _recentRooms = []; @override void initState() { super.initState(); _customRelayUrl = SettingsService.instanceOrNull?.getCustomRelayUrl(); + _recentRooms = RecentRoomsService.getRecentRooms(); _checkHealth(); } + bool get _isBusy => _isCreating || _isJoining || _enteringRoomCode != null; + + String? get _plexDisplayName => context.userProfile.currentUser?.displayName; + Future _checkHealth() async { try { final client = HttpClient(); @@ -117,7 +126,7 @@ class _NotInSessionViewState extends State<_NotInSessionView> { child: Padding( padding: const EdgeInsets.all(24), child: Column( - mainAxisAlignment: MainAxisAlignment.center, + mainAxisSize: MainAxisSize.min, children: [ Icon(Symbols.group_rounded, size: 80, color: theme.colorScheme.primary), const SizedBox(height: 24), @@ -153,10 +162,10 @@ class _NotInSessionViewState extends State<_NotInSessionView> { SizedBox( width: double.infinity, child: FocusableButton( - autofocus: true, - onPressed: _isCreating || _isJoining ? null : _createSession, + autofocus: _recentRooms.isEmpty, + onPressed: _isBusy ? null : _createSession, child: FilledButton.icon( - onPressed: _isCreating || _isJoining ? null : _createSession, + onPressed: _isBusy ? null : _createSession, icon: _isCreating ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2)) : const Icon(Symbols.add_rounded), @@ -168,9 +177,9 @@ class _NotInSessionViewState extends State<_NotInSessionView> { SizedBox( width: double.infinity, child: FocusableButton( - onPressed: _isCreating || _isJoining ? null : _joinSession, + onPressed: _isBusy ? null : _joinSession, child: OutlinedButton.icon( - onPressed: _isCreating || _isJoining ? null : _joinSession, + onPressed: _isBusy ? null : _joinSession, icon: _isJoining ? const SizedBox(width: 20, height: 20, child: CircularProgressIndicator(strokeWidth: 2)) : const Icon(Symbols.group_add_rounded), @@ -178,6 +187,22 @@ class _NotInSessionViewState extends State<_NotInSessionView> { ), ), ), + if (_recentRooms.isNotEmpty) ...[ + const SizedBox(height: 32), + Align( + alignment: Alignment.centerLeft, + child: Text(t.watchTogether.recentRooms, style: theme.textTheme.titleSmall), + ), + const SizedBox(height: 8), + ..._recentRooms.map((room) => _RecentRoomTile( + room: room, + isBusy: _isBusy, + isEntering: _enteringRoomCode == room.code, + onTap: () => _enterRoom(room), + onRename: () => _renameRoom(room), + onRemove: () => _removeRoom(room), + )), + ], ], ), ), @@ -192,7 +217,12 @@ class _NotInSessionViewState extends State<_NotInSessionView> { setState(() => _isCreating = true); try { - await widget.watchTogether.createSession(controlMode: controlMode); + final sessionId = await widget.watchTogether.createSession( + controlMode: controlMode, + displayName: _plexDisplayName, + ); + await RecentRoomsService.addOrUpdateRoom(sessionId); + if (mounted) setState(() => _recentRooms = RecentRoomsService.getRecentRooms()); } catch (e) { appLogger.e('Failed to create session', error: e); if (mounted) { @@ -250,7 +280,9 @@ class _NotInSessionViewState extends State<_NotInSessionView> { setState(() => _isJoining = true); try { - await widget.watchTogether.joinSession(sessionId); + await widget.watchTogether.joinSession(sessionId, displayName: _plexDisplayName); + await RecentRoomsService.addOrUpdateRoom(sessionId); + if (mounted) setState(() => _recentRooms = RecentRoomsService.getRecentRooms()); } catch (e) { appLogger.e('Failed to join session', error: e); if (mounted) { @@ -262,6 +294,133 @@ class _NotInSessionViewState extends State<_NotInSessionView> { } } } + + Future _enterRoom(RecentRoom room) async { + setState(() => _enteringRoomCode = room.code); + + try { + await widget.watchTogether.enterRoom(room.code, displayName: _plexDisplayName); + await RecentRoomsService.addOrUpdateRoom(room.code); + if (mounted) setState(() => _recentRooms = RecentRoomsService.getRecentRooms()); + } catch (e) { + appLogger.e('Failed to enter room', error: e); + if (mounted) { + ScaffoldMessenger.of(context).showSnackBar(SnackBar(content: Text('${t.watchTogether.failedToJoin}: $e'))); + } + } finally { + if (mounted) { + setState(() => _enteringRoomCode = null); + } + } + } + + Future _renameRoom(RecentRoom room) async { + final controller = TextEditingController(text: room.name ?? ''); + final name = await showDialog( + context: context, + builder: (context) => AlertDialog( + title: Text(t.watchTogether.renameRoom), + content: TextField( + controller: controller, + autofocus: true, + decoration: InputDecoration(hintText: room.code), + onSubmitted: (value) => Navigator.pop(context, value), + ), + actions: [ + TextButton(onPressed: () => Navigator.pop(context), child: Text(t.common.cancel)), + FilledButton(onPressed: () => Navigator.pop(context, controller.text), child: Text(t.common.save)), + ], + ), + ); + controller.dispose(); + if (name == null || !mounted) return; + + await RecentRoomsService.renameRoom(room.code, name.isEmpty ? null : name); + setState(() => _recentRooms = RecentRoomsService.getRecentRooms()); + } + + Future _removeRoom(RecentRoom room) async { + await RecentRoomsService.removeRoom(room.code); + setState(() => _recentRooms = RecentRoomsService.getRecentRooms()); + } +} + +class _RecentRoomTile extends StatelessWidget { + final RecentRoom room; + final bool isBusy; + final bool isEntering; + final VoidCallback onTap; + final VoidCallback onRename; + final VoidCallback onRemove; + + const _RecentRoomTile({ + required this.room, + required this.isBusy, + required this.isEntering, + required this.onTap, + required this.onRename, + required this.onRemove, + }); + + @override + Widget build(BuildContext context) { + final theme = Theme.of(context); + final title = room.name ?? room.code; + + return Padding( + padding: const EdgeInsets.only(bottom: 4), + child: FocusableWrapper( + useBackgroundFocus: true, + borderRadius: 12, + onSelect: isBusy ? null : onTap, + onLongPress: () => _showActions(context), + child: ListTile( + shape: const RoundedRectangleBorder(borderRadius: BorderRadius.all(Radius.circular(12))), + leading: isEntering + ? const SizedBox(width: 24, height: 24, child: CircularProgressIndicator(strokeWidth: 2)) + : const Icon(Symbols.meeting_room_rounded), + title: Text(title, maxLines: 1, overflow: TextOverflow.ellipsis), + subtitle: room.name != null + ? Text(room.code, style: TextStyle(fontFamily: 'monospace', color: theme.colorScheme.onSurfaceVariant)) + : null, + trailing: IconButton( + icon: const Icon(Symbols.more_vert_rounded), + onPressed: () => _showActions(context), + ), + onTap: isBusy ? null : onTap, + ), + ), + ); + } + + void _showActions(BuildContext context) { + showModalBottomSheet( + context: context, + builder: (context) => SafeArea( + child: Column( + mainAxisSize: MainAxisSize.min, + children: [ + ListTile( + leading: const Icon(Symbols.edit_rounded), + title: Text(t.watchTogether.renameRoom), + onTap: () { + Navigator.pop(context); + onRename(); + }, + ), + ListTile( + leading: Icon(Symbols.delete_rounded, color: Theme.of(context).colorScheme.error), + title: Text(t.watchTogether.removeRoom, style: TextStyle(color: Theme.of(context).colorScheme.error)), + onTap: () { + Navigator.pop(context); + onRemove(); + }, + ), + ], + ), + ), + ); + } } /// Content shown when in an active session (without scroll wrapper) diff --git a/lib/watch_together/services/recent_rooms_service.dart b/lib/watch_together/services/recent_rooms_service.dart new file mode 100644 index 00000000..94fa0209 --- /dev/null +++ b/lib/watch_together/services/recent_rooms_service.dart @@ -0,0 +1,78 @@ +import 'dart:convert'; + +import '../../services/settings_service.dart'; + +class RecentRoom { + final String code; + final String? name; + final DateTime lastUsed; + + const RecentRoom({required this.code, this.name, required this.lastUsed}); + + Map toJson() => { + 'code': code, + if (name != null) 'name': name, + 'lastUsed': lastUsed.millisecondsSinceEpoch, + }; + + factory RecentRoom.fromJson(Map json) => RecentRoom( + code: json['code'] as String, + name: json['name'] as String?, + lastUsed: DateTime.fromMillisecondsSinceEpoch(json['lastUsed'] as int), + ); + + RecentRoom copyWith({String? code, String? name, DateTime? lastUsed, bool clearName = false}) => RecentRoom( + code: code ?? this.code, + name: clearName ? null : (name ?? this.name), + lastUsed: lastUsed ?? this.lastUsed, + ); +} + +class RecentRoomsService { + static const int _maxRooms = 20; + + static List getRecentRooms() { + final json = SettingsService.instanceOrNull?.getRecentRooms(); + if (json == null) return []; + try { + final list = jsonDecode(json) as List; + final rooms = list.map((e) => RecentRoom.fromJson(e as Map)).toList(); + rooms.sort((a, b) => b.lastUsed.compareTo(a.lastUsed)); + return rooms; + } catch (_) { + return []; + } + } + + static Future _save(List rooms) async { + rooms.sort((a, b) => b.lastUsed.compareTo(a.lastUsed)); + if (rooms.length > _maxRooms) rooms.removeRange(_maxRooms, rooms.length); + await SettingsService.instanceOrNull?.setRecentRooms(jsonEncode(rooms.map((r) => r.toJson()).toList())); + } + + static Future addOrUpdateRoom(String code, {String? name}) async { + final rooms = getRecentRooms(); + final index = rooms.indexWhere((r) => r.code == code); + if (index >= 0) { + rooms[index] = rooms[index].copyWith(lastUsed: DateTime.now(), name: name ?? rooms[index].name); + } else { + rooms.add(RecentRoom(code: code, name: name, lastUsed: DateTime.now())); + } + await _save(rooms); + } + + static Future removeRoom(String code) async { + final rooms = getRecentRooms(); + rooms.removeWhere((r) => r.code == code); + await _save(rooms); + } + + static Future renameRoom(String code, String? name) async { + final rooms = getRecentRooms(); + final index = rooms.indexWhere((r) => r.code == code); + if (index >= 0) { + rooms[index] = rooms[index].copyWith(name: name, clearName: name == null); + await _save(rooms); + } + } +} diff --git a/lib/watch_together/services/watch_together_peer_service.dart b/lib/watch_together/services/watch_together_peer_service.dart index 7ae18984..678446c5 100644 --- a/lib/watch_together/services/watch_together_peer_service.dart +++ b/lib/watch_together/services/watch_together_peer_service.dart @@ -1,5 +1,6 @@ import 'dart:async'; import 'dart:convert'; +import 'dart:math'; import 'package:uuid/uuid.dart'; import '../../utils/future_extensions.dart'; @@ -98,9 +99,11 @@ class WatchTogetherPeerService with KeepaliveMixin { /// List of connected peer IDs List get connectedPeers => _connectedPeers.toList(); - /// Generate a short, readable session ID - String _generateSessionId() { - return const Uuid().v4().substring(0, 8).toUpperCase(); + /// Generate a short, readable session ID (5 alphanumeric chars) + static String _generateSessionId() { + const chars = 'ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789'; + final random = Random.secure(); + return String.fromCharCodes(List.generate(5, (_) => chars.codeUnitAt(random.nextInt(chars.length)))); } /// Connect to the relay WebSocket and set up the message listener. @@ -308,13 +311,14 @@ class WatchTogetherPeerService with KeepaliveMixin { /// Create a new session as host /// /// Returns the session ID that others can use to join. - Future createSession() async { + /// If [sessionId] is provided, uses that instead of generating a new one. + Future createSession({String? sessionId}) async { if (_channel != null) { await disconnect(); } _isHost = true; - _sessionId = _generateSessionId(); + _sessionId = sessionId?.toUpperCase() ?? _generateSessionId(); _myPeerId = 'wt-$_sessionId'; _reconnectAttempts = 0; @@ -380,6 +384,20 @@ class WatchTogetherPeerService with KeepaliveMixin { } } + /// Try to join a session; if it doesn't exist, create it as host. + /// + /// Returns `true` if the user became the host (room was empty). + Future joinOrCreateSession(String sessionId) async { + try { + await joinSession(sessionId); + return false; // joined as guest + } on PeerError catch (e) { + if (e.type != PeerErrorType.serverError) rethrow; + await createSession(sessionId: sessionId); + return true; // created as host + } + } + /// Broadcast a message to all connected peers void broadcast(SyncMessage message) { final payload = message.toJson(); diff --git a/lib/watch_together/widgets/join_session_dialog.dart b/lib/watch_together/widgets/join_session_dialog.dart index 5f98269e..fd81cfad 100644 --- a/lib/watch_together/widgets/join_session_dialog.dart +++ b/lib/watch_together/widgets/join_session_dialog.dart @@ -72,7 +72,7 @@ class _JoinSessionDialogState extends State { border: const OutlineInputBorder(), ), textCapitalization: TextCapitalization.characters, - maxLength: 8, + maxLength: 5, inputFormatters: [ FilteringTextInputFormatter.allow(RegExp(r'[A-Za-z0-9]')), UpperCaseTextFormatter(), @@ -81,8 +81,8 @@ class _JoinSessionDialogState extends State { if (value == null || value.isEmpty) { return t.watchTogether.pleaseEnterCode; } - if (value.length != 8) { - return t.watchTogether.codeMustBe8Chars; + if (value.length != 5) { + return t.watchTogether.codeMustBe5Chars; } return null; }, @@ -123,7 +123,7 @@ class _JoinSessionDialogState extends State { // Clean the pasted text - extract alphanumeric characters and take first 8 final cleaned = data!.text!.replaceAll(RegExp(r'[^A-Za-z0-9]'), '').toUpperCase(); if (cleaned.isNotEmpty) { - _sessionIdController.text = cleaned.substring(0, cleaned.length.clamp(0, 8)); + _sessionIdController.text = cleaned.substring(0, cleaned.length.clamp(0, 5)); _sessionIdController.selection = TextSelection.collapsed(offset: _sessionIdController.text.length); } } diff --git a/lib/watch_together/widgets/watch_together_overlay.dart b/lib/watch_together/widgets/watch_together_overlay.dart index 54166ba1..4f37a29c 100644 --- a/lib/watch_together/widgets/watch_together_overlay.dart +++ b/lib/watch_together/widgets/watch_together_overlay.dart @@ -346,9 +346,13 @@ class _ParticipantNotificationOverlayState extends State t.watchTogether.participantJoined(name: n.event.displayName), + ParticipantEventType.left => t.watchTogether.participantLeft(name: n.event.displayName), + ParticipantEventType.paused => t.watchTogether.participantPaused(name: n.event.displayName), + ParticipantEventType.resumed => t.watchTogether.participantResumed(name: n.event.displayName), + ParticipantEventType.seeked => t.watchTogether.participantSeeked(name: n.event.displayName), + }; return Container( margin: const EdgeInsets.only(bottom: 4), padding: const EdgeInsets.symmetric(horizontal: 16, vertical: 8),