From 074eab22f3a0a6defb8101807219c16b866e0f7b Mon Sep 17 00:00:00 2001 From: Damontecres Date: Sat, 12 Sep 2026 15:14:14 -0400 Subject: [PATCH 01/58] Increase memory for CI builds (#1954) ## Description Increases the memory available for CI builds and splits up builds a bit more There are no user facing changes ### Related issues Recently dev builds failed due to OOM ### Testing https://github.com/damontecres/Wholphin/actions/runs/34710759629 ## Screenshots N/A ## AI or LLM usage None --- .github/workflows/main.yml | 5 +++-- .github/workflows/pr.yml | 2 +- .github/workflows/release.yml | 5 +++-- 3 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 75abc88d0..3bd2e54dc 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -16,7 +16,7 @@ concurrency: env: BRANCH_NAME: ${{ github.ref_name }} - GRADLE_OPTS: "-Dorg.gradle.jvmargs='-Xmx8g -XX:MaxMetaspaceSize=512m'" + GRADLE_OPTS: "-Dorg.gradle.jvmargs='-Xmx12g -XX:MaxMetaspaceSize=1024m'" jobs: build: @@ -54,7 +54,8 @@ jobs: ORG_GRADLE_PROJECT_WholphinExtensionsUsername: "${{ secrets.EXTENSIONS_USERNAME }}" ORG_GRADLE_PROJECT_WholphinExtensionsPassword: "${{ secrets.EXTENSIONS_PASSWORD }}" run: | - ./gradlew clean assembleDefaultRelease assembleDefaultDebug --no-daemon + ./gradlew clean assembleDefaultDebug + ./gradlew assembleDefaultRelease - name: Verify signatures run: | diff --git a/.github/workflows/pr.yml b/.github/workflows/pr.yml index ffb33c03e..9e5184063 100644 --- a/.github/workflows/pr.yml +++ b/.github/workflows/pr.yml @@ -9,7 +9,7 @@ defaults: env: BUILD_DIRS_ARTIFACT: build-dirs - GRADLE_OPTS: "-Dorg.gradle.jvmargs='-Xmx8g -XX:MaxMetaspaceSize=512m'" + GRADLE_OPTS: "-Dorg.gradle.jvmargs='-Xmx12g -XX:MaxMetaspaceSize=1024m'" jobs: pre-commit: diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 290d065e1..4822ce7e4 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -11,7 +11,7 @@ defaults: env: APK_SHORT_NAME: Wholphin.apk - GRADLE_OPTS: "-Dorg.gradle.jvmargs='-Xmx8g -XX:MaxMetaspaceSize=512m'" + GRADLE_OPTS: "-Dorg.gradle.jvmargs='-Xmx12g -XX:MaxMetaspaceSize=1024m'" TAG_NAME: ${{ github.ref_name }} jobs: @@ -36,7 +36,8 @@ jobs: ORG_GRADLE_PROJECT_WholphinExtensionsUsername: "${{ secrets.EXTENSIONS_USERNAME }}" ORG_GRADLE_PROJECT_WholphinExtensionsPassword: "${{ secrets.EXTENSIONS_PASSWORD }}" run: | - ./gradlew clean bundleAppstoreRelease bundleFiretvRelease + ./gradlew clean bundleAppstoreRelease + ./gradlew bundleFiretvRelease ./gradlew assembleDefaultRelease - name: Verify signatures run: | From 98fba1340f5ef748f25e2cd1b931b08a743fd899 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Sat, 12 Sep 2026 18:38:54 -0400 Subject: [PATCH 02/58] Fix OS screensaver starting during playback with dim enabled (#1956) ## Description Fixes some devices activating the OS screensaver during playback when the in-app dim was enabled. I was not able to reproduce this on Shield 2017/2019, but could easily on a Fire TV Stick. #1834 added the dim feature and, when enabled, would not pass the keep screen on flag to the OS during playback. It seems maybe regular Android TV's screensaver also listened to active media session to prevent starting but maybe Fire TV OS doesn't. I'm not 100% sure. This PR changes the logic slightly so that the keep screen on flag is always set during playback. ### Related issues Fixes #1894 Related to dim feature from https://github.com/damontecres/Wholphin/pull/1834 ### Testing Emulator, shield 2019, & fire tv stick 4k ## Screenshots N/A ## AI or LLM usage None --- .../damontecres/wholphin/services/ScreensaverService.kt | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt index 09aa29a82..c3fcb4786 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt @@ -173,9 +173,10 @@ class ScreensaverService */ fun keepScreenOn(keep: Boolean) { scope.launchDefault { - val screensaverEnabled = _state.value.run { enabled || dimEnabled } + val screensaverEnabled = state.value.enabled + val dimEnabled = state.value.dimEnabled Timber.d("Keep screen on: %s, screensaverEnabled=%s", keep, screensaverEnabled) - if (screensaverEnabled) { + if (screensaverEnabled || dimEnabled) { // Page is requesting to keep screen on, so we don't wait to show the screensaver _state.update { it.copy( @@ -187,7 +188,9 @@ class ScreensaverService if (!keep) { pulse() } - } else { + } + if (!screensaverEnabled) { + // If in-app screensaver is not enabled, send keep screen on to the OS keepScreenOnInternal(keep) } } From 02163ba47c31d2f5041984576150f4e6bc2837ff Mon Sep 17 00:00:00 2001 From: SergeiAK Date: Sat, 22 Aug 2026 13:13:16 +0000 Subject: [PATCH 03/58] Translated using Weblate (Russian) Currently translated at 65.0% (421 of 647 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/ru/ --- app/src/main/res/values-ru/strings.xml | 135 ++++++++++++++++++++++--- 1 file changed, 122 insertions(+), 13 deletions(-) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e83ae0a83..a17f3cbc4 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -20,7 +20,7 @@ Подтвердить Продолжить просмотр Оценка критиков - %.1f секунда + %.2f секунд По умолчанию Удалить Умер @@ -124,13 +124,15 @@ Продолжительность Дополнительно - Другое + Другой Других Других Других За кадром + За кадром + За кадром За кадром @@ -272,7 +274,7 @@ Автовоспроизведение следующей серии Применяется только к Телешоу Объединить Продолжить просмотр и Далее - Прямое воспроизведение ASS-субтитров + Использовать libaas для ASS-субтитров Прямое воспроизведение PGS-субтитров Размер шрифта Цвет щрифта @@ -294,12 +296,16 @@ %d объектов - Отрывки - Отрывки + Образец + Образца + Образцов + Образцов - Дополнительные материалы - Дополнительные материалы + Дополнительный материал + Дополнительных материала + Дополнительных материалов + Дополнительных материалов Выбор плеера Толщина обводки @@ -307,12 +313,16 @@ Максимальное количество элементов в ряду на домашней странице Форсированные - Саундтреки - Саундтреки + Тематическая песня + Тематические песни + Тематических песен + Тематических песен - Видеоролики - Видеоролики + Тематическое видео + Тематических видео + Тематических видео + Тематических видео Настроить элементы боковой панели Выберите элементы для показа на панели, остальные элементы будут скрыты @@ -326,8 +336,10 @@ Смена страниц по выделению Остановка воспроизведения при бездействии - Короткометражки - Короткометражки + Короткометражка + Короткометражки + Короткометражек + Короткометражек Конец в %1$s Введите адрес сервера @@ -416,4 +428,101 @@ Светло-серый Темно-серый Желтый + + Фильм + Фильма + Фильмов + Фильмов + + + Сериал + Сериала + Сериалов + Сериалов + + + Человек + Человека + Человек + Человек + + + %1$d год + %1$d года + %1$d лет + %1$d лет + + Использовать настройки профиля с сервера + Трейлеры + За кадром + Тематические Песни + Тематические Видео + Клипы + Удаленные Сцены + Интервью + Сцены + Требуется логин для профиля + [0–9] + AVC + SDR + HDR10 + HDR10+ + HLG + Hz + Показать все результаты в одном списке + Показать результаты по категориям + Показать кнопку голосового ввода + DTS:X + DTS:HD + DTS:HD MA + TrueHD + DD + DD+ + DTS + Прямое воспроизведение Dolby Vision Profile 7 + Серверная версия не поддерживается + Избранное %s + Отключение выгрузки аудио-декодирования может исправить фризы воспроизведения на некоторых устройствах + NAL + Показывать названия + Повтор + Сначала показывать избранные каналы + Сортировка каналов по недавно просмотренным + Стиль фона + Комбинированные результаты поиска + Видимый + Скрытый + Результаты + Переключение разрешения + Локальный + Очистить выбор дорожки + Игнорировать проверки совместимости устройства + Обзор + Запрос + В ожидании + Удалить сервер Seerr + Сервер Seerr добавлен + Быстрое Поключение + Авторизовать другое устройство для поключения к вашей учетной записи + Ввести код Быстрого Подключения + В коде должно быть 6 цифр + Устройство авторизовано успешно + URL + В тренде + Ожидаемые сериалы + Ожидаемые фильмы + Запросить в 4K + Программное декодирование AV1 + MPV сейчас плеер по умолчанию исключая HDR.\nВы можете изменить это в настройках. + Воспроизвести слайдшоу + Остановить слайдшоу + Больше нет фото + Повернуть влево + Повернуть вправо + Приблизить + Отдалить + Продолжитьльность слайдшоу + Воспроизводить видео во время слайдшоу + Отправить лог информации о медиа на сервер + Без ограничения From f7c391ad54877bb0f2eb0c03ccb41c5f45cff0d0 Mon Sep 17 00:00:00 2001 From: jatag41526 Date: Sat, 22 Aug 2026 07:03:10 +0000 Subject: [PATCH 04/58] Translated using Weblate (Russian) Currently translated at 65.0% (421 of 647 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/ru/ --- app/src/main/res/values-ru/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index a17f3cbc4..3fa698a28 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -525,4 +525,7 @@ Воспроизводить видео во время слайдшоу Отправить лог информации о медиа на сервер Без ограничения + HDR + Размытие + Расположение From f3239c7b15b69cf16fc13d3d3d2ff3a66f043073 Mon Sep 17 00:00:00 2001 From: true-seeker Date: Sat, 22 Aug 2026 07:02:48 +0000 Subject: [PATCH 05/58] Translated using Weblate (Russian) Currently translated at 65.0% (421 of 647 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/ru/ --- app/src/main/res/values-ru/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index 3fa698a28..ba8bb7b3d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -528,4 +528,6 @@ HDR Размытие Расположение + Никогда не использовать FFmpeg + Использовать FFmpeg только, если не существует встроенных декодеров From d6eb23d29f8b362e2143b336972396d53901477e Mon Sep 17 00:00:00 2001 From: olliveiraanderson Date: Sun, 23 Aug 2026 00:11:30 +0000 Subject: [PATCH 06/58] Translated using Weblate (Portuguese (Brazil)) Currently translated at 96.1% (622 of 647 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pt_BR/ --- app/src/main/res/values-pt-rBR/strings.xml | 62 ++++++++++++++++++++++ 1 file changed, 62 insertions(+) diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 37217d9fc..e42abda34 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -655,4 +655,66 @@ %1$ds Configuração de Perfil de Usuário do servidor %1$s left + Conteúdos extras + Curtos + + Trailer + Trailers + Trailers + + Exigir login para o perfil + 0-9 + Desativar aceleração de hardware de áudio + Desativar a aceleração de hardware de áudio pode corrigir travamentos durante a reprodução em alguns dispositivos + + Usar AC3 para áudio surround + Marrom + Idioma de exibição + Exibir classificações + Personalizar itens exibidos + Proteção do perfil + Clique em Continuar assistindo/Próximo + Reproduzir ao clicar + Mostrar detalhes ao clicar + Estrelas + Tomates + Ordenar + Dividir em linhas separadas + %1$s Filmes + %1$s Séries + Perfil de qualidade + Padrão + Pasta raiz + Popularidade + Status de produção + Usar estilo diferente para HDR + Configurações experimentais + Ativar configurações experimentais? + Configurações experimentais podem ser instáveis!\n\nElas podem ser removidas ou alteradas em qualquer versão. + Tunelamento de vídeo + Modo da barra de busca com D-Pad + Mínimo + Navegação rápida + Barra de progresso pequena na parte inferior da tela (padrão) + Barra de progresso com imagens de pré-visualização do Trickplay + Sem barra de progresso + Bloqueado + Indisponível + Modo de legenda + Usar configuração do seu perfil + Padrão + Inteligente + Somente forçadas + Sempre + Nenhuma + Idioma preferido das legendas + Idioma preferido do áudio + Mais configurações do perfil + Substituir as configurações do seu perfil definidas pelo servidor + Qualquer idioma + Correspondências de hash + SDH + Programas de TV + Gravações + Pastas From 9602b4f3f0f97122a18d93391255b70a08954f30 Mon Sep 17 00:00:00 2001 From: Prahlis Date: Sun, 23 Aug 2026 08:20:13 +0000 Subject: [PATCH 07/58] Translated using Weblate (Swedish) Currently translated at 90.5% (586 of 647 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/sv/ --- app/src/main/res/values-sv/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 4f29ade68..655d33acf 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -346,7 +346,7 @@ Växling av uppdateringsfrekvens Referensbilder Röd - Växling av uppdateringsfrekvens + Växling av upplösning Försök igen Tillåt omvisning i “Nästa upp” API nyckel From 048a5f35b68ca2e555e91080ec3c3f4a2ca81e78 Mon Sep 17 00:00:00 2001 From: arcker95 Date: Sun, 23 Aug 2026 20:02:01 +0000 Subject: [PATCH 08/58] Translated using Weblate (Italian) Currently translated at 100.0% (647 of 647 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/it/ --- app/src/main/res/values-it/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 973cde7ac..217053150 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -726,4 +726,7 @@ Appare in Disabilita offload hardware audio Disabilitare l’offload della decodifica audio può risolvere i blocchi di riproduzione su alcuni dispositivi + Scurisci lo schermo dopo un ritardo + Percentuale di oscuramento + Scurisci schermo From db7cfaee9b06a3ed879af5c124137ba9fde9c0d5 Mon Sep 17 00:00:00 2001 From: tal_sarid Date: Mon, 24 Aug 2026 04:06:45 +0000 Subject: [PATCH 09/58] Translated using Weblate (Hebrew) Currently translated at 100.0% (647 of 647 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/he/ --- app/src/main/res/values-iw/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 59c9fb5af..812130026 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -726,4 +726,7 @@ מופיע ב־ השבתת פענוח אודיו בהורדה עשויה לתקן קפיאות בניגון במכשירים מסוימים השבתת פענוח אודיו בהורדה + עמעם תצוגה אחרי השהייה + עוצמת העמעום + עמעם תצוגה From 4f4e13723c0634721256b95701143d2b0c81088c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Mon, 24 Aug 2026 15:26:06 +0000 Subject: [PATCH 10/58] Translated using Weblate (Estonian) Currently translated at 100.0% (647 of 647 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/et/ --- app/src/main/res/values-et/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 4ce7a329a..a1576ce9b 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -704,4 +704,7 @@ On osalenud siin: Lülita graafikaprotsessori kasutamine heli töötluseks välja Kui lülita graafikaprotsessori kasutamise heli töötluseks välja, siis see võib mõnede seadmete puhul parandada taasesituse hangumise vead + Hämarda ekraani peale viivitust + Hämardamise protsent + Hämarda ekraani From 8931bf5d7a67c651fe04d9296698380cab94695f Mon Sep 17 00:00:00 2001 From: danpergal84 Date: Thu, 27 Aug 2026 12:38:38 +0000 Subject: [PATCH 11/58] Translated using Weblate (Spanish) Currently translated at 100.0% (649 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/ --- app/src/main/res/values-es/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 0c739c8a5..582a44054 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -726,4 +726,9 @@ Aparece en Desactivar el procesamiento de audio por hardware Desactivar el procesamiento de audio por hardware puede solucionar los bloqueos de reproducción en algunos dispositivos + Atenuar la pantalla tras un tiempo de inactividad + Porcentaje de atenuación + Atenuar pantalla + Álbumes de fotos + ¡No se encontraron favoritos! From 0b275a2daa848f1f8eebc22cb552e37b4480fc24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Thu, 27 Aug 2026 08:27:33 +0000 Subject: [PATCH 12/58] Translated using Weblate (Estonian) Currently translated at 100.0% (649 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/et/ --- app/src/main/res/values-et/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index a1576ce9b..380be3e5c 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -707,4 +707,6 @@ Hämarda ekraani peale viivitust Hämardamise protsent Hämarda ekraani + Fotoalbumid + Ühtegi lemmikut ei leidu! From c0401914a749932709dc15a9edac18f2c24597b8 Mon Sep 17 00:00:00 2001 From: SimonHung Date: Thu, 27 Aug 2026 17:44:17 +0000 Subject: [PATCH 13/58] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (649 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 673402e37..543994c86 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -685,4 +685,6 @@ 閒置後調暗螢幕 調暗程度 螢幕調暗 + 未添加任何最愛項目! + 相簿 From 5a9e5c406ff405d3baf382cee93ac014b9bfb7df Mon Sep 17 00:00:00 2001 From: Outbreak2096 Date: Thu, 27 Aug 2026 02:52:53 +0000 Subject: [PATCH 14/58] Translated using Weblate (Chinese (Simplified Han script)) Currently translated at 100.0% (649 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hans/ --- app/src/main/res/values-zh-rCN/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 613a5cbdc..a8f40586f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -685,4 +685,6 @@ 延迟后调暗屏幕 调暗百分比 调暗屏幕 + 相册 + 未找到收藏! From e73d0961abf107a2f8ec005305c1b004114f3d59 Mon Sep 17 00:00:00 2001 From: arcker95 Date: Fri, 28 Aug 2026 16:49:35 +0000 Subject: [PATCH 15/58] Translated using Weblate (Italian) Currently translated at 100.0% (649 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/it/ --- app/src/main/res/values-it/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 217053150..20e750adb 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -729,4 +729,6 @@ Scurisci lo schermo dopo un ritardo Percentuale di oscuramento Scurisci schermo + Album fotografici + Nessun preferito trovato! From cbebc9e63ce2e87c7280915b5e40ae55e8676d65 Mon Sep 17 00:00:00 2001 From: tal_sarid Date: Sat, 29 Aug 2026 08:35:31 +0000 Subject: [PATCH 16/58] Translated using Weblate (Hebrew) Currently translated at 100.0% (649 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/he/ --- app/src/main/res/values-iw/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index 812130026..ddd2b7004 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -729,4 +729,6 @@ עמעם תצוגה אחרי השהייה עוצמת העמעום עמעם תצוגה + אלבומי תמונות + לא נמצאו מועדפים! From 0398f3f68953c85f684762495551395e9a7251bf Mon Sep 17 00:00:00 2001 From: n8llcaster Date: Sat, 29 Aug 2026 20:05:45 +0000 Subject: [PATCH 17/58] Translated using Weblate (Slovak) Currently translated at 100.0% (649 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/sk/ --- app/src/main/res/values-sk/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index bd4c36d92..23af0764d 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -751,4 +751,6 @@ Stlmiť obrazovku po uplynutí času Úroveň stlmenia Stlmenie obrazovky + Fotoalbumy + Nenašli sa žiadne obľúbené položky! From cf83266e4a00048471994a9020f5a2c730439442 Mon Sep 17 00:00:00 2001 From: Prahlis Date: Mon, 31 Aug 2026 08:28:21 +0000 Subject: [PATCH 18/58] Translated using Weblate (Swedish) Currently translated at 90.4% (587 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/sv/ --- app/src/main/res/values-sv/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 655d33acf..f50e2bee1 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -648,4 +648,5 @@ Brun Visa visualizer Visa bakgrund + Uppspelning backend From 7f587b9f50acc3eda8d970ae079c7325d328c309 Mon Sep 17 00:00:00 2001 From: brdaustra Date: Sun, 30 Aug 2026 15:28:23 +0000 Subject: [PATCH 19/58] =?UTF-8?q?Translated=20using=20Weblate=20(Norwegian?= =?UTF-8?q?=20Bokm=C3=A5l)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 99.3% (645 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/nb_NO/ --- app/src/main/res/values-nb-rNO/strings.xml | 372 ++++++++++++++++++++- 1 file changed, 358 insertions(+), 14 deletions(-) diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 9ddb6c770..9e26efccd 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -23,7 +23,7 @@ Bekreft Fortsett å se Kritikervurdering - %.1f sekunder + %.2f sekunder Regissør Deaktivert Oppdagede servere @@ -120,7 +120,7 @@ Bytt bruker Best vurderte usette - Trailere + Trailer Trailere DVR-plan @@ -137,6 +137,7 @@ Videoer Se direkte + %1$d år gammel %1$d år gammel Spill av med transkoding @@ -163,39 +164,39 @@ Bak kulissene - Temasanger - Temasanger + Kjenningsmelodi + Kjenningsmelodier - Temavideoer - Temavideoer + Kjenningsvideo + Kjenningsvideoer Klipp Klipp - Slettede scener + Slettet scene Slettede scener - Intervjuer + Intervju Intervjuer - Scener + Scene Scener - Smakebiter + Smakebit Smakebiter - Bakomfilmer - Bakomfilmer + Novellefilm + Novellefilmer - Kortfilmer + Kortfilm Kortfilmer @@ -223,7 +224,7 @@ Sjekk etter oppdateringer Gjelder kun TV-serier Slå sammen \'Fortsett å se\' og \'Neste\' - Direkteavspilling av ASS-undertekster + Bruk libass for ASS-undertekster Direkteavspilling av PGS-undertekster Alltid nedmiks til stereo Bruk FFmpeg-dekodermodul @@ -365,4 +366,347 @@ Spill trailer Ingen trailere Fjern valg av spor + %1$dh %2$dm %3$ds + %1$dt %2$dmin + %1$dmin %2$ds + %1$dt + %1$dmin + %1$ds + Bare Tvungne Undertekster + Brukerprofil instillinger fra server + Stemmesøk + Begynner + Snakk for å søke + Behandler + Trykk tilbake for å avbryte + Lydopptaksfeil + Stemmegjenkjenningsfeil + Mikrofonadgang nødvendig + Nettverksfeil + Nettverksavbrudd + Ingen tale gjenkjent + Stemmegjenkjenning opptatt + Tjenerfeil + Ingen tale oppdaget + Ukjent feil + Feil ved oppstart av stemmegjenkjenning + Tidsavbrudd ved stemmegjenkjenning + Prøv igjen + %1$s gjenstår + Trailere + Annet + Bak kulissene + Kjenningsmelodier + Kjenningsvideoer + Klipp + Slettede Scener + Intervju + Scener + Prøver + Novellefilmer + Kortfilmer + Favoritt %s + + %s dag + %s dager + + + %s minutt + %s minutter + + + Film + Filmer + + + Serie + Serier + + + Person + Personer + + Krev innlogging for profil + [0-9] + Deaktiver avlasting av lydmaskinvare + Deaktivering av lyddekodingavlasting kan fikse fryst bilde ved avspilling på noen enheter + Kombinert søkeresultater + Vis alle resultater i én liste + Vis resultater etter kategori + Vis knapp for stemmesøk + Synlig + Skjult + Resultater + DTS:X + DTS:HD + DTS:HD MA + TrueHD + DD + DD+ + DTS + Direct play Dolby Vision Profile 7 + Ignorer kompatibilitetsjekk av enheten + Oppdag + Forespørsel + Venter + Seerr integrering + Fjern Seerr Tjener + Seerr tjener lagt til + Rask Tilkobling + Autoriser annen enhet til å logge på kontoen din + For episoder + Forslag for %1$s + Øk størrelsen for alle kort + Reduser størrelsen for alle kort + Bruk miniatyrbilder + Instillinger lagret + Vis forhåndsinnstillinger + Innebygde forhåndsinnstillinger for å raskt style alle rader + Velg rader og bilder på hjemmesiden + + Bruk AC3 for surround sound lyd + Start etter + Animer + Maks aldersgrense + Ingen maks + For alle aldre + Opp til %s år + Skjermsparer + Innstillinger for skjermsparer + Start skjermsparer + Varighet + Bilder + Inkluder typer + Hvilke elementer å vise bilder for + Tilpass + Beskjær + Fyll + Fyll bredde + Fyll høyde + Wholphin Standard + Wholphin Kompakt + Serieminiatyrbilder + Episodeminiatyrbilder + Lavest + Lav + Middels + Høy + Full + Lilla + Oransje + Dristig Blå + Svart + Brun + Ignorer + Hopp over automatisk + Spør om å hoppe over + Bare bruk FFmpeg dersom ingen innebygd dekoder eksisterer + Foretrekk å bruke FFmpeg over innebygde dekodere + Aldri bruk FFmpeg dekodere + Ved slutten av avspilling + Under rulletekst/avslutning + Aldri + Hvit + Lysegrå + Mørkegrå + Gul + Cyan + Magenta + Kant + Skygge + Innpakning + Innebokset + Foretrekk MPV + Ekstern Spiller + Bruk ExoPlayer for avspilling av HDR + Plakat (2:3) + 16:9 + 4:3 + Kvadrat (1:1) + Primær + Miniatyr + Rutenett + Liste + Kompakt Liste + Bilde med dynamisk farge + Bare bilde + + API-nøkkel + Logg inn på Seerr-tjener + Jellyfinbruker + Lokal bruker + + Ingen eksterne undertekster ble funnet + Nåværende + Bruk appens skjermsparer + Er du sikker på at du vil slette dette elementet? + Vis alternativ for behandling av medier + Søk %s + Skuespiller + Komponist + Forfatter + Gjesteartist + Produsent + Dirigent + Sangtekstforfatter + Arrangør + Ingeniør + Lydmikser + Skaper + Artist + Albumartist + Album + Populære sanger + Spill av neste + Musikkvideoer + Sangtekst + Skjul sangtekst + Vis sangtekst + Låt har sangtekst + Fjern fra kø + Vis albumomslag + Vis visualizer + Vis bakgrunn + Spill av som? + Visualisering av lyden som spilles av, krever tillatelse til å ta opp lyd. + Fortsett + Velg alle + Seperate typer + Foretrekk å vise logoer for titler + Wholphin oppdatert til %s + Fjern fra fortsett å se + Fjern serier fra neste + Vis serier fjernet fra neste + Vis mer + Oppdag TV-serier + Oppdag Filmer + Studioer i %1$s + Sist avspilt + System standard + Direkteavspilling med libass + Direkteavspilling med ExoPlayer innebygd + Brenn/transkode på tjener + SSA/ASS avspilling av undertekst + Kinomodus + Vis %1$s mer + Hopp over oppførsel + For introer, rulletekst, etc + Spill med + Transkoding + Språk på brukergrensesnitt + Visning av anmeldelser + Tilpass hva å vise + Profilbeskyttelse + Fortsett å se/Neste klikkoppførsel + Spill av ved klikk + Vis detaljer ved klikk + Stjerner + Tomater + Sorter + Del inn i separate rader + %1$s Filmer + %1$s TV-Serier + Kvalitetsprofil + Standard + Root-mappe + Populæritet + Produksjonstatus + Bruk annen stil for HDR + Eksperimentelle innstillinger + Aktiver eksperimentelle innstillinger? + Eksperimentelle innstillinger kan være ustabile!\n\nDisse kan fjernes eller endre i hvilken som helst versjon. + Skriv inn Quick Connect kode + Kode må være 6 siffer + Enhet autorisering vellykket + Passord + Brukernavn + URL + Trender + Kommende Filmer + Kommende TV-Serier + Forespør i 4K + AV1 programvaredekoding + MPV er nå standardspiller utenom for HDR.\nDu kan endre dette i innstillinger. + HDR undertekststil + Bildeundertekst ugjennomsiktighet + Lysstyrke + Kontrast + Metning + Tone + Rød + Grønn + Blå + Uklarhet + Lagre for album + Start lysbildefremvisning + Stans lysbildefremvisning + Ved start + Ingen flere bilder + Roter venstre + Roter høyre + Zoom inn + Zoom ut + Lysbildefremvisningens varighet + Spill av video under lysbildefremvisning + Send mediainformasjonslogg til tjener + Ingen grense + Maks dager i Neste + Album + Artister + Sanger + Gå til artist + Spilles nå + Øyeblikkelig miks + Gå til album + Legg til i kø + Legg til rad + Sjangre i %1$s + Nylig utgitt i %1$s + Høyde + Bruk på alle rader + Tilpass hjemmesiden + Hjemmerader + Last inn fra tjeners brukerprofil + Lagre på tjeners brukerprofil + Last inn fra web-client + Bruk seriebilde + Legg til rad for %1$s + Overskriv innstillinger på tjener? + Overskriv lokale innstillinger? + Videotunnelering + D-Pad søkelinjemodus + Minimal + Lurespilling + Liten søkelinje langs bunnen av skjermen (standard) + Søkelinje med lurespilling forhåndsvisningsbilde + Ingen søkelinje + Blokkeringslistet + Utilgjengelig + Undertekstmodus + Din brukerprofilinnstilling + Standard + Smart + Bare Tvunget + Alltid + Ingen + Foretrukket undertekstspråk + Foretrukket lydspråk + Flere brukerprofilinnstillinger + Overskriv brukerprofilinnstillingene dine fra tjeneren + Hvilket som helst språk + Hash-matcher + SDH + TV-program + Opptak + Mapper + Brukervisninger + Bøker + Lydbøker + Tjenerversjon ikke støttet + Opptrer på + Dim skjerm etter forsinking + Dim prosent + Dim skjerm + Fotoalbum + Ingen favoritter funnet! From c8d202c3fae976eabeedb6d90e00c9870a339222 Mon Sep 17 00:00:00 2001 From: TheMatrixan Date: Sun, 30 Aug 2026 12:18:06 +0000 Subject: [PATCH 20/58] Translated using Weblate (Polish) Currently translated at 99.6% (647 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pl/ --- app/src/main/res/values-pl/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 796d5fd73..30e8bffac 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -749,4 +749,6 @@ Przyciemnienie ekranu Opóźnienie przyciemniana ekranu Procent przyciemniane + Albumy zdjęć + Nic jeszcze nie polubiono! From de6cf7fb59959146fb04d7bcf0dac73002d8feef Mon Sep 17 00:00:00 2001 From: American_Jesus Date: Tue, 1 Sep 2026 15:52:22 +0000 Subject: [PATCH 21/58] Translated using Weblate (Portuguese) Currently translated at 100.0% (649 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pt/ --- app/src/main/res/values-pt/strings.xml | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index f7878d428..dfa96fd19 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -726,4 +726,9 @@ Aparece em Desativar a processamento de áudio por hardware Desativar o processamento de áudio por hardware pode resolver os bloqueios de reprodução em alguns dispositivos + Escurecimento do ecrã após tempo de inatividade + Percentagem do escurecimento + Escurecer ecrã + Álbum de fotos + Não foram encontrados favoritos! From 8c2cbcb7b210acbf2311cd34062cf69d10c84a39 Mon Sep 17 00:00:00 2001 From: Andi Chandler Date: Tue, 1 Sep 2026 17:29:51 +0000 Subject: [PATCH 22/58] Translated using Weblate (English (United Kingdom)) Currently translated at 7.8% (51 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/en_GB/ --- app/src/main/res/values-en-rGB/strings.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 417bac6b7..4e3381c09 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -36,4 +36,14 @@ Skip behaviour Customise which to display Continue watching/Next up click behaviour + About + Active Recordings + Add Server + Add User + Audio + Birthplace + Bitrate + Born + Cancel Recording + Cancel Series Recording From 2ade8173ff1bffd0731e087bbfced49bf0e4103b Mon Sep 17 00:00:00 2001 From: alexkahler Date: Tue, 1 Sep 2026 20:59:07 +0000 Subject: [PATCH 23/58] Translated using Weblate (Danish) Currently translated at 99.8% (648 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/da/ --- app/src/main/res/values-da/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index ad04be001..870d8c853 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -707,4 +707,6 @@ Dæmp skærmen efter ventetid Dæmpning (%) Dæmp skærmen + Fotoalbummer + Ingen favoritter fundet! From 026f8c7592d16c24cd0dea8f9afb098c96162c3a Mon Sep 17 00:00:00 2001 From: Bone NI Date: Wed, 2 Sep 2026 09:27:27 +0000 Subject: [PATCH 24/58] Added translation using Weblate (Lao) --- app/src/main/res/values-lo/strings.xml | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 app/src/main/res/values-lo/strings.xml diff --git a/app/src/main/res/values-lo/strings.xml b/app/src/main/res/values-lo/strings.xml new file mode 100644 index 000000000..55344e519 --- /dev/null +++ b/app/src/main/res/values-lo/strings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file From 6e12b0c776cf929b3e7f77c87d6e41ad73bc4c78 Mon Sep 17 00:00:00 2001 From: lednurb Date: Fri, 4 Sep 2026 13:54:13 +0000 Subject: [PATCH 25/58] Translated using Weblate (Dutch) Currently translated at 92.6% (601 of 649 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/nl/ --- app/src/main/res/values-nl/strings.xml | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index 3dff3a5f9..bf02bbead 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -42,8 +42,8 @@ Grootte Geforceerd Genres - Ga naar serie - Ga naar item + Naar seizoenen + Naar afspelen Afspeelbediening verbergen Foutopsporingsinformatie verbergen Verbergen @@ -660,4 +660,6 @@ Scenes [0-9] DTS:X + Meer instellingen gebruikersprofiel + "D-pad zoekbalk" From d6b1d77c28ff2f46b5af002bff3d0d463a6c9740 Mon Sep 17 00:00:00 2001 From: North-DaCoder Date: Sat, 5 Sep 2026 08:34:42 +0000 Subject: [PATCH 26/58] Translated using Weblate (German) Currently translated at 100.0% (656 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/de/ --- app/src/main/res/values-de/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 8d13051d3..e183feb9a 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -704,4 +704,16 @@ Nicht unterstützte Server-Version Audio-Hardware-Offload deaktivieren Das Auslagern der Audio Decodierung zu deaktivieren könnte Wiedergabeprobleme auf einigen Gräten beheben + %1$dh %2$ds + S%1$d F%2$d + S%1$d F%2$d-E%3$d + S%1$02dF%2$02d + S%1$02dF%2$02d-E%3$02d + F%1$d + Staffel %1$d + Bildschirm nach Verzögerung dimmen + Dimmen in Prozent + Bildschirm dimmen + Fotoalben + Keine Favoriten vorhanden! From 114df65de33df17235547fa8486eea837724d4a4 Mon Sep 17 00:00:00 2001 From: SimonHung Date: Sat, 5 Sep 2026 01:53:36 +0000 Subject: [PATCH 27/58] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (656 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 543994c86..782e3e0a8 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -687,4 +687,11 @@ 螢幕調暗 未添加任何最愛項目! 相簿 + %1$d小時%2$d分 + 第%1$d季 第%2$d集 + 第%1$d季 第%2$d-%3$d集 + 第%1$02d季 第%2$02d集 + 第%1$02d季 第%2$02d-%3$02d集 + 第 %1$d 集 + 第 %1$d 季 From 8e050a08301d23cd79d0b6e633db57fca6652b94 Mon Sep 17 00:00:00 2001 From: lednurb Date: Sat, 5 Sep 2026 06:56:38 +0000 Subject: [PATCH 28/58] Translated using Weblate (Dutch) Currently translated at 91.6% (601 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/nl/ --- app/src/main/res/values-nl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index bf02bbead..2d6bd0c4c 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -43,7 +43,7 @@ Geforceerd Genres Naar seizoenen - Naar afspelen + Naar inhoud Afspeelbediening verbergen Foutopsporingsinformatie verbergen Verbergen From 7b39854621e0c20f03b6e54fa070db80fd69f2e9 Mon Sep 17 00:00:00 2001 From: tal_sarid Date: Fri, 4 Sep 2026 17:13:30 +0000 Subject: [PATCH 29/58] Translated using Weblate (Hebrew) Currently translated at 100.0% (656 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/he/ --- app/src/main/res/values-iw/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index ddd2b7004..a41912061 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -731,4 +731,11 @@ עמעם תצוגה אלבומי תמונות לא נמצאו מועדפים! + %1$d שע\' %2$d שנ\' + עונה %1$d פרק %2$d + עונה %1$d פרקים %2$d-%3$d + עונה %1$02d פרק %2$02d + עונה %1$02d פרקים %2$02d-%3$02d + פרק %1$d + עונה %1$d From 1e86616d2a12a64969f753c1e2561352f185839a Mon Sep 17 00:00:00 2001 From: idezentas Date: Fri, 4 Sep 2026 21:04:54 +0000 Subject: [PATCH 30/58] Translated using Weblate (Turkish) Currently translated at 99.8% (655 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/tr/ --- app/src/main/res/values-tr/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index ba80486ef..409059eb9 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -706,4 +706,13 @@ Belirli bir süre sonra ekranı karart Karartma yüzdesi Ekranı karart + %1$dsa %2$dsn + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + Sezon %1$d + Fotoğraf Albümleri + Favori bulunamadı! From 5ee6b31018dc96d18490a9e7f5c470c2df678ad1 Mon Sep 17 00:00:00 2001 From: danpergal84 Date: Sun, 6 Sep 2026 17:29:53 +0000 Subject: [PATCH 31/58] Translated using Weblate (Spanish) Currently translated at 100.0% (656 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/es/ --- app/src/main/res/values-es/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 582a44054..c70eeeba6 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -731,4 +731,11 @@ Atenuar pantalla Álbumes de fotos ¡No se encontraron favoritos! + %1$dh %2$ds + T%1$d E%2$d + T%1$d E%2$d-E%3$d + T%1$02dE%2$02d + T%1$02dE%2$02d-E%3$02d + E%1$d + Temporada %1$d From 60a0d633bd1235130b9d8c87c8735f710e2c5103 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Sat, 5 Sep 2026 20:55:20 +0000 Subject: [PATCH 32/58] Translated using Weblate (Estonian) Currently translated at 100.0% (656 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/et/ --- app/src/main/res/values-et/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 380be3e5c..0ef7ed29c 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -709,4 +709,11 @@ Hämarda ekraani Fotoalbumid Ühtegi lemmikut ei leidu! + %1$d t %2$d sek + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + %1$d. hooaeg From 3598e3010b828cce9fd194c3d4fd38ab5d55ef01 Mon Sep 17 00:00:00 2001 From: marinosGR Date: Sun, 6 Sep 2026 00:00:23 +0000 Subject: [PATCH 33/58] Translated using Weblate (Greek) Currently translated at 51.0% (335 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/el/ --- app/src/main/res/values-el/strings.xml | 281 ++++++++++++++++++++----- 1 file changed, 231 insertions(+), 50 deletions(-) diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index a4070ecab..732ffe5da 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -18,20 +18,20 @@ Τα βίντεο Παρασκήνιο - Θεματικά τραγούδια + Θεματικό τραγούδι Θεματικά τραγούδια - Θεματικά βίντεο + Θεματικό βίντεο Θεματικά βίντεο - Επόμενο - Ενεργοποιήστε την επανάληψη παρακολούθησης στα \'Επόμενα\' + Επόμενα + Ενεργοποιήστε την επαναπροβολή στα \'Επόμενα\' Γραμματοσειρά Μέγεθος Κειμένου Χρώμα Κειμένου - Φωτεινότητα κειμένου - Βιβλιοθήκης + Έντονη γραμματοσειρά + Βιβλιοθήκη Σειρές Είδη Συλλογή @@ -43,25 +43,25 @@ Στούντιο SDR Ηθοποιοί και Συνεργείο - Περισσότερα Σαν Αυτό + Παραπλήσια Επεισόδια #ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ %s λήψη %s λήψεις - λήψη… + Γίνεται λήψη… Διαδρομή Όνομα - Δοχείο + Κοντέινερ Μέγεθος Κωδικοποιητής AVC Ρυθμός bit Βάθος bit bit - Εξαναγκασμός - Σχέδιο + Αναγκαστικό + Διάταξη Προεπιλογή Εξωτερικό Προσθήκη χρήστη @@ -69,25 +69,25 @@ Κεφάλαια Διάλεξε %1$s Προσθήκη διακομιστή - Ακύρωση καταχώρησης - Ακύρωση σειρά καταχώρησης + Ακύρωση καταγραφής + Ακύρωση καταγραφής σειράς Καθαρισμός μνήμης εικόνας Διαφήμιση Αξιολόγηση κοινού Ένα σφάλμα εμφανίστηκε! Πατήστε το κομπί για να στείλετε αρχείο καταγραφής στο διακομιστή. Επικύρωση - Συνεχίστε να βλέπετε + Συνέχεια προβολής Κριτική - %.1f δευτερόλεπτα + %.2f δευτερόλεπτα Διαγραφή Πέθανε Σκηνοθετήθηκε από %1$s - Απενεργοποιημένος - Ενεργοποιημένος - Καταχώρηση διακομιστή IP or URL + Απενεργοποιημένο + Ενεργοποιημένο + Δώσε την IP ή το URL του διακομιστή Σφάλμα φόρτωσης συλλογής %1$s - Πηγαίνετε στις σειρές - Πηγαίνετε στο + Πήγαινε στη σειρά + Πήγαινε σε αυτό Απόκρυψη χειρισμού αναπαραγωγής Απόκρυψη πληροφοριών εντοπισμού σφαλμάτων Απόκρυψη @@ -96,15 +96,15 @@ Πληροφορίες άδειας Ζωντανή Τηλεόραση Φόρτωση… - Επισήμανση ολόκληρης σειράς ως αναπαραγμένης? - Επισήμανση ολόκληρης σειράς ως μη αναπαραγμένης? + Επισήμανση ολόκληρης σειράς ως αναπαραγμένης; + Επισήμανση ολόκληρης σειράς ως μη αναπαραγμένης; Επισήμανση ως μη ολοκληρωμένο Επισήμανση ως ολοκληρωμένο Μέγιστος ρυθμός μετάδοσης δεδομένων Περισσότερα Χωρίς δεδομένα Χωρίς αποτελέσματα - Χωρίς προγραμματισμένες καταχωρήσεις + Χωρίς προγραμματισμένες καταγραφές Χωρίς διαθέσιμη ενημέρωση Έναρξη από εδώ Έναρξη @@ -117,16 +117,16 @@ Ρυθμίσεις προφίλ χρήστη Ουρά Ανακεφαλαίωση - Πρόσφατα πρόσθετα σε %1$s - Πρόσφατα πρόσθετα + Πρόσφατες προσθήκες σε %1$s + Πρόσφατες προσθήκες Πρόσφατα καταγεγραμμένο - Πρόσφατα κυκλοφόρησε + Πρόσφατης κυκλοφορίας Προτεινόμενο - Πρόγραμμα καταγραφής - Σειρά καταγραφής - Κατάργηση από τα αγαπημένα + Καταγραφή προγράμματος + Καταγραφή σειράς + Αναίρεση αγαπημένου Επανεκκίνηση - Περίληψη + Συνέχισε Αποθήκευση Αναζήτηση Αναζήτηση… @@ -136,9 +136,9 @@ Εμφάνιση επόμενου Εμφάνιση Τυχαία αναπαραγωγή - Αναπήδηση - Προσθήκη ημερομηνίας - Προσθήκη ημερομηνίας επεισοδίου + Παράλειψε + Ημερομηνία Προσθήκης + Ημερομηνία Προσθήκης Επεισοδίου Ημερομηνία αναπαραγωγής Ημερομηνία κυκλοφορίας Όνομα @@ -146,17 +146,17 @@ Υποβολή Ενεργές καταγραφές Κανένας - Εμφάνιση πληροφοριών αναπαραγωγής σφαλμάτων - Λήψη αρχείων διαρκεί πολύ χρονο, ίσως χρειαστεί να επανεκκινήσετε την αναπαραγωγή + Εμφάνιση πληροφοριών σφαλμάτων αναπαραγωγής + Καθυστέρηση της λήψης, ίσως απαιτείται επανεκκίνηση αναπαραγωγής Αλλαγή διακομιστή - Αντικατάσταση - Κορυφαίες βαθμολογίες σε ταινίες που δεν έχω δει + Αλλαγή + Κορυφαίας βαθμολογίας που δεν έχουν προβληθεί Οδηγός Σεζόν Περισσότερες Σεζόν Άγνωστο Αναβαθμίσεις - Εκδοχή + Έκδοση Κλίμακα βίντεο Ζωντανή μετάδοση Πληροφορίες μέσων @@ -167,46 +167,227 @@ Πλάγια γραμματοσειρά Επιτυχής Γονεική αξιολόγηση - Χρόνος εκτέλεσης + Διάρκεια Επιπρόσθετα Άλλο - Άλλο + Άλλα - Παρασκήνια + Παρασκήνιο Παρασκήνια - Αποσπάσματα + Απόσπασμα Αποσπάσματα - Διαγραμμένες σκηνές + Διαγραμμένη σκηνή Διαγραμμένες σκηνές - Συνεντεύξεις + Συνέντευξη Συνεντεύξεις - Σκηνές + Σκηνή Σκηνές - Δείγματα + Δείγμα Δείγματα - Χαρακτηριστικά - Χαρακτηριστικά + Προωθητικό βίντεο + Προωθητικά βίντεο Αυτόματος έλεγχος για ενημερώσεις Καθυστέρηση πριν την έναρξη του επόμενου Αυτόματη έναρξη επόμενου Έλεχγος για ενημερώσεις Ισχύει μόνο για τηλεοπτικές σειρές - Προεπιλεγμένη αναπαραγωγή περιεχομένου + Προεπιλεγμένη κλίμακα περιεχομένου Εγκατάσταση ενημέρωσης - Εγκατεστημένη εκδοχή + Εγκατεστημένη έκδοση + Πληροφορίες + %1$dω %2$dλ %3$dδ + %1$dω %2$dλ + %1$dλ %2$dδ + %1$dω + %1$dλ + %1$dδ + %1$s απέμεινε + %1$dω %2$dδ + Εντοπισμένοι διακομιστές + Τελειώνει σε %1$s + Εναλλαγή ανάλυσης + Τοπικό + Αναπαραγωγή τρέιλερ + Λείπει τρέιλερ + Καθαρισμός επιλογών καναλιού + Δώσε την διεύθυνση διακομιστή + Δεν βρέθηκαν διακομιστές + Μετρητής προβολών + Μόνο αναγκαστικοί υπότιτλοι + Τίτλοι τέλους + Σφάλμα καταγραφής ήχου + Η οπτικοποίηση του ήχου αναπαραγωγής απαιτεί άδεια καταγραφής ήχου. + Καταγραφές + Ρυθμίσεις του προφίλ χρήστη στο διακομιστή + Λειτουργία υποτίτλων + Χρήση των ρυθμίσεων του προφίλ + Προεπιλογή + Έξυπνο + Μόνο αναγκαστικοί + Πάντα + Κανένα + Προτιμώμενη γλώσσα υποτίτλων + Προτιμώμενη γλώσσα ήχου + Περισσότερες ρυθμίσεις του προφίλ χρήστη + Παράκαμψη των ρυθμίσεων του προφίλ χρήστη στον διακομιστή + Οποιαδήποτε γλώσσα + Λειτουργία σινεμά + + + Ταινία + Ταινίες + + + Άτομο + Άτομα + + + + Τρέιλερ + Τρέιλερ + + Προγραμματισμός DVR + + Σειρά + Σειρές + + Διεπαφή + + %1$d έτους + %1$d ετών + + Αναπαραγωγή με μετατροπή + Σειρά τηλεοπτικής προβολής + Παύση/Αναπαραγωγή με το κεντρικό κουμπί του σταυρού πλοήγησης + + Μικρού μήκους + Μικρού μήκους + + + %d ώρα + %d ώρες + + + %d αντικείμενο + %d αντικείμενα + + + %d δευτερόλεπτο + %d δευτερόλεπτα + + Η συσκευή υποστηρίζει AC3/Dolby Digital + Προηγμένη διεπαφή + + Ανώτατο όριο ημερών στα Επόμενα + Στο τέλος της αναπαραγωγής + Κατά τη διάρκεια των τίτλων τέλους + Αφαίρεση σειράς από τα Επόμενα + Εμφάνιση σειρών που αφαιρέθηκαν από τα Επόμενα + Ποτέ + Συμπεριφορά επιλογής του Συνέχεια προβολής/Επόμενο + Αφαίρεση από συνέχεια προβολής + Αναπαραγωγή κατά την επιλογή + Προβολή λεπτομέρειον κατά την επιλογή + Χρήση libass για ASS υπότιτλους + Απευθείας αναπαραγωγή PGS υπότιτλων + Πάντα υποβιβασμός σε στέρεο + Χρήση αποκωδικοποιητή FFmpeg + Ανώτατο όριο αντικειμένων στις γραμμές της αρχικής + Διάλεξε τα προεπιλεγμένα αντικείμενα για εμφάνιση, θα γίνει απόκρυψη των υπόλοιπων + Απομνημόνευση επιλεγμένων καρτελών + Ρυθμίσεις + Ανακάλυψε + Αίτημα + Εκκρεμί + Κωδικός + Όνομα χρήστη + Τάσεις + Κόκκινο + Πράσινο + Μπλε + Θόλωση + Ύψος + Προσαρμογή + Περικοπή + Γέμισμα + Χαμηλότερο + Χαμηλό + Μεσαίο + Υψηλό + Τέρμα + Μωβ + Πορτοκαλί + Μαύρο + Αγνόηση + Άσπρο + Κίτρινο + Κυανό + Πορφυρώ + Περίγραμμα + Σκιά + Αναδίπλωση + Πλαίσιο + Διάρκεια + Προστασία οθόνης + Φωτογραφίες + Ηθοποιός + Συνθέτης + Συγγραφέας + Παραγωγός + Μαέστρος + Στιχουργός + Ενορχηστρωτής + Υπεύθυνος μίξης + Δημιουργός + Καλλιτέχνης + Άλμπουμ + Καλλιτέχνες + Τραγούδια + Άλμπουμ + Στίχοι + Συνεχεία + Μετατροπή + Επαναφορά + Παίχτηκε + Φίλτρο + Έτος + Δεκαετία + Αφαίρεση + Περιθώριο + Λανθασμένο + Στήλες + Διάστιχο + Αυτόματα + Σύνδεση + Γενικά + Τίτλος + Επίπεδο + Ανάλυση + Αναμορφικό + Πεπλεγμένο + Ρυθμός ανανέωσης + Επανάληψη + Επανάλαβε + Φωτεινότητα + Αντίθεση + Κορεσμός + Απόχρωση + Καθυστέρηση υποτίτλων + Ανακάλυψε ταινίες + Ανακάλυψε σειρές From 6647cf338c537e18df34fd1e69e8982a0df54e59 Mon Sep 17 00:00:00 2001 From: Prahlis Date: Mon, 7 Sep 2026 09:10:31 +0000 Subject: [PATCH 34/58] Translated using Weblate (Swedish) Currently translated at 90.3% (593 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/sv/ --- app/src/main/res/values-sv/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index f50e2bee1..7dba993c6 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -649,4 +649,10 @@ Visa visualizer Visa bakgrund Uppspelning backend + Säsong %1$d + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + %1$dt %2$ds + Oskärpa From c0f6293ea703fcb8e28cf299663937b24e2bb3a8 Mon Sep 17 00:00:00 2001 From: n8llcaster Date: Mon, 7 Sep 2026 11:48:42 +0000 Subject: [PATCH 35/58] Translated using Weblate (Slovak) Currently translated at 100.0% (656 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/sk/ --- app/src/main/res/values-sk/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index 23af0764d..a57d33110 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -753,4 +753,11 @@ Stlmenie obrazovky Fotoalbumy Nenašli sa žiadne obľúbené položky! + %1$dh %2$ds + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + Séria %1$d From 7bd1e993d0321d36789ba618c9446351be580c10 Mon Sep 17 00:00:00 2001 From: opakholis Date: Tue, 8 Sep 2026 07:22:34 +0000 Subject: [PATCH 36/58] Translated using Weblate (Indonesian) Currently translated at 100.0% (656 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/id/ --- app/src/main/res/values-in/strings.xml | 30 ++++++++++++++++++++++++++ 1 file changed, 30 insertions(+) diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index fbd7ed5d7..f3c0a00c5 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -664,4 +664,34 @@ Semua bahasa Cokelat Mode bilah geser D-Pad + %1$dj %2$dd + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + Season %1$d + Nonaktifkan offload audio hardware + Menonaktifkan dekode audio offload dapat mengatasi masalah pemutaran macet pada beberapa perangkat + Minimal + Trickplay + Bilah geser kecil di sepanjang bawah layar (default) + Bilah geser dengan gambar pratinjau trickplay + Tanpa bilah geser + Daftar blokir + Hash sesuai + SDH + Acara TV + Rekaman + Folder + Tampilan pengguna + Buku + Buku audio + Versi server tidak didukung + Tampil pada + Redupkan layar setelah penundaan + Persentase redup + Redupkan layar + Album foto + Favorit tidak ditemukan! From 8a570b89fbad812ebe0f8adadb56b8fb1fe30ce2 Mon Sep 17 00:00:00 2001 From: Andi Chandler Date: Mon, 7 Sep 2026 11:31:59 +0000 Subject: [PATCH 37/58] Translated using Weblate (English (United Kingdom)) Currently translated at 8.6% (57 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/en_GB/ --- app/src/main/res/values-en-rGB/strings.xml | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 4e3381c09..6f57641a1 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -46,4 +46,10 @@ Born Cancel Recording Cancel Series Recording + %1$dh %2$ds + %1$dm %2$ds + %1$dh + Cancel + Chapters + Choose %1$s From bad47eb6387b0de2dc6f474f420d2e076e09c7f3 Mon Sep 17 00:00:00 2001 From: MrGegaz Date: Tue, 8 Sep 2026 20:39:41 +0000 Subject: [PATCH 38/58] Translated using Weblate (Croatian) Currently translated at 100.0% (656 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/hr/ --- app/src/main/res/values-hr/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index dfdb469a9..b42d06834 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -729,4 +729,13 @@ Zatamni zaslon nakon odgode Postotak zatamnjenja Zatamni zaslon + %1$dh %2$ds + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + Sezona %1$d + Albumi slika + Nijedan favorit nije pronađen! From f79c0bdfe79b2de5c74e137fbd7cc45e403d575a Mon Sep 17 00:00:00 2001 From: American_Jesus Date: Wed, 9 Sep 2026 11:27:15 +0000 Subject: [PATCH 39/58] Translated using Weblate (Portuguese) Currently translated at 100.0% (656 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pt/ --- app/src/main/res/values-pt/strings.xml | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index dfa96fd19..b95286c42 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -731,4 +731,11 @@ Escurecer ecrã Álbum de fotos Não foram encontrados favoritos! + %1$dh %2$ds + T%1$d E%2$d + T%1$d E%2$d-E%3$d + T%1$02dE%2$02d + T%1$02dE%2$02d-E%3$02d + E%1$d + Temporada %1$d From 584c3566d6c74bf9eff3974cdb9d46af241b9ad7 Mon Sep 17 00:00:00 2001 From: Prahlis Date: Wed, 9 Sep 2026 19:32:54 +0000 Subject: [PATCH 40/58] Translated using Weblate (Swedish) Currently translated at 90.7% (595 of 656 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/sv/ --- app/src/main/res/values-sv/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 7dba993c6..c38d1bd81 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -655,4 +655,6 @@ S%1$02dE%2$02d %1$dt %2$ds Oskärpa + S%1$02dE%2$02d-E%3$02d + E%1$d From 0db4d45dbcec7675e12e887fcc8eb7233987f1cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Priit=20J=C3=B5er=C3=BC=C3=BCt?= Date: Fri, 11 Sep 2026 10:27:40 +0000 Subject: [PATCH 41/58] Translated using Weblate (Estonian) Currently translated at 100.0% (657 of 657 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/et/ --- app/src/main/res/values-et/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 0ef7ed29c..ffbec2a7c 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -716,4 +716,5 @@ S%1$02dE%2$02d-E%3$02d E%1$d %1$d. hooaeg + Eemalda esitusloendist From cd0128e1db7ed242ec5c5cc0d93092fd2e578106 Mon Sep 17 00:00:00 2001 From: SimonHung Date: Fri, 11 Sep 2026 00:25:21 +0000 Subject: [PATCH 42/58] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (657 of 657 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 782e3e0a8..854c7275c 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -694,4 +694,5 @@ 第%1$02d季 第%2$02d-%3$02d集 第 %1$d 集 第 %1$d 季 + 從播放清單中移除 From 391a38e777d2dc3d4fe10668d1abbbaf35c45d21 Mon Sep 17 00:00:00 2001 From: TheMatrixan Date: Fri, 11 Sep 2026 14:17:45 +0000 Subject: [PATCH 43/58] Translated using Weblate (Polish) Currently translated at 98.6% (648 of 657 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pl/ --- app/src/main/res/values-pl/strings.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 30e8bffac..4799f7d0b 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -751,4 +751,5 @@ Procent przyciemniane Albumy zdjęć Nic jeszcze nie polubiono! + Sezon %1$d From 923f0b7ea30c96d2f94f94943782ea372890c635 Mon Sep 17 00:00:00 2001 From: SimonHung Date: Sat, 12 Sep 2026 08:53:01 +0000 Subject: [PATCH 44/58] Translated using Weblate (Chinese (Traditional Han script)) Currently translated at 100.0% (659 of 659 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/zh_Hant/ --- app/src/main/res/values-zh-rTW/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 854c7275c..ca8bd810c 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -695,4 +695,6 @@ 第 %1$d 集 第 %1$d 季 從播放清單中移除 + 您目前無存取此伺服器的權限 + 登入憑證已過期,請重新登入 From f181770870f29c1e72418c70b241e9d0f1b141d6 Mon Sep 17 00:00:00 2001 From: arcker95 Date: Fri, 11 Sep 2026 20:14:13 +0000 Subject: [PATCH 45/58] Translated using Weblate (Italian) Currently translated at 100.0% (659 of 659 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/it/ --- app/src/main/res/values-it/strings.xml | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index 20e750adb..7e347b2d8 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -731,4 +731,14 @@ Scurisci schermo Album fotografici Nessun preferito trovato! + %1$do %2$ds + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + Stagione %1$d + Rimuovi dalla playlist + Credenziali scadute, effettua di nuovo l’accesso + Non sei autorizzato ad accedere al server in questo momento From a60433dbbfbd1d857e36c53c163b5f5fd1ea4a07 Mon Sep 17 00:00:00 2001 From: TheMatrixan Date: Sat, 12 Sep 2026 12:21:05 +0000 Subject: [PATCH 46/58] Translated using Weblate (Polish) Currently translated at 99.6% (657 of 659 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/pl/ --- app/src/main/res/values-pl/strings.xml | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 4799f7d0b..deb42ecd9 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -752,4 +752,13 @@ Albumy zdjęć Nic jeszcze nie polubiono! Sezon %1$d + Usuń z listy odtwarzania + Sesja wygasła, zaloguj się ponownie + Nie jesteś upoważniony do dostępu do serwera w tym momencie + O%1$d + %1$dh %2$ds + S%1$d O%2$d + S%1$d O%2$d-%3$d + S%1$02d O%2$02d + S%1$02d O%2$02d-%3$02d From af6a3b99169b329eac9e8b0cac642bd7f386d275 Mon Sep 17 00:00:00 2001 From: Prahlis Date: Sat, 12 Sep 2026 19:55:42 +0000 Subject: [PATCH 47/58] Translated using Weblate (Swedish) Currently translated at 92.2% (608 of 659 strings) Translation: Wholphin/Wholphin Translate-URL: https://translate.codeberg.org/projects/wholphin/wholphin/sv/ --- app/src/main/res/values-sv/strings.xml | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index c38d1bd81..6664b928b 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -657,4 +657,17 @@ Oskärpa S%1$02dE%2$02d-E%3$02d E%1$d + Visa %1$s till + Standard + Föredraget ljudspråk + Fler profilinställningar + Använd profilinställningar + Ignorera serverns profilinställningar + Serverns version stöds inte + Medverkar i + Fotoalbum + Inga favoriter hittades! + Experimentella inställningar kan vara instabila!\n\n Dessa kan tas bort eller ändras i framtida versioner. + Ta bort från spellista + Trailers From 190c6be41abe1642b1623f1dee544426558e8cb2 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Sat, 12 Sep 2026 18:43:14 -0400 Subject: [PATCH 48/58] Run pre-commit --- app/src/main/res/values-lo/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-lo/strings.xml b/app/src/main/res/values-lo/strings.xml index 55344e519..045e125f3 100644 --- a/app/src/main/res/values-lo/strings.xml +++ b/app/src/main/res/values-lo/strings.xml @@ -1,3 +1,3 @@ - \ No newline at end of file + From 45c1ceca270afabd2e1ca012d6e7374b32d4f00d Mon Sep 17 00:00:00 2001 From: Damontecres Date: Sun, 13 Sep 2026 20:09:08 -0400 Subject: [PATCH 49/58] Fix favorites not querying for people & fix restoring focus on search page for discover items (#1961) ## Description Fix two bugs: - Favorites page didn't query for favorite people, introduced by #1864 - Fixes restoring focus on the search page after clicking a discover item and going back ### Related issues N/A ### Testing Emulator ## Screenshots N/A ## AI or LLM usage None --- .../damontecres/wholphin/ui/detail/FavoritesViewModel.kt | 4 +++- .../github/damontecres/wholphin/ui/search/SearchPage.kt | 2 +- .../damontecres/wholphin/ui/search/SearchViewModel.kt | 9 ++++++++- 3 files changed, 12 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/FavoritesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/FavoritesViewModel.kt index 8471c40dc..adbd9a44b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/FavoritesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/FavoritesViewModel.kt @@ -787,7 +787,9 @@ fun List.possibleFavoriteTypes(sort: Boolean) = flatMap { it.collectionType.baseItemKinds } .distinct() .filter { it in favoriteOptions } - .let { list -> + .let { + // No library for persons, so always include it + val list = it + listOf(BaseItemKind.PERSON) if (sort) { list.sortedWith(compareBy { favoriteOptions.indexOf(it) }) } else { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt index e353287db..f20222d96 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt @@ -113,7 +113,7 @@ fun SearchPage( val focusRequesters = remember(state.includedSearchableTypes.size) { List(RESULTS_START + state.includedSearchableTypes.size) { FocusRequester() } } - val seerrActive by viewModel.seerrActive.collectAsState(initial = false) + val seerrActive by viewModel.seerrActive.collectAsState() var selectedTab by rememberSaveable(seerrActive, state.discoverEnabled) { mutableIntStateOf(0) } var showViewOptions by rememberSaveable { mutableStateOf(false) } var showFilterTypeDialog by rememberSaveable { mutableStateOf(false) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt index 81dfa95fd..044178b2b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt @@ -43,9 +43,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jellyfin.sdk.api.client.ApiClient @@ -79,7 +81,12 @@ class SearchViewModel private val navDrawerService: NavDrawerService, ) : ViewModel(), ContextMenuProvider { - val seerrActive = seerrService.active + val seerrActive = + seerrService.active.stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = false, + ) private val _state = MutableStateFlow(SearchState()) val state: StateFlow = _state From 7af7bc0aa70229fda6222f2d17d1b1ddbc2373c5 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Mon, 14 Sep 2026 10:04:09 -0400 Subject: [PATCH 50/58] Release v1.0.8 From 62965eae48e1e36aeb3100a22695c2a57bc73f58 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Tue, 15 Sep 2026 10:53:14 -0400 Subject: [PATCH 51/58] Fix CI setup step (#1974) ## Description Looks like `tools` was replaced by `cmdline-tools` (https://developer.android.com/tools#tools-sdk), so updating the setup action for this. ### Related issues https://github.com/android-actions/setup-android/issues/537 ### Testing N/A ## Screenshots N/A ## AI or LLM usage None --- .github/actions/setup/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index bf6de7a14..0fb980d70 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -23,7 +23,7 @@ runs: - name: Setup Android SDK uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4 with: - packages: "tools platform-tools build-tools;${{ env.BUILD_TOOLS_VERSION }} ndk;${{ env.NDK_VERSION }}" + packages: "platform-tools build-tools;${{ env.BUILD_TOOLS_VERSION }} ndk;${{ env.NDK_VERSION }}" - name: Add NDK to path shell: bash run: | From 61f7f70d36500b300358722e3977ccef10490b2c Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:45:06 -0400 Subject: [PATCH 52/58] Update dependency org.robolectric:robolectric to v4.17 (#1446) --- gradle/libs.versions.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7c6ca928..dc7608cfd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ coreKtx = "1.19.0" appcompat = "1.8.0" composeBom = "2026.09.00" mockk = "1.14.11" -robolectric = "4.16.1" +robolectric = "4.17" multiplatformMarkdownRenderer = "0.45.0" okhttpBom = "5.5.0" programguide = "1.6.0" From 8effd2b1e71567ce64093b5262150e6a0ae2be1a Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Tue, 15 Sep 2026 12:46:02 -0400 Subject: [PATCH 53/58] Update actions/setup-java action to v6 (#1907) --- .github/actions/setup/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index 0fb980d70..b551456ac 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -10,7 +10,7 @@ runs: with: python-version: '3.14' - name: Setup JDK - uses: actions/setup-java@b6effb05e454b25005698d916606bdc6ffcbf961 # v5 + uses: actions/setup-java@de7274f081f381c8f8158605e0321c36c376e2e6 # v6 with: distribution: zulu java-version: '21' From ffdebd43c3b5d87721062b1db4e31c967e858332 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Tue, 15 Sep 2026 13:07:08 -0400 Subject: [PATCH 54/58] Update intent parsing to allow for user switching to home page (#1962) ## Description Makes the `itemId` parameter optional allowing just passing `serverId`/`userId` to switch the user and load the app at the home page for that user. Dev note: I added a new assert function `assertIs` which asserts that an object `is` of a type. It also has a contract so the compiler will smart-cast usages after the `assertIs`. Checking a result type is a very common pattern in the unit tests, so this assert will simplify a lot of current & future tests. ### Related issues Closes https://github.com/damontecres/Wholphin/discussions/1824 ### Testing Emulator and added unit tests for `parseIntent` ## Screenshots N/A ## AI or LLM usage None --- Intents.md | 10 +- .../wholphin/services/IntentService.kt | 41 ++- .../wholphin/services/IntentServiceTest.kt | 273 +++++++++++++++++- .../damontecres/wholphin/test/TestUtils.kt | 21 ++ 4 files changed, 321 insertions(+), 24 deletions(-) diff --git a/Intents.md b/Intents.md index 025117878..7f01a5012 100644 --- a/Intents.md +++ b/Intents.md @@ -63,7 +63,7 @@ Action: `android.intent.action.VIEW` Shorthand action: `view` Parameters: -- `itemId` - Required, the UUID of the media item +- `itemId` - Optional, the UUID of the media item, if not set, goes to home screen ### Examples @@ -135,12 +135,20 @@ adb shell am start -d 'wholphin://play?itemId=5cf8f8e7-2a5f-4aa9-8c12-ddf63d42ee Full format ```bash +# Switch user, go to item adb shell am start \ -a android.intent.action.VIEW \ -n 'com.github.damontecres.wholphin/.MainActivity' \ --es itemId "5cf8f8e7-2a5f-4aa9-8c12-ddf63d42ee6d" \ --es serverId "341880f9-ca88-4038-8718-f6b2407950b1" \ --es userId "5c8575e0-468d-44fb-8c70-00aa79160587" + +# Switch user, go to home screen +adb shell am start \ + -a android.intent.action.VIEW \ + -n 'com.github.damontecres.wholphin/.MainActivity' \ + --es serverId "341880f9-ca88-4038-8718-f6b2407950b1" \ + --es userId "5c8575e0-468d-44fb-8c70-00aa79160587" ``` URI based diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/IntentService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/IntentService.kt index a37a0ed27..2dd0cde5c 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/IntentService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/IntentService.kt @@ -55,30 +55,37 @@ class IntentService val query = intent.getStringParam(SearchManager.QUERY) return IntentResult.Target(listOf(Destination.Search(query ?: ""))) } - val itemId = - intent.getStringParam("itemId")?.toUUIDOrNull() - ?: return IntentResult.Error("No item id provided") + val itemId = intent.getStringParam("itemId")?.toUUIDOrNull() val item = - try { - api.userLibraryApi - .getItem(itemId) - .content - .let { BaseItem(it) } - } catch (ex: Exception) { - Timber.w(ex, "Error fetching item %s", itemId) - return IntentResult.Error("Could not fetch item $itemId") + itemId?.let { + try { + api.userLibraryApi + .getItem(itemId) + .content + .let { BaseItem(it) } + } catch (ex: Exception) { + Timber.w(ex, "Error fetching item %s", itemId) + return IntentResult.Error("Could not fetch item $itemId") + } } - - val itemDestination = item.destination() + val itemDestination = item?.destination() val destinations = when (action) { Intent.ACTION_VIEW, "view" -> { - listOf(itemDestination) + if (itemDestination != null) { + listOf(itemDestination) + } else { + // Will go to the home page + emptyList() + } } - "com.github.damontecres.wholphin.PLAYBACK", "play" -> { + ACTION_PLAYBACK, "play" -> { + if (itemId == null || itemDestination == null) { + return IntentResult.Error("Cannot start playback with no itemId") + } val position = intent.getLongParam("position")?.coerceAtLeast(0) val shuffle = intent.getBooleanExtra("shuffle", false) @@ -104,7 +111,7 @@ class IntentService } } - return IntentResult.Target(destinations) + return IntentResult.Target(destinations, true) } internal suspend fun prepare(intent: Intent): IntentResult? { @@ -195,6 +202,8 @@ class IntentService const val INTENT_SEASON_ID = "seaId" const val INTENT_SERVER_ID = "serverId" const val INTENT_USER_ID = "userId" + + const val ACTION_PLAYBACK = "com.github.damontecres.wholphin.PLAYBACK" } } diff --git a/app/src/test/java/com/github/damontecres/wholphin/services/IntentServiceTest.kt b/app/src/test/java/com/github/damontecres/wholphin/services/IntentServiceTest.kt index bb4904aa3..ee32c7dda 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/services/IntentServiceTest.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/services/IntentServiceTest.kt @@ -1,24 +1,34 @@ package com.github.damontecres.wholphin.services +import android.app.SearchManager import android.content.Intent import com.github.damontecres.wholphin.data.CurrentUser +import com.github.damontecres.wholphin.data.JellyfinServerDao import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.update +import com.github.damontecres.wholphin.test.assertIs import com.github.damontecres.wholphin.test.currentUser +import com.github.damontecres.wholphin.test.movie import com.github.damontecres.wholphin.test.server import com.github.damontecres.wholphin.test.user +import com.github.damontecres.wholphin.ui.nav.Destination +import com.github.damontecres.wholphin.ui.successResponse import com.github.damontecres.wholphin.ui.toServerString import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.mockk +import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.runTest import org.jellyfin.sdk.api.client.ApiClient +import org.jellyfin.sdk.api.client.extensions.userLibraryApi +import org.jellyfin.sdk.api.operations.UserLibraryApi import org.jellyfin.sdk.model.UUID +import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before @@ -32,13 +42,16 @@ import org.robolectric.annotation.Config class IntentServiceTest { private val api: ApiClient = mockk() private val serverRepository: ServerRepository = mockk() + private val serverDao: JellyfinServerDao = mockk() private val userPreferencesService: UserPreferencesService = mockk() + private val userLibraryApi: UserLibraryApi = mockk() lateinit var intentService: IntentService private val serverId = UUID.randomUUID() private val userId = UUID.randomUUID() private val currentUser = currentUser(serverId, userId) + private val itemId = UUID.randomUUID() private val protectedCurrentUser = CurrentUser( @@ -49,6 +62,9 @@ class IntentServiceTest { @Before fun setup() { intentService = IntentService(api, serverRepository, userPreferencesService) + every { api.userLibraryApi } returns userLibraryApi + coEvery { userLibraryApi.getItem(itemId) } returns successResponse(movie(itemId)) + every { serverRepository.serverDao } returns serverDao } private fun setupPreferences(block: AppPreferences.Builder.() -> Unit) { @@ -63,16 +79,20 @@ class IntentServiceTest { } } + private fun setupAutoSignInUnprotected() { + setupPreferences { + signInAutomatically = true + currentServerId = serverId.toServerString() + currentUserId = userId.toServerString() + } + every { serverRepository.current } returns MutableStateFlow(currentUser) + coEvery { serverRepository.restoreSession(serverId, userId) } returns currentUser + } + @Test fun `Test auto sign in with unprotected profile`() = runTest { - setupPreferences { - signInAutomatically = true - currentServerId = serverId.toServerString() - currentUserId = userId.toServerString() - } - every { serverRepository.current } returns MutableStateFlow(currentUser) - coEvery { serverRepository.restoreSession(serverId, userId) } returns currentUser + setupAutoSignInUnprotected() val intent = Intent() val result = intentService.prepare(intent) @@ -221,4 +241,243 @@ class IntentServiceTest { coVerify(exactly = 0) { serverRepository.restoreSession(serverId, userId) } } + + @Test + fun `Test parseIntent with view item`() = + runTest { + setupAutoSignInUnprotected() + + val intent = + Intent().apply { + action = Intent.ACTION_VIEW + putExtra("itemId", itemId.toString()) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(1, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + assertIs(first) + + assertEquals(itemId, first.itemId) + + coVerify { userLibraryApi.getItem(itemId) } + } + + @Test + fun `Test parseIntent with view and no itemId`() = + runTest { + setupAutoSignInUnprotected() + + val intent = + Intent().apply { + action = Intent.ACTION_VIEW + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(0, destinations.size) + assertTrue(result.addHomeToBackStack) + + coVerify(exactly = 0) { userLibraryApi.getItem(itemId) } + } + + @Test + fun `Test parseIntent with view and switch user`() = + runTest { + setupAutoSignInUnprotected() + + val newUserId = UUID.randomUUID() + val newServerId = UUID.randomUUID() + val newCurrentUser = currentUser(newServerId, newUserId) + + every { serverRepository.current } returns MutableStateFlow(currentUser) + coEvery { + serverRepository.restoreSession( + newServerId, + newUserId, + ) + } returns newCurrentUser + every { serverDao.getUser(newServerId, newUserId) } returns newCurrentUser.user + + val intent = + Intent().apply { + action = Intent.ACTION_VIEW + putExtra("userId", newUserId.toString()) + putExtra("serverId", newServerId.toString()) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(0, destinations.size) + assertTrue(result.addHomeToBackStack) + + coVerify(exactly = 0) { serverRepository.restoreSession(serverId, userId) } + verify { serverDao.getUser(newServerId, newUserId) } + coVerify { serverRepository.restoreSession(newServerId, newUserId) } + coVerify(exactly = 0) { userLibraryApi.getItem(itemId) } + } + + @Test + fun `Test parseIntent with view, itemId, and switch user`() = + runTest { + setupAutoSignInUnprotected() + + val newUserId = UUID.randomUUID() + val newServerId = UUID.randomUUID() + val newCurrentUser = currentUser(newServerId, newUserId) + + every { serverRepository.current } returns MutableStateFlow(currentUser) + coEvery { + serverRepository.restoreSession( + newServerId, + newUserId, + ) + } returns newCurrentUser + every { serverDao.getUser(newServerId, newUserId) } returns newCurrentUser.user + + val intent = + Intent().apply { + action = Intent.ACTION_VIEW + putExtra("userId", newUserId.toString()) + putExtra("serverId", newServerId.toString()) + putExtra("itemId", itemId.toString()) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(1, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + assertIs(first) + + assertEquals(itemId, first.itemId) + + coVerify(exactly = 0) { serverRepository.restoreSession(serverId, userId) } + verify { serverDao.getUser(newServerId, newUserId) } + coVerify { serverRepository.restoreSession(newServerId, newUserId) } + coVerify { userLibraryApi.getItem(itemId) } + } + + @Test + fun `Test parseIntent with search`() = + runTest { + setupAutoSignInUnprotected() + + val intent = + Intent().apply { + action = Intent.ACTION_SEARCH + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(1, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + assertIs(first) + + assertEquals("", first.query) + } + + @Test + fun `Test parseIntent with search query`() = + runTest { + setupAutoSignInUnprotected() + + val query = "query123" + val intent = + Intent().apply { + action = Intent.ACTION_SEARCH + putExtra(SearchManager.QUERY, query) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(1, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + assertIs(first) + + assertEquals(query, first.query) + } + + @Test + fun `Test parseIntent with play but no itemId`() = + runTest { + setupAutoSignInUnprotected() + + val intent = + Intent().apply { + action = IntentService.ACTION_PLAYBACK + } + val result = intentService.parseIntent(intent) + assertIs(result) + } + + @Test + fun `Test parseIntent with play itemId`() = + runTest { + setupAutoSignInUnprotected() + + val intent = + Intent().apply { + action = IntentService.ACTION_PLAYBACK + putExtra("itemId", itemId.toString()) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(2, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + val second = destinations[1] + assertIs(first) + assertIs(second) + + assertEquals(itemId, first.itemId) + assertEquals(itemId, second.itemId) + + coVerify { userLibraryApi.getItem(itemId) } + } + + @Test + fun `Test parseIntent with play itemId and params`() = + runTest { + setupAutoSignInUnprotected() + + val position = 4_000L + val shuffle = true + val intent = + Intent().apply { + action = IntentService.ACTION_PLAYBACK + putExtra("itemId", itemId.toString()) + putExtra("position", position) + putExtra("shuffle", shuffle) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(2, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + val second = destinations[1] + assertIs(first) + assertIs(second) + + assertEquals(itemId, first.itemId) + assertEquals(itemId, second.itemId) + assertEquals(shuffle, second.shuffle) + assertEquals(position, second.positionMs) + + coVerify { userLibraryApi.getItem(itemId) } + } } diff --git a/app/src/test/java/com/github/damontecres/wholphin/test/TestUtils.kt b/app/src/test/java/com/github/damontecres/wholphin/test/TestUtils.kt index 7f51e994c..34d962da2 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/test/TestUtils.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/test/TestUtils.kt @@ -4,12 +4,16 @@ import com.github.damontecres.wholphin.data.CurrentUser import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.ui.isNotNullOrBlank +import com.ibm.icu.impl.Assert import io.mockk.MockKMatcherScope import org.jellyfin.sdk.model.UUID import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.NameGuidPair import org.jellyfin.sdk.model.api.UserDto +import org.junit.Assert.assertNotNull +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract fun MockKMatcherScope.nonBlankString() = match { it.isNotNullOrBlank() } @@ -128,3 +132,20 @@ fun currentUser( server(serverId), user(userId), ) + +/** + * Asserts that the object is not null and is type `T` + * + * Includes a [contract] to smart-cast the object to T + */ +@OptIn(ExperimentalContracts::class) +inline fun assertIs(obj: Any?) { + contract { + returns() implies (obj is T) + } + assertNotNull(obj) + val result = T::class.isInstance(obj) + if (!result) { + Assert.fail("Expected type=${T::class.qualifiedName}, actual type= ${obj!!::class.qualifiedName} ") + } +} From 32a56e83875fa9474ad9d57da0248ee144c887c5 Mon Sep 17 00:00:00 2001 From: Damontecres Date: Thu, 17 Sep 2026 14:41:36 -0400 Subject: [PATCH 55/58] Wait for switched user's home settings before fetching data (#1981) ## Description There is a race condition when switching users between `HomeViewModel.init` and `HomeSetttingsService.loadCurrentSettings`. This happens more often if the new user doesn't have a customized home page. The result is that the previous user's home page settings are used for the initial load of the home page. This PR fixes this by collecting the flows for current user & home settings, waiting until both are valid and refer to the same user, then fetch the data. ### Related issues Fixes #1976 Maybe related (and fixes) #1980, but need to investigate more ### Testing Emulator. It's easy to reproduce by adding a manual `delay(2.seconds)` at the start of `HomeSetttingsService.loadCurrentSettings`. ## Screenshots N/A ## AI or LLM usage None --- .../wholphin/services/HomeSettingsService.kt | 16 +- .../services/SetupNavigationManager.kt | 3 - .../wholphin/ui/main/HomeViewModel.kt | 307 +++++++++++------- .../ui/main/settings/HomeSettingsViewModel.kt | 8 +- .../wholphin/ui/setup/SwitchUserViewModel.kt | 1 + 5 files changed, 200 insertions(+), 135 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt index e7b5db000..3371f45eb 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt @@ -245,7 +245,7 @@ class HomeSettingsService settings.rows.mapIndexed { index, config -> resolve(index, config) } - HomePageResolvedSettings(resolvedRows) + HomePageResolvedSettings(userId, resolvedRows) } else { createDefault(userId) } @@ -256,12 +256,15 @@ class HomeSettingsService /** * Resolve the settings and set them to be the current settings */ - suspend fun updateCurrent(settings: HomePageSettings) { + suspend fun updateCurrent( + userId: UUID, + settings: HomePageSettings, + ) { val resolvedRows = settings.rows.mapIndexed { index, config -> resolve(index, config) } - val resolvedSettings = HomePageResolvedSettings(resolvedRows) + val resolvedSettings = HomePageResolvedSettings(userId, resolvedRows) currentSettings.update { resolvedSettings } } @@ -312,7 +315,7 @@ class HomeSettingsService ), ) val rowConfig = continueWatchingRow + includedIds - return HomePageResolvedSettings(rowConfig) + return HomePageResolvedSettings(userId, rowConfig) } /** @@ -428,7 +431,7 @@ class HomeSettingsService null } }.flatten() - HomePageResolvedSettings(rowConfigs) + HomePageResolvedSettings(userId, rowConfigs) } else { null } @@ -1224,10 +1227,11 @@ data class HomeRowConfigDisplay( * @see HomePageSettings */ data class HomePageResolvedSettings( + val userId: UUID, val rows: List, ) { companion object { - val EMPTY = HomePageResolvedSettings(listOf()) + val EMPTY = HomePageResolvedSettings(UUID.randomUUID(), emptyList()) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/SetupNavigationManager.kt b/app/src/main/java/com/github/damontecres/wholphin/services/SetupNavigationManager.kt index fa42469e4..abe39959e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/SetupNavigationManager.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/SetupNavigationManager.kt @@ -27,9 +27,6 @@ class SetupNavigationManager fun navigateTo(destination: SetupDestination) { backStack[0] = destination log() - if (destination !is SetupDestination.AppContent) { - navigationManager.reloadHome() - } } private fun log() { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt index bceccc5e4..9e4051967 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt @@ -20,6 +20,8 @@ import com.github.damontecres.wholphin.services.ServerReportService import com.github.damontecres.wholphin.services.UserPreferencesService import com.github.damontecres.wholphin.services.deleteItem import com.github.damontecres.wholphin.services.tvAccess +import com.github.damontecres.wholphin.ui.collectLatestIn +import com.github.damontecres.wholphin.ui.combinePair import com.github.damontecres.wholphin.ui.data.RowColumn import com.github.damontecres.wholphin.ui.launchDefault import com.github.damontecres.wholphin.ui.launchIO @@ -31,6 +33,8 @@ import com.github.damontecres.wholphin.util.LoadingState import com.github.damontecres.wholphin.util.WholphinDispatchers import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext +import kotlinx.coroutines.CancellationException +import kotlinx.coroutines.Job import kotlinx.coroutines.async import kotlinx.coroutines.awaitAll import kotlinx.coroutines.flow.MutableStateFlow @@ -44,6 +48,7 @@ import kotlinx.coroutines.sync.withPermit import kotlinx.coroutines.withContext import org.jellyfin.sdk.api.client.exception.InvalidStatusException import org.jellyfin.sdk.model.api.BaseItemKind +import org.jellyfin.sdk.model.api.UserDto import timber.log.Timber import java.util.UUID import javax.inject.Inject @@ -68,150 +73,204 @@ class HomeViewModel private val _state = MutableStateFlow(HomeState.EMPTY) val state: StateFlow = _state + private var dataLoadingJob: Job? = null + init { datePlayedService.invalidateAll() -// init() + serverRepository.currentUserDtoFlow + .combinePair(homeSettingsService.currentSettings) + .collectLatestIn(viewModelScope) { (userDto, settings) -> + Timber.v( + "Got new userDto & settings, userId=%s, settings?=%s", + userDto?.id, + settings != HomePageResolvedSettings.EMPTY, + ) + _state.update { HomeState.EMPTY } + dataLoadingJob?.cancel() + if (userDto == null) { + Timber.d("UserDto is null") + return@collectLatestIn + } + if (settings == HomePageResolvedSettings.EMPTY) { + Timber.d("Home settings are empty") + return@collectLatestIn + } + if (userDto.id != settings.userId) { + Timber.d("User IDs don't match: %s vs %s", userDto?.id, settings.userId) + return@collectLatestIn + } + dataLoadingJob = + viewModelScope.launchIO { + loadHomeRows(userDto, settings) + } + } } fun init() { viewModelScope.launchIO { Timber.d("init HomeViewModel") - try { - val preferences = userPreferencesService.getCurrent() - val prefs = preferences.appPreferences.homePagePreferences - - serverRepository.currentUserDto?.let { userDto -> - val libraries = - navDrawerService.getAllUserLibraries(userDto.id, userDto.tvAccess) + serverRepository.currentUserDto?.let { userDto -> + if (dataLoadingJob?.isActive == false) { val settings = homeSettingsService.currentSettings.first { it != HomePageResolvedSettings.EMPTY } - val state = state.value - - // Refreshing if a load has already occurred and the rows haven't significantly changed - val refresh = - state.loadingState == LoadingState.Success && state.settings == settings - Timber.v( - "refresh=%s, state.loadingState=%s, %s rows", - refresh, - state.loadingState, - settings.rows.size, - ) - _state.update { - it.copy( - loadingState = if (refresh) LoadingState.Success else LoadingState.Loading, - refreshState = LoadingState.Loading, - settings = settings, - homeRows = - if (refresh) { - it.homeRows - } else { - List(settings.rows.size) { - HomeRowLoadingState.Pending(EmptyStringProvider) - } - }, + if (userDto.id == settings.userId) { + dataLoadingJob = + viewModelScope.launchIO { + loadHomeRows(userDto, settings) + } + } else { + Timber.d( + "Init user IDs don't match: %s vs %s", + userDto.id, + settings.userId, ) } + } else { + Timber.v("Data loading job is active") + } + } + } + } - val semaphore = Semaphore(4) + suspend fun loadHomeRows( + userDto: UserDto, + settings: HomePageResolvedSettings, + ) { + Timber.i("Starting loadHomeRows") + try { + val preferences = userPreferencesService.getCurrent() + val prefs = preferences.appPreferences.homePagePreferences - val deferred = - settings.rows - .map { row -> - viewModelScope.async(WholphinDispatchers.IO) { - semaphore.withPermit { - Timber.v("Fetching row: %s", row) - try { - homeSettingsService.fetchDataForRow( - row = row.config, - scope = viewModelScope, - prefs = prefs, - userDto = userDto, - libraries = libraries, - limit = prefs.maxItemsPerRow, - isRefresh = refresh, - ) - } catch (ex: InvalidStatusException) { - if (ex.status == 404) { - Timber.w(ex, "404 on row %s", row) - HomeRowLoadingState.Success( - row.title, - emptyList(), - ) - } else { - Timber.e( - ex, - "Error %s on row %s", - ex.status, - row, - ) - HomeRowLoadingState.Error( - row.title, - exception = ex, - ) - } - } catch (ex: Exception) { - Timber.e(ex, "Error on row %s", row) - HomeRowLoadingState.Error( - row.title, - exception = ex, - ) - } - } - } + val libraries = + navDrawerService.getAllUserLibraries(userDto.id, userDto.tvAccess) + + val state = state.value + + // Refreshing if a load has already occurred and the rows haven't significantly changed + val refresh = + state.loadingState == LoadingState.Success && state.settings == settings + Timber.v( + "refresh=%s, state.loadingState=%s, %s rows", + refresh, + state.loadingState, + settings.rows.size, + ) + _state.update { + it.copy( + loadingState = if (refresh) LoadingState.Success else LoadingState.Loading, + refreshState = LoadingState.Loading, + settings = settings, + homeRows = + if (refresh) { + it.homeRows + } else { + List(settings.rows.size) { + HomeRowLoadingState.Pending(EmptyStringProvider) } + }, + ) + } - if (refresh) { - // Replace rows as they complete - val remaining = deferred.withIndex().toMutableList() - while (remaining.isNotEmpty()) { - val (rowIndex, rowData) = - select { - // "Return" the first remaining that is completed - remaining - .forEach { (rowIndex, deferred) -> - deferred.onAwait { rowIndex to it } - } - } - Timber.v("Got row data index=%s", rowIndex) - remaining.removeIf { it.index == rowIndex } - _state.update { state -> - val newRows = - state.homeRows.toMutableList().apply { - set(rowIndex, rowData) + val semaphore = Semaphore(4) + + val deferred = + settings.rows + .map { row -> + viewModelScope.async(WholphinDispatchers.IO) { + semaphore.withPermit { + Timber.v("Fetching row: %s", row) + try { + homeSettingsService.fetchDataForRow( + row = row.config, + scope = viewModelScope, + prefs = prefs, + userDto = userDto, + libraries = libraries, + limit = prefs.maxItemsPerRow, + isRefresh = refresh, + ) + } catch (ex: InvalidStatusException) { + if (ex.status == 404) { + Timber.w(ex, "404 on row %s", row) + HomeRowLoadingState.Success( + row.title, + emptyList(), + ) + } else { + Timber.e( + ex, + "Error %s on row %s", + ex.status, + row, + ) + HomeRowLoadingState.Error( + row.title, + exception = ex, + ) } - state.copy( - homeRows = newRows, - ) + } catch (ex: Exception) { + Timber.e(ex, "Error on row %s", row) + HomeRowLoadingState.Error( + row.title, + exception = ex, + ) + } } } - _state.update { - it.copy( - loadingState = LoadingState.Success, - refreshState = LoadingState.Success, - ) - } - } else { - val rows = deferred.awaitAll() - Timber.v("Got all rows") - _state.update { - it.copy( - loadingState = LoadingState.Success, - refreshState = LoadingState.Success, - homeRows = rows, - ) + } + + if (refresh) { + // Replace rows as they complete + val remaining = deferred.withIndex().toMutableList() + while (remaining.isNotEmpty()) { + val (rowIndex, rowData) = + select { + // "Return" the first remaining that is completed + remaining + .forEach { (rowIndex, deferred) -> + deferred.onAwait { rowIndex to it } + } } + Timber.v("Got row data index=%s", rowIndex) + remaining.removeIf { it.index == rowIndex } + _state.update { state -> + val newRows = + state.homeRows.toMutableList().apply { + set(rowIndex, rowData) + } + state.copy( + homeRows = newRows, + ) } - Timber.d("Home page load complete") } - } catch (ex: Exception) { - Timber.e(ex, "Exception during home page loading") - if (state.value.loadingState == LoadingState.Success) { - showToast(context, "Error refreshing home: ${ex.localizedMessage}") - _state.update { it.copy(refreshState = LoadingState.Error(ex)) } - } else { - _state.update { - it.copy(loadingState = LoadingState.Error(ex)) - } + _state.update { + it.copy( + loadingState = LoadingState.Success, + refreshState = LoadingState.Success, + ) + } + } else { + val rows = deferred.awaitAll() + Timber.v("Got all rows") + _state.update { + it.copy( + loadingState = LoadingState.Success, + refreshState = LoadingState.Success, + homeRows = rows, + ) + } + } + Timber.d("Home page load complete") + } catch (ex: CancellationException) { + throw ex + } catch (ex: Exception) { + Timber.e(ex, "Exception during home page loading") + if (state.value.loadingState == LoadingState.Success) { + showToast(context, "Error refreshing home: ${ex.localizedMessage}") + _state.update { it.copy(refreshState = LoadingState.Error(ex)) } + } else { + _state.update { + it.copy(loadingState = LoadingState.Error(ex)) } } } @@ -298,7 +357,7 @@ data class HomeState( HomeState( LoadingState.Pending, LoadingState.Pending, - listOf(), + emptyList(), HomePageResolvedSettings.EMPTY, ) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt index 64c0308ed..e3eafcbb8 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt @@ -595,7 +595,7 @@ class HomeSettingsViewModel } if (shouldSave) { homeSettingsService.saveToLocal(user.id, settings) - homeSettingsService.updateCurrent(settings) + homeSettingsService.updateCurrent(user.id, settings) showSaveToast() } else { Timber.d("No changes") @@ -616,7 +616,11 @@ class HomeSettingsViewModel _state.update { update.invoke(it) } - homeSettingsService.currentSettings.update { HomePageResolvedSettings(state.value.rows) } + serverRepository.currentUser?.id?.let { userId -> + homeSettingsService.currentSettings.update { + HomePageResolvedSettings(userId, state.value.rows) + } + } } fun resizeCards(relative: Int) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt index c47b86c54..e1a6dd962 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt @@ -132,6 +132,7 @@ class SwitchUserViewModel viewModelScope.async(WholphinDispatchers.IO) { try { val current = serverRepository.changeUser(server, user) + navigationManager.reloadHome() setupNavigationManager.navigateTo(SetupDestination.AppContent(current)) SwitchUserResult.Success } catch (ex: InvalidStatusException) { From 170989c31e2cfa8d7b295e3decc46eb96c4c92ca Mon Sep 17 00:00:00 2001 From: Damontecres Date: Thu, 17 Sep 2026 14:41:45 -0400 Subject: [PATCH 56/58] Guard against crash if episode has `seasonId` but no `seriesId` (#1964) ## Description When clicking on an episode, the app assumes if there is a season ID for the episode, then there must be a series ID. However, on my JF 12.0 server, an episode (with no metadata) appeared on my Continue Watching and it has a season ID (which in turn has a series ID) but no series ID. I'm not sure if this really a valid situation or if it's a server bug, but Wholphin shouldn't crash at least. So this PR checks both `seasonId` and `seriesId` and if either is null, falls back to the generic "Episode Page". Similarly, if a season has no `seriesId` (again, not sure if a valid situation), a crash won't occur, however there is no generic "Season Page", so an error page is shown. ### Related issues TODO, check JF server issues, maybe fixed in 12.1? ### Testing Emulator ## Screenshots N/a ## AI or LLM usage None --- .../wholphin/data/model/BaseItem.kt | 25 +++++++++++++------ 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt index dd1236a49..eefb001b8 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt @@ -237,21 +237,30 @@ data class BaseItem( // Redirect episodes & seasons to their series if possible when (type) { BaseItemKind.EPISODE -> { - data.seasonId?.let { seasonId -> + val seasonId = data.seasonId + val seriesId = data.seriesId + if (seasonId != null && seriesId != null) { Destination.SeriesOverview( - data.seriesId!!, + seriesId, BaseItemKind.SERIES, SeasonEpisodeIds(seasonId, data.parentIndexNumber, id, indexNumber), ) - } ?: Destination.MediaItem(this) + } else { + Destination.MediaItem(this) + } } BaseItemKind.SEASON -> { - Destination.SeriesOverview( - data.seriesId!!, - BaseItemKind.SERIES, - SeasonEpisodeIds(id, indexNumber, null, null), - ) + val seriesId = data.seriesId + if (seriesId != null) { + Destination.SeriesOverview( + seriesId, + BaseItemKind.SERIES, + SeasonEpisodeIds(id, indexNumber, null, null), + ) + } else { + Destination.MediaItem(this) + } } BaseItemKind.TV_CHANNEL -> { From 365f8f8adacf4dcaf7d05317917830ed3a439668 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <29139614+renovate[bot]@users.noreply.github.com> Date: Fri, 18 Sep 2026 11:16:50 -0400 Subject: [PATCH 57/58] Update android-actions/setup-android digest to be39fa8 (#1986) --- .github/actions/setup/action.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/actions/setup/action.yml b/.github/actions/setup/action.yml index b551456ac..24711c4a6 100644 --- a/.github/actions/setup/action.yml +++ b/.github/actions/setup/action.yml @@ -21,7 +21,7 @@ runs: echo "BUILD_TOOLS_VERSION=36.0.0" >> $GITHUB_ENV echo "NDK_VERSION=29.0.14206865" >> $GITHUB_ENV - name: Setup Android SDK - uses: android-actions/setup-android@40fd30fb8d7440372e1316f5d1809ec01dcd3699 # v4 + uses: android-actions/setup-android@be39fa834029ff78f1a44aa3bb0819b8fc2bd8fd # v4 with: packages: "platform-tools build-tools;${{ env.BUILD_TOOLS_VERSION }} ndk;${{ env.NDK_VERSION }}" - name: Add NDK to path From bc7d7fe31ada448ec8a98ff5cf60f5bbb9d9dd9c Mon Sep 17 00:00:00 2001 From: "github-actions[bot]" <41898282+github-actions[bot]@users.noreply.github.com> Date: Mon, 21 Sep 2026 12:59:01 +0000 Subject: [PATCH 58/58] Prepare semantic upstream review 365f8f8adacf4dcaf7d05317917830ed3a439668 against 4a6db6df866c4182576a3a9f96e3de697d5596e7 No upstream ancestry accepted. --- .upstream-sync/blocked-context.json | 13 + Intents.md | 10 +- .../wholphin/data/model/BaseItem.kt | 25 +- .../wholphin/services/HomeSettingsService.kt | 16 +- .../wholphin/services/ScreensaverService.kt | 9 +- .../services/SetupNavigationManager.kt | 3 - .../wholphin/ui/detail/FavoritesViewModel.kt | 4 +- .../ui/main/settings/HomeSettingsViewModel.kt | 8 +- .../wholphin/ui/search/SearchPage.kt | 2 +- .../wholphin/ui/search/SearchViewModel.kt | 9 +- .../wholphin/ui/setup/SwitchUserViewModel.kt | 1 + app/src/main/res/values-da/strings.xml | 2 + app/src/main/res/values-de/strings.xml | 12 + app/src/main/res/values-el/strings.xml | 281 ++++++++++--- app/src/main/res/values-en-rGB/strings.xml | 16 + app/src/main/res/values-es/strings.xml | 12 + app/src/main/res/values-et/strings.xml | 13 + app/src/main/res/values-hr/strings.xml | 9 + app/src/main/res/values-in/strings.xml | 30 ++ app/src/main/res/values-it/strings.xml | 15 + app/src/main/res/values-iw/strings.xml | 12 + app/src/main/res/values-lo/strings.xml | 3 + app/src/main/res/values-nb-rNO/strings.xml | 372 +++++++++++++++++- app/src/main/res/values-nl/strings.xml | 6 +- app/src/main/res/values-pl/strings.xml | 12 + app/src/main/res/values-pt-rBR/strings.xml | 62 +++ app/src/main/res/values-pt/strings.xml | 12 + app/src/main/res/values-ru/strings.xml | 140 ++++++- app/src/main/res/values-sk/strings.xml | 9 + app/src/main/res/values-sv/strings.xml | 24 +- app/src/main/res/values-tr/strings.xml | 9 + app/src/main/res/values-zh-rCN/strings.xml | 2 + app/src/main/res/values-zh-rTW/strings.xml | 12 + .../wholphin/services/IntentServiceTest.kt | 273 ++++++++++++- .../damontecres/wholphin/test/TestUtils.kt | 21 + gradle/libs.versions.toml | 2 +- 36 files changed, 1347 insertions(+), 114 deletions(-) create mode 100644 .upstream-sync/blocked-context.json create mode 100644 app/src/main/res/values-lo/strings.xml diff --git a/.upstream-sync/blocked-context.json b/.upstream-sync/blocked-context.json new file mode 100644 index 000000000..ffaca70cb --- /dev/null +++ b/.upstream-sync/blocked-context.json @@ -0,0 +1,13 @@ +{ + "conflicts": [ + ".github/actions/setup/action.yml", + ".github/workflows/pr.yml", + "app/src/main/java/com/github/damontecres/wholphin/services/IntentService.kt", + "app/src/main/java/com/github/damontecres/wholphin/ui/main/HomeViewModel.kt" + ], + "downstream": "4a6db6df866c4182576a3a9f96e3de697d5596e7", + "mergeBase": "4a118b6ddbbbb5b473f3c96011a0ac05781c9b76", + "policyVersion": 1, + "schemaVersion": 1, + "upstream": "365f8f8adacf4dcaf7d05317917830ed3a439668" +} diff --git a/Intents.md b/Intents.md index 6e428ab60..af13f29d2 100644 --- a/Intents.md +++ b/Intents.md @@ -63,7 +63,7 @@ Action: `android.intent.action.VIEW` Shorthand action: `view` Parameters: -- `itemId` - Required, the UUID of the media item +- `itemId` - Optional, the UUID of the media item, if not set, goes to home screen ### Examples @@ -135,12 +135,20 @@ adb shell am start -d 'mosaic://play?itemId=5cf8f8e7-2a5f-4aa9-8c12-ddf63d42ee6d Full format ```bash +# Switch user, go to item adb shell am start \ -a android.intent.action.VIEW \ -n 'com.github.damontecres.wholphin/.MainActivity' \ --es itemId "5cf8f8e7-2a5f-4aa9-8c12-ddf63d42ee6d" \ --es serverId "341880f9-ca88-4038-8718-f6b2407950b1" \ --es userId "5c8575e0-468d-44fb-8c70-00aa79160587" + +# Switch user, go to home screen +adb shell am start \ + -a android.intent.action.VIEW \ + -n 'com.github.damontecres.wholphin/.MainActivity' \ + --es serverId "341880f9-ca88-4038-8718-f6b2407950b1" \ + --es userId "5c8575e0-468d-44fb-8c70-00aa79160587" ``` URI based diff --git a/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt b/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt index dd1236a49..eefb001b8 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/data/model/BaseItem.kt @@ -237,21 +237,30 @@ data class BaseItem( // Redirect episodes & seasons to their series if possible when (type) { BaseItemKind.EPISODE -> { - data.seasonId?.let { seasonId -> + val seasonId = data.seasonId + val seriesId = data.seriesId + if (seasonId != null && seriesId != null) { Destination.SeriesOverview( - data.seriesId!!, + seriesId, BaseItemKind.SERIES, SeasonEpisodeIds(seasonId, data.parentIndexNumber, id, indexNumber), ) - } ?: Destination.MediaItem(this) + } else { + Destination.MediaItem(this) + } } BaseItemKind.SEASON -> { - Destination.SeriesOverview( - data.seriesId!!, - BaseItemKind.SERIES, - SeasonEpisodeIds(id, indexNumber, null, null), - ) + val seriesId = data.seriesId + if (seriesId != null) { + Destination.SeriesOverview( + seriesId, + BaseItemKind.SERIES, + SeasonEpisodeIds(id, indexNumber, null, null), + ) + } else { + Destination.MediaItem(this) + } } BaseItemKind.TV_CHANNEL -> { diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt index e7b5db000..3371f45eb 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/HomeSettingsService.kt @@ -245,7 +245,7 @@ class HomeSettingsService settings.rows.mapIndexed { index, config -> resolve(index, config) } - HomePageResolvedSettings(resolvedRows) + HomePageResolvedSettings(userId, resolvedRows) } else { createDefault(userId) } @@ -256,12 +256,15 @@ class HomeSettingsService /** * Resolve the settings and set them to be the current settings */ - suspend fun updateCurrent(settings: HomePageSettings) { + suspend fun updateCurrent( + userId: UUID, + settings: HomePageSettings, + ) { val resolvedRows = settings.rows.mapIndexed { index, config -> resolve(index, config) } - val resolvedSettings = HomePageResolvedSettings(resolvedRows) + val resolvedSettings = HomePageResolvedSettings(userId, resolvedRows) currentSettings.update { resolvedSettings } } @@ -312,7 +315,7 @@ class HomeSettingsService ), ) val rowConfig = continueWatchingRow + includedIds - return HomePageResolvedSettings(rowConfig) + return HomePageResolvedSettings(userId, rowConfig) } /** @@ -428,7 +431,7 @@ class HomeSettingsService null } }.flatten() - HomePageResolvedSettings(rowConfigs) + HomePageResolvedSettings(userId, rowConfigs) } else { null } @@ -1224,10 +1227,11 @@ data class HomeRowConfigDisplay( * @see HomePageSettings */ data class HomePageResolvedSettings( + val userId: UUID, val rows: List, ) { companion object { - val EMPTY = HomePageResolvedSettings(listOf()) + val EMPTY = HomePageResolvedSettings(UUID.randomUUID(), emptyList()) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt b/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt index 09aa29a82..c3fcb4786 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/ScreensaverService.kt @@ -173,9 +173,10 @@ class ScreensaverService */ fun keepScreenOn(keep: Boolean) { scope.launchDefault { - val screensaverEnabled = _state.value.run { enabled || dimEnabled } + val screensaverEnabled = state.value.enabled + val dimEnabled = state.value.dimEnabled Timber.d("Keep screen on: %s, screensaverEnabled=%s", keep, screensaverEnabled) - if (screensaverEnabled) { + if (screensaverEnabled || dimEnabled) { // Page is requesting to keep screen on, so we don't wait to show the screensaver _state.update { it.copy( @@ -187,7 +188,9 @@ class ScreensaverService if (!keep) { pulse() } - } else { + } + if (!screensaverEnabled) { + // If in-app screensaver is not enabled, send keep screen on to the OS keepScreenOnInternal(keep) } } diff --git a/app/src/main/java/com/github/damontecres/wholphin/services/SetupNavigationManager.kt b/app/src/main/java/com/github/damontecres/wholphin/services/SetupNavigationManager.kt index fa42469e4..abe39959e 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/services/SetupNavigationManager.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/services/SetupNavigationManager.kt @@ -27,9 +27,6 @@ class SetupNavigationManager fun navigateTo(destination: SetupDestination) { backStack[0] = destination log() - if (destination !is SetupDestination.AppContent) { - navigationManager.reloadHome() - } } private fun log() { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/FavoritesViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/FavoritesViewModel.kt index 8471c40dc..adbd9a44b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/detail/FavoritesViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/detail/FavoritesViewModel.kt @@ -787,7 +787,9 @@ fun List.possibleFavoriteTypes(sort: Boolean) = flatMap { it.collectionType.baseItemKinds } .distinct() .filter { it in favoriteOptions } - .let { list -> + .let { + // No library for persons, so always include it + val list = it + listOf(BaseItemKind.PERSON) if (sort) { list.sortedWith(compareBy { favoriteOptions.indexOf(it) }) } else { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt index 64c0308ed..e3eafcbb8 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/main/settings/HomeSettingsViewModel.kt @@ -595,7 +595,7 @@ class HomeSettingsViewModel } if (shouldSave) { homeSettingsService.saveToLocal(user.id, settings) - homeSettingsService.updateCurrent(settings) + homeSettingsService.updateCurrent(user.id, settings) showSaveToast() } else { Timber.d("No changes") @@ -616,7 +616,11 @@ class HomeSettingsViewModel _state.update { update.invoke(it) } - homeSettingsService.currentSettings.update { HomePageResolvedSettings(state.value.rows) } + serverRepository.currentUser?.id?.let { userId -> + homeSettingsService.currentSettings.update { + HomePageResolvedSettings(userId, state.value.rows) + } + } } fun resizeCards(relative: Int) { diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt index e353287db..f20222d96 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchPage.kt @@ -113,7 +113,7 @@ fun SearchPage( val focusRequesters = remember(state.includedSearchableTypes.size) { List(RESULTS_START + state.includedSearchableTypes.size) { FocusRequester() } } - val seerrActive by viewModel.seerrActive.collectAsState(initial = false) + val seerrActive by viewModel.seerrActive.collectAsState() var selectedTab by rememberSaveable(seerrActive, state.discoverEnabled) { mutableIntStateOf(0) } var showViewOptions by rememberSaveable { mutableStateOf(false) } var showFilterTypeDialog by rememberSaveable { mutableStateOf(false) } diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt index 81dfa95fd..044178b2b 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/search/SearchViewModel.kt @@ -43,9 +43,11 @@ import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.qualifiers.ApplicationContext import kotlinx.coroutines.CancellationException import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.SharingStarted import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.first import kotlinx.coroutines.flow.firstOrNull +import kotlinx.coroutines.flow.stateIn import kotlinx.coroutines.flow.update import kotlinx.coroutines.launch import org.jellyfin.sdk.api.client.ApiClient @@ -79,7 +81,12 @@ class SearchViewModel private val navDrawerService: NavDrawerService, ) : ViewModel(), ContextMenuProvider { - val seerrActive = seerrService.active + val seerrActive = + seerrService.active.stateIn( + scope = viewModelScope, + started = SharingStarted.Eagerly, + initialValue = false, + ) private val _state = MutableStateFlow(SearchState()) val state: StateFlow = _state diff --git a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt index c47b86c54..e1a6dd962 100644 --- a/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt +++ b/app/src/main/java/com/github/damontecres/wholphin/ui/setup/SwitchUserViewModel.kt @@ -132,6 +132,7 @@ class SwitchUserViewModel viewModelScope.async(WholphinDispatchers.IO) { try { val current = serverRepository.changeUser(server, user) + navigationManager.reloadHome() setupNavigationManager.navigateTo(SetupDestination.AppContent(current)) SwitchUserResult.Success } catch (ex: InvalidStatusException) { diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml index 72c7ab266..9a76ef527 100644 --- a/app/src/main/res/values-da/strings.xml +++ b/app/src/main/res/values-da/strings.xml @@ -707,4 +707,6 @@ Dæmp skærmen efter ventetid Dæmpning (%) Dæmp skærmen + Fotoalbummer + Ingen favoritter fundet! diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 2e447e0e3..def327d3e 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -704,4 +704,16 @@ Nicht unterstützte Server-Version Audio-Hardware-Offload deaktivieren Das Auslagern der Audio Decodierung zu deaktivieren könnte Wiedergabeprobleme auf einigen Gräten beheben + %1$dh %2$ds + S%1$d F%2$d + S%1$d F%2$d-E%3$d + S%1$02dF%2$02d + S%1$02dF%2$02d-E%3$02d + F%1$d + Staffel %1$d + Bildschirm nach Verzögerung dimmen + Dimmen in Prozent + Bildschirm dimmen + Fotoalben + Keine Favoriten vorhanden! diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index a4070ecab..732ffe5da 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -18,20 +18,20 @@ Τα βίντεο Παρασκήνιο - Θεματικά τραγούδια + Θεματικό τραγούδι Θεματικά τραγούδια - Θεματικά βίντεο + Θεματικό βίντεο Θεματικά βίντεο - Επόμενο - Ενεργοποιήστε την επανάληψη παρακολούθησης στα \'Επόμενα\' + Επόμενα + Ενεργοποιήστε την επαναπροβολή στα \'Επόμενα\' Γραμματοσειρά Μέγεθος Κειμένου Χρώμα Κειμένου - Φωτεινότητα κειμένου - Βιβλιοθήκης + Έντονη γραμματοσειρά + Βιβλιοθήκη Σειρές Είδη Συλλογή @@ -43,25 +43,25 @@ Στούντιο SDR Ηθοποιοί και Συνεργείο - Περισσότερα Σαν Αυτό + Παραπλήσια Επεισόδια #ΑΒΓΔΕΖΗΘΙΚΛΜΝΞΟΠΡΣΤΥΦΧΨΩ %s λήψη %s λήψεις - λήψη… + Γίνεται λήψη… Διαδρομή Όνομα - Δοχείο + Κοντέινερ Μέγεθος Κωδικοποιητής AVC Ρυθμός bit Βάθος bit bit - Εξαναγκασμός - Σχέδιο + Αναγκαστικό + Διάταξη Προεπιλογή Εξωτερικό Προσθήκη χρήστη @@ -69,25 +69,25 @@ Κεφάλαια Διάλεξε %1$s Προσθήκη διακομιστή - Ακύρωση καταχώρησης - Ακύρωση σειρά καταχώρησης + Ακύρωση καταγραφής + Ακύρωση καταγραφής σειράς Καθαρισμός μνήμης εικόνας Διαφήμιση Αξιολόγηση κοινού Ένα σφάλμα εμφανίστηκε! Πατήστε το κομπί για να στείλετε αρχείο καταγραφής στο διακομιστή. Επικύρωση - Συνεχίστε να βλέπετε + Συνέχεια προβολής Κριτική - %.1f δευτερόλεπτα + %.2f δευτερόλεπτα Διαγραφή Πέθανε Σκηνοθετήθηκε από %1$s - Απενεργοποιημένος - Ενεργοποιημένος - Καταχώρηση διακομιστή IP or URL + Απενεργοποιημένο + Ενεργοποιημένο + Δώσε την IP ή το URL του διακομιστή Σφάλμα φόρτωσης συλλογής %1$s - Πηγαίνετε στις σειρές - Πηγαίνετε στο + Πήγαινε στη σειρά + Πήγαινε σε αυτό Απόκρυψη χειρισμού αναπαραγωγής Απόκρυψη πληροφοριών εντοπισμού σφαλμάτων Απόκρυψη @@ -96,15 +96,15 @@ Πληροφορίες άδειας Ζωντανή Τηλεόραση Φόρτωση… - Επισήμανση ολόκληρης σειράς ως αναπαραγμένης? - Επισήμανση ολόκληρης σειράς ως μη αναπαραγμένης? + Επισήμανση ολόκληρης σειράς ως αναπαραγμένης; + Επισήμανση ολόκληρης σειράς ως μη αναπαραγμένης; Επισήμανση ως μη ολοκληρωμένο Επισήμανση ως ολοκληρωμένο Μέγιστος ρυθμός μετάδοσης δεδομένων Περισσότερα Χωρίς δεδομένα Χωρίς αποτελέσματα - Χωρίς προγραμματισμένες καταχωρήσεις + Χωρίς προγραμματισμένες καταγραφές Χωρίς διαθέσιμη ενημέρωση Έναρξη από εδώ Έναρξη @@ -117,16 +117,16 @@ Ρυθμίσεις προφίλ χρήστη Ουρά Ανακεφαλαίωση - Πρόσφατα πρόσθετα σε %1$s - Πρόσφατα πρόσθετα + Πρόσφατες προσθήκες σε %1$s + Πρόσφατες προσθήκες Πρόσφατα καταγεγραμμένο - Πρόσφατα κυκλοφόρησε + Πρόσφατης κυκλοφορίας Προτεινόμενο - Πρόγραμμα καταγραφής - Σειρά καταγραφής - Κατάργηση από τα αγαπημένα + Καταγραφή προγράμματος + Καταγραφή σειράς + Αναίρεση αγαπημένου Επανεκκίνηση - Περίληψη + Συνέχισε Αποθήκευση Αναζήτηση Αναζήτηση… @@ -136,9 +136,9 @@ Εμφάνιση επόμενου Εμφάνιση Τυχαία αναπαραγωγή - Αναπήδηση - Προσθήκη ημερομηνίας - Προσθήκη ημερομηνίας επεισοδίου + Παράλειψε + Ημερομηνία Προσθήκης + Ημερομηνία Προσθήκης Επεισοδίου Ημερομηνία αναπαραγωγής Ημερομηνία κυκλοφορίας Όνομα @@ -146,17 +146,17 @@ Υποβολή Ενεργές καταγραφές Κανένας - Εμφάνιση πληροφοριών αναπαραγωγής σφαλμάτων - Λήψη αρχείων διαρκεί πολύ χρονο, ίσως χρειαστεί να επανεκκινήσετε την αναπαραγωγή + Εμφάνιση πληροφοριών σφαλμάτων αναπαραγωγής + Καθυστέρηση της λήψης, ίσως απαιτείται επανεκκίνηση αναπαραγωγής Αλλαγή διακομιστή - Αντικατάσταση - Κορυφαίες βαθμολογίες σε ταινίες που δεν έχω δει + Αλλαγή + Κορυφαίας βαθμολογίας που δεν έχουν προβληθεί Οδηγός Σεζόν Περισσότερες Σεζόν Άγνωστο Αναβαθμίσεις - Εκδοχή + Έκδοση Κλίμακα βίντεο Ζωντανή μετάδοση Πληροφορίες μέσων @@ -167,46 +167,227 @@ Πλάγια γραμματοσειρά Επιτυχής Γονεική αξιολόγηση - Χρόνος εκτέλεσης + Διάρκεια Επιπρόσθετα Άλλο - Άλλο + Άλλα - Παρασκήνια + Παρασκήνιο Παρασκήνια - Αποσπάσματα + Απόσπασμα Αποσπάσματα - Διαγραμμένες σκηνές + Διαγραμμένη σκηνή Διαγραμμένες σκηνές - Συνεντεύξεις + Συνέντευξη Συνεντεύξεις - Σκηνές + Σκηνή Σκηνές - Δείγματα + Δείγμα Δείγματα - Χαρακτηριστικά - Χαρακτηριστικά + Προωθητικό βίντεο + Προωθητικά βίντεο Αυτόματος έλεγχος για ενημερώσεις Καθυστέρηση πριν την έναρξη του επόμενου Αυτόματη έναρξη επόμενου Έλεχγος για ενημερώσεις Ισχύει μόνο για τηλεοπτικές σειρές - Προεπιλεγμένη αναπαραγωγή περιεχομένου + Προεπιλεγμένη κλίμακα περιεχομένου Εγκατάσταση ενημέρωσης - Εγκατεστημένη εκδοχή + Εγκατεστημένη έκδοση + Πληροφορίες + %1$dω %2$dλ %3$dδ + %1$dω %2$dλ + %1$dλ %2$dδ + %1$dω + %1$dλ + %1$dδ + %1$s απέμεινε + %1$dω %2$dδ + Εντοπισμένοι διακομιστές + Τελειώνει σε %1$s + Εναλλαγή ανάλυσης + Τοπικό + Αναπαραγωγή τρέιλερ + Λείπει τρέιλερ + Καθαρισμός επιλογών καναλιού + Δώσε την διεύθυνση διακομιστή + Δεν βρέθηκαν διακομιστές + Μετρητής προβολών + Μόνο αναγκαστικοί υπότιτλοι + Τίτλοι τέλους + Σφάλμα καταγραφής ήχου + Η οπτικοποίηση του ήχου αναπαραγωγής απαιτεί άδεια καταγραφής ήχου. + Καταγραφές + Ρυθμίσεις του προφίλ χρήστη στο διακομιστή + Λειτουργία υποτίτλων + Χρήση των ρυθμίσεων του προφίλ + Προεπιλογή + Έξυπνο + Μόνο αναγκαστικοί + Πάντα + Κανένα + Προτιμώμενη γλώσσα υποτίτλων + Προτιμώμενη γλώσσα ήχου + Περισσότερες ρυθμίσεις του προφίλ χρήστη + Παράκαμψη των ρυθμίσεων του προφίλ χρήστη στον διακομιστή + Οποιαδήποτε γλώσσα + Λειτουργία σινεμά + + + Ταινία + Ταινίες + + + Άτομο + Άτομα + + + + Τρέιλερ + Τρέιλερ + + Προγραμματισμός DVR + + Σειρά + Σειρές + + Διεπαφή + + %1$d έτους + %1$d ετών + + Αναπαραγωγή με μετατροπή + Σειρά τηλεοπτικής προβολής + Παύση/Αναπαραγωγή με το κεντρικό κουμπί του σταυρού πλοήγησης + + Μικρού μήκους + Μικρού μήκους + + + %d ώρα + %d ώρες + + + %d αντικείμενο + %d αντικείμενα + + + %d δευτερόλεπτο + %d δευτερόλεπτα + + Η συσκευή υποστηρίζει AC3/Dolby Digital + Προηγμένη διεπαφή + + Ανώτατο όριο ημερών στα Επόμενα + Στο τέλος της αναπαραγωγής + Κατά τη διάρκεια των τίτλων τέλους + Αφαίρεση σειράς από τα Επόμενα + Εμφάνιση σειρών που αφαιρέθηκαν από τα Επόμενα + Ποτέ + Συμπεριφορά επιλογής του Συνέχεια προβολής/Επόμενο + Αφαίρεση από συνέχεια προβολής + Αναπαραγωγή κατά την επιλογή + Προβολή λεπτομέρειον κατά την επιλογή + Χρήση libass για ASS υπότιτλους + Απευθείας αναπαραγωγή PGS υπότιτλων + Πάντα υποβιβασμός σε στέρεο + Χρήση αποκωδικοποιητή FFmpeg + Ανώτατο όριο αντικειμένων στις γραμμές της αρχικής + Διάλεξε τα προεπιλεγμένα αντικείμενα για εμφάνιση, θα γίνει απόκρυψη των υπόλοιπων + Απομνημόνευση επιλεγμένων καρτελών + Ρυθμίσεις + Ανακάλυψε + Αίτημα + Εκκρεμί + Κωδικός + Όνομα χρήστη + Τάσεις + Κόκκινο + Πράσινο + Μπλε + Θόλωση + Ύψος + Προσαρμογή + Περικοπή + Γέμισμα + Χαμηλότερο + Χαμηλό + Μεσαίο + Υψηλό + Τέρμα + Μωβ + Πορτοκαλί + Μαύρο + Αγνόηση + Άσπρο + Κίτρινο + Κυανό + Πορφυρώ + Περίγραμμα + Σκιά + Αναδίπλωση + Πλαίσιο + Διάρκεια + Προστασία οθόνης + Φωτογραφίες + Ηθοποιός + Συνθέτης + Συγγραφέας + Παραγωγός + Μαέστρος + Στιχουργός + Ενορχηστρωτής + Υπεύθυνος μίξης + Δημιουργός + Καλλιτέχνης + Άλμπουμ + Καλλιτέχνες + Τραγούδια + Άλμπουμ + Στίχοι + Συνεχεία + Μετατροπή + Επαναφορά + Παίχτηκε + Φίλτρο + Έτος + Δεκαετία + Αφαίρεση + Περιθώριο + Λανθασμένο + Στήλες + Διάστιχο + Αυτόματα + Σύνδεση + Γενικά + Τίτλος + Επίπεδο + Ανάλυση + Αναμορφικό + Πεπλεγμένο + Ρυθμός ανανέωσης + Επανάληψη + Επανάλαβε + Φωτεινότητα + Αντίθεση + Κορεσμός + Απόχρωση + Καθυστέρηση υποτίτλων + Ανακάλυψε ταινίες + Ανακάλυψε σειρές diff --git a/app/src/main/res/values-en-rGB/strings.xml b/app/src/main/res/values-en-rGB/strings.xml index 417bac6b7..6f57641a1 100644 --- a/app/src/main/res/values-en-rGB/strings.xml +++ b/app/src/main/res/values-en-rGB/strings.xml @@ -36,4 +36,20 @@ Skip behaviour Customise which to display Continue watching/Next up click behaviour + About + Active Recordings + Add Server + Add User + Audio + Birthplace + Bitrate + Born + Cancel Recording + Cancel Series Recording + %1$dh %2$ds + %1$dm %2$ds + %1$dh + Cancel + Chapters + Choose %1$s diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index 5eee727c4..cb3513a80 100644 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -726,4 +726,16 @@ Aparece en Desactivar el procesamiento de audio por hardware Desactivar el procesamiento de audio por hardware puede solucionar los bloqueos de reproducción en algunos dispositivos + Atenuar la pantalla tras un tiempo de inactividad + Porcentaje de atenuación + Atenuar pantalla + Álbumes de fotos + ¡No se encontraron favoritos! + %1$dh %2$ds + T%1$d E%2$d + T%1$d E%2$d-E%3$d + T%1$02dE%2$02d + T%1$02dE%2$02d-E%3$02d + E%1$d + Temporada %1$d diff --git a/app/src/main/res/values-et/strings.xml b/app/src/main/res/values-et/strings.xml index 74cc50555..2e3548f8d 100644 --- a/app/src/main/res/values-et/strings.xml +++ b/app/src/main/res/values-et/strings.xml @@ -704,4 +704,17 @@ On osalenud siin: Lülita graafikaprotsessori kasutamine heli töötluseks välja Kui lülita graafikaprotsessori kasutamise heli töötluseks välja, siis see võib mõnede seadmete puhul parandada taasesituse hangumise vead + Hämarda ekraani peale viivitust + Hämardamise protsent + Hämarda ekraani + Fotoalbumid + Ühtegi lemmikut ei leidu! + %1$d t %2$d sek + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + %1$d. hooaeg + Eemalda esitusloendist diff --git a/app/src/main/res/values-hr/strings.xml b/app/src/main/res/values-hr/strings.xml index 45442137c..e01773c79 100644 --- a/app/src/main/res/values-hr/strings.xml +++ b/app/src/main/res/values-hr/strings.xml @@ -729,4 +729,13 @@ Zatamni zaslon nakon odgode Postotak zatamnjenja Zatamni zaslon + %1$dh %2$ds + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + Sezona %1$d + Albumi slika + Nijedan favorit nije pronađen! diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 573230c37..b6f315ddd 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -664,4 +664,34 @@ Semua bahasa Cokelat Mode bilah geser D-Pad + %1$dj %2$dd + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + Season %1$d + Nonaktifkan offload audio hardware + Menonaktifkan dekode audio offload dapat mengatasi masalah pemutaran macet pada beberapa perangkat + Minimal + Trickplay + Bilah geser kecil di sepanjang bawah layar (default) + Bilah geser dengan gambar pratinjau trickplay + Tanpa bilah geser + Daftar blokir + Hash sesuai + SDH + Acara TV + Rekaman + Folder + Tampilan pengguna + Buku + Buku audio + Versi server tidak didukung + Tampil pada + Redupkan layar setelah penundaan + Persentase redup + Redupkan layar + Album foto + Favorit tidak ditemukan! diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index e973af469..b7d7acaeb 100644 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -726,4 +726,19 @@ Appare in Disabilita offload hardware audio Disabilitare l’offload della decodifica audio può risolvere i blocchi di riproduzione su alcuni dispositivi + Scurisci lo schermo dopo un ritardo + Percentuale di oscuramento + Scurisci schermo + Album fotografici + Nessun preferito trovato! + %1$do %2$ds + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + Stagione %1$d + Rimuovi dalla playlist + Credenziali scadute, effettua di nuovo l’accesso + Non sei autorizzato ad accedere al server in questo momento diff --git a/app/src/main/res/values-iw/strings.xml b/app/src/main/res/values-iw/strings.xml index c8d4ef228..38df9e364 100644 --- a/app/src/main/res/values-iw/strings.xml +++ b/app/src/main/res/values-iw/strings.xml @@ -726,4 +726,16 @@ מופיע ב־ השבתת פענוח אודיו בהורדה עשויה לתקן קפיאות בניגון במכשירים מסוימים השבתת פענוח אודיו בהורדה + עמעם תצוגה אחרי השהייה + עוצמת העמעום + עמעם תצוגה + אלבומי תמונות + לא נמצאו מועדפים! + %1$d שע\' %2$d שנ\' + עונה %1$d פרק %2$d + עונה %1$d פרקים %2$d-%3$d + עונה %1$02d פרק %2$02d + עונה %1$02d פרקים %2$02d-%3$02d + פרק %1$d + עונה %1$d diff --git a/app/src/main/res/values-lo/strings.xml b/app/src/main/res/values-lo/strings.xml new file mode 100644 index 000000000..045e125f3 --- /dev/null +++ b/app/src/main/res/values-lo/strings.xml @@ -0,0 +1,3 @@ + + + diff --git a/app/src/main/res/values-nb-rNO/strings.xml b/app/src/main/res/values-nb-rNO/strings.xml index 9ddb6c770..9e26efccd 100644 --- a/app/src/main/res/values-nb-rNO/strings.xml +++ b/app/src/main/res/values-nb-rNO/strings.xml @@ -23,7 +23,7 @@ Bekreft Fortsett å se Kritikervurdering - %.1f sekunder + %.2f sekunder Regissør Deaktivert Oppdagede servere @@ -120,7 +120,7 @@ Bytt bruker Best vurderte usette - Trailere + Trailer Trailere DVR-plan @@ -137,6 +137,7 @@ Videoer Se direkte + %1$d år gammel %1$d år gammel Spill av med transkoding @@ -163,39 +164,39 @@ Bak kulissene - Temasanger - Temasanger + Kjenningsmelodi + Kjenningsmelodier - Temavideoer - Temavideoer + Kjenningsvideo + Kjenningsvideoer Klipp Klipp - Slettede scener + Slettet scene Slettede scener - Intervjuer + Intervju Intervjuer - Scener + Scene Scener - Smakebiter + Smakebit Smakebiter - Bakomfilmer - Bakomfilmer + Novellefilm + Novellefilmer - Kortfilmer + Kortfilm Kortfilmer @@ -223,7 +224,7 @@ Sjekk etter oppdateringer Gjelder kun TV-serier Slå sammen \'Fortsett å se\' og \'Neste\' - Direkteavspilling av ASS-undertekster + Bruk libass for ASS-undertekster Direkteavspilling av PGS-undertekster Alltid nedmiks til stereo Bruk FFmpeg-dekodermodul @@ -365,4 +366,347 @@ Spill trailer Ingen trailere Fjern valg av spor + %1$dh %2$dm %3$ds + %1$dt %2$dmin + %1$dmin %2$ds + %1$dt + %1$dmin + %1$ds + Bare Tvungne Undertekster + Brukerprofil instillinger fra server + Stemmesøk + Begynner + Snakk for å søke + Behandler + Trykk tilbake for å avbryte + Lydopptaksfeil + Stemmegjenkjenningsfeil + Mikrofonadgang nødvendig + Nettverksfeil + Nettverksavbrudd + Ingen tale gjenkjent + Stemmegjenkjenning opptatt + Tjenerfeil + Ingen tale oppdaget + Ukjent feil + Feil ved oppstart av stemmegjenkjenning + Tidsavbrudd ved stemmegjenkjenning + Prøv igjen + %1$s gjenstår + Trailere + Annet + Bak kulissene + Kjenningsmelodier + Kjenningsvideoer + Klipp + Slettede Scener + Intervju + Scener + Prøver + Novellefilmer + Kortfilmer + Favoritt %s + + %s dag + %s dager + + + %s minutt + %s minutter + + + Film + Filmer + + + Serie + Serier + + + Person + Personer + + Krev innlogging for profil + [0-9] + Deaktiver avlasting av lydmaskinvare + Deaktivering av lyddekodingavlasting kan fikse fryst bilde ved avspilling på noen enheter + Kombinert søkeresultater + Vis alle resultater i én liste + Vis resultater etter kategori + Vis knapp for stemmesøk + Synlig + Skjult + Resultater + DTS:X + DTS:HD + DTS:HD MA + TrueHD + DD + DD+ + DTS + Direct play Dolby Vision Profile 7 + Ignorer kompatibilitetsjekk av enheten + Oppdag + Forespørsel + Venter + Seerr integrering + Fjern Seerr Tjener + Seerr tjener lagt til + Rask Tilkobling + Autoriser annen enhet til å logge på kontoen din + For episoder + Forslag for %1$s + Øk størrelsen for alle kort + Reduser størrelsen for alle kort + Bruk miniatyrbilder + Instillinger lagret + Vis forhåndsinnstillinger + Innebygde forhåndsinnstillinger for å raskt style alle rader + Velg rader og bilder på hjemmesiden + + Bruk AC3 for surround sound lyd + Start etter + Animer + Maks aldersgrense + Ingen maks + For alle aldre + Opp til %s år + Skjermsparer + Innstillinger for skjermsparer + Start skjermsparer + Varighet + Bilder + Inkluder typer + Hvilke elementer å vise bilder for + Tilpass + Beskjær + Fyll + Fyll bredde + Fyll høyde + Wholphin Standard + Wholphin Kompakt + Serieminiatyrbilder + Episodeminiatyrbilder + Lavest + Lav + Middels + Høy + Full + Lilla + Oransje + Dristig Blå + Svart + Brun + Ignorer + Hopp over automatisk + Spør om å hoppe over + Bare bruk FFmpeg dersom ingen innebygd dekoder eksisterer + Foretrekk å bruke FFmpeg over innebygde dekodere + Aldri bruk FFmpeg dekodere + Ved slutten av avspilling + Under rulletekst/avslutning + Aldri + Hvit + Lysegrå + Mørkegrå + Gul + Cyan + Magenta + Kant + Skygge + Innpakning + Innebokset + Foretrekk MPV + Ekstern Spiller + Bruk ExoPlayer for avspilling av HDR + Plakat (2:3) + 16:9 + 4:3 + Kvadrat (1:1) + Primær + Miniatyr + Rutenett + Liste + Kompakt Liste + Bilde med dynamisk farge + Bare bilde + + API-nøkkel + Logg inn på Seerr-tjener + Jellyfinbruker + Lokal bruker + + Ingen eksterne undertekster ble funnet + Nåværende + Bruk appens skjermsparer + Er du sikker på at du vil slette dette elementet? + Vis alternativ for behandling av medier + Søk %s + Skuespiller + Komponist + Forfatter + Gjesteartist + Produsent + Dirigent + Sangtekstforfatter + Arrangør + Ingeniør + Lydmikser + Skaper + Artist + Albumartist + Album + Populære sanger + Spill av neste + Musikkvideoer + Sangtekst + Skjul sangtekst + Vis sangtekst + Låt har sangtekst + Fjern fra kø + Vis albumomslag + Vis visualizer + Vis bakgrunn + Spill av som? + Visualisering av lyden som spilles av, krever tillatelse til å ta opp lyd. + Fortsett + Velg alle + Seperate typer + Foretrekk å vise logoer for titler + Wholphin oppdatert til %s + Fjern fra fortsett å se + Fjern serier fra neste + Vis serier fjernet fra neste + Vis mer + Oppdag TV-serier + Oppdag Filmer + Studioer i %1$s + Sist avspilt + System standard + Direkteavspilling med libass + Direkteavspilling med ExoPlayer innebygd + Brenn/transkode på tjener + SSA/ASS avspilling av undertekst + Kinomodus + Vis %1$s mer + Hopp over oppførsel + For introer, rulletekst, etc + Spill med + Transkoding + Språk på brukergrensesnitt + Visning av anmeldelser + Tilpass hva å vise + Profilbeskyttelse + Fortsett å se/Neste klikkoppførsel + Spill av ved klikk + Vis detaljer ved klikk + Stjerner + Tomater + Sorter + Del inn i separate rader + %1$s Filmer + %1$s TV-Serier + Kvalitetsprofil + Standard + Root-mappe + Populæritet + Produksjonstatus + Bruk annen stil for HDR + Eksperimentelle innstillinger + Aktiver eksperimentelle innstillinger? + Eksperimentelle innstillinger kan være ustabile!\n\nDisse kan fjernes eller endre i hvilken som helst versjon. + Skriv inn Quick Connect kode + Kode må være 6 siffer + Enhet autorisering vellykket + Passord + Brukernavn + URL + Trender + Kommende Filmer + Kommende TV-Serier + Forespør i 4K + AV1 programvaredekoding + MPV er nå standardspiller utenom for HDR.\nDu kan endre dette i innstillinger. + HDR undertekststil + Bildeundertekst ugjennomsiktighet + Lysstyrke + Kontrast + Metning + Tone + Rød + Grønn + Blå + Uklarhet + Lagre for album + Start lysbildefremvisning + Stans lysbildefremvisning + Ved start + Ingen flere bilder + Roter venstre + Roter høyre + Zoom inn + Zoom ut + Lysbildefremvisningens varighet + Spill av video under lysbildefremvisning + Send mediainformasjonslogg til tjener + Ingen grense + Maks dager i Neste + Album + Artister + Sanger + Gå til artist + Spilles nå + Øyeblikkelig miks + Gå til album + Legg til i kø + Legg til rad + Sjangre i %1$s + Nylig utgitt i %1$s + Høyde + Bruk på alle rader + Tilpass hjemmesiden + Hjemmerader + Last inn fra tjeners brukerprofil + Lagre på tjeners brukerprofil + Last inn fra web-client + Bruk seriebilde + Legg til rad for %1$s + Overskriv innstillinger på tjener? + Overskriv lokale innstillinger? + Videotunnelering + D-Pad søkelinjemodus + Minimal + Lurespilling + Liten søkelinje langs bunnen av skjermen (standard) + Søkelinje med lurespilling forhåndsvisningsbilde + Ingen søkelinje + Blokkeringslistet + Utilgjengelig + Undertekstmodus + Din brukerprofilinnstilling + Standard + Smart + Bare Tvunget + Alltid + Ingen + Foretrukket undertekstspråk + Foretrukket lydspråk + Flere brukerprofilinnstillinger + Overskriv brukerprofilinnstillingene dine fra tjeneren + Hvilket som helst språk + Hash-matcher + SDH + TV-program + Opptak + Mapper + Brukervisninger + Bøker + Lydbøker + Tjenerversjon ikke støttet + Opptrer på + Dim skjerm etter forsinking + Dim prosent + Dim skjerm + Fotoalbum + Ingen favoritter funnet! diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index d5a348967..c8d968685 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -42,8 +42,8 @@ Grootte Geforceerd Genres - Ga naar serie - Ga naar item + Naar seizoenen + Naar inhoud Afspeelbediening verbergen Foutopsporingsinformatie verbergen Verbergen @@ -660,4 +660,6 @@ Scenes [0-9] DTS:X + Meer instellingen gebruikersprofiel + "D-pad zoekbalk" diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 28629d944..7e968044a 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -749,4 +749,16 @@ Przyciemnienie ekranu Opóźnienie przyciemniana ekranu Procent przyciemniane + Albumy zdjęć + Nic jeszcze nie polubiono! + Sezon %1$d + Usuń z listy odtwarzania + Sesja wygasła, zaloguj się ponownie + Nie jesteś upoważniony do dostępu do serwera w tym momencie + O%1$d + %1$dh %2$ds + S%1$d O%2$d + S%1$d O%2$d-%3$d + S%1$02d O%2$02d + S%1$02d O%2$02d-%3$02d diff --git a/app/src/main/res/values-pt-rBR/strings.xml b/app/src/main/res/values-pt-rBR/strings.xml index 6255f0350..033eb50b2 100644 --- a/app/src/main/res/values-pt-rBR/strings.xml +++ b/app/src/main/res/values-pt-rBR/strings.xml @@ -655,4 +655,66 @@ %1$ds Configuração de Perfil de Usuário do servidor %1$s left + Conteúdos extras + Curtos + + Trailer + Trailers + Trailers + + Exigir login para o perfil + 0-9 + Desativar aceleração de hardware de áudio + Desativar a aceleração de hardware de áudio pode corrigir travamentos durante a reprodução em alguns dispositivos + + Usar AC3 para áudio surround + Marrom + Idioma de exibição + Exibir classificações + Personalizar itens exibidos + Proteção do perfil + Clique em Continuar assistindo/Próximo + Reproduzir ao clicar + Mostrar detalhes ao clicar + Estrelas + Tomates + Ordenar + Dividir em linhas separadas + %1$s Filmes + %1$s Séries + Perfil de qualidade + Padrão + Pasta raiz + Popularidade + Status de produção + Usar estilo diferente para HDR + Configurações experimentais + Ativar configurações experimentais? + Configurações experimentais podem ser instáveis!\n\nElas podem ser removidas ou alteradas em qualquer versão. + Tunelamento de vídeo + Modo da barra de busca com D-Pad + Mínimo + Navegação rápida + Barra de progresso pequena na parte inferior da tela (padrão) + Barra de progresso com imagens de pré-visualização do Trickplay + Sem barra de progresso + Bloqueado + Indisponível + Modo de legenda + Usar configuração do seu perfil + Padrão + Inteligente + Somente forçadas + Sempre + Nenhuma + Idioma preferido das legendas + Idioma preferido do áudio + Mais configurações do perfil + Substituir as configurações do seu perfil definidas pelo servidor + Qualquer idioma + Correspondências de hash + SDH + Programas de TV + Gravações + Pastas diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index d54608d7a..4bc79ea74 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -726,4 +726,16 @@ Aparece em Desativar a processamento de áudio por hardware Desativar o processamento de áudio por hardware pode resolver os bloqueios de reprodução em alguns dispositivos + Escurecimento do ecrã após tempo de inatividade + Percentagem do escurecimento + Escurecer ecrã + Álbum de fotos + Não foram encontrados favoritos! + %1$dh %2$ds + T%1$d E%2$d + T%1$d E%2$d-E%3$d + T%1$02dE%2$02d + T%1$02dE%2$02d-E%3$02d + E%1$d + Temporada %1$d diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index e83ae0a83..ba8bb7b3d 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -20,7 +20,7 @@ Подтвердить Продолжить просмотр Оценка критиков - %.1f секунда + %.2f секунд По умолчанию Удалить Умер @@ -124,13 +124,15 @@ Продолжительность Дополнительно - Другое + Другой Других Других Других За кадром + За кадром + За кадром За кадром @@ -272,7 +274,7 @@ Автовоспроизведение следующей серии Применяется только к Телешоу Объединить Продолжить просмотр и Далее - Прямое воспроизведение ASS-субтитров + Использовать libaas для ASS-субтитров Прямое воспроизведение PGS-субтитров Размер шрифта Цвет щрифта @@ -294,12 +296,16 @@ %d объектов - Отрывки - Отрывки + Образец + Образца + Образцов + Образцов - Дополнительные материалы - Дополнительные материалы + Дополнительный материал + Дополнительных материала + Дополнительных материалов + Дополнительных материалов Выбор плеера Толщина обводки @@ -307,12 +313,16 @@ Максимальное количество элементов в ряду на домашней странице Форсированные - Саундтреки - Саундтреки + Тематическая песня + Тематические песни + Тематических песен + Тематических песен - Видеоролики - Видеоролики + Тематическое видео + Тематических видео + Тематических видео + Тематических видео Настроить элементы боковой панели Выберите элементы для показа на панели, остальные элементы будут скрыты @@ -326,8 +336,10 @@ Смена страниц по выделению Остановка воспроизведения при бездействии - Короткометражки - Короткометражки + Короткометражка + Короткометражки + Короткометражек + Короткометражек Конец в %1$s Введите адрес сервера @@ -416,4 +428,106 @@ Светло-серый Темно-серый Желтый + + Фильм + Фильма + Фильмов + Фильмов + + + Сериал + Сериала + Сериалов + Сериалов + + + Человек + Человека + Человек + Человек + + + %1$d год + %1$d года + %1$d лет + %1$d лет + + Использовать настройки профиля с сервера + Трейлеры + За кадром + Тематические Песни + Тематические Видео + Клипы + Удаленные Сцены + Интервью + Сцены + Требуется логин для профиля + [0–9] + AVC + SDR + HDR10 + HDR10+ + HLG + Hz + Показать все результаты в одном списке + Показать результаты по категориям + Показать кнопку голосового ввода + DTS:X + DTS:HD + DTS:HD MA + TrueHD + DD + DD+ + DTS + Прямое воспроизведение Dolby Vision Profile 7 + Серверная версия не поддерживается + Избранное %s + Отключение выгрузки аудио-декодирования может исправить фризы воспроизведения на некоторых устройствах + NAL + Показывать названия + Повтор + Сначала показывать избранные каналы + Сортировка каналов по недавно просмотренным + Стиль фона + Комбинированные результаты поиска + Видимый + Скрытый + Результаты + Переключение разрешения + Локальный + Очистить выбор дорожки + Игнорировать проверки совместимости устройства + Обзор + Запрос + В ожидании + Удалить сервер Seerr + Сервер Seerr добавлен + Быстрое Поключение + Авторизовать другое устройство для поключения к вашей учетной записи + Ввести код Быстрого Подключения + В коде должно быть 6 цифр + Устройство авторизовано успешно + URL + В тренде + Ожидаемые сериалы + Ожидаемые фильмы + Запросить в 4K + Программное декодирование AV1 + MPV сейчас плеер по умолчанию исключая HDR.\nВы можете изменить это в настройках. + Воспроизвести слайдшоу + Остановить слайдшоу + Больше нет фото + Повернуть влево + Повернуть вправо + Приблизить + Отдалить + Продолжитьльность слайдшоу + Воспроизводить видео во время слайдшоу + Отправить лог информации о медиа на сервер + Без ограничения + HDR + Размытие + Расположение + Никогда не использовать FFmpeg + Использовать FFmpeg только, если не существует встроенных декодеров diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index c0c2ad092..3d1991188 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -751,4 +751,13 @@ Stlmiť obrazovku po uplynutí času Úroveň stlmenia Stlmenie obrazovky + Fotoalbumy + Nenašli sa žiadne obľúbené položky! + %1$dh %2$ds + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + Séria %1$d diff --git a/app/src/main/res/values-sv/strings.xml b/app/src/main/res/values-sv/strings.xml index 258a5de20..b5b2954e8 100644 --- a/app/src/main/res/values-sv/strings.xml +++ b/app/src/main/res/values-sv/strings.xml @@ -346,7 +346,7 @@ Växling av uppdateringsfrekvens Referensbilder Röd - Växling av uppdateringsfrekvens + Växling av upplösning Försök igen Tillåt omvisning i “Nästa upp” API nyckel @@ -648,4 +648,26 @@ Brun Visa visualizer Visa bakgrund + Uppspelning backend + Säsong %1$d + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + %1$dt %2$ds + Oskärpa + S%1$02dE%2$02d-E%3$02d + E%1$d + Visa %1$s till + Standard + Föredraget ljudspråk + Fler profilinställningar + Använd profilinställningar + Ignorera serverns profilinställningar + Serverns version stöds inte + Medverkar i + Fotoalbum + Inga favoriter hittades! + Experimentella inställningar kan vara instabila!\n\n Dessa kan tas bort eller ändras i framtida versioner. + Ta bort från spellista + Trailers diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 951b510c3..2717aa787 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -706,4 +706,13 @@ Belirli bir süre sonra ekranı karart Karartma yüzdesi Ekranı karart + %1$dsa %2$dsn + S%1$d E%2$d + S%1$d E%2$d-E%3$d + S%1$02dE%2$02d + S%1$02dE%2$02d-E%3$02d + E%1$d + Sezon %1$d + Fotoğraf Albümleri + Favori bulunamadı! diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index 91dc97bbe..f676b9f1b 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -685,4 +685,6 @@ 延迟后调暗屏幕 调暗百分比 调暗屏幕 + 相册 + 未找到收藏! diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index f2e45616a..a9f7cc5fb 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -685,4 +685,16 @@ 閒置後調暗螢幕 調暗程度 螢幕調暗 + 未添加任何最愛項目! + 相簿 + %1$d小時%2$d分 + 第%1$d季 第%2$d集 + 第%1$d季 第%2$d-%3$d集 + 第%1$02d季 第%2$02d集 + 第%1$02d季 第%2$02d-%3$02d集 + 第 %1$d 集 + 第 %1$d 季 + 從播放清單中移除 + 您目前無存取此伺服器的權限 + 登入憑證已過期,請重新登入 diff --git a/app/src/test/java/com/github/damontecres/wholphin/services/IntentServiceTest.kt b/app/src/test/java/com/github/damontecres/wholphin/services/IntentServiceTest.kt index bb4904aa3..ee32c7dda 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/services/IntentServiceTest.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/services/IntentServiceTest.kt @@ -1,24 +1,34 @@ package com.github.damontecres.wholphin.services +import android.app.SearchManager import android.content.Intent import com.github.damontecres.wholphin.data.CurrentUser +import com.github.damontecres.wholphin.data.JellyfinServerDao import com.github.damontecres.wholphin.data.ServerRepository import com.github.damontecres.wholphin.preferences.AppPreferences import com.github.damontecres.wholphin.preferences.UserPreferences import com.github.damontecres.wholphin.preferences.update +import com.github.damontecres.wholphin.test.assertIs import com.github.damontecres.wholphin.test.currentUser +import com.github.damontecres.wholphin.test.movie import com.github.damontecres.wholphin.test.server import com.github.damontecres.wholphin.test.user +import com.github.damontecres.wholphin.ui.nav.Destination +import com.github.damontecres.wholphin.ui.successResponse import com.github.damontecres.wholphin.ui.toServerString import io.mockk.coEvery import io.mockk.coVerify import io.mockk.every import io.mockk.mockk +import io.mockk.verify import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.flow import kotlinx.coroutines.test.runTest import org.jellyfin.sdk.api.client.ApiClient +import org.jellyfin.sdk.api.client.extensions.userLibraryApi +import org.jellyfin.sdk.api.operations.UserLibraryApi import org.jellyfin.sdk.model.UUID +import org.junit.Assert.assertEquals import org.junit.Assert.assertNull import org.junit.Assert.assertTrue import org.junit.Before @@ -32,13 +42,16 @@ import org.robolectric.annotation.Config class IntentServiceTest { private val api: ApiClient = mockk() private val serverRepository: ServerRepository = mockk() + private val serverDao: JellyfinServerDao = mockk() private val userPreferencesService: UserPreferencesService = mockk() + private val userLibraryApi: UserLibraryApi = mockk() lateinit var intentService: IntentService private val serverId = UUID.randomUUID() private val userId = UUID.randomUUID() private val currentUser = currentUser(serverId, userId) + private val itemId = UUID.randomUUID() private val protectedCurrentUser = CurrentUser( @@ -49,6 +62,9 @@ class IntentServiceTest { @Before fun setup() { intentService = IntentService(api, serverRepository, userPreferencesService) + every { api.userLibraryApi } returns userLibraryApi + coEvery { userLibraryApi.getItem(itemId) } returns successResponse(movie(itemId)) + every { serverRepository.serverDao } returns serverDao } private fun setupPreferences(block: AppPreferences.Builder.() -> Unit) { @@ -63,16 +79,20 @@ class IntentServiceTest { } } + private fun setupAutoSignInUnprotected() { + setupPreferences { + signInAutomatically = true + currentServerId = serverId.toServerString() + currentUserId = userId.toServerString() + } + every { serverRepository.current } returns MutableStateFlow(currentUser) + coEvery { serverRepository.restoreSession(serverId, userId) } returns currentUser + } + @Test fun `Test auto sign in with unprotected profile`() = runTest { - setupPreferences { - signInAutomatically = true - currentServerId = serverId.toServerString() - currentUserId = userId.toServerString() - } - every { serverRepository.current } returns MutableStateFlow(currentUser) - coEvery { serverRepository.restoreSession(serverId, userId) } returns currentUser + setupAutoSignInUnprotected() val intent = Intent() val result = intentService.prepare(intent) @@ -221,4 +241,243 @@ class IntentServiceTest { coVerify(exactly = 0) { serverRepository.restoreSession(serverId, userId) } } + + @Test + fun `Test parseIntent with view item`() = + runTest { + setupAutoSignInUnprotected() + + val intent = + Intent().apply { + action = Intent.ACTION_VIEW + putExtra("itemId", itemId.toString()) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(1, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + assertIs(first) + + assertEquals(itemId, first.itemId) + + coVerify { userLibraryApi.getItem(itemId) } + } + + @Test + fun `Test parseIntent with view and no itemId`() = + runTest { + setupAutoSignInUnprotected() + + val intent = + Intent().apply { + action = Intent.ACTION_VIEW + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(0, destinations.size) + assertTrue(result.addHomeToBackStack) + + coVerify(exactly = 0) { userLibraryApi.getItem(itemId) } + } + + @Test + fun `Test parseIntent with view and switch user`() = + runTest { + setupAutoSignInUnprotected() + + val newUserId = UUID.randomUUID() + val newServerId = UUID.randomUUID() + val newCurrentUser = currentUser(newServerId, newUserId) + + every { serverRepository.current } returns MutableStateFlow(currentUser) + coEvery { + serverRepository.restoreSession( + newServerId, + newUserId, + ) + } returns newCurrentUser + every { serverDao.getUser(newServerId, newUserId) } returns newCurrentUser.user + + val intent = + Intent().apply { + action = Intent.ACTION_VIEW + putExtra("userId", newUserId.toString()) + putExtra("serverId", newServerId.toString()) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(0, destinations.size) + assertTrue(result.addHomeToBackStack) + + coVerify(exactly = 0) { serverRepository.restoreSession(serverId, userId) } + verify { serverDao.getUser(newServerId, newUserId) } + coVerify { serverRepository.restoreSession(newServerId, newUserId) } + coVerify(exactly = 0) { userLibraryApi.getItem(itemId) } + } + + @Test + fun `Test parseIntent with view, itemId, and switch user`() = + runTest { + setupAutoSignInUnprotected() + + val newUserId = UUID.randomUUID() + val newServerId = UUID.randomUUID() + val newCurrentUser = currentUser(newServerId, newUserId) + + every { serverRepository.current } returns MutableStateFlow(currentUser) + coEvery { + serverRepository.restoreSession( + newServerId, + newUserId, + ) + } returns newCurrentUser + every { serverDao.getUser(newServerId, newUserId) } returns newCurrentUser.user + + val intent = + Intent().apply { + action = Intent.ACTION_VIEW + putExtra("userId", newUserId.toString()) + putExtra("serverId", newServerId.toString()) + putExtra("itemId", itemId.toString()) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(1, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + assertIs(first) + + assertEquals(itemId, first.itemId) + + coVerify(exactly = 0) { serverRepository.restoreSession(serverId, userId) } + verify { serverDao.getUser(newServerId, newUserId) } + coVerify { serverRepository.restoreSession(newServerId, newUserId) } + coVerify { userLibraryApi.getItem(itemId) } + } + + @Test + fun `Test parseIntent with search`() = + runTest { + setupAutoSignInUnprotected() + + val intent = + Intent().apply { + action = Intent.ACTION_SEARCH + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(1, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + assertIs(first) + + assertEquals("", first.query) + } + + @Test + fun `Test parseIntent with search query`() = + runTest { + setupAutoSignInUnprotected() + + val query = "query123" + val intent = + Intent().apply { + action = Intent.ACTION_SEARCH + putExtra(SearchManager.QUERY, query) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(1, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + assertIs(first) + + assertEquals(query, first.query) + } + + @Test + fun `Test parseIntent with play but no itemId`() = + runTest { + setupAutoSignInUnprotected() + + val intent = + Intent().apply { + action = IntentService.ACTION_PLAYBACK + } + val result = intentService.parseIntent(intent) + assertIs(result) + } + + @Test + fun `Test parseIntent with play itemId`() = + runTest { + setupAutoSignInUnprotected() + + val intent = + Intent().apply { + action = IntentService.ACTION_PLAYBACK + putExtra("itemId", itemId.toString()) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(2, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + val second = destinations[1] + assertIs(first) + assertIs(second) + + assertEquals(itemId, first.itemId) + assertEquals(itemId, second.itemId) + + coVerify { userLibraryApi.getItem(itemId) } + } + + @Test + fun `Test parseIntent with play itemId and params`() = + runTest { + setupAutoSignInUnprotected() + + val position = 4_000L + val shuffle = true + val intent = + Intent().apply { + action = IntentService.ACTION_PLAYBACK + putExtra("itemId", itemId.toString()) + putExtra("position", position) + putExtra("shuffle", shuffle) + } + val result = intentService.parseIntent(intent) + assertIs(result) + + val destinations = result.destinations + assertEquals(2, destinations.size) + assertTrue(result.addHomeToBackStack) + val first = destinations.first() + val second = destinations[1] + assertIs(first) + assertIs(second) + + assertEquals(itemId, first.itemId) + assertEquals(itemId, second.itemId) + assertEquals(shuffle, second.shuffle) + assertEquals(position, second.positionMs) + + coVerify { userLibraryApi.getItem(itemId) } + } } diff --git a/app/src/test/java/com/github/damontecres/wholphin/test/TestUtils.kt b/app/src/test/java/com/github/damontecres/wholphin/test/TestUtils.kt index 7f51e994c..34d962da2 100644 --- a/app/src/test/java/com/github/damontecres/wholphin/test/TestUtils.kt +++ b/app/src/test/java/com/github/damontecres/wholphin/test/TestUtils.kt @@ -4,12 +4,16 @@ import com.github.damontecres.wholphin.data.CurrentUser import com.github.damontecres.wholphin.data.model.JellyfinServer import com.github.damontecres.wholphin.data.model.JellyfinUser import com.github.damontecres.wholphin.ui.isNotNullOrBlank +import com.ibm.icu.impl.Assert import io.mockk.MockKMatcherScope import org.jellyfin.sdk.model.UUID import org.jellyfin.sdk.model.api.BaseItemDto import org.jellyfin.sdk.model.api.BaseItemKind import org.jellyfin.sdk.model.api.NameGuidPair import org.jellyfin.sdk.model.api.UserDto +import org.junit.Assert.assertNotNull +import kotlin.contracts.ExperimentalContracts +import kotlin.contracts.contract fun MockKMatcherScope.nonBlankString() = match { it.isNotNullOrBlank() } @@ -128,3 +132,20 @@ fun currentUser( server(serverId), user(userId), ) + +/** + * Asserts that the object is not null and is type `T` + * + * Includes a [contract] to smart-cast the object to T + */ +@OptIn(ExperimentalContracts::class) +inline fun assertIs(obj: Any?) { + contract { + returns() implies (obj is T) + } + assertNotNull(obj) + val result = T::class.isInstance(obj) + if (!result) { + Assert.fail("Expected type=${T::class.qualifiedName}, actual type= ${obj!!::class.qualifiedName} ") + } +} diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index e7c6ca928..dc7608cfd 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -14,7 +14,7 @@ coreKtx = "1.19.0" appcompat = "1.8.0" composeBom = "2026.09.00" mockk = "1.14.11" -robolectric = "4.16.1" +robolectric = "4.17" multiplatformMarkdownRenderer = "0.45.0" okhttpBom = "5.5.0" programguide = "1.6.0"