From d303ac48af6fa47049c3e4828f6efc1d3141e1b4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?St=C3=A9phane?= Date: Fri, 8 May 2026 19:41:54 +0200 Subject: [PATCH 1/7] fix(image): register SvgDecoder so SVG logos actually render Adds the coil-svg KMP module dependency and registers SvgDecoder.Factory() on the singleton ImageLoader in App.kt. coil-svg was never declared in libs.versions.toml, so SVG payloads silently fell through Coil's decoder chain and ended up as empty ImageDecoderException swallowed by AsyncImage. Coil 3.x's coil-svg is a Kotlin Multiplatform module, so registering the factory in commonMain covers Android and iOS in one place. Affects: - TMDB network/channel logos (TMDB serves a mix of PNG and SVG) - Stremio addon manifests with an SVG `logo` field - Custom artwork URLs in the collection editor that happen to be SVG Mirrors the equivalent fix in NuvioTV (PR #1772). --- composeApp/build.gradle.kts | 1 + composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt | 4 ++++ gradle/libs.versions.toml | 1 + 3 files changed, 6 insertions(+) diff --git a/composeApp/build.gradle.kts b/composeApp/build.gradle.kts index 71d3b924..5c5811e4 100644 --- a/composeApp/build.gradle.kts +++ b/composeApp/build.gradle.kts @@ -260,6 +260,7 @@ kotlin { commonMain.dependencies { implementation(libs.coil.compose) implementation(libs.coil.network.ktor3) + implementation(libs.coil.svg) implementation("dev.chrisbanes.haze:haze:1.7.2") implementation(libs.compose.runtime) implementation(libs.compose.foundation) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt index 3eebbdac..05296b44 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/App.kt @@ -73,6 +73,7 @@ import coil3.ImageLoader import coil3.compose.setSingletonImageLoaderFactory import coil3.request.CachePolicy import coil3.request.crossfade +import coil3.svg.SvgDecoder import com.nuvio.app.core.build.AppFeaturePolicy import com.nuvio.app.core.auth.AuthRepository import com.nuvio.app.core.auth.AuthState @@ -300,6 +301,9 @@ fun App() { .crossfade(true) .diskCachePolicy(CachePolicy.ENABLED) .memoryCachePolicy(CachePolicy.ENABLED) + .components { + add(SvgDecoder.Factory()) + } .configurePlatformImageLoader() .build() } diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml index ae480b9f..d260a9e5 100644 --- a/gradle/libs.versions.toml +++ b/gradle/libs.versions.toml @@ -51,6 +51,7 @@ compose-uiToolingPreview = { module = "org.jetbrains.compose.ui:ui-tooling-previ coil-compose = { module = "io.coil-kt.coil3:coil-compose", version.ref = "coil" } coil-gif = { module = "io.coil-kt.coil3:coil-gif", version.ref = "coil" } coil-network-ktor3 = { module = "io.coil-kt.coil3:coil-network-ktor3", version.ref = "coil" } +coil-svg = { module = "io.coil-kt.coil3:coil-svg", version.ref = "coil" } kotlinx-serialization-json = { module = "org.jetbrains.kotlinx:kotlinx-serialization-json", version.ref = "kotlinx-serialization" } ktor-client-android = { module = "io.ktor:ktor-client-android", version.ref = "ktor" } kermit = { module = "co.touchlab:kermit", version.ref = "kermit" } From 5a8c6ccb82e76d3deb23bc00d5237bab7d113c0c Mon Sep 17 00:00:00 2001 From: Pupon11 Date: Sat, 9 May 2026 18:26:15 +0200 Subject: [PATCH 2/7] Update print statement from 'Hello' to 'Goodbye' --- .../composeResources/values-cs/strings.xml | 1245 +++++++++++++++++ 1 file changed, 1245 insertions(+) create mode 100644 composeApp/src/commonMain/composeResources/values-cs/strings.xml diff --git a/composeApp/src/commonMain/composeResources/values-cs/strings.xml b/composeApp/src/commonMain/composeResources/values-cs/strings.xml new file mode 100644 index 00000000..d1c9be28 --- /dev/null +++ b/composeApp/src/commonMain/composeResources/values-cs/strings.xml @@ -0,0 +1,1245 @@ + + Otevřené uznání a kredity projektu + Zpět + Zrušit + Zavřít + Smazat + Hotovo + Upravit + Importovat + Další + OK + Přehrát + Předchozí + Odstranit + Změnit pořadí + Obnovit výchozí + Pokračovat + Opakovat + Uložit + Instaluje se + Doplňky + Aktivní + %1$d katalogů + Nastavitelné + Obnovuje se + %1$d zdrojů + Nedostupné + Konfigurovat doplněk + Smazat doplněk + Přidejte URL manifestu a začněte do Nuvia načítat katalogy, metadata, streamy nebo titulky. + Zatím nejsou nainstalovány žádné doplňky. + Zadejte URL doplňku. + URL doplňku + Nainstalovat doplněk + Načítání podrobností manifestu... + Ověřování URL manifestu a načítání podrobností o doplňku před instalací. + Kontrola doplňku + Instalace selhala + Doplněk %1$s byl úspěšně ověřen a přidán. + Doplněk nainstalován + Posunout doplněk dolů + Posunout doplněk nahoru + Aktivní + Doplňky + Katalogy + Obnovit doplněk + Přidat doplněk + Nainstalované doplňky + Přehled + %1$d pravidel id + Verze %1$s + Vybráno + Kopírovat JSON + %1$d kolekce, %2$d složka(y) + Smazat "%1$s"? Tuto akci nelze vrátit zpět. + Smazat kolekci + Přidat katalog + Přidat složku + Všechny žánry + Přidejte katalogy z nainstalovaných doplňků a určete, co se má v této složce zobrazovat. + Zatím žádné zdroje katalogů + Vybrat + Emoji + URL obrázku + Žádný + Obal + Vytvořit kolekci + Hotovo + Upravit kolekci + Upravit složku + Nastavte identitu složky, vzhled a zdroje katalogů se stejnou strukturou jako v hlavním editoru kolekcí. + Přidejte jednu a začněte. + Zatím žádné složky + Složky + Filtr žánrů + Zobrazit pouze obal + Skrýt název + Nová složka + Zobrazit tuto kolekci nad všemi běžnými katalogy na domovské obrazovce. Více připnutých kolekcí se řadí podle pořadí vytvoření. + Připnout nad katalogy + URL obrázku na pozadí (volitelné) + Název složky + URL animovaného GIFu (přehrává se pouze při zaměření) + Název kolekce + Uložit změny + Uložit + Vzhled + Základy + Zdroje katalogů + Vyberte katalogy doplňků, které by měla tato složka sdružovat. + Vybrat katalogy + Vybrat žánr + %1$d vybráno + %1$d katalogů + %1$d vybráno + Plakát + Čtverec + Širokoúhlý + Sloučit všechny katalogy do jedné karty + Zobrazit kartu \"Vše\" + Přehrát nastavený GIF místo statického obalu, pokud je k dispozici. + Zobrazit GIF, když je nastavený + %1$d zdroj(ů) · %2$s + Tvar dlaždice + Řádky + Karty + Režim zobrazení + Zdroje TMDB + Veřejný seznam + Produkce + Síť + Kolekce + Osoba + Režisér + Vlastní + Vyberte si předpřipravený zdroj. Po přidání ho můžete upravit nebo odstranit. + Vložte URL veřejného seznamu TMDB nebo pouze číslo z URL. + Vyhledávejte podle názvu studia, nebo vložte ID/URL společnosti na TMDB a rovnou ji přidejte. + Zadejte ID sítě. Běžné sítě jsou k dispozici v předvolbách a rychlých filtrech. + Vyhledejte název filmové kolekce nebo vložte ID kolekce z TMDB. + Zadejte ID nebo URL osoby na TMDB pro vytvoření řádku z hereckých rolí. + Zadejte ID nebo URL osoby na TMDB pro vytvoření řádku z režisérských rolí. + Vytvořte živý řádek TMDB pomocí volitelných filtrů. Pokud filtr nepotřebujete, nechte pole prázdná. + Veřejný seznam TMDB + ID sítě + ID kolekce + ID osoby + Název, ID nebo URL produkční společnosti + TMDB ID nebo URL + https://www.themoviedb.org/list/8504994 nebo 8504994 + 213 pro Netflix, 49 pro HBO, 2739 pro Disney+ + 10 pro Star Wars Collection + Marvel Studios, 420, nebo URL společnosti + 31 pro Toma Hankse, nebo URL osoby + Příklady: Marvel Studios, 420, nebo https://www.themoviedb.org/company/420. + Příklad: Star Wars Collection, Harry Potter Collection, nebo URL kolekce. + Příklady ID: Netflix 213, HBO 49, Disney+ 2739. + Příklad: https://www.themoviedb.org/list/8504994 nebo 8504994. + Příklad: https://www.themoviedb.org/person/31-tom-hanks nebo 31. + Zobrazovaný název + Zobrazí se jako název řádku/karty. Pokud je prázdné, Nuvio jej vytvoří ze zdroje. + Filmy Marvel, Netflix Originals, Pixar + Filmy s Tomem Hanksem, Oblíbení herci + Filmy od Christophera Nolana, Oblíbení režiséři + Nejlepší akční filmy, Korejská dramata, Animace 2024 + Výsledky vyhledávání + Kolekce TMDB + Společnost TMDB %1$d + Kolekce TMDB %1$d + Typ + Filmy + Seriály + Obojí + Seřadit + Filtry + Pokud filtr nepotřebujete, nechte pole prázdná. + Rychlé žánry + Rychlé jazyky + Rychlé země + Rychlá klíčová slova + Rychlá studia + Rychlé sítě + ID žánrů + Použijte čísla žánrů TMDB. Oddělte více žánrů čárkami pro „A“, nebo svislítky pro „NEBO“. + Datum vydání nebo vysílání od + Datum vydání nebo vysílání do + Použijte formát RRRR-MM-DD, například 2024-01-01. + Minimální hodnocení + Maximální hodnocení + Hodnocení TMDB od 0 do 10. Příklad: 7.0. + Minimální počet hlasů + Slouží k vynechání neznámých titulů s malým počtem hlasů. Příklad: 100. + Původní jazyk + Použijte dvoupísmenné kódy jazyků, například en, ko, ja, cs. + Země původu + Použijte dvoupísmenné kódy zemí, například US, KR, JP, CZ. + ID klíčových slov + Použijte čísla klíčových slov TMDB. Rychlé volby vyplní běžné příklady. + 9715 pro superhrdinu + ID společností + Použijte ID studií/společností. Rychlé volby vyplní běžné příklady. + 420 pro Marvel Studios + ID sítí + Pouze pro seriály. Použijte ID sítí jako Netflix 213 nebo HBO 49. + 213 pro Netflix + Rok + Použijte čtyřmístný rok, například 2024. + Předvolby + Hledat + Přidat zdroj + Přidat seznam Trakt + Upravit seznam Trakt + Seznamy Trakt + Seznam Trakt + Vyhledat název, URL na Trakltu, nebo ID seznamu + Použijte URL veřejného seznamu Trakt, číselné ID seznamu, nebo vyhledávejte podle názvu. + Víkendové sledování, Vítězové ocenění + Výsledky vyhledávání + Trendy seznamy + Populární seznamy + Směr řazení + Vzestupně + Sestupně + Pořadí v seznamu + Nedávno přidáno + Název + Datum vydání + Délka + Populární + Procenta + Hlasy + Akční + Dobrodružný + Animovaný + Komedie + Horor + Sci-Fi + Drama + Krimi + Reality + Angličtina + Korejština + Japonština + Hindština + Španělština + Spojené státy + Korea + Japonsko + Indie + Velká Británie + Superhrdina + Podle knižní předlohy + Cestování v čase + Vesmír + Marvel + Disney + Pixar + Lucasfilm + Warner Bros. + Netflix + HBO + Disney+ + Prime Video + Hulu + Původní + Populární + Nejlépe hodnocené + Nedávné + Seznam TMDB + Filmová kolekce TMDB + Produkce + Síť + Osoba + Režisér + Objevování TMDB + Vytvořte ji pro organizaci vašich katalogů. + Zatím žádné kolekce + %1$d složka(y) + Nebyly nalezeny žádné položky + Složka nebyla nalezena + Kolekce + Importovat kolekce + JSON + Vložte níže JSON vašich kolekcí. + Import + Nová kolekce + Připnuté + Vše + Vaše kolekce + Vytvořeno s ❤️ od Tapframe a přátel + Verze %1$s (%2$s) + Vypnuto + Zapnuto + Pozastavit + Znovu načíst + Už máte účet? + Pokračovat bez účtu + Vytvořit účet + Nemáte účet? + E-mail + nebo + Heslo + Přihlaste se pro přístup k vaší knihovně a postupu + Přihlásit se + Zaregistrujte se pro synchronizaci dat napříč zařízeními + Zaregistrovovat se + Vaše data budou uložena pouze lokálně + Streamujte všechno, všude + Vítejte zpět + Knihovna + Knihovna Trakt + Domů + Knihovna + Profil + Hledat + Zvukové stopy + Zvuk + Vestavěné + Spodní odsazení + Zavřít přehrávač + Barva + Právě hraje + E%1$d + S%1$dE%2$d + S%1$dE%2$d • %3$s + Epizody + Velikost písma + %1$dsp + Uzamknout ovládání přehrávače + Nejsou k dispozici žádné zvukové stopy + Nejsou k dispozici žádné epizody + Nebyly nalezeny žádné streamy + Žádné + Obrys + Epizody + Zdroje + Streamy + Chyba přehrávání + Přehrávání + Klepnutím načtěte titulky + Vrátit se zpět + Obnovit výchozí + Vyplnit + Přizpůsobit + Přiblížit + Přetočit o 10 sekund zpět + -%1$ds + +%1$ds + -%1$ds + +%1$ds + Přetočit o 10 sekund vpřed + Zdroje + Styl + Titulky + Titulky + Jas %1$s + Hlasitost %1$s + Ztlumeno + Staženo + Vysílá se + Bude oznámeno + Klepnutím odemknete + Stopa %1$d + Odemknout ovládání přehrávače + Právě sledujete + Přidat profil + Vymazat hledání + Objevovat + Nainstalovaným doplňkům se nepodařilo vrátit platné výsledky vyhledávání. + Hledání selhalo + Před vyhledáváním nainstalujte a ověřte alespoň jeden doplněk. + Žádné aktivní doplňky + Nainstalované prohledávatelné katalogy nevrátily žádné shody pro tento dotaz. + Nebyly nalezeny žádné výsledky + Vaše nainstalované doplňky neumožňují vyhledávání v katalozích. + Žádné prohledávatelné katalogy + Hledat filmy, seriály... + Nedávná hledání + Odstranit nedávné hledání + O aplikaci + Obecné + Účet + Doplňky + Rozvržení + Obsah a objevování + Pokračovat ve sledování + Rozvržení domovské obrazovky + Integrace + Hodnocení MDBList + Stránka podrobností + Oznámení + Přehrávání + Pluginy + Styl karty plakátu + Nastavení + Podporovatelé a přispěvatelé + Obohacení TMDB + Trakt + O APLIKACI + Účet a stav synchronizace + ÚČET + Struktura domovské obrazovky a styly plakátů + Stáhnout nejnovější verzi + Zkontrolovat aktualizace + Spravovat doplňky a zdroje objevování. + Spravovat vaše stažené filmy a epizody. + Stahování + OBECNÉ + Spravovat dostupné integrace + Spravovat upozornění na vydání epizod a odeslat zkušební oznámení. + Přepnout na jiný profil. + Přepnout profil + Otevřít obrazovku připojení k Trakt + Nebylo nalezeno žádné nastavení. + Hledat v nastavení... + VÝSLEDKY + Načítání vašich seznamů Trakt… + Vyberte, kam na Trakt chcete tento titul uložit + Přispět + Přejít na podrobnosti + Odstranit + Spustit od začátku + Přehrát + %1$d/10 + Recenze + Spoiler + Zatím nejsou k dispozici žádné recenze Trakt. + %1$d To se mi líbí + Tento komentář obsahuje spoilery. + Tento komentář obsahuje spoilery a byl skryt. + Komentáře + Trailer + %1$s (%2$d) + Trailery + Žádné dokončené epizody + Zatím žádná stahování + %1$d stažená epizoda (epizody) + Aktivní + Filmy + Seriály + Zobrazit stahování + Dokončeno • %1$s + Stahování • %1$s + Selhalo + Pozastaveno • %1$s + Zhlédnuto + Série %1$d + Speciály + Pokračujte tam, kde jste přestali + Přidat do knihovny + Označit jako nezhlédnuté + Označit jako zhlédnuté + Odebrat z knihovny + Zobrazit vše + Přehrát ručně + Logo %1$s + Účet + Smazat účet + Tímto trvale smažete svůj účet a všechna přidružená data. + Tuto akci nelze vrátit zpět. Všechna vaše data, profily a historie synchronizace budou trvale smazány. + Smazat účet? + E-mail + Nepřihlášen + Odhlásit se + Budete vráceni na přihlašovací obrazovku. + Odhlásit se? + Stav + Anonymní + Přihlášen + AMOLED Černá + Použít čistě černé pozadí pro OLED obrazovky. + Jazyk aplikace + Vyberte jazyk + Nastavení pro sekci Pokračovat ve sledování. + Tekuté sklo (Liquid Glass) + Použít nativní lištu panelů na iPhonu v iOS 26 a novějším. Okamžité přepínání profilů z lišty panelů není při zapnutí dostupné. + Vyladit šířku karty a poloměr rohů. + ZOBRAZENÍ + DOMŮ + MOTIV + Kolekce • %1$s + Zobrazovaný název + Nainstalujte doplněk s katalogy kompatibilními s dlaždicemi pro konfiguraci řádků domovské obrazovky. + Žádné domácí katalogy + Zdroj pro Carousel (Hero) + Skryto + Ponechat zaměření na Domů + %1$s • Dosažen limit (max %2$d) + Nebyly vybrány žádné zdroje pro Carousel + Není v Carouselu + Chcete-li kolekci přesunout, odeberte její připnutí nahoru + Připnuto + Připnuto nahoru + Změnit pořadí + KATALOGY + KATALOGY A KOLEKCE + KOLEKCE + Rozvržení domovské obrazovky + Katalogy Carouselu (Hero) + Vybráno %1$d z %2$d + Zobrazit sekci Carousel (Hero) + Zobrazit carousel na vrcholu domovské obrazovky. + Skrýt nevydaný obsah + Skrýt filmy a seriály, které ještě nebyly vydány. + %1$d z %2$d katalogů je viditelných • %3$d vybraných zdrojů Carouselu + Otevřete katalog pouze tehdy, když jej potřebujete přejmenovat nebo změnit jeho pořadí. + Viditelné + Skrýt hodnotu + Přehrávač, titulky a automatické přehrávání + Poloměr rohů + Styl karty plakátu + Šířka + Vlastní + Vyladit šířku karty a poloměr rohů. + Skrýt štítky + Plakáty na šířku + Živý náhled + %1$s (%2$s) + Poloměr rohů: %1$ddp + Výška: %1$ddp + Šířka: %1$ddp + Klasický + Pilulka + Zaoblený + Ostrý + Decentní + Vyvážená + Komfortní + Kompaktní + Hustá + Velká + Standardní + Zobrazit hodnotu + Zobrazit vyskakovací okno pro pokračování, když aplikaci otevřete poté, co jste ji opustili z přehrávače. + Výzva k pokračování po spuštění + Rozmazat náhledy dalších epizod v sekci Pokračovat ve sledování, abyste se vyhnuli spoilerům. + Rozmazat nezhlédnuté v Pokračovat ve sledování + Zahrnout do Pokračovat ve sledování i nadcházející epizody, než se budou vysílat. + Zobrazit nevysílané v Další na řadě + Styl karty plakátu + PŘI SPUŠTĚNÍ + CHOVÁNÍ DALŠÍ NA ŘADĚ + VIDITELNOST + Zobrazit lištu Pokračovat ve sledování na domovské obrazovce. + Zobrazit Pokračovat ve sledování + Plakát + Karta plakátu zaměřená na grafiku + Široký + Horizontální karta s vysokou hustotou informací + Zobrazit další epizodu podle nejdále zhlédnuté epizody. Vypněte pro opakované sledování, aby se použila naposledy zhlédnutá epizoda. + Další na řadě od nejdále zhlédnuté + Upřednostňovat náhledy epizod, pokud jsou k dispozici. + Upřednostňovat náhledy epizod v Pokračovat ve sledování + DOMŮ + ZDROJE + Instalujte, odebírejte, obnovujte a řaďte své zdroje obsahu. + Interní instalace repozitářů JavaScriptových scraperů a testovacích poskytovatelů. + Upravte rozložení domovské obrazovky, viditelnost obsahu a chování plakátů. + Nastavení pro obrazovky podrobností a epizod. + Vytvářejte vlastní seskupení katalogů se složkami na domovské obrazovce. + Integrace + Ovládací prvky pro obohacení metadat + Externí poskytovatelé hodnocení + Než zapnete hodnocení, přidejte níže svůj MDBList API klíč. + Vyžadováno pro načítání hodnocení z MDBListu + API klíč + API klíč + Povolit hodnocení MDBList + Načítat hodnocení od externích poskytovatelů na obrazovce s metadaty + API klíč + Externí poskytovatelé hodnocení + Hodnocení MDBList + Akce + Ovládací prvky pro přehrávání a ukládání. + Obsazení + Hlavní seznam herců. + Filmové pozadí + Rozmazané pozadí za obsahem, podobné obrazovce streamu. + Kolekce + Související kolekce nebo filmová série. + Komentáře + Recenze z Trakt + Podrobnosti + Délka, stav, vydání, jazyk a související informace. + Karty epizod + Vyberte, jak se budou epizody vykreslovat na obrazovce s metadaty. + Horizontální + Řádkové karty ve stylu pozadí + Seznam + Skládané karty zaměřené na detaily + Epizody + Sezóny a seznam epizod pro seriály. + Rozmazat nezhlédnuté epizody + Rozmazat náhledy epizod, dokud nebudou zhlédnuty, abyste se vyhnuli spoilerům. + Skupina %1$d + Podobné + Doporučení TMDB na stránce podrobností + Žádné + Přehled + Synopse, hodnocení, žánry a hlavní tvůrci. + Produkce + Studia a sítě. + VZHLED + SEKCE + Skupina karet %1$d + Rozvržení karet + Seskupte sekce do karet podobně jako v TV aplikaci. Přiřaďte až 3 sekce do jedné skupiny. + Trailery + Trailery a zástupci pro přehrávání. + Oznámení jsou v Nuviu momentálně zakázána. + Upozornění na vydání epizod + Naplánovat lokální oznámení, když bude dostupná nová epizoda pro uložený seriál. + Systémová oznámení jsou pro Nuvio zakázána. Povolte je pro přijímání upozornění a zkušebních oznámení. + Na tomto zařízení je aktuálně naplánováno %1$d upozornění na vydání. + UPOZORNĚNÍ + TEST + Odeslat zkušební oznámení + Odesílání zkušebního oznámení... + Odeslat lokální zkušební oznámení pro %1$s. + Pro otestování oznámení nejprve uložte nějaký seriál do knihovny. + Zkušební oznámení + Komunita + Podívejte se na lidi, kteří budují a podporují Nuvio napříč Mobilem, TV a Webem. + API pro podporovatele není nakonfigurováno. Přidejte DONATIONS_BASE_URL do local.properties. + Přispěvatelé + Podporovatelé + Otevřít GitHub + Profil na GitHubu není k dispozici + Nebyla připojena žádná zpráva. + Načítání přispěvatelů... + Načítání podporovatelů... + Nepodařilo se načíst přispěvatele + Nepodařilo se načíst podporovatele + Nebyli nalezeni žádní přispěvatelé. + Nebyli nalezeni žádní podporovatelé. + Nelze načíst přispěvatele. + Nelze načíst podporovatele. + Momentálně nelze načíst přispěvatele. + Momentálně nelze načíst podporovatele. + %1$d celkových commitů + Led + Úno + Bře + Dub + Kvě + Čvn + Čvc + Srp + Zář + Říj + Lis + Pro + %1$s %2$s, %3$s + Všechny nainstalované doplňky + Všechny povolené pluginy + Povolené doplňky + Povolené pluginy + Anime Skip (přeskakování) + AnimeSkip Client ID + Zadejte své AnimeSkip API klientské ID. Získáte jej na anime-skip.com. + Povolit odesílání intra + Zobrazit tlačítko pro odeslání časových značek intra/outra do komunitní databáze. + IntroDB API klíč + Zadejte svůj IntroDB API klíč pro odesílání časových značek. Vyžadováno pro odesílání. + Hledat také časové značky pro přeskočení v AnimeSkip (vyžaduje klientské ID). + Automaticky přehrát další epizodu + Po zobrazení výzvy automaticky spustit další epizodu. + Pouze dekodéry zařízení + Upřednostnit dekodéry aplikace (FFmpeg) + Upřednostnit dekodéry zařízení + Priorita dekodéru + Klepnutím mimo zavřete + Klepnutím mimo uložíte a zavřete + %1$d den + %1$d dní + %1$d hodina + %1$d hodin + Použít libass pro titulky ASS/SSA + Experimentální: pokročilé vykreslování ASS/SSA (styly, pozicování, animace) + Rychlost při podržení + Podržet pro zrychlení + Dlouhým stisknutím kdekoli na ploše přehrávače dočasně zvýšíte rychlost přehrávání. + Neplatný vzor regulárního výrazu (regex) + Doba mezipaměti posledního odkazu + Záložní DV7 - HEVC + Mapovat Dolby Vision profil 7 na standardní HEVC pro zařízení bez hardwarové podpory DV + Prahové minuty + Záložní řešení, pokud neexistuje časová značka pro outro. + %1$s min + Žádné položky nejsou k dispozici + Nenastaveno + Výchozí (soubor médií) + Jazyk zařízení + Vynucené + Žádné + Upřednostnit Binge skupinu (Další epizoda) + Zkusit přednostně stejný profil zdroje (stejný doplněk/skupinu kvality) před běžnými pravidly automatického přehrávání. + Preferovaný jazyk zvuku + Preferovaný jazyk titulků + Předvolby + Porovnává se s názvem streamu/titulem/popisem/doplňkem/url. Příklad: 4K|2160p|Remux + Vzor regulárního výrazu + Není nastaven žádný vzor. Příklad: 4K|2160p|Remux + Jakékoli 1080p+ + AVC / x264 + BluRay kvalita + Dolby Atmos / DTS + Angličtina + HDR / Dolby Vision + HEVC / x265 + Žádné CAM/TS + Žádné REMUX/HDR + 1080p Standard + 4K / Remux + 720p / Menší + WEB zdroje + Režim vykreslování Libass + Standardní Cues + Efekty Canvas + Efekty OpenGL + Překrytí Canvas + Překrytí OpenGL (Doporučeno) + Znovu použít poslední odkaz + Automaticky přehrát poslední funkční stream pro tento stejný film/epizodu, pokud je mezipaměť stále platná. + Sekundární jazyk zvuku + Sekundární preferovaný jazyk + DEKODÉR + DALŠÍ EPIZODA + PŘEHRÁVAČ + PŘESKOČENÍ SEGMENTŮ + AUTOMATICKÉ PŘEHRÁVÁNÍ STREAMU + VÝBĚR STREAMU + TITULKY A ZVUK + VYKRESLOVÁNÍ TITULKŮ + %1$d vybráno + Načítací obrazovka + Zobrazovat načítací obrazovku, dokud se neobjeví první snímek videa. + Přeskočit intro + Použít introdb.app k detekci inter a shrnutí děje. + Rozsah zdrojů pro auto. přehrávání + Všechny nainstalované doplňky + Automatické přehrávání zvažuje pouze streamy pocházející z vašich nainstalovaných doplňků. + Všechny zdroje + Automatické přehrávání může používat nainstalované doplňky i povolené pluginy. + Pouze povolené pluginy + Automatické přehrávání zvažuje pouze streamy pocházející z povolených pluginů. + Pouze nainstalované doplňky + Automatické přehrávání zvažuje pouze streamy pocházející z vašich nainstalovaných doplňků. + Automatický výběr streamu + Automaticky přehrát první zdroj + Automaticky přehrát první dostupný zdroj. + Ručně (vybrat stream) + Vždy zobrazit seznam zdrojů a nechat mě vybrat. + Automatické přehrávání (shoda regex) + Přehrát první zdroj, jehož text odpovídá vašemu vzoru regulárního výrazu. + Časový limit výběru streamu + Čas čekání na doplňky před výběrem. + Prahové minuty + Režim prahu další epizody + Minut před koncem + Procenta + Prahová procenta + Záložní řešení, pokud neexistuje časová značka pro outro. + %1$s% + Okamžitě + %1$ss + Neomezeně + Tunelované přehrávání (Tunneled Playback) + Hardwarová synchronizace zvuku a videa. Může zlepšit přehrávání na některých zařízeních Android TV. + Před zapnutím obohacení zadejte níže svůj vlastní TMDB API klíč. + API klíč + Povolit obohacení TMDB + Použít TMDB jako zdroj metadat k vylepšení dat z doplňků. + Zadejte svůj TMDB v3 API klíč. + Kód jazyka + Grafika + Loga a obrázky na pozadí z TMDB + Základní informace + Popis, žánry a hodnocení z TMDB + Kolekce + Filmové kolekce TMDB v pořadí vydání + Obsazení a štáb + Obsazení s fotkami, režisér a scénárista z TMDB + Podrobnosti + Délka, stav, země a jazyk z TMDB + Epizody + Názvy epizod, přehledy, náhledy a délka z TMDB + Podobné + Doporučení TMDB na stránce podrobností + Sítě + Sítě s logy z TMDB + Produkce + Produkční společnosti z TMDB + Plakáty sérií + Použít plakáty sérií z TMDB ve výběru sérií na obrazovce s metadaty u seriálů. + Trailery + Kandidáti na trailery z videí na TMDB pro sekci detailů o trailerech + Osobní API klíč + Jazyk + Jazyk metadat z TMDB pro název, logo a povolená pole + PŘIHLAŠOVACÍ ÚDAJE + LOKALIZACE + MODULY + Obohacení TMDB + Po schválení budete automaticky přesměrováni zpět. + AUTENTIZACE + Komentáře + Zobrazit recenze z Traktu na stránkách s metadaty + Připojit Trakt + Připojeno jako %1$s + Uživatel Traktu + Odpojit + Nepodařilo se otevřít prohlížeč + FUNKCE + Dokončete přihlášení k Traktu ve vašem prohlížeči + Synchronizujte svůj seznam ke zhlédnutí, postup sledování, pokračování ve sledování, scrobbling a osobní seznamy s Traktem. + Chybí přihlašovací údaje k Traktu v local.properties (TRAKT_CLIENT_ID / TRAKT_CLIENT_SECRET). + Otevřít přihlášení k Traktu + Vaše akce uložení nyní mohou cílit na seznam ke zhlédnutí a osobní seznamy na Traktu. + Přihlaste se pomocí Traktu pro povolení ukládání do seznamů a režimu knihovny Trakt. + Zdroj knihovny + Vyberte, kterou knihovnu použít pro ukládání a prohlížení vaší kolekce + Zdroj knihovny + Vyberte, kam ukládat a spravovat položky vaší knihovny + Trakt + Knihovna Nuvio + Vybrána knihovna Trakt + Vybrána knihovna Nuvio + Postup sledování + Vyberte, který zdroj postupu pohání obnovení a pokračování ve sledování + Postup sledování + Vyberte, zda by obnovení a pokračování ve sledování mělo využívat Trakt nebo Nuvio Sync, zatímco Trakt scrobbling zůstane aktivní. + Trakt + Nuvio Sync + Zdroj postupu sledování nastaven na Trakt + Zdroj postupu sledování nastaven na Nuvio Sync + Okno pro pokračování ve sledování + Historie Traktu zohledněná pro pokračování ve sledování + Okno pro pokračování ve sledování + Vyberte, kolik aktivity z Traktu se má objevit v sekci pokračování ve sledování. + Celá historie + %1$d dní + Hodnocení diváků + IMDb + Letterboxd + Metacritic + Rotten Tomatoes + TMDB + Trakt + Neznámý + Jantarový + Karmínový + Smaragdový + Oceán + Růžový + Fialový + Bílý + Další epizoda + Hledání zdroje… + Přehrávání přes %1$s za %2$d… + Náhled další epizody + Nevysílané + Přeskočit + Přeskočit intro + Přeskočit outro + Přeskočit shrnutí + Nebyly nalezeny žádné titulky + Afrikánština + Albánština + Amharština + Arabština + Arménština + Ázerbájdžánština + Baskičtina + Běloruština + Bengálština + Bosenština + Bulharština + Barmština + Katalánština + Čínština + Čínština (zjednodušená) + Čínština (tradiční) + Chorvatština + Čeština + Dánština + Nizozemština + Angličtina + Estonština + Filipínština + Finština + Francouzština + Galicijština + Gruzínština + Němčina + Řečtina + Gudžarátština + Hebrejština + Hindština + Maďarština + Islandština + Indonéština + Irština + Italština + Japonština + Kannadština + Kazaština + Khmerština + Korejština + Laoština + Lotyština + Litevština + Makedonština + Malajština + Malajálamština + Maltština + Maráthština + Mongolština + Nepálština + Norština + Perština + Polština + Portugalština (Portugalsko) + Portugalština (Brazílie) + Paňdžábština + Rumunština + Ruština + Srbština + Sinhálština + Slovenština + Slovinština + Španělština + Španělština (Latinská Amerika) + Svahilština + Švédština + Tamilština + Telugština + Thajština + Turečtina + Ukrajinština + Urdština + Uzbečtina + Vietnamština + Velština + Zuluština + Vymazat + Pokračovat + Ignorovat + Instalovat + Později + Ne + Aktualizovat + Ano + Opravdu chcete ukončit aplikaci? + Ukončit aplikaci + Tento katalog nevrátil žádné položky. + Nebyly nalezeny žádné tituly + Zkontrolujte své připojení k Wi-Fi nebo mobilním datům a zkuste to znovu. + Režisér + Nepodařilo se načíst + Podobné + Série + Tento doplněk vrátil videa pro seriál, ale žádné neobsahovalo čísla série nebo epizody. + Tento doplněk neposkytl metadata epizod pro tento seriál. + Epizody ještě nebyly tímto doplňkem zveřejněny. + Vaše zařízení je online, ale Nuvio se nemohlo spojit s požadovanými servery. + Zobrazit méně + Zobrazit více ▾ + Scénárista + Všechny žánry + Katalog + %1$s • %2$s + Vybranému katalogu se nepodařilo vrátit položky objevování. + Nepodařilo se načíst objevování + Nainstalované doplňky neposkytují katalogy kompatibilní s dlaždicemi pro objevování. + Žádné katalogy pro objevování + Vybraný katalog a filtry nevrátily žádné položky. + Nebyly nalezeny žádné tituly + Před procházením katalogů pro objevování nainstalujte a ověřte alespoň jeden doplněk. + Vybrat katalog + Vybrat žánr + Vybrat typ + Typ + Označit předchozí jako nezhlédnuté + Označit předchozí jako zhlédnuté + Označit %1$s jako nezhlédnutou + Označit %1$s jako zhlédnutou + Označit jako nezhlédnuté + Označit jako zhlédnuté + Další na řadě + %1$s zhlédnuto + Nainstalujte a ověřte alespoň jeden doplněk, než se načtou řádky katalogu na domovské obrazovce. + Nainstalované doplňky aktuálně neposkytují katalogy kompatibilní s dlaždicemi bez vyžadovaných doplňujících dat. + Žádné řádky domovské obrazovky nejsou k dispozici + Zobrazit podrobnosti + Ovládací prvky pro přehrávání a ukládání. + Akce + Hlavní seznam herců. + Související kolekce nebo filmová série. + Kolekce + Sekce komentářů Trakt. + Délka, stav, vydání, jazyk a související informace. + Podrobnosti + Sezóny a seznam epizod pro seriály. + Řádek doporučení. + Podobné + Synopse, hodnocení, žánry a hlavní tvůrci. + Přehled + Studia a sítě. + Produkce + Trailery a zástupci pro přehrávání. + Zpět online + Nelze se spojit se servery + Žádné připojení k internetu + (věk %1$d) + Narozen(a) %1$s%2$s + Zemřel(a) %1$s + Známý(á) z: %1$s + Nejnovější + Nepodařilo se načíst podrobnosti pro %1$s + Populární + Něco se pokazilo + Připravované + Zpět + Zrušit + Zadejte PIN + Zadejte PIN pro %1$s + Zapomněli jste PIN? + Nesprávný PIN + Uzamčeno. Zkuste to znovu za %1$ds + Možnosti avatarů se zobrazí zde, jakmile se načte katalog. + Avatar: %1$s + Zadejte platnou URL adresu obrázku s http:// nebo https://. + Vyberte avatara + Níže vyberte avatara. + Vytvořit profil + Vybrána vlastní URL avatara. + Vlastní URL avatara + Vložte odkaz na obrázek, nebo ponechte prázdné pro použití vestavěného katalogu avatarů. + https://example.com/avatar.png + Všechna data pro "%1$s" budou trvale smazána. + Smazat profil + Přidat profil + Upravit profil + Zadejte aktuální PIN + Zadejte nový PIN + Profil %1$d + Načítání avatarů... + Spravovat profily + Název profilu + Nový profil + Primární doplňky vypnuty + Primární doplňky zapnuty + Odstranit PIN pro %1$s + Odstranit zámek PIN + Ukládání... + Zabezpečení + Přidejte PIN, pokud chcete mít tento profil před přepnutím uzamčený. + Tento profil je chráněn kódem PIN. + Vyberte avatara pro tento profil. + Nastavit zámek PIN + Nepojmenovaný profil + Použít primární doplňky + Sdílet nastavení doplňků hlavního profilu namísto správy samostatného seznamu. + Kdo se dívá? + Staženo + Pokračovat + Aktivní scrapery + Kontrola dalších doplňků… + Kopírovat odkaz na stream + Stáhnout soubor + Nainstalovaným doplňkům se nepodařilo vrátit platnou odpověď pro streamy. + Nepodařilo se načíst streamy + Nejprve nainstalujte doplněk, abyste mohli načíst streamy pro tento titul. + Vaše nainstalované doplňky neposkytují streamy pro tento typ titulu. + Není k dispozici žádný doplněk pro streamování + Žádný z vašich nainstalovaných doplňků nevrátil streamy pro tento titul. + S%1$d E%2$d + Epizoda + S%1$dE%2$d - %3$s + Načítání… + Hledání zdroje… + Hledání streamů… + Odkaz na stream zkopírován + Není k dispozici žádný přímý odkaz na stream + Nejsou k dispozici žádná metadata + Obnovit streamy + Pokračovat od %1$d%% + Pokračovat od %1$s + VELIKOST %1$s + Torrent streamy nejsou podporovány + Zavřít trailer + Trailer nelze přehrát + Nepodařilo se načíst seznamy Trakt + Nepodařilo se aktualizovat seznamy Trakt + %1$s • %2$s + Kontrola aktualizací selhala + Stahování selhalo + Stahování %1$d%% + Nelze zahájit instalaci + Používáte nejnovější verzi. + Povolte instalace aplikací pro Nuvio, poté se vraťte a pokračujte. + Stahování aktualizace... + Nebyly nalezeny žádné aktualizace. + Nová verze je připravena k instalaci. + Aktualizace v aplikaci nejsou v tomto sestavení k dispozici. + Příprava stahování + Poznámky k vydání + Povolte instalace pro pokračování + Aktualizace k dispozici + Stav aktualizace + Tento doplněk je již nainstalován. + Zadejte platnou URL doplňku + Nelze načíst manifest + Nuvio + Smazání účtu selhalo + Přihlášení selhalo + Odhlášení selhalo + Registrace selhala + Nelze načíst položky katalogu. + Další na řadě + Další na řadě • S%1$dE%2$d + Logo %1$s + Nepodařilo se načíst komentáře + Nepodařilo se načíst podrobnosti z žádného doplňku. + Sítě + Žádný doplněk neposkytuje metadata pro tento obsah. + Stahování selhalo + Zobrazuje aktuální průběh a ovládání stahování. + Stahování + Stahování dokončeno + Stahování %1$s • %2$s + Stahování %1$s • %2$s / %3$s + Stahování selhalo + Pozastaveno %1$s + Odebrat + Odebrat %1$s z %2$s? + Odebrat %1$s z vaší knihovny? + Odebrat z knihovny? + Film + Upozornění na vydání nové epizody uloženého seriálu. + Náhled upozornění na vydání epizody. + Nepodařilo se odeslat zkušební oznámení. + Zkušební oznámení odesláno pro %1$s. + Tento stream nelze přehrát. + PIN tohoto profilu se změnil. Pro obnovení zámku na tomto zařízení se jednou připojte k internetu. + Zámek PIN se nepodařilo odstranit. Zkuste to znovu. + Pro odstranění zámku PIN se připojte k internetu. + Tento PIN zatím nelze na tomto zařízení ověřit offline. Nejprve se připojte a odemkněte jej online. + Nepodařilo se nastavit PIN. Zkuste to znovu. + Pro nastavení PIN kódu se připojte k internetu. + Tento profil používá primární doplňky. + Nepodařilo se načíst %1$s + Stream + Vložené + Autorizace odepřena + Dokončete přihlášení k Traktu ve vašem prohlížeči + Neplatný Trakt callback + Neplatný stav Trakt callbacku + Neplatná odpověď s tokenem Trakt + Nepodařilo se načíst knihovnu Trakt + Seznam %1$d + Trakt nevrátil autorizační kód + Chybí přihlašovací údaje k Traktu + Nepodařilo se načíst postup na Traktu + Nepodařilo se dokončit přihlášení k Traktu + Uživatel Traktu + Seznam ke zhlédnutí + Trailer + Neznámé + Doplněk + Uloženo + Přehrát %1$s + Pokračovat %1$s + JSON je prázdný. + Kolekce %1$d má prázdné ID. + Kolekce '%1$s' má prázdný název. + Složka %1$d v '%2$s' má prázdné ID. + Složka '%1$s' v '%2$s' má prázdný název. + Zdroj %1$d ve složce '%2$s' má prázdná pole. + Zdroj %1$d ve složce '%2$s' nemá ID seznamu Trakt. + Neplatný JSON: %1$s + Doplněk nenalezen: %1$s + Leden + Únor + Březen + Duben + Květen + Červen + Červenec + Srpen + Září + Říjen + Listopad + Prosinec + Led + Úno + Bře + Dub + Kvě + Čvn + Čvc + Srp + Zář + Říj + Lis + Pro + Produkční společnost + Síť + Nepodařilo se načíst %1$s + Populární + Nedávné + %1$s • %2$s + Nejlépe hodnocené + Věkové hodnocení + Podrobnosti o filmu + Původní jazyk + Země původu + Informace o vydání + Délka + Plakáty + Text + Podrobnosti o seriálu + Stav + Videa + SOUBOR + Není k dispozici žádný přímý odkaz na stream + Nahradilo předchozí stahování + Stahování zahájeno + Nepodporovaný formát streamu pro stahování + Prázdné tělo odpovědi + Požadavek selhal s chybou HTTP %1$d + Systém stahování není inicializován + Požadavek na stažení selhal + %1$s - %2$s + Uložené tituly se zde objeví poté, co klepnete na Uložit na obrazovce s podrobnostmi. + Vaše knihovna je prázdná + Knihovnu se nepodařilo načíst + Ostatní + Knihovna + Připojte Trakt a ukládejte tituly do svého seznamu ke zhlédnutí nebo do osobních seznamů. + Vaše knihovna Trakt je prázdná + Knihovnu Trakt se nepodařilo načíst + Knihovna Trakt + Anime + Kanály + Filmy + Seriály + TV + %1$s je nyní venku + %1$s • %2$s je nyní venku + Nová epizoda je nyní venku + %1$s je nyní venku + Vydání epizod + Tvůrce + Režisér + Scénárista + Hodnocení diváků + Nebyl nalezen žádný přehratelný stream traileru. + Série %1$d - %2$s + B + KB + MB + GB + From c8eb59cb13c4af4ecd6958a2dcbf7faf217fffb0 Mon Sep 17 00:00:00 2001 From: Pupon11 Date: Sat, 9 May 2026 18:39:49 +0200 Subject: [PATCH 3/7] Add Czech locale to locale_config.xml --- composeApp/src/androidMain/res/xml/locale_config.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/composeApp/src/androidMain/res/xml/locale_config.xml b/composeApp/src/androidMain/res/xml/locale_config.xml index 7d3f2e85..2badd023 100644 --- a/composeApp/src/androidMain/res/xml/locale_config.xml +++ b/composeApp/src/androidMain/res/xml/locale_config.xml @@ -9,4 +9,5 @@ + From d55269e8d328ba158e133dc9031d099873156c28 Mon Sep 17 00:00:00 2001 From: Pupon11 Date: Sat, 9 May 2026 18:44:03 +0200 Subject: [PATCH 4/7] Add Czech language support to AppLanguage enum --- .../kotlin/com/nuvio/app/features/settings/AppLanguage.kt | 2 ++ 1 file changed, 2 insertions(+) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AppLanguage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AppLanguage.kt index 025d6acc..e629434d 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AppLanguage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/AppLanguage.kt @@ -10,6 +10,7 @@ import nuvio.composeapp.generated.resources.lang_turkish import nuvio.composeapp.generated.resources.lang_italian import nuvio.composeapp.generated.resources.lang_greek import nuvio.composeapp.generated.resources.lang_polish +import nuvio.composeapp.generated.resources.lang_czech import org.jetbrains.compose.resources.StringResource enum class AppLanguage( @@ -25,6 +26,7 @@ enum class AppLanguage( ITALIAN("it", Res.string.lang_italian), GREEK("el", Res.string.lang_greek), POLISH("pl", Res.string.lang_polish), + CZECH("cs", Res.string.lang_czech), ; companion object { From f7cb75abb12d81efe8544af5b8ebc846a207c29a Mon Sep 17 00:00:00 2001 From: Pupon11 Date: Sat, 9 May 2026 20:09:06 +0200 Subject: [PATCH 5/7] Update strings.xml changed localization for "settings_playback_minutes_value", "settings_playback_threshold_percentage_value", and "settings_playback_timeout_seconds" to handle new rules better From 1fa84569790253c571a392712c5dc37d56e8acdf Mon Sep 17 00:00:00 2001 From: Guilherme Lima Pereira Date: Sat, 9 May 2026 17:31:29 -0300 Subject: [PATCH 6/7] feat(settings): add Continue Watching sort mode setting Port of NuvioMedia/NuvioTV#1704. Adds a Sort Order setting to the Continue Watching section with two modes: - Default: sort all items by most recently watched - Streaming Style: released/in-progress items first (by recency), unaired next-up items pushed to the end sorted by ascending air date --- .../composeResources/values/strings.xml | 6 + .../com/nuvio/app/features/home/HomeScreen.kt | 59 ++++++- .../settings/ContinueWatchingSettingsPage.kt | 148 ++++++++++++++++++ .../settings/SettingsFullScreenPages.kt | 1 + .../app/features/settings/SettingsScreen.kt | 2 + .../ContinueWatchingPreferencesRepository.kt | 11 ++ .../watchprogress/WatchProgressModels.kt | 7 + 7 files changed, 232 insertions(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index 55d0bbf0..f91bd40b 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -517,6 +517,12 @@ Blur Unwatched in Continue Watching Include upcoming episodes in Continue Watching before they air. Show Unaired Next Up Episodes + SORT ORDER + Sort Order + Default + Sort all items by recency + Streaming Style + Released items first, upcoming at the end Poster Card Style ON LAUNCH UP NEXT BEHAVIOR diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt index e549850b..287875a2 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/home/HomeScreen.kt @@ -42,6 +42,7 @@ import com.nuvio.app.features.watchprogress.ContinueWatchingEnrichmentCache import com.nuvio.app.features.watchprogress.CurrentDateProvider import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepository import com.nuvio.app.features.watchprogress.ContinueWatchingItem +import com.nuvio.app.features.watchprogress.ContinueWatchingSortMode import com.nuvio.app.features.watchprogress.isSeriesTypeForContinueWatching import com.nuvio.app.features.watchprogress.nextUpDismissKey import com.nuvio.app.features.watchprogress.WatchProgressClock @@ -247,11 +248,14 @@ fun HomeScreen( visibleContinueWatchingEntries, cachedInProgressItems, effectivNextUpItems, + continueWatchingPreferences.sortMode, ) { buildHomeContinueWatchingItems( visibleEntries = visibleContinueWatchingEntries, cachedInProgressByVideoId = cachedInProgressItems, nextUpItemsBySeries = effectivNextUpItems, + sortMode = continueWatchingPreferences.sortMode, + todayIsoDate = CurrentDateProvider.todayIsoDate(), ) } val availableManifests = remember(addonsUiState.addons) { @@ -633,6 +637,8 @@ internal fun buildHomeContinueWatchingItems( visibleEntries: List, cachedInProgressByVideoId: Map = emptyMap(), nextUpItemsBySeries: Map>, + sortMode: ContinueWatchingSortMode = ContinueWatchingSortMode.DEFAULT, + todayIsoDate: String = "", ): List { val inProgressSeriesIds = visibleEntries .asSequence() @@ -641,7 +647,7 @@ internal fun buildHomeContinueWatchingItems( .filter(String::isNotBlank) .toSet() - return buildList { + val candidates = buildList { addAll( visibleEntries.map { entry -> val liveItem = entry.toContinueWatchingItem() @@ -663,13 +669,62 @@ internal fun buildHomeContinueWatchingItems( }, ) } + + // Deduplicate by series/content id first (order-stable) + val seen = mutableSetOf() + val deduplicated = candidates .sortedWith( compareByDescending { it.lastUpdatedEpochMs } .thenByDescending { it.isProgressEntry }, ) .filter { candidate -> candidate.item.shouldDisplayInContinueWatching() } - .distinctBy { candidate -> candidate.item.parentMetaId.ifBlank { candidate.item.videoId } } + .filter { candidate -> + val key = candidate.item.parentMetaId.ifBlank { candidate.item.videoId } + seen.add(key) + } + + return when (sortMode) { + ContinueWatchingSortMode.DEFAULT -> deduplicated.map(HomeContinueWatchingCandidate::item) + ContinueWatchingSortMode.STREAMING_STYLE -> applyStreamingStyleSort(deduplicated, todayIsoDate) + } +} + +private fun applyStreamingStyleSort( + candidates: List, + todayIsoDate: String, +): List { + val (released, unreleased) = candidates.partition { candidate -> + val item = candidate.item + if (!item.isNextUp) { + true // in-progress items are always "released" + } else { + val itemReleased = item.released + if (itemReleased.isNullOrBlank() || todayIsoDate.isBlank()) { + true // no date info → treat as released + } else { + isReleasedBy(todayIsoDate = todayIsoDate, releasedDate = itemReleased) + } + } + } + + // Released: most recently watched first (already sorted by dedup pass) + val sortedReleased = released.map(HomeContinueWatchingCandidate::item) + + // Unaired: soonest air date first; unknown dates go to the end + val sortedUnreleased = unreleased + .sortedWith { a, b -> + val dateA = a.item.released?.takeIf { it.isNotBlank() } + val dateB = b.item.released?.takeIf { it.isNotBlank() } + when { + dateA == null && dateB == null -> 0 + dateA == null -> 1 + dateB == null -> -1 + else -> dateA.compareTo(dateB) + } + } .map(HomeContinueWatchingCandidate::item) + + return sortedReleased + sortedUnreleased } private data class CompletedSeriesCandidate( diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ContinueWatchingSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ContinueWatchingSettingsPage.kt index c3d81354..f0483992 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ContinueWatchingSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/ContinueWatchingSettingsPage.kt @@ -5,18 +5,27 @@ import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.Spacer import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyListScope import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.rounded.Check import androidx.compose.material.icons.rounded.CheckCircle +import androidx.compose.material3.BasicAlertDialog +import androidx.compose.material3.ExperimentalMaterial3Api import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Surface import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha @@ -25,6 +34,7 @@ import androidx.compose.ui.unit.dp import com.nuvio.app.features.home.components.ContinueWatchingStylePreview import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepository import com.nuvio.app.features.watchprogress.ContinueWatchingSectionStyle +import com.nuvio.app.features.watchprogress.ContinueWatchingSortMode import nuvio.composeapp.generated.resources.Res import nuvio.composeapp.generated.resources.settings_continue_watching_resume_prompt_description import nuvio.composeapp.generated.resources.settings_continue_watching_resume_prompt_title @@ -34,10 +44,16 @@ import nuvio.composeapp.generated.resources.settings_continue_watching_show_unai import nuvio.composeapp.generated.resources.settings_continue_watching_show_unaired_next_up_title import nuvio.composeapp.generated.resources.settings_continue_watching_section_card_style import nuvio.composeapp.generated.resources.settings_continue_watching_section_on_launch +import nuvio.composeapp.generated.resources.settings_continue_watching_section_sort_order import nuvio.composeapp.generated.resources.settings_continue_watching_section_up_next_behavior import nuvio.composeapp.generated.resources.settings_continue_watching_section_visibility import nuvio.composeapp.generated.resources.settings_continue_watching_show_description import nuvio.composeapp.generated.resources.settings_continue_watching_show_title +import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_default +import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_default_desc +import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_streaming +import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_streaming_desc +import nuvio.composeapp.generated.resources.settings_continue_watching_sort_mode_title import nuvio.composeapp.generated.resources.settings_continue_watching_style_poster import nuvio.composeapp.generated.resources.settings_continue_watching_style_poster_description import nuvio.composeapp.generated.resources.settings_continue_watching_style_wide @@ -58,6 +74,7 @@ internal fun LazyListScope.continueWatchingSettingsContent( showUnairedNextUp: Boolean, blurNextUp: Boolean, showResumePromptOnLaunch: Boolean, + sortMode: ContinueWatchingSortMode, ) { item { SettingsSection( @@ -145,6 +162,39 @@ internal fun LazyListScope.continueWatchingSettingsContent( } } } + item { + var showSortModeSheet by remember { mutableStateOf(false) } + SettingsSection( + title = stringResource(Res.string.settings_continue_watching_section_sort_order), + isTablet = isTablet, + ) { + SettingsGroup(isTablet = isTablet) { + val currentModeLabel = stringResource( + when (sortMode) { + ContinueWatchingSortMode.DEFAULT -> Res.string.settings_continue_watching_sort_mode_default + ContinueWatchingSortMode.STREAMING_STYLE -> Res.string.settings_continue_watching_sort_mode_streaming + } + ) + SettingsNavigationRow( + title = stringResource(Res.string.settings_continue_watching_sort_mode_title), + description = currentModeLabel, + isTablet = isTablet, + onClick = { showSortModeSheet = true }, + ) + } + } + + if (showSortModeSheet) { + ContinueWatchingSortModeDialog( + currentMode = sortMode, + onModeSelected = { mode -> + ContinueWatchingPreferencesRepository.setSortMode(mode) + showSortModeSheet = false + }, + onDismiss = { showSortModeSheet = false }, + ) + } + } } @Composable @@ -250,3 +300,101 @@ private val ContinueWatchingSectionStyle.descriptionRes: StringResource ContinueWatchingSectionStyle.Wide -> Res.string.settings_continue_watching_style_wide_description ContinueWatchingSectionStyle.Poster -> Res.string.settings_continue_watching_style_poster_description } + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +private fun ContinueWatchingSortModeDialog( + currentMode: ContinueWatchingSortMode, + onModeSelected: (ContinueWatchingSortMode) -> Unit, + onDismiss: () -> Unit, +) { + val options = listOf( + Triple( + ContinueWatchingSortMode.DEFAULT, + Res.string.settings_continue_watching_sort_mode_default, + Res.string.settings_continue_watching_sort_mode_default_desc, + ), + Triple( + ContinueWatchingSortMode.STREAMING_STYLE, + Res.string.settings_continue_watching_sort_mode_streaming, + Res.string.settings_continue_watching_sort_mode_streaming_desc, + ), + ) + + BasicAlertDialog( + onDismissRequest = onDismiss, + ) { + Surface( + modifier = Modifier.fillMaxWidth(), + shape = RoundedCornerShape(20.dp), + color = MaterialTheme.colorScheme.surface, + ) { + Column( + modifier = Modifier.padding(20.dp), + verticalArrangement = Arrangement.spacedBy(12.dp), + ) { + Text( + text = stringResource(Res.string.settings_continue_watching_sort_mode_title), + style = MaterialTheme.typography.titleLarge, + color = MaterialTheme.colorScheme.onSurface, + fontWeight = FontWeight.SemiBold, + ) + + Column( + modifier = Modifier.fillMaxWidth(), + verticalArrangement = Arrangement.spacedBy(8.dp), + ) { + options.forEach { (mode, titleRes, descriptionRes) -> + val isSelected = mode == currentMode + val containerColor = if (isSelected) { + MaterialTheme.colorScheme.primary.copy(alpha = 0.14f) + } else { + MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f) + } + + Surface( + modifier = Modifier + .fillMaxWidth() + .clickable { onModeSelected(mode) }, + shape = RoundedCornerShape(12.dp), + color = containerColor, + ) { + Row( + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 14.dp, vertical = 12.dp), + verticalAlignment = Alignment.CenterVertically, + ) { + Column(modifier = Modifier.weight(1f)) { + Text( + text = stringResource(titleRes), + style = MaterialTheme.typography.bodyLarge, + color = MaterialTheme.colorScheme.onSurface, + ) + Spacer(modifier = Modifier.height(2.dp)) + Text( + text = stringResource(descriptionRes), + style = MaterialTheme.typography.bodySmall, + color = MaterialTheme.colorScheme.onSurfaceVariant, + ) + } + Box( + modifier = Modifier.size(24.dp), + contentAlignment = Alignment.Center, + ) { + if (isSelected) { + Icon( + imageVector = Icons.Rounded.Check, + contentDescription = null, + tint = MaterialTheme.colorScheme.primary, + ) + } + } + } + } + } + } + } + } + } +} diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsFullScreenPages.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsFullScreenPages.kt index 143ef517..cbb6bfa4 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsFullScreenPages.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsFullScreenPages.kt @@ -135,6 +135,7 @@ fun ContinueWatchingSettingsScreen( showUnairedNextUp = continueWatchingPreferencesUiState.showUnairedNextUp, blurNextUp = continueWatchingPreferencesUiState.blurNextUp, showResumePromptOnLaunch = continueWatchingPreferencesUiState.showResumePromptOnLaunch, + sortMode = continueWatchingPreferencesUiState.sortMode, ) } } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt index 45a52feb..c75ae332 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/SettingsScreen.kt @@ -490,6 +490,7 @@ private fun MobileSettingsScreen( showUnairedNextUp = continueWatchingPreferencesUiState.showUnairedNextUp, blurNextUp = continueWatchingPreferencesUiState.blurNextUp, showResumePromptOnLaunch = continueWatchingPreferencesUiState.showResumePromptOnLaunch, + sortMode = continueWatchingPreferencesUiState.sortMode, ) SettingsPage.PosterCustomization -> posterCustomizationSettingsContent( isTablet = false, @@ -842,6 +843,7 @@ private fun TabletSettingsScreen( showUnairedNextUp = continueWatchingPreferencesUiState.showUnairedNextUp, blurNextUp = continueWatchingPreferencesUiState.blurNextUp, showResumePromptOnLaunch = continueWatchingPreferencesUiState.showResumePromptOnLaunch, + sortMode = continueWatchingPreferencesUiState.sortMode, ) SettingsPage.PosterCustomization -> posterCustomizationSettingsContent( isTablet = true, diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/ContinueWatchingPreferencesRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/ContinueWatchingPreferencesRepository.kt index 9845b680..93704067 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/ContinueWatchingPreferencesRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/ContinueWatchingPreferencesRepository.kt @@ -22,6 +22,8 @@ private data class StoredContinueWatchingPreferences( val blurNextUp: Boolean = false, val dismissedNextUpKeys: Set = emptySet(), val showResumePromptOnLaunch: Boolean = true, + @SerialName("sort_mode") + val sortMode: ContinueWatchingSortMode = ContinueWatchingSortMode.DEFAULT, ) object ContinueWatchingPreferencesRepository { @@ -97,6 +99,7 @@ object ContinueWatchingPreferencesRepository { blurNextUp = stored.blurNextUp, dismissedNextUpKeys = stored.dismissedNextUpKeys, showResumePromptOnLaunch = stored.showResumePromptOnLaunch, + sortMode = stored.sortMode, ) } else { ContinueWatchingPreferencesUiState() @@ -155,6 +158,13 @@ object ContinueWatchingPreferencesRepository { persist() } + fun setSortMode(mode: ContinueWatchingSortMode) { + ensureLoaded() + if (_uiState.value.sortMode == mode) return + _uiState.value = _uiState.value.copy(sortMode = mode) + persist() + } + fun removeDismissedNextUpKeysForContent(contentId: String) { ensureLoaded() val normalizedContentId = contentId.trim() @@ -178,6 +188,7 @@ object ContinueWatchingPreferencesRepository { blurNextUp = _uiState.value.blurNextUp, dismissedNextUpKeys = _uiState.value.dismissedNextUpKeys, showResumePromptOnLaunch = _uiState.value.showResumePromptOnLaunch, + sortMode = _uiState.value.sortMode, ), ), ) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt index 1c27213d..0485986b 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/watchprogress/WatchProgressModels.kt @@ -17,6 +17,12 @@ enum class ContinueWatchingSectionStyle { Poster, } +@Serializable +enum class ContinueWatchingSortMode { + DEFAULT, + STREAMING_STYLE, +} + @Serializable data class WatchProgressEntry( val contentType: String, @@ -175,6 +181,7 @@ data class ContinueWatchingPreferencesUiState( val blurNextUp: Boolean = false, val dismissedNextUpKeys: Set = emptySet(), val showResumePromptOnLaunch: Boolean = true, + val sortMode: ContinueWatchingSortMode = ContinueWatchingSortMode.DEFAULT, ) internal fun nextUpDismissKey( From 5623cd99c2a2bace43bcd0b8ec85b4eddcf539bf Mon Sep 17 00:00:00 2001 From: Guilherme Lima Pereira Date: Sat, 9 May 2026 17:37:32 -0300 Subject: [PATCH 7/7] fix(trakt): replace invalid RegexOption.DOT_MATCHES_ALL with inline flag RegexOption.DOT_MATCHES_ALL is not available in Kotlin Common (KMP). Replaced with inline (?s) flag to fix commonMain compilation. --- .../com/nuvio/app/features/trakt/TraktCommentsRepository.kt | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktCommentsRepository.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktCommentsRepository.kt index 378b8ef3..a2bd8a03 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktCommentsRepository.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/trakt/TraktCommentsRepository.kt @@ -15,8 +15,7 @@ private const val COMMENTS_SORT = "likes" private const val COMMENTS_LIMIT = 100 private const val COMMENTS_CACHE_TTL_MS = 10 * 60_000L private val INLINE_SPOILER_REGEX = Regex( - "\\[spoiler\\].*?\\[/spoiler\\]", - setOf(RegexOption.IGNORE_CASE, RegexOption.DOT_MATCHES_ALL), + "(?is)\\[spoiler\\].*?\\[/spoiler\\]" ) private val INLINE_SPOILER_TAG_REGEX = Regex("\\[/?spoiler\\]", RegexOption.IGNORE_CASE)