Merge branch 'cmp-rewrite' of https://github.com/khanh71/NuvioMobile into cmp-rewrite

This commit is contained in:
Trần Khánh 2026-07-13 18:51:56 +07:00
commit fbc8e9534c
40 changed files with 1162 additions and 363 deletions

View file

@ -48,6 +48,7 @@ import com.nuvio.app.features.trakt.TraktLibraryStorage
import com.nuvio.app.features.trakt.TraktSettingsStorage
import com.nuvio.app.features.tmdb.TmdbSettingsStorage
import com.nuvio.app.features.updater.AndroidAppUpdaterPlatform
import com.nuvio.app.core.ui.CardDepthStyleStorage
import com.nuvio.app.core.ui.PosterCardStyleStorage
import com.nuvio.app.features.watched.WatchedStorage
import com.nuvio.app.features.streams.StreamLinkCacheStorage
@ -90,6 +91,7 @@ class MainActivity : AppCompatActivity() {
SearchHistoryStorage.initialize(applicationContext)
SeasonViewModeStorage.initialize(applicationContext)
PosterCardStyleStorage.initialize(applicationContext)
CardDepthStyleStorage.initialize(applicationContext)
DebridSettingsStorage.initialize(applicationContext)
TmdbSettingsStorage.initialize(applicationContext)
MdbListSettingsStorage.initialize(applicationContext)

View file

@ -0,0 +1,26 @@
package com.nuvio.app.core.ui
import android.content.Context
import android.content.SharedPreferences
import com.nuvio.app.core.storage.ProfileScopedKey
internal actual object CardDepthStyleStorage {
private const val preferencesName = "nuvio_card_depth_style"
private const val payloadKey = "card_depth_style_payload"
private var preferences: SharedPreferences? = null
fun initialize(context: Context) {
preferences = context.getSharedPreferences(preferencesName, Context.MODE_PRIVATE)
}
actual fun loadPayload(): String? =
preferences?.getString(ProfileScopedKey.of(payloadKey), null)
actual fun savePayload(payload: String) {
preferences
?.edit()
?.putString(ProfileScopedKey.of(payloadKey), payload)
?.apply()
}
}

View file

@ -433,7 +433,9 @@
<string name="compose_settings_root_check_updates_description">Изтегли последна версия</string>
<string name="compose_settings_root_check_updates_title">Провери за актуализации</string>
<string name="compose_settings_root_content_discovery_description">Управлявай добавки и източници за discovery.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Управлявайте добавките и изберете какво да се показва в Nuvio.</string>
<string name="compose_settings_root_downloads_description">Управлявай изтеглените филми и епизоди.</string>
<string name="compose_settings_root_downloads_description_appstore">Управлявайте запазеното на това устройство.</string>
<string name="compose_settings_root_downloads_title">Изтегляния</string>
<string name="compose_settings_root_general_section">ОБЩИ</string>
<string name="compose_settings_root_integrations_description">Управлявай наличните интеграции</string>
@ -1845,11 +1847,13 @@
<string name="player_error_torrent">Грешка в торента: %1$s</string>
<!-- Current cmp-rewrite string additions -->
<string name="action_switch">Превключи</string>
<string name="addons_appstore_add_description">Свържете собствен медиен сървър, за да разглеждате и възпроизвеждате от личната си библиотека.</string>
<string name="addons_appstore_empty_subtitle">Добавете URL на сървър по-горе, когато искате Nuvio да показва вашата лична библиотека.</string>
<string name="addons_appstore_empty_title">Няма свързани лични библиотеки.</string>
<string name="addons_appstore_input_placeholder">URL на сървър</string>
<string name="addons_appstore_install_button">Добави</string>
<string name="addons_appstore_add_description">Пренесете библиотеката си в Nuvio с добавките, които използвате.</string>
<string name="addons_appstore_empty_subtitle">Добавете добавка, за да започнете да изграждате библиотеката си.</string>
<string name="addons_appstore_empty_title">Настройте Nuvio по свой начин.</string>
<string name="addons_appstore_input_placeholder">URL на добавка</string>
<string name="addons_appstore_install_button">Добавяне на добавка</string>
<string name="store_empty_unavailable_title">Все още няма нищо тук</string>
<string name="store_empty_unavailable_message">Текущите ви добавки все още нямат какво да покажат тук.</string>
<string name="community_donation_progress_title">Сървър и поддръжка за този месец</string>
<string name="community_donation_progress_complete">Покрито. Допълнителната подкрепа вече отива за разработка.</string>
<string name="community_donation_progress_remaining">След 100% допълнителната подкрепа отива за разработка.</string>
@ -1861,7 +1865,7 @@
<string name="details_actions_menu_label">Още действия</string>
<string name="p2p_error_unknown">Неизвестна грешка в торента</string>
<string name="settings_appearance_app_language_device">Език на устройството</string>
<string name="settings_content_discovery_addons_description_appstore">Свържете лични медийни източници и управлявайте достъпа до собствената си библиотека.</string>
<string name="settings_content_discovery_addons_description_appstore">Добавяйте и управлявайте добавките, които използвате с Nuvio.</string>
<string name="settings_playback_external_player_send_skip_segments">Изпращане на времеви маркери за интро и аутро</string>
<string name="settings_playback_external_player_send_skip_segments_description">Предайте откритите времеви маркери за интро и аутро към външния плейър за автоматично прескачане. Работи само в плейъри, които го поддържат; останалите плейъри го игнорират.</string>
<string name="settings_playback_touch_gestures">Жестове с докосване</string>

View file

@ -32,11 +32,13 @@
<string name="addons_badge_unavailable">Nedostupné</string>
<string name="addons_configure">Konfigurovat doplněk</string>
<string name="addons_delete">Smazat doplněk</string>
<string name="addons_appstore_add_description">Připojte svůj vlastní mediální server a procházejte i přehrávejte ze své osobní knihovny.</string>
<string name="addons_appstore_empty_subtitle">Přidejte výše adresu URL serveru, pokud chcete, aby Nuvio zobrazovalo vaši soukromou knihovnu.</string>
<string name="addons_appstore_empty_title">Nejsou připojeny žádné osobní knihovny.</string>
<string name="addons_appstore_input_placeholder">URL serveru</string>
<string name="addons_appstore_install_button">Přidat</string>
<string name="addons_appstore_add_description">Přeneste svou knihovnu do Nuvia pomocí doplňků, které používáte.</string>
<string name="addons_appstore_empty_subtitle">Přidejte doplněk a začněte vytvářet svou knihovnu.</string>
<string name="addons_appstore_empty_title">Přizpůsobte si Nuvio.</string>
<string name="addons_appstore_input_placeholder">URL doplňku</string>
<string name="addons_appstore_install_button">Přidat doplněk</string>
<string name="store_empty_unavailable_title">Zatím tu nic není</string>
<string name="store_empty_unavailable_message">Vaše současné doplňky tu zatím nemají co zobrazit.</string>
<string name="addons_empty_subtitle">Přidejte URL manifestu, abyste mohli do Nuvio začít načítat katalogy, metadata, streamy nebo titulky.</string>
<string name="addons_empty_title">Zatím nejsou nainstalovány žádné doplňky.</string>
<string name="addons_error_enter_url">Zadejte URL doplňku.</string>
@ -442,7 +444,9 @@
<string name="compose_settings_root_check_updates_description">Stáhnout nejnovější verzi</string>
<string name="compose_settings_root_check_updates_title">Zkontrolovat aktualizace</string>
<string name="compose_settings_root_content_discovery_description">Správa doplňků a zdrojů objevování.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Spravujte doplňky a vyberte, co se má v Nuviu zobrazovat.</string>
<string name="compose_settings_root_downloads_description">Spravujte své stažené filmy a epizody.</string>
<string name="compose_settings_root_downloads_description_appstore">Spravujte obsah uložený v tomto zařízení.</string>
<string name="compose_settings_root_downloads_title">Stažené položky</string>
<string name="compose_settings_root_general_section">OBECNÉ</string>
<string name="compose_settings_root_integrations_description">Spravovat dostupné integrace</string>
@ -638,7 +642,7 @@
<string name="settings_content_discovery_section_home">DOMŮ</string>
<string name="settings_content_discovery_section_sources">ZDROJE</string>
<string name="settings_content_discovery_addons_description">Instalovat, odstraňovat, obnovovat a třídit vaše zdroje obsahu.</string>
<string name="settings_content_discovery_addons_description_appstore">Připojit osobní mediální zdroje a spravovat přístup k vlastní knihovně.</string>
<string name="settings_content_discovery_addons_description_appstore">Přidávejte a spravujte doplňky, které používáte s Nuviem.</string>
<string name="settings_content_discovery_plugins_description">Instalovat repozitáře pro JavaScriptové scarpování a testovat poskytovatele interně.</string>
<string name="settings_content_discovery_homescreen_description">Upravit rozvržení domovské obrazovky, viditelnost obsahu a chování plakátů.</string>
<string name="settings_content_discovery_meta_screen_description">Nastavení pro obrazovku detailu a epizody.</string>

View file

@ -27,6 +27,13 @@
<string name="addons_badge_unavailable">Nicht verfügbar</string>
<string name="addons_configure">Addon konfigurieren</string>
<string name="addons_delete">Addon löschen</string>
<string name="addons_appstore_add_description">Bring deine Mediathek mit den Addons, die du nutzt, zu Nuvio.</string>
<string name="addons_appstore_empty_subtitle">Füge ein Addon hinzu, um deine Mediathek aufzubauen.</string>
<string name="addons_appstore_empty_title">Richte Nuvio für dich ein.</string>
<string name="addons_appstore_input_placeholder">Addon-URL</string>
<string name="addons_appstore_install_button">Addon hinzufügen</string>
<string name="store_empty_unavailable_title">Hier gibt es noch nichts</string>
<string name="store_empty_unavailable_message">Deine aktuellen Addons zeigen hier noch nichts an.</string>
<string name="addons_empty_subtitle">Füge eine Manifest-URL hinzu, um Kataloge, Metadaten, Streams oder Untertitel in Nuvio zu laden.</string>
<string name="addons_empty_title">Noch keine Addons installiert.</string>
<string name="addons_error_enter_url">Gib eine Addon-URL ein.</string>
@ -383,7 +390,9 @@
<string name="compose_settings_root_check_updates_description">Suche nach neuen Versionen der App.</string>
<string name="compose_settings_root_check_updates_title">Nach Updates suchen</string>
<string name="compose_settings_root_content_discovery_description">Verwalte Addons und Entdeckungsquellen.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Verwalte Addons und wähle aus, was in Nuvio angezeigt wird.</string>
<string name="compose_settings_root_downloads_description">Verwalte deine heruntergeladenen Filme und Episoden.</string>
<string name="compose_settings_root_downloads_description_appstore">Verwalte, was du auf diesem Gerät gespeichert hast.</string>
<string name="compose_settings_root_downloads_title">Downloads</string>
<string name="compose_settings_root_general_section">ALLGEMEIN</string>
<string name="compose_settings_root_integrations_description">TMDB- und MDBList-Dienste verbinden.</string>
@ -519,6 +528,7 @@
<string name="settings_content_discovery_section_home">START</string>
<string name="settings_content_discovery_section_sources">QUELLEN</string>
<string name="settings_content_discovery_addons_description">Installiere, entferne, aktualisiere und sortiere deine Inhaltsquellen.</string>
<string name="settings_content_discovery_addons_description_appstore">Füge die Addons hinzu, die du mit Nuvio nutzt, und verwalte sie.</string>
<string name="settings_content_discovery_plugins_description">Installiere JavaScript-Scraper-Repositories und teste Anbieter intern.</string>
<string name="settings_content_discovery_homescreen_description">Lege fest, welche Kataloge auf der Startseite und in welcher Reihenfolge erscheinen.</string>
<string name="settings_content_discovery_meta_screen_description">Detail-Abschnitte deaktivieren und alles unterhalb des Hero neu anordnen.</string>

View file

@ -233,7 +233,9 @@
<string name="compose_settings_root_check_updates_description">Ελέγξτε για νέες εκδόσεις της εφαρμογής.</string>
<string name="compose_settings_root_check_updates_title">Έλεγχος για ενημερώσεις</string>
<string name="compose_settings_root_content_discovery_description">Διαχείριση πρόσθετων και πηγών ανακάλυψης.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Διαχειριστείτε τα πρόσθετα και επιλέξτε τι εμφανίζεται στο Nuvio.</string>
<string name="compose_settings_root_downloads_description">Διαχειριστείτε τις κατεβασμένες ταινίες και επεισόδιά σας.</string>
<string name="compose_settings_root_downloads_description_appstore">Διαχειριστείτε όσα έχετε αποθηκεύσει σε αυτή τη συσκευή.</string>
<string name="compose_settings_root_downloads_title">Λήψεις</string>
<string name="compose_settings_root_general_section">ΓΕΝΙΚΑ</string>
<string name="compose_settings_root_integrations_description">Σύνδεση υπηρεσιών TMDB και MDBList.</string>
@ -1061,11 +1063,13 @@
<string name="action_saving">Αποθήκευση…</string>
<string name="action_switch">Εναλλαγή</string>
<string name="action_validate">Επικύρωση</string>
<string name="addons_appstore_add_description">Συνδέστε τον δικό σας διακομιστή μέσων για να περιηγηθείτε και να αναπαραγάγετε από τη δική σας προσωπική βιβλιοθήκη.</string>
<string name="addons_appstore_empty_subtitle">Προσθέστε μια διεύθυνση URL διακομιστή παραπάνω όταν θέλετε το Nuvio να εμφανίζει τη δική σας ιδιωτική βιβλιοθήκη.</string>
<string name="addons_appstore_empty_title">Δεν υπάρχουν προσωπικές βιβλιοθήκες συνδεδεμένες.</string>
<string name="addons_appstore_input_placeholder">Διεύθυνση URL διακομιστή</string>
<string name="addons_appstore_install_button">Προσθήκη</string>
<string name="addons_appstore_add_description">Φέρτε τη βιβλιοθήκη σας στο Nuvio με τα πρόσθετα που χρησιμοποιείτε.</string>
<string name="addons_appstore_empty_subtitle">Προσθέστε ένα πρόσθετο για να αρχίσετε να δημιουργείτε τη βιβλιοθήκη σας.</string>
<string name="addons_appstore_empty_title">Κάντε το Nuvio δικό σας.</string>
<string name="addons_appstore_input_placeholder">URL πρόσθετου</string>
<string name="addons_appstore_install_button">Προσθήκη πρόσθετου</string>
<string name="store_empty_unavailable_title">Δεν υπάρχει τίποτα εδώ ακόμη</string>
<string name="store_empty_unavailable_message">Τα πρόσθετα που χρησιμοποιείτε δεν έχουν ακόμη τίποτα να εμφανίσουν εδώ.</string>
<string name="addons_badge_disabled">Απενεργοποιημένο</string>
<string name="addons_manifest_missing_field">Το manifest είναι κενό "%1$s"</string>
<string name="cloud_library_connect_action">Σύνδεση λογαριασμού</string>
@ -1520,7 +1524,7 @@
<string name="settings_appearance_app_language_device">Device Γλώσσα</string>
<string name="settings_appearance_liquid_glass">Liquid Glass</string>
<string name="settings_appearance_liquid_glass_description">Use the native iPhone tab bar on iOS 26 and later.</string>
<string name="settings_content_discovery_addons_description_appstore">Connect personal media sources and manage access to your own Βιβλιοθήκη.</string>
<string name="settings_content_discovery_addons_description_appstore">Προσθέστε και διαχειριστείτε τα πρόσθετα που χρησιμοποιείτε με το Nuvio.</string>
<string name="settings_continue_watching_blur_next_up_description">Blur next episode thumbnails in Συνέχεια Watching to avoid spoilers.</string>
<string name="settings_continue_watching_blur_next_up_title">Blur Unwatched in Συνέχεια Watching</string>
<string name="settings_continue_watching_section_sort_order">Ταξινόμηση ORDER</string>

View file

@ -32,11 +32,13 @@
<string name="addons_badge_unavailable">No disponible</string>
<string name="addons_configure">Configurar complemento</string>
<string name="addons_delete">Eliminar complemento</string>
<string name="addons_appstore_add_description">Conecte su propio servidor multimedia para navegar y reproducir desde su biblioteca personal.</string>
<string name="addons_appstore_empty_subtitle">Agregue una URL de servidor arriba cuando desee que Nuvio muestre su biblioteca privada.</string>
<string name="addons_appstore_empty_title">No hay bibliotecas personales conectadas.</string>
<string name="addons_appstore_input_placeholder">URL del servidor</string>
<string name="addons_appstore_install_button">Agregar</string>
<string name="addons_appstore_add_description">Lleva tu biblioteca a Nuvio con los complementos que utilizas.</string>
<string name="addons_appstore_empty_subtitle">Agrega un complemento para empezar a crear tu biblioteca.</string>
<string name="addons_appstore_empty_title">Haz tuyo Nuvio.</string>
<string name="addons_appstore_input_placeholder">URL del complemento</string>
<string name="addons_appstore_install_button">Agregar complemento</string>
<string name="store_empty_unavailable_title">Aún no hay nada aquí</string>
<string name="store_empty_unavailable_message">Tus complementos actuales aún no tienen nada que mostrar aquí.</string>
<string name="addons_empty_subtitle">Agrega una URL de manifiesto para empezar a cargar catálogos, metadatos, streams o subtítulos en Nuvio.</string>
<string name="addons_empty_title">Aún no hay complementos instalados.</string>
<string name="addons_error_enter_url">Introduce una URL de complemento.</string>
@ -442,7 +444,9 @@
<string name="compose_settings_root_check_updates_description">Buscar nuevas versiones de la app.</string>
<string name="compose_settings_root_check_updates_title">Buscar actualizaciones</string>
<string name="compose_settings_root_content_discovery_description">Administra complementos y fuentes de descubrimiento.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Administra tus complementos y elige qué aparece en Nuvio.</string>
<string name="compose_settings_root_downloads_description">Administra tus películas y episodios descargados.</string>
<string name="compose_settings_root_downloads_description_appstore">Administra lo que has guardado en este dispositivo.</string>
<string name="compose_settings_root_downloads_title">Descargas</string>
<string name="compose_settings_root_general_section">GENERAL</string>
<string name="compose_settings_root_integrations_description">Conecta los servicios TMDB y MDBList.</string>
@ -638,7 +642,7 @@
<string name="settings_content_discovery_section_home">INICIO</string>
<string name="settings_content_discovery_section_sources">FUENTES</string>
<string name="settings_content_discovery_addons_description">Instala, elimina, actualiza y ordena tus fuentes de contenido.</string>
<string name="settings_content_discovery_addons_description_appstore">Conecte fuentes de medios personales y administre el acceso a su propia biblioteca.</string>
<string name="settings_content_discovery_addons_description_appstore">Agrega y administra los complementos que utilizas con Nuvio.</string>
<string name="settings_content_discovery_plugins_description">Instala repositorios de scrapers en JavaScript y prueba proveedores internamente.</string>
<string name="settings_content_discovery_homescreen_description">Controla qué catálogos aparecen en Inicio y en qué orden.</string>
<string name="settings_content_discovery_meta_screen_description">Desactiva secciones de detalles y reordena todo debajo del Destacado.</string>

View file

@ -33,11 +33,13 @@
<string name="addons_badge_unavailable">Indisponible</string>
<string name="addons_configure">Configurer laddon</string>
<string name="addons_delete">Supprimer laddon</string>
<string name="addons_appstore_add_description">Connectez votre propre serveur multimédia pour parcourir et lire depuis votre bibliothèque personnelle.</string>
<string name="addons_appstore_empty_subtitle">Ajoutez une URL de serveur ci-dessus pour que Nuvio affiche votre bibliothèque privée.</string>
<string name="addons_appstore_empty_title">Aucune bibliothèque personnelle connectée.</string>
<string name="addons_appstore_input_placeholder">URL du serveur</string>
<string name="addons_appstore_install_button">Ajouter</string>
<string name="addons_appstore_add_description">Retrouvez votre bibliothèque dans Nuvio avec les addons que vous utilisez.</string>
<string name="addons_appstore_empty_subtitle">Ajoutez un addon pour commencer à créer votre bibliothèque.</string>
<string name="addons_appstore_empty_title">Personnalisez Nuvio.</string>
<string name="addons_appstore_input_placeholder">URL de laddon</string>
<string name="addons_appstore_install_button">Ajouter un addon</string>
<string name="store_empty_unavailable_title">Rien à afficher pour le moment</string>
<string name="store_empty_unavailable_message">Vos addons actuels nont encore rien à afficher ici.</string>
<string name="addons_empty_subtitle">Ajoutez une URL de manifeste pour commencer à charger des catalogues, métadonnées, streams ou sous-titres dans Nuvio.</string>
<string name="addons_empty_title">Aucun addon installé.</string>
<string name="addons_error_enter_url">Saisissez une URL daddon.</string>
@ -434,7 +436,9 @@
<string name="compose_settings_root_check_updates_description">Rechercher de nouvelles versions de lapplication.</string>
<string name="compose_settings_root_check_updates_title">Vérifier les mises à jour</string>
<string name="compose_settings_root_content_discovery_description">Gérez les addons et sources de découverte.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Gérez vos addons et choisissez ce qui apparaît dans Nuvio.</string>
<string name="compose_settings_root_downloads_description">Gérez vos films et épisodes téléchargés.</string>
<string name="compose_settings_root_downloads_description_appstore">Gérez ce que vous avez enregistré sur cet appareil.</string>
<string name="compose_settings_root_downloads_title">Téléchargements</string>
<string name="compose_settings_root_general_section">GÉNÉRAL</string>
<string name="compose_settings_root_integrations_description">Connectez les services TMDB et MDBList.</string>
@ -648,7 +652,7 @@
<string name="settings_content_discovery_section_home">ACCUEIL</string>
<string name="settings_content_discovery_section_sources">SOURCES</string>
<string name="settings_content_discovery_addons_description">Installez, supprimez, mettez à jour et ordonnez vos sources de contenu.</string>
<string name="settings_content_discovery_addons_description_appstore">Connectez des sources multimédias personnelles et gérez laccès à votre propre bibliothèque.</string>
<string name="settings_content_discovery_addons_description_appstore">Ajoutez et gérez les addons que vous utilisez avec Nuvio.</string>
<string name="settings_content_discovery_plugins_description">Installez des dépôts de scrapers JavaScript et testez des fournisseurs en interne.</string>
<string name="settings_content_discovery_homescreen_description">Contrôle quels catalogues apparaissent à laccueil et dans quel ordre.</string>
<string name="settings_content_discovery_meta_screen_description">Désactivez des sections de détails et réorganisez tout sous le Hero.</string>

View file

@ -401,7 +401,9 @@
<string name="compose_settings_root_check_updates_description">A legújabb verzió letöltése</string>
<string name="compose_settings_root_check_updates_title">Frissítések keresése</string>
<string name="compose_settings_root_content_discovery_description">Kiegészítők és tartalomforrások kezelése</string>
<string name="compose_settings_root_content_discovery_description_appstore">Kezeld a kiegészítőket, és válaszd ki, mi jelenjen meg a Nuvióban.</string>
<string name="compose_settings_root_downloads_description">A letöltött filmek és epizódok kezelése</string>
<string name="compose_settings_root_downloads_description_appstore">Kezeld az eszközre mentett tartalmakat.</string>
<string name="compose_settings_root_downloads_title">Letöltések</string>
<string name="compose_settings_root_general_section">ÁLTALÁNOS</string>
<string name="compose_settings_root_integrations_description">Elérhető integrációk kezelése</string>
@ -1339,11 +1341,13 @@
<string name="unit_bytes_gb">GB</string>
<string name="action_switch">Váltás</string>
<string name="addons_badge_disabled">Kikapcsolva</string>
<string name="addons_appstore_add_description">Csatlakoztasd saját médiaszerveredet a személyes könyvtárad böngészéséhez és lejátszásához.</string>
<string name="addons_appstore_empty_subtitle">Adj meg egy szerver URL-t fent, ha szeretnéd, hogy a Nuvio megjelenítse a privát könyvtáradat.</string>
<string name="addons_appstore_empty_title">Nincsenek csatlakoztatott személyes könyvtárak.</string>
<string name="addons_appstore_input_placeholder">Szerver URL</string>
<string name="addons_appstore_install_button">Hozzáadás</string>
<string name="addons_appstore_add_description">Hozd el a könyvtáradat a Nuvióba a használt kiegészítőkkel.</string>
<string name="addons_appstore_empty_subtitle">Adj hozzá egy kiegészítőt a könyvtárad felépítéséhez.</string>
<string name="addons_appstore_empty_title">Szabd személyre a Nuviót.</string>
<string name="addons_appstore_input_placeholder">Kiegészítő URL-je</string>
<string name="addons_appstore_install_button">Kiegészítő hozzáadása</string>
<string name="store_empty_unavailable_title">Itt még nincs semmi</string>
<string name="store_empty_unavailable_message">A jelenlegi kiegészítőid még nem jelenítenek meg itt semmit.</string>
<string name="collections_editor_tmdb_genres_movie_placeholder">28,12</string>
<string name="collections_editor_tmdb_genres_series_placeholder">18,35</string>
<string name="collections_editor_tmdb_date_from_placeholder">2020-01-01</string>
@ -1397,7 +1401,7 @@
<string name="settings_show_secret">Érték megjelenítése</string>
<string name="settings_continue_watching_style_card">Kártya</string>
<string name="settings_continue_watching_style_card_description">TV-stílusú fekvő kártya</string>
<string name="settings_content_discovery_addons_description_appstore">Személyes médiaforrások csatlakoztatása és a saját könyvtáradhoz való hozzáférés kezelése.</string>
<string name="settings_content_discovery_addons_description_appstore">Add hozzá és kezeld a Nuvióval használt kiegészítőket.</string>
<string name="settings_debrid_cloud_library">Felhő könyvtár</string>
<string name="settings_debrid_cloud_library_description">A csatlakoztatott fiókjaidban már meglévő fájlok böngészése és lejátszása.</string>
<string name="settings_debrid_resolve_with">Feloldás ezzel</string>

View file

@ -31,6 +31,13 @@
<string name="addons_badge_unavailable">Tidak Tersedia</string>
<string name="addons_configure">Konfigurasi addon</string>
<string name="addons_delete">Hapus addon</string>
<string name="addons_appstore_add_description">Bawa pustaka Anda ke Nuvio dengan addon yang Anda gunakan.</string>
<string name="addons_appstore_empty_subtitle">Tambahkan addon untuk mulai membangun pustaka Anda.</string>
<string name="addons_appstore_empty_title">Jadikan Nuvio milik Anda.</string>
<string name="addons_appstore_input_placeholder">URL Addon</string>
<string name="addons_appstore_install_button">Tambah Addon</string>
<string name="store_empty_unavailable_title">Belum ada apa pun di sini</string>
<string name="store_empty_unavailable_message">Addon yang Anda gunakan belum memiliki apa pun untuk ditampilkan di sini.</string>
<string name="addons_empty_subtitle">Tambahkan URL manifes untuk mulai memuat katalog, metadata, streaming, atau subtitle ke Nuvio.</string>
<string name="addons_empty_title">Belum ada addon yang terpasang.</string>
<string name="addons_error_enter_url">Masukkan URL addon.</string>
@ -412,7 +419,9 @@
<string name="compose_settings_root_check_updates_description">Unduh versi terbaru</string>
<string name="compose_settings_root_check_updates_title">Periksa pembaruan</string>
<string name="compose_settings_root_content_discovery_description">Kelola addon dan sumber penemuan.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Kelola addon dan pilih apa yang tampil di Nuvio.</string>
<string name="compose_settings_root_downloads_description">Kelola film dan episode yang telah diunduh.</string>
<string name="compose_settings_root_downloads_description_appstore">Kelola yang Anda simpan di perangkat ini.</string>
<string name="compose_settings_root_downloads_title">Unduhan</string>
<string name="compose_settings_root_general_section">UMUM</string>
<string name="compose_settings_root_integrations_description">Kelola integrasi yang tersedia</string>
@ -597,6 +606,7 @@
<string name="settings_content_discovery_section_home">BERANDA</string>
<string name="settings_content_discovery_section_sources">SUMBER</string>
<string name="settings_content_discovery_addons_description">Pasang, hapus, perbarui, dan urutkan sumber konten Anda.</string>
<string name="settings_content_discovery_addons_description_appstore">Tambahkan dan kelola addon yang Anda gunakan dengan Nuvio.</string>
<string name="settings_content_discovery_plugins_description">Pasang repositori scraper JavaScript dan uji penyedia secara internal.</string>
<string name="settings_content_discovery_homescreen_description">Sesuaikan tata letak beranda, visibilitas konten, dan poster</string>
<string name="settings_content_discovery_meta_screen_description">Pengaturan untuk layar detail dan episode.</string>

View file

@ -31,6 +31,13 @@
<string name="addons_badge_unavailable">Tidak Tersedia</string>
<string name="addons_configure">Konfigurasi addon</string>
<string name="addons_delete">Hapus addon</string>
<string name="addons_appstore_add_description">Bawa pustaka Anda ke Nuvio dengan addon yang Anda gunakan.</string>
<string name="addons_appstore_empty_subtitle">Tambahkan addon untuk mulai membangun pustaka Anda.</string>
<string name="addons_appstore_empty_title">Jadikan Nuvio milik Anda.</string>
<string name="addons_appstore_input_placeholder">URL Addon</string>
<string name="addons_appstore_install_button">Tambah Addon</string>
<string name="store_empty_unavailable_title">Belum ada apa pun di sini</string>
<string name="store_empty_unavailable_message">Addon yang Anda gunakan belum memiliki apa pun untuk ditampilkan di sini.</string>
<string name="addons_empty_subtitle">Tambahkan URL manifes untuk mulai memuat katalog, metadata, streaming, atau subtitle ke Nuvio.</string>
<string name="addons_empty_title">Belum ada addon yang terpasang.</string>
<string name="addons_error_enter_url">Masukkan URL addon.</string>
@ -412,7 +419,9 @@
<string name="compose_settings_root_check_updates_description">Unduh versi terbaru</string>
<string name="compose_settings_root_check_updates_title">Periksa pembaruan</string>
<string name="compose_settings_root_content_discovery_description">Kelola addon dan sumber penemuan.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Kelola addon dan pilih apa yang tampil di Nuvio.</string>
<string name="compose_settings_root_downloads_description">Kelola film dan episode yang telah diunduh.</string>
<string name="compose_settings_root_downloads_description_appstore">Kelola yang Anda simpan di perangkat ini.</string>
<string name="compose_settings_root_downloads_title">Unduhan</string>
<string name="compose_settings_root_general_section">UMUM</string>
<string name="compose_settings_root_integrations_description">Kelola integrasi yang tersedia</string>
@ -597,6 +606,7 @@
<string name="settings_content_discovery_section_home">BERANDA</string>
<string name="settings_content_discovery_section_sources">SUMBER</string>
<string name="settings_content_discovery_addons_description">Pasang, hapus, perbarui, dan urutkan sumber konten Anda.</string>
<string name="settings_content_discovery_addons_description_appstore">Tambahkan dan kelola addon yang Anda gunakan dengan Nuvio.</string>
<string name="settings_content_discovery_plugins_description">Pasang repositori scraper JavaScript dan uji penyedia secara internal.</string>
<string name="settings_content_discovery_homescreen_description">Sesuaikan tata letak beranda, visibilitas konten, dan poster</string>
<string name="settings_content_discovery_meta_screen_description">Pengaturan untuk layar detail dan episode.</string>

View file

@ -27,6 +27,13 @@
<string name="addons_badge_unavailable">Non Disponibile</string>
<string name="addons_configure">Configura addon</string>
<string name="addons_delete">Cancella addon</string>
<string name="addons_appstore_add_description">Porta la tua libreria su Nuvio con gli addon che usi.</string>
<string name="addons_appstore_empty_subtitle">Aggiungi un addon per iniziare a creare la tua libreria.</string>
<string name="addons_appstore_empty_title">Fai tuo Nuvio.</string>
<string name="addons_appstore_input_placeholder">URL addon</string>
<string name="addons_appstore_install_button">Aggiungi addon</string>
<string name="store_empty_unavailable_title">Non cè ancora niente qui</string>
<string name="store_empty_unavailable_message">Gli addon che usi non hanno ancora nulla da mostrare qui.</string>
<string name="addons_empty_subtitle">Aggiungi un manifest URL per iniziare a caricare cataloghi , metadata, flussi o sottotitoli dentro Nuvio.</string>
<string name="addons_empty_title">Nessun addon installato ancora.</string>
<string name="addons_error_enter_url">Inserisci l'URL dell'addon.</string>
@ -245,7 +252,9 @@
<string name="compose_settings_root_check_updates_description">Controlla se ci sono nuove versioni dell'app.</string>
<string name="compose_settings_root_check_updates_title">Verifica aggiornamenti</string>
<string name="compose_settings_root_content_discovery_description">Gestisci gli addon e le sorgenti di scoperta.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Gestisci gli addon e scegli cosa mostrare in Nuvio.</string>
<string name="compose_settings_root_downloads_description">Gestisci i film e gli episodi scaricati.</string>
<string name="compose_settings_root_downloads_description_appstore">Gestisci ciò che hai salvato su questo dispositivo.</string>
<string name="compose_settings_root_downloads_title">Download</string>
<string name="compose_settings_root_general_section">GENERALI</string>
<string name="compose_settings_root_integrations_description">Collega i servizi TMDB e MDBList.</string>
@ -382,6 +391,7 @@
<string name="settings_content_discovery_section_home">HOME</string>
<string name="settings_content_discovery_section_sources">SORGENTI</string>
<string name="settings_content_discovery_addons_description">Installa, rimuovi, aggiorna e ordina le tue sorgenti di contenuto.</string>
<string name="settings_content_discovery_addons_description_appstore">Aggiungi e gestisci gli addon che usi con Nuvio.</string>
<string name="settings_content_discovery_plugins_description">Installa repository di scraper JavaScript e testa i provider internamente.</string>
<string name="settings_content_discovery_homescreen_description">Controlla quali cataloghi appaiono in Home e in quale ordine.</string>
<string name="settings_content_discovery_meta_screen_description">Disabilita le sezioni dei dettagli e riordina tutto ciò che sta sotto l'elemento Hero.</string>

View file

@ -32,11 +32,13 @@
<string name="addons_badge_unavailable">利用不可</string>
<string name="addons_configure">アドオンを設定</string>
<string name="addons_delete">アドオンを削除</string>
<string name="addons_appstore_add_description">独自のメディアサーバーを接続して、個人ライブラリを参照・再生します。</string>
<string name="addons_appstore_empty_subtitle">Nuvioに個人ライブラリを表示させたい場合は、上にサーバーURLを追加してください。</string>
<string name="addons_appstore_empty_title">接続された個人ライブラリはありません。</string>
<string name="addons_appstore_input_placeholder">サーバーURL</string>
<string name="addons_appstore_install_button">追加</string>
<string name="addons_appstore_add_description">いつものアドオンで、ライブラリをNuvioにまとめましょう。</string>
<string name="addons_appstore_empty_subtitle">アドオンを追加して、ライブラリを作り始めましょう。</string>
<string name="addons_appstore_empty_title">Nuvioを自分好みに。</string>
<string name="addons_appstore_input_placeholder">アドオンURL</string>
<string name="addons_appstore_install_button">アドオンを追加</string>
<string name="store_empty_unavailable_title">まだ何もありません</string>
<string name="store_empty_unavailable_message">現在のアドオンには、ここに表示できるものがまだありません。</string>
<string name="addons_empty_subtitle">マニフェストURLを追加して、カタログ・メタデータ・ストリーム・字幕をNuvioに読み込みましょう。</string>
<string name="addons_empty_title">アドオンがありません</string>
<string name="addons_error_enter_url">アドオンのURLを入力してください。</string>
@ -442,7 +444,9 @@
<string name="compose_settings_root_check_updates_description">最新リリースをダウンロード</string>
<string name="compose_settings_root_check_updates_title">アップデートを確認</string>
<string name="compose_settings_root_content_discovery_description">アドオンと探索ソースを管理</string>
<string name="compose_settings_root_content_discovery_description_appstore">アドオンを管理して、Nuvioに表示する内容を選べます。</string>
<string name="compose_settings_root_downloads_description">ダウンロードした映画とエピソードを管理</string>
<string name="compose_settings_root_downloads_description_appstore">このデバイスに保存した項目を管理します。</string>
<string name="compose_settings_root_downloads_title">ダウンロード</string>
<string name="compose_settings_root_general_section">一般</string>
<string name="compose_settings_root_integrations_description">利用可能な連携を管理</string>
@ -638,7 +642,7 @@
<string name="settings_content_discovery_section_home">ホーム</string>
<string name="settings_content_discovery_section_sources">ソース</string>
<string name="settings_content_discovery_addons_description">コンテンツソースのインストール・削除・更新・並べ替え</string>
<string name="settings_content_discovery_addons_description_appstore">個人メディアソースを接続し、自分専用ライブラリへのアクセスを管理します。</string>
<string name="settings_content_discovery_addons_description_appstore">Nuvioで使うアドオンを追加・管理します。</string>
<string name="settings_content_discovery_plugins_description">JavaScriptスクレイパーリポジトリのインストールと内部テスト</string>
<string name="settings_content_discovery_homescreen_description">ホームレイアウト・コンテンツ表示・ポスターの動作を調整</string>
<string name="settings_content_discovery_meta_screen_description">詳細画面とエピソード画面の設定</string>

View file

@ -29,6 +29,13 @@
<string name="addons_badge_unavailable">Utilgjengelig</string>
<string name="addons_configure">Konfigurer tillegg</string>
<string name="addons_delete">Slett tillegg</string>
<string name="addons_appstore_add_description">Ta med biblioteket ditt til Nuvio med tilleggene du bruker.</string>
<string name="addons_appstore_empty_subtitle">Legg til et tillegg for å begynne å bygge biblioteket ditt.</string>
<string name="addons_appstore_empty_title">Gjør Nuvio til ditt.</string>
<string name="addons_appstore_input_placeholder">URL til tillegg</string>
<string name="addons_appstore_install_button">Legg til tillegg</string>
<string name="store_empty_unavailable_title">Det er ingenting her ennå</string>
<string name="store_empty_unavailable_message">Tilleggene du bruker, har ingenting å vise her ennå.</string>
<string name="addons_empty_subtitle">Legg til en manifest-URL for å starte lasting av kataloger, metadata, strømmer eller undertekster i Nuvio.</string>
<string name="addons_empty_title">Ingen tillegg installert ennå.</string>
<string name="addons_error_enter_url">Skriv inn en tilleggs-URL.</string>
@ -400,7 +407,9 @@
<string name="compose_settings_root_check_updates_description">Last ned nyeste versjon</string>
<string name="compose_settings_root_check_updates_title">Se etter oppdateringer</string>
<string name="compose_settings_root_content_discovery_description">Administrer tillegg og oppdagelseskilder.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Administrer tillegg og velg hva som vises i Nuvio.</string>
<string name="compose_settings_root_downloads_description">Administrer nedlastede filmer og episoder.</string>
<string name="compose_settings_root_downloads_description_appstore">Administrer det du har lagret på denne enheten.</string>
<string name="compose_settings_root_downloads_title">Nedlastinger</string>
<string name="compose_settings_root_general_section">GENERELT</string>
<string name="compose_settings_root_integrations_description">Administrer tilgjengelige integrasjoner</string>
@ -581,6 +590,7 @@
<string name="settings_content_discovery_section_home">HJEM</string>
<string name="settings_content_discovery_section_sources">KILDER</string>
<string name="settings_content_discovery_addons_description">Installer, fjern, oppdater og sorter innholdskilder.</string>
<string name="settings_content_discovery_addons_description_appstore">Legg til og administrer tilleggene du bruker med Nuvio.</string>
<string name="settings_content_discovery_plugins_description">Installer JavaScript-scraper-repositories og test providere internt.</string>
<string name="settings_content_discovery_homescreen_description">Juster hjemmeoppsett, synlighet og plakat-oppførsel.</string>
<string name="settings_content_discovery_meta_screen_description">Innstillinger for detalj- og episodeskjermer.</string>

View file

@ -32,11 +32,13 @@
<string name="addons_badge_unavailable">Niet beschikbaar</string>
<string name="addons_configure">Addon configureren</string>
<string name="addons_delete">Addon verwijderen</string>
<string name="addons_appstore_add_description">Verbind je eigen mediaserver om je persoonlijke bibliotheek te bekijken en af te spelen.</string>
<string name="addons_appstore_empty_subtitle">Voeg hierboven een server-URL toe als je wilt dat Nuvio je privébibliotheek toont.</string>
<string name="addons_appstore_empty_title">Geen persoonlijke bibliotheken verbonden.</string>
<string name="addons_appstore_input_placeholder">Server-URL</string>
<string name="addons_appstore_install_button">Toevoegen</string>
<string name="addons_appstore_add_description">Breng je bibliotheek naar Nuvio met de add-ons die je gebruikt.</string>
<string name="addons_appstore_empty_subtitle">Voeg een add-on toe om je bibliotheek op te bouwen.</string>
<string name="addons_appstore_empty_title">Maak Nuvio helemaal van jou.</string>
<string name="addons_appstore_input_placeholder">Add-on-URL</string>
<string name="addons_appstore_install_button">Add-on toevoegen</string>
<string name="store_empty_unavailable_title">Hier is nog niets</string>
<string name="store_empty_unavailable_message">Je huidige add-ons hebben hier nog niets om te tonen.</string>
<string name="addons_empty_subtitle">Voeg een manifest-URL toe om catalogi, metadata, streams of ondertitels in Nuvio te laden.</string>
<string name="addons_empty_title">Nog geen addons geïnstalleerd.</string>
<string name="addons_error_enter_url">Voer een addon-URL in.</string>
@ -442,7 +444,9 @@
<string name="compose_settings_root_check_updates_description">Download de laatste versie</string>
<string name="compose_settings_root_check_updates_title">Controleren op updates</string>
<string name="compose_settings_root_content_discovery_description">Beheer addons en ontdekkingsbronnen.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Beheer add-ons en kies wat er in Nuvio verschijnt.</string>
<string name="compose_settings_root_downloads_description">Beheer je gedownloade films en afleveringen.</string>
<string name="compose_settings_root_downloads_description_appstore">Beheer wat je op dit apparaat hebt opgeslagen.</string>
<string name="compose_settings_root_downloads_title">Downloads</string>
<string name="compose_settings_root_general_section">ALGEMEEN</string>
<string name="compose_settings_root_integrations_description">Beheer beschikbare integraties</string>
@ -656,7 +660,7 @@
<string name="settings_content_discovery_section_home">HOME</string>
<string name="settings_content_discovery_section_sources">BRONNEN</string>
<string name="settings_content_discovery_addons_description">Installeer, verwijder, vernieuw en sorteer je contentbronnen.</string>
<string name="settings_content_discovery_addons_description_appstore">Verbind persoonlijke mediabronnen en beheer toegang tot je eigen bibliotheek.</string>
<string name="settings_content_discovery_addons_description_appstore">Voeg de add-ons toe die je met Nuvio gebruikt en beheer ze.</string>
<string name="settings_content_discovery_plugins_description">Installeer JavaScript-scraperrepository's en test providers intern.</string>
<string name="settings_content_discovery_homescreen_description">Pas startschermindeling, zichtbaarheid van inhoud en postergedrag aan</string>
<string name="settings_content_discovery_meta_screen_description">Instellingen voor de detail- en afleveringsschermen.</string>

View file

@ -31,6 +31,13 @@
<string name="addons_badge_unavailable">Niedostępny</string>
<string name="addons_configure">Konfiguruj dodatek</string>
<string name="addons_delete">Usuń dodatek</string>
<string name="addons_appstore_add_description">Przenieś swoją bibliotekę do Nuvio za pomocą używanych dodatków.</string>
<string name="addons_appstore_empty_subtitle">Dodaj dodatek, aby zacząć tworzyć swoją bibliotekę.</string>
<string name="addons_appstore_empty_title">Dostosuj Nuvio do siebie.</string>
<string name="addons_appstore_input_placeholder">URL dodatku</string>
<string name="addons_appstore_install_button">Dodaj dodatek</string>
<string name="store_empty_unavailable_title">Na razie nic tu nie ma</string>
<string name="store_empty_unavailable_message">Używane dodatki nie mają jeszcze nic do wyświetlenia.</string>
<string name="addons_empty_subtitle">Dodaj URL manifestu, aby zacząć ładować katalogi, metadane, strumienie lub napisy do Nuvio.</string>
<string name="addons_empty_title">Brak zainstalowanych dodatków.</string>
<string name="addons_error_enter_url">Wprowadź URL dodatku.</string>
@ -412,7 +419,9 @@
<string name="compose_settings_root_check_updates_description">Sprawdź dostępność nowych wersji aplikacji.</string>
<string name="compose_settings_root_check_updates_title">Sprawdź aktualizacje</string>
<string name="compose_settings_root_content_discovery_description">Zarządzaj dodatkami i źródłami odkrywania.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Zarządzaj dodatkami i wybierz, co ma być widoczne w Nuvio.</string>
<string name="compose_settings_root_downloads_description">Zarządzaj pobranymi filmami i odcinkami.</string>
<string name="compose_settings_root_downloads_description_appstore">Zarządzaj zawartością zapisaną na tym urządzeniu.</string>
<string name="compose_settings_root_downloads_title">Pobrane</string>
<string name="compose_settings_root_general_section">OGÓLNE</string>
<string name="compose_settings_root_integrations_description">Połącz usługi TMDB i MDBList.</string>
@ -597,6 +606,7 @@
<string name="settings_content_discovery_section_home">EKRAN GŁÓWNY</string>
<string name="settings_content_discovery_section_sources">ŹRÓDŁA</string>
<string name="settings_content_discovery_addons_description">Instaluj, usuwaj, odświeżaj i sortuj źródła treści.</string>
<string name="settings_content_discovery_addons_description_appstore">Dodawaj używane w Nuvio dodatki i zarządzaj nimi.</string>
<string name="settings_content_discovery_plugins_description">Instaluj repozytoria scraperów JavaScript i testuj dostawców wewnętrznie.</string>
<string name="settings_content_discovery_homescreen_description">Kontroluj, które katalogi pojawiają się na ekranie głównym i w jakiej kolejności.</string>
<string name="settings_content_discovery_meta_screen_description">Wyłącz sekcje szczegółów i zmień kolejność wszystkiego poniżej Hero.</string>

View file

@ -30,6 +30,13 @@
<string name="addons_badge_unavailable">Indisponível</string>
<string name="addons_configure">Configurar addon</string>
<string name="addons_delete">Excluir addon</string>
<string name="addons_appstore_add_description">Leve sua biblioteca para o Nuvio com os addons que você usa.</string>
<string name="addons_appstore_empty_subtitle">Adicione um addon para começar a montar sua biblioteca.</string>
<string name="addons_appstore_empty_title">Deixe o Nuvio do seu jeito.</string>
<string name="addons_appstore_input_placeholder">URL do addon</string>
<string name="addons_appstore_install_button">Adicionar addon</string>
<string name="store_empty_unavailable_title">Ainda não há nada aqui</string>
<string name="store_empty_unavailable_message">Os addons que você usa ainda não têm nada para mostrar aqui.</string>
<string name="addons_empty_subtitle">Adicione uma URL de manifesto para começar a carregar catálogos, metadados, streams ou legendas no Nuvio.</string>
<string name="addons_empty_title">Nenhum addon instalado ainda.</string>
<string name="addons_error_enter_url">Insira uma URL de addon.</string>
@ -401,7 +408,9 @@
<string name="compose_settings_root_check_updates_description">Baixar a versão mais recente</string>
<string name="compose_settings_root_check_updates_title">Verificar atualizações</string>
<string name="compose_settings_root_content_discovery_description">Gerenciar addons e fontes de descoberta.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Gerencie addons e escolha o que aparece no Nuvio.</string>
<string name="compose_settings_root_downloads_description">Gerencie seus filmes e episódios baixados.</string>
<string name="compose_settings_root_downloads_description_appstore">Gerencie o que você salvou neste dispositivo.</string>
<string name="compose_settings_root_downloads_title">Downloads</string>
<string name="compose_settings_root_general_section">GERAL</string>
<string name="compose_settings_root_integrations_description">Gerenciar integrações disponíveis</string>
@ -582,6 +591,7 @@
<string name="settings_content_discovery_section_home">PÁGINA INICIAL</string>
<string name="settings_content_discovery_section_sources">FONTES</string>
<string name="settings_content_discovery_addons_description">Instale, remova, atualize e ordene suas fontes de conteúdo.</string>
<string name="settings_content_discovery_addons_description_appstore">Adicione e gerencie os addons que você usa com o Nuvio.</string>
<string name="settings_content_discovery_plugins_description">Instale repositórios de scraper JavaScript e teste provedores internamente.</string>
<string name="settings_content_discovery_homescreen_description">Ajuste layout da página inicial, visibilidade de conteúdo e comportamento de pôsteres</string>
<string name="settings_content_discovery_meta_screen_description">Configurações para as telas de detalhes e episódios.</string>

View file

@ -31,6 +31,13 @@
<string name="addons_badge_unavailable">Indisponível</string>
<string name="addons_configure">Configurar addon</string>
<string name="addons_delete">Eliminar addon</string>
<string name="addons_appstore_add_description">Leva a tua biblioteca para o Nuvio com os addons que usas.</string>
<string name="addons_appstore_empty_subtitle">Adiciona um addon para começares a criar a tua biblioteca.</string>
<string name="addons_appstore_empty_title">Deixa o Nuvio à tua maneira.</string>
<string name="addons_appstore_input_placeholder">URL do addon</string>
<string name="addons_appstore_install_button">Adicionar addon</string>
<string name="store_empty_unavailable_title">Ainda não há nada aqui</string>
<string name="store_empty_unavailable_message">Os addons que usas ainda não têm nada para mostrar aqui.</string>
<string name="addons_empty_subtitle">Adiciona um URL de manifesto para começares a carregar catálogos, metadados, streams ou legendas no Nuvio.</string>
<string name="addons_empty_title">Ainda não tens addons instalados.</string>
<string name="addons_error_enter_url">Introduz o URL de um addon.</string>
@ -432,7 +439,9 @@
<string name="compose_settings_root_check_updates_description">Transferir a versão mais recente</string>
<string name="compose_settings_root_check_updates_title">Procurar atualizações</string>
<string name="compose_settings_root_content_discovery_description">Gere addons e fontes de descoberta.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Gere os addons e escolhe o que aparece no Nuvio.</string>
<string name="compose_settings_root_downloads_description">Gere os teus filmes e episódios transferidos.</string>
<string name="compose_settings_root_downloads_description_appstore">Gere o que guardaste neste dispositivo.</string>
<string name="compose_settings_root_downloads_title">Transferências</string>
<string name="compose_settings_root_general_section">GERAL</string>
<string name="compose_settings_root_integrations_description">Gere as integrações disponíveis</string>
@ -627,6 +636,7 @@
<string name="settings_content_discovery_section_home">PÁGINA INICIAL</string>
<string name="settings_content_discovery_section_sources">FONTES</string>
<string name="settings_content_discovery_addons_description">Instala, remove, atualiza e ordena as tuas fontes de conteúdo.</string>
<string name="settings_content_discovery_addons_description_appstore">Adiciona e gere os addons que usas com o Nuvio.</string>
<string name="settings_content_discovery_plugins_description">Instala repositórios de scrapers JavaScript e testa fornecedores internamente.</string>
<string name="settings_content_discovery_homescreen_description">Ajusta o esquema da página inicial, a visibilidade do conteúdo e o comportamento dos cartazes.</string>
<string name="settings_content_discovery_meta_screen_description">Definições para as páginas de detalhes e de episódios.</string>

View file

@ -32,11 +32,13 @@
<string name="addons_badge_unavailable">Indisponibil</string>
<string name="addons_configure">Configurează extensia</string>
<string name="addons_delete">Șterge extensia</string>
<string name="addons_appstore_add_description">Conectează-ți propriul server media pentru a naviga și a reda din biblioteca personală.</string>
<string name="addons_appstore_empty_subtitle">Adaugă un URL de server mai sus când vrei ca Nuvio să îți afișeze biblioteca privată.</string>
<string name="addons_appstore_empty_title">Nicio bibliotecă personală conectată.</string>
<string name="addons_appstore_input_placeholder">URL Server</string>
<string name="addons_appstore_install_button">Adaugă</string>
<string name="addons_appstore_add_description">Folosește extensiile preferate pentru a-ți aduce biblioteca în Nuvio.</string>
<string name="addons_appstore_empty_subtitle">Adaugă o extensie pentru a începe să-ți construiești biblioteca.</string>
<string name="addons_appstore_empty_title">Personalizează Nuvio.</string>
<string name="addons_appstore_input_placeholder">URL extensie</string>
<string name="addons_appstore_install_button">Adaugă extensie</string>
<string name="store_empty_unavailable_title">Nu este nimic aici încă</string>
<string name="store_empty_unavailable_message">Extensiile pe care le folosești nu au încă nimic de afișat aici.</string>
<string name="addons_empty_subtitle">Adaugă un URL de manifest pentru a începe încărcarea de cataloage, metadate, fluxuri sau subtitrări în Nuvio.</string>
<string name="addons_empty_title">Nu există extensii instalate încă.</string>
<string name="addons_error_enter_url">Introdu un URL pentru extensie.</string>
@ -442,7 +444,9 @@
<string name="compose_settings_root_check_updates_description">Descarcă cea mai recentă versiune</string>
<string name="compose_settings_root_check_updates_title">Caută actualizări</string>
<string name="compose_settings_root_content_discovery_description">Gestionează extensiile și sursele de descoperire.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Gestionează extensiile și alege ce apare în Nuvio.</string>
<string name="compose_settings_root_downloads_description">Gestionează filmele și episoadele descărcate.</string>
<string name="compose_settings_root_downloads_description_appstore">Gestionează ce ai salvat pe acest dispozitiv.</string>
<string name="compose_settings_root_downloads_title">Descărcări</string>
<string name="compose_settings_root_general_section">GENERAL</string>
<string name="compose_settings_root_integrations_description">Gestionează integrările disponibile</string>
@ -656,7 +660,7 @@
<string name="settings_content_discovery_section_home">ECRAN PRINCIPAL</string>
<string name="settings_content_discovery_section_sources">SURSE</string>
<string name="settings_content_discovery_addons_description">Instalează, elimină, reîmprospătează și sortează sursele de conținut.</string>
<string name="settings_content_discovery_addons_description_appstore">Conectează surse media personale și gestionează accesul la propria bibliotecă.</string>
<string name="settings_content_discovery_addons_description_appstore">Adaugă și gestionează extensiile pe care le folosești cu Nuvio.</string>
<string name="settings_content_discovery_plugins_description">Instalează depozite de scraper JavaScript și testează furnizorii intern.</string>
<string name="settings_content_discovery_homescreen_description">Ajustează aspectul ecranului principal, vizibilitatea conținutului și comportamentul posterului</string>
<string name="settings_content_discovery_meta_screen_description">Setări pentru ecranele de detalii și de episoade.</string>
@ -1932,4 +1936,4 @@
<item quantity="one">%d titlu</item>
<item quantity="other">%d titluri</item>
</plurals>
</resources>
</resources>

View file

@ -434,7 +434,9 @@
<string name="compose_settings_root_check_updates_description">Stiahnuť najnovšie vydanie</string>
<string name="compose_settings_root_check_updates_title">Skontrolovať aktualizácie</string>
<string name="compose_settings_root_content_discovery_description">Spravovať doplnky a zdroje objavovania.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Spravujte doplnky a vyberte, čo sa má zobrazovať v Nuviu.</string>
<string name="compose_settings_root_downloads_description">Spravovať stiahnuté filmy a epizódy.</string>
<string name="compose_settings_root_downloads_description_appstore">Spravujte obsah uložený v tomto zariadení.</string>
<string name="compose_settings_root_downloads_title">Stiahnuté</string>
<string name="compose_settings_root_general_section">VŠEOBECNÉ</string>
<string name="compose_settings_root_integrations_description">Spravovať dostupné integrácie</string>
@ -1898,15 +1900,17 @@
<string name="player_error_torrent">Chyba torrentu: %1$s</string>
<!-- Current cmp-rewrite string additions -->
<string name="action_switch">Prepnúť</string>
<string name="addons_appstore_add_description">Pripojte vlastný mediálny server na prehliadanie a prehrávanie z vašej osobnej knižnice.</string>
<string name="addons_appstore_empty_subtitle">Pridajte URL servera vyššie, ak chcete, aby Nuvio zobrazovalo vašu súkromnú knižnicu.</string>
<string name="addons_appstore_empty_title">Nie sú pripojené žiadne osobné knižnice.</string>
<string name="addons_appstore_input_placeholder">URL servera</string>
<string name="addons_appstore_install_button">Pridať</string>
<string name="addons_appstore_add_description">Preneste svoju knižnicu do Nuvia pomocou doplnkov, ktoré používate.</string>
<string name="addons_appstore_empty_subtitle">Pridajte doplnok a začnite si vytvárať knižnicu.</string>
<string name="addons_appstore_empty_title">Prispôsobte si Nuvio.</string>
<string name="addons_appstore_input_placeholder">URL doplnku</string>
<string name="addons_appstore_install_button">Pridať doplnok</string>
<string name="store_empty_unavailable_title">Zatiaľ tu nič nie je</string>
<string name="store_empty_unavailable_message">Doplnky, ktoré používate, tu zatiaľ nemajú čo zobraziť.</string>
<string name="compose_auth_terms_prefix">Registráciou súhlasím s</string>
<string name="compose_auth_terms_link">Podmienkami</string>
<string name="settings_appearance_app_language_device">Jazyk zariadenia</string>
<string name="settings_content_discovery_addons_description_appstore">Pripojte osobné mediálne zdroje a spravujte prístup k vlastnej knižnici.</string>
<string name="settings_content_discovery_addons_description_appstore">Pridávajte a spravujte doplnky, ktoré používate s Nuviom.</string>
<string name="settings_playback_external_player_send_skip_segments">Odosielať časové značky intra a outra</string>
<string name="settings_playback_external_player_send_skip_segments_description">Odovzdať vyriešené časové značky intra a outra externému prehrávaču na automatické preskakovanie. Funguje iba v prehrávačoch, ktoré to podporujú; ostatné prehrávače to ignorujú.</string>
<string name="settings_playback_option_original">Pôvodný jazyk</string>

View file

@ -31,6 +31,13 @@
<string name="addons_badge_unavailable">Kullanılamıyor</string>
<string name="addons_configure">Eklentiyi ayarla</string>
<string name="addons_delete">Eklentiyi sil</string>
<string name="addons_appstore_add_description">Kullandığın eklentilerle kitaplığını Nuvio'ya taşı.</string>
<string name="addons_appstore_empty_subtitle">Kitaplığını oluşturmaya başlamak için bir eklenti ekle.</string>
<string name="addons_appstore_empty_title">Nuvio'yu kendine göre ayarla.</string>
<string name="addons_appstore_input_placeholder">Eklenti URL'si</string>
<string name="addons_appstore_install_button">Eklenti ekle</string>
<string name="store_empty_unavailable_title">Burada henüz bir şey yok</string>
<string name="store_empty_unavailable_message">Kullandığın eklentilerin burada gösterecek bir şeyi yok.</string>
<string name="addons_empty_subtitle">Nuvio'ya katalog, meta veri, yayın veya altyazı yüklemeye başlamak için bir manifest URL'si ekle.</string>
<string name="addons_empty_title">Henüz eklenti kurulmamış.</string>
<string name="addons_error_enter_url">Bir eklenti URL'si gir.</string>
@ -402,7 +409,9 @@
<string name="compose_settings_root_check_updates_description">Uygulamanın yeni sürümü var mı kontrol et.</string>
<string name="compose_settings_root_check_updates_title">Güncellemeleri kontrol et</string>
<string name="compose_settings_root_content_discovery_description">Eklentileri ve keşif kaynaklarını yönet.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Eklentileri yönet ve Nuvioda nelerin görüneceğini seç.</string>
<string name="compose_settings_root_downloads_description">İndirdiğin film ve bölümleri yönet.</string>
<string name="compose_settings_root_downloads_description_appstore">Bu cihaza kaydettiklerini yönet.</string>
<string name="compose_settings_root_downloads_title">İndirilenler</string>
<string name="compose_settings_root_general_section">GENEL</string>
<string name="compose_settings_root_integrations_description">TMDB ve MDBList servislerini bağla.</string>
@ -587,6 +596,7 @@
<string name="settings_content_discovery_section_home">ANA SAYFA</string>
<string name="settings_content_discovery_section_sources">KAYNAKLAR</string>
<string name="settings_content_discovery_addons_description">İçerik kaynaklarını kur, kaldır, yenile ve sırala.</string>
<string name="settings_content_discovery_addons_description_appstore">Nuvio ile kullandığın eklentileri ekle ve yönet.</string>
<string name="settings_content_discovery_plugins_description">JavaScript scraper depoları kur ve sağlayıcıları içeriden test et.</string>
<string name="settings_content_discovery_homescreen_description">Ana sayfada hangi katalogların hangi sırayla görüneceğini kontrol et.</string>
<string name="settings_content_discovery_meta_screen_description">Detay bölümlerini kapat ve öne çıkanların altındaki her şeyi yeniden sırala.</string>

View file

@ -32,11 +32,13 @@
<string name="addons_badge_unavailable">Unavailable</string>
<string name="addons_configure">Configure addon</string>
<string name="addons_delete">Delete addon</string>
<string name="addons_appstore_add_description">Connect your own media server to browse and play from your personal library.</string>
<string name="addons_appstore_empty_subtitle">Add a server URL above when you want Nuvio to show your private library.</string>
<string name="addons_appstore_empty_title">No personal libraries connected.</string>
<string name="addons_appstore_input_placeholder">Server URL</string>
<string name="addons_appstore_install_button">Add</string>
<string name="addons_appstore_add_description">Bring your library into Nuvio with the addons you use.</string>
<string name="addons_appstore_empty_subtitle">Add an addon to start building your library.</string>
<string name="addons_appstore_empty_title">Make Nuvio yours.</string>
<string name="addons_appstore_input_placeholder">Addon URL</string>
<string name="addons_appstore_install_button">Add Addon</string>
<string name="store_empty_unavailable_title">Nothing here yet</string>
<string name="store_empty_unavailable_message">Your current addons dont have anything to show here.</string>
<string name="addons_empty_subtitle">Add a manifest URL to start loading catalogs, metadata, streams or subtitles into Nuvio.</string>
<string name="addons_empty_title">No addons installed yet.</string>
<string name="addons_error_enter_url">Enter an addon URL.</string>
@ -442,7 +444,9 @@
<string name="compose_settings_root_check_updates_description">Download latest release</string>
<string name="compose_settings_root_check_updates_title">Check for updates</string>
<string name="compose_settings_root_content_discovery_description">Manage addons and discovery sources.</string>
<string name="compose_settings_root_content_discovery_description_appstore">Manage addons and choose what appears in Nuvio.</string>
<string name="compose_settings_root_downloads_description">Manage your downloaded movies and episodes.</string>
<string name="compose_settings_root_downloads_description_appstore">Manage what youve saved on this device.</string>
<string name="compose_settings_root_downloads_title">Downloads</string>
<string name="compose_settings_root_general_section">GENERAL</string>
<string name="compose_settings_root_integrations_description">Manage available integrations</string>
@ -601,6 +605,37 @@
<string name="settings_homescreen_visible">Visible</string>
<string name="settings_hide_secret">Hide value</string>
<string name="settings_playback_subtitle">Player, subtitles, and auto-play</string>
<string name="settings_card_depth_title">Card Depth Effect</string>
<string name="settings_card_depth_description">Adds a lit top edge and a soft sheen to image cards for a subtle sense of depth.</string>
<string name="settings_card_depth_enabled">Enable depth effect</string>
<string name="settings_card_depth_edge">Edge glow</string>
<string name="settings_card_depth_edge_subtle">Subtle</string>
<string name="settings_card_depth_edge_balanced">Balanced</string>
<string name="settings_card_depth_edge_bold">Bold</string>
<string name="settings_card_depth_sheen">Top sheen</string>
<string name="settings_card_depth_sheen_off">Off</string>
<string name="settings_card_depth_sheen_soft">Soft</string>
<string name="settings_card_depth_sheen_bright">Bright</string>
<string name="settings_card_depth_apply_to">Apply to</string>
<string name="settings_card_depth_fine_tune">Fine-tune</string>
<string name="settings_card_depth_fine_tune_title">Fine-tune Depth</string>
<string name="settings_card_depth_fine_tune_hint">Drag the dot right for a brighter edge, up for more sheen. Use the slider to extend the glow around the full outline.</string>
<string name="settings_card_depth_pad_edge_axis">Edge glow →</string>
<string name="settings_card_depth_pad_sheen_axis">↑ Sheen</string>
<string name="settings_card_depth_edge_value">Edge glow</string>
<string name="settings_card_depth_sheen_value">Sheen</string>
<string name="settings_card_depth_edge_coverage">Edge coverage</string>
<string name="settings_card_depth_coverage_top">Top only</string>
<string name="settings_card_depth_coverage_half">Half</string>
<string name="settings_card_depth_coverage_full">Full outline</string>
<string name="settings_card_depth_coverage_value">Edge coverage</string>
<string name="settings_card_depth_preview_title">Episode Title</string>
<string name="settings_card_depth_preview_meta">S1 · E1 · 45 min</string>
<string name="settings_card_depth_surface_posters">Posters</string>
<string name="settings_card_depth_surface_continue_watching">Continue Watching</string>
<string name="settings_card_depth_surface_episodes">Episode cards</string>
<string name="settings_card_depth_surface_cast">Cast</string>
<string name="settings_card_depth_surface_trailers">Trailers</string>
<string name="settings_poster_card_radius">Corner Radius</string>
<string name="settings_poster_card_style">Poster Card Style</string>
<string name="settings_poster_card_width">Width</string>
@ -656,7 +691,7 @@
<string name="settings_content_discovery_section_home">HOME</string>
<string name="settings_content_discovery_section_sources">SOURCES</string>
<string name="settings_content_discovery_addons_description">Install, remove, refresh, and sort your content sources.</string>
<string name="settings_content_discovery_addons_description_appstore">Connect personal media sources and manage access to your own library.</string>
<string name="settings_content_discovery_addons_description_appstore">Add and manage the addons you use with Nuvio.</string>
<string name="settings_content_discovery_plugins_description">Install JavaScript scraper repositories and test providers internally.</string>
<string name="settings_content_discovery_homescreen_description">Adjust home layout, content visibility, and poster behavior</string>
<string name="settings_content_discovery_meta_screen_description">Settings for the detail and episode screens.</string>

View file

@ -27,6 +27,7 @@ import com.nuvio.app.features.streams.StreamLaunchStore
import com.nuvio.app.features.streams.StreamsRepository
import com.nuvio.app.features.trakt.TraktAuthRepository
import com.nuvio.app.features.trakt.TraktSettingsRepository
import com.nuvio.app.core.ui.CardDepthStyleRepository
import com.nuvio.app.core.ui.PosterCardStyleRepository
import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepository
import com.nuvio.app.features.watchprogress.ContinueWatchingEnrichmentCache
@ -61,6 +62,7 @@ internal object LocalAccountDataCleaner {
CollectionRepository.clearLocalState()
ThemeSettingsRepository.clearLocalState()
PosterCardStyleRepository.clearLocalState()
CardDepthStyleRepository.clearLocalState()
TraktAuthRepository.clearLocalState()
TraktSettingsRepository.clearLocalState()
PlayerSettingsRepository.clearLocalState()

View file

@ -17,6 +17,8 @@ import com.nuvio.app.features.notifications.EpisodeReleaseNotificationsRepositor
import com.nuvio.app.features.player.PlayerSettingsStorage
import com.nuvio.app.features.player.PlayerSettingsRepository
import com.nuvio.app.features.profiles.ProfileRepository
import com.nuvio.app.core.ui.CardDepthStyleRepository
import com.nuvio.app.core.ui.CardDepthStyleStorage
import com.nuvio.app.core.ui.PosterCardStyleRepository
import com.nuvio.app.core.ui.PosterCardStyleStorage
import com.nuvio.app.features.settings.ThemeSettingsStorage
@ -27,7 +29,6 @@ import com.nuvio.app.features.tmdb.TmdbSettingsStorage
import com.nuvio.app.features.tmdb.TmdbSettingsRepository
import com.nuvio.app.features.trakt.TraktCommentsStorage
import com.nuvio.app.features.trakt.TraktCommentsSettings
import com.nuvio.app.features.trakt.ProfileSettingsWatchSourceOutbox
import com.nuvio.app.features.trakt.TraktSettingsStorage
import com.nuvio.app.features.trakt.TraktSettingsRepository
import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesStorage
@ -35,21 +36,16 @@ import com.nuvio.app.features.watchprogress.ContinueWatchingPreferencesRepositor
import io.github.jan.supabase.postgrest.postgrest
import io.github.jan.supabase.postgrest.rpc
import kotlin.concurrent.Volatile
import kotlinx.atomicfu.locks.SynchronizedObject
import kotlinx.atomicfu.locks.synchronized
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.FlowPreview
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.launch
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
@ -65,22 +61,10 @@ import kotlinx.serialization.json.put
private const val PUSH_DEBOUNCE_MS = 1500L
private data class ObservedProfileSettingsChange(
val signature: String,
val accountId: String?,
)
private data class SkippedProfileSettingsPush(
val signature: String,
val accountId: String?,
val profileId: Int,
)
object ProfileSettingsSync {
private val scope = CoroutineScope(SupervisorJob() + Dispatchers.Default)
private val log = Logger.withTag("ProfileSettingsSync")
private val syncMutex = Mutex()
private val observeLock = SynchronizedObject()
private val json = Json {
ignoreUnknownKeys = true
encodeDefaults = true
@ -93,62 +77,33 @@ object ProfileSettingsSync {
private var isServerSyncInFlight: Boolean = false
@Volatile
private var skipNextPush: SkippedProfileSettingsPush? = null
@Volatile
private var pushEnabledAccountId: String? = null
@Volatile
private var pendingLocalPush: SkippedProfileSettingsPush? = null
private var skipNextPushSignature: String? = null
private var observeJob: Job? = null
private var pendingPushRetryJob: Job? = null
fun startObserving() = synchronized(observeLock) {
if (observeJob?.isActive == true) return@synchronized
fun startObserving() {
if (observeJob?.isActive == true) return
ensureRepositoriesLoaded()
observeLocalChangesAndPush()
}
fun clearAccountState() {
synchronized(observeLock) {
observeJob?.cancel()
observeJob = null
}
skipNextPush = null
pushEnabledAccountId = null
pendingLocalPush = null
pendingPushRetryJob?.cancel()
pendingPushRetryJob = null
observeJob?.cancel()
observeJob = null
skipNextPushSignature = null
}
suspend fun pull(profileId: Int): Boolean {
startObserving()
val accountId = currentCloudAccountId() ?: return false
ensureRepositoriesLoaded()
return syncMutex.withLock {
if (!isCurrentSyncTarget(profileId = profileId, accountId = accountId)) {
if (ProfileRepository.activeProfileId != profileId) {
log.d { "pull(profileId=$profileId) — skipped because profile is no longer active" }
return@withLock false
}
isServerSyncInFlight = true
try {
pushEnabledAccountId = accountId
hydrateDurableWatchSourcePush(profileId = profileId, accountId = accountId)
if (
!pushCurrentStateLocked(
profileId = profileId,
accountId = accountId,
forceCurrentState = false,
)
) {
schedulePendingPushRetry()
return@withLock false
}
val observedSignatureAtStart = currentObservedStateSignature()
val localBlob = exportSettingsBlob()
if (!isCurrentSyncTarget(profileId = profileId, accountId = accountId)) {
throw CancellationException("Profile settings pull target changed")
}
if (ProfileRepository.activeProfileId != profileId) return@withLock false
val localSignature = buildSignature(localBlob)
val params = buildJsonObject {
@ -156,105 +111,41 @@ object ProfileSettingsSync {
put("p_platform", MOBILE_SYNC_PLATFORM)
}
val result = SupabaseProvider.client.postgrest.rpc("sync_pull_profile_settings_blob", params)
if (!isCurrentSyncTarget(profileId = profileId, accountId = accountId)) {
throw CancellationException("Profile settings pull target changed")
}
if (ProfileRepository.activeProfileId != profileId) return@withLock false
val response = result.decodeList<SettingsBlobResponse>().firstOrNull()
val remoteJson = response?.settingsJson
val pendingDuringPull = pendingLocalPush?.let { pending ->
pending.accountId == accountId && pending.profileId == profileId
} == true
val durableSourceChangeDuringPull =
ProfileSettingsWatchSourceOutbox.pendingFor(accountId, profileId) != null
if (
pendingDuringPull ||
durableSourceChangeDuringPull ||
currentObservedStateSignature() != observedSignatureAtStart
) {
if (
!pushCurrentStateLocked(
profileId = profileId,
accountId = accountId,
forceCurrentState = true,
)
) {
schedulePendingPushRetry()
}
return@withLock false
}
if (remoteJson == null) {
log.i { "pull(profileId=$profileId) — no remote settings blob found" }
if (localSignature != defaultSignature()) {
pushToRemoteLocked(profileId, localBlob, accountId)
}
pushEnabledAccountId = accountId
return@withLock false
}
val remoteBlob = try {
json.decodeFromJsonElement(MobileProfileSettingsBlob.serializer(), remoteJson)
} catch (error: Throwable) {
log.e(error) { "pull(profileId=$profileId) — failed to decode remote settings blob" }
throw error
}
var restoredPendingSourceAfterRemoteApply = false
isApplyingRemoteBlob = true
try {
val remoteBlob = runCatching {
json.decodeFromJsonElement(MobileProfileSettingsBlob.serializer(), remoteJson)
}.getOrElse { error ->
log.e(error) { "pull(profileId=$profileId) — failed to decode remote settings blob" }
return@withLock false
}
val remoteSignature = buildSignature(remoteBlob)
if (remoteSignature == localSignature) {
log.d { "pull(profileId=$profileId) — remote matches local" }
pushEnabledAccountId = accountId
return@withLock false
}
if (!isCurrentSyncTarget(profileId = profileId, accountId = accountId)) {
throw CancellationException("Profile settings pull target changed")
}
if (ProfileRepository.activeProfileId != profileId) return@withLock false
applyRemoteBlob(remoteBlob)
ProfileSettingsWatchSourceOutbox.pendingFor(accountId, profileId)?.let { pendingSource ->
if (TraktSettingsRepository.uiState.value.watchProgressSource != pendingSource.source) {
TraktSettingsRepository.setWatchProgressSource(pendingSource.source, profileId)
}
restoredPendingSourceAfterRemoteApply = true
}
skipNextPush = SkippedProfileSettingsPush(
signature = currentObservedStateSignature(),
accountId = currentCloudAccountId(),
profileId = profileId,
)
skipNextPushSignature = currentObservedStateSignature()
} finally {
isApplyingRemoteBlob = false
}
if (restoredPendingSourceAfterRemoteApply) {
pendingLocalPush = SkippedProfileSettingsPush(
signature = currentObservedStateSignature(),
accountId = accountId,
profileId = profileId,
)
if (
!pushCurrentStateLocked(
profileId = profileId,
accountId = accountId,
forceCurrentState = true,
)
) {
schedulePendingPushRetry()
}
return@withLock false
}
log.i { "pull(profileId=$profileId) — applied remote settings blob" }
pushEnabledAccountId = accountId
true
} catch (error: CancellationException) {
throw error
} catch (error: Exception) {
log.e(error) { "pull(profileId=$profileId) — FAILED" }
throw error
false
} finally {
isServerSyncInFlight = false
}
@ -263,22 +154,16 @@ object ProfileSettingsSync {
suspend fun pushCurrentProfileToRemote(): Boolean {
ensureRepositoriesLoaded()
val accountId = currentCloudAccountId() ?: return false
return syncMutex.withLock {
try {
runCatching {
val profileId = ProfileRepository.activeProfileId
if (!isCurrentSyncTarget(profileId = profileId, accountId = accountId)) return@withLock false
pushCurrentStateLocked(
profileId = profileId,
accountId = accountId,
forceCurrentState = true,
)
} catch (error: CancellationException) {
throw error
} catch (error: Throwable) {
val blob = exportSettingsBlob()
if (ProfileRepository.activeProfileId != profileId) return@runCatching false
pushToRemoteLocked(profileId, blob)
true
}.onFailure { error ->
log.e(error) { "pushCurrentProfileToRemote() — FAILED" }
false
}
}.getOrDefault(false)
}
}
@ -289,6 +174,7 @@ object ProfileSettingsSync {
ThemeSettingsRepository.amoledEnabled.map { "amoled" },
ThemeSettingsRepository.liquidGlassNativeTabBarEnabled.map { "liquid_glass_tab_bar" },
PosterCardStyleRepository.uiState.map { "poster_card_style" },
CardDepthStyleRepository.uiState.map { "card_depth_style" },
PlayerSettingsRepository.uiState.map { "player" },
StreamBadgeSettingsRepository.uiState.map { "stream_badges" },
DebridSettingsRepository.uiState.map { "debrid" },
@ -303,84 +189,24 @@ object ProfileSettingsSync {
)
observeJob = scope.launch {
combine(signatureFlows) {
ObservedProfileSettingsChange(
signature = currentObservedStateSignature(),
accountId = currentCloudAccountId(),
)
}
combine(signatureFlows) { currentObservedStateSignature() }
.drop(1)
.distinctUntilChanged()
.onEach { change ->
val authState = AuthRepository.state.value
if (authState !is AuthState.Authenticated || authState.isAnonymous) return@onEach
if (change.accountId == null || change.accountId != authState.userId) return@onEach
val observedChange = SkippedProfileSettingsPush(
signature = change.signature,
accountId = change.accountId,
profileId = ProfileRepository.activeProfileId,
)
if (skipNextPush != observedChange) {
pendingLocalPush = observedChange
schedulePendingPushRetry()
}
}
.debounce(PUSH_DEBOUNCE_MS)
.collect { change ->
.collect { signature ->
val authState = AuthRepository.state.value
if (authState !is AuthState.Authenticated || authState.isAnonymous) return@collect
if (change.accountId == null || change.accountId != authState.userId) return@collect
val profileId = ProfileRepository.activeProfileId
val observedChange = SkippedProfileSettingsPush(
signature = change.signature,
accountId = change.accountId,
profileId = profileId,
)
if (skipNextPush == observedChange) {
skipNextPush = null
if (pendingLocalPush == observedChange) {
pendingLocalPush = null
}
if (isApplyingRemoteBlob || isServerSyncInFlight) return@collect
if (signature == skipNextPushSignature) {
skipNextPushSignature = null
return@collect
}
pendingLocalPush = observedChange
if (pushEnabledAccountId != change.accountId) return@collect
if (isApplyingRemoteBlob || isServerSyncInFlight) return@collect
pushCurrentProfileToRemote()
}
}
}
private fun schedulePendingPushRetry() {
if (pendingPushRetryJob?.isActive == true) return
pendingPushRetryJob = scope.launch {
var retryDelayMs = 5_000L
while (pendingLocalPush != null) {
delay(retryDelayMs)
val pending = pendingLocalPush ?: break
if (
pending.accountId == currentCloudAccountId() &&
pending.profileId == ProfileRepository.activeProfileId &&
pushEnabledAccountId == pending.accountId &&
!isApplyingRemoteBlob &&
!isServerSyncInFlight &&
pushCurrentProfileToRemote()
) {
break
}
retryDelayMs = (retryDelayMs * 2L).coerceAtMost(60_000L)
}
}
}
private suspend fun pushToRemoteLocked(
profileId: Int,
blob: MobileProfileSettingsBlob,
accountId: String,
) {
if (!isCurrentSyncTarget(profileId = profileId, accountId = accountId)) {
throw CancellationException("Profile settings push target changed")
}
private suspend fun pushToRemoteLocked(profileId: Int, blob: MobileProfileSettingsBlob) {
val params = buildJsonObject {
put("p_profile_id", profileId)
put("p_platform", MOBILE_SYNC_PLATFORM)
@ -388,80 +214,16 @@ object ProfileSettingsSync {
putSyncOriginClientId()
}
SupabaseProvider.client.postgrest.rpc("sync_push_profile_settings_blob", params)
if (!isCurrentSyncTarget(profileId = profileId, accountId = accountId)) {
throw CancellationException("Profile settings push target changed")
}
log.d { "pushToRemoteLocked(profileId=$profileId) — success" }
}
private fun hydrateDurableWatchSourcePush(profileId: Int, accountId: String) {
val durableChange = ProfileSettingsWatchSourceOutbox.pendingFor(accountId, profileId) ?: return
if (TraktSettingsRepository.uiState.value.watchProgressSource != durableChange.source) {
TraktSettingsRepository.setWatchProgressSource(durableChange.source, profileId)
}
pendingLocalPush = SkippedProfileSettingsPush(
signature = currentObservedStateSignature(),
accountId = accountId,
profileId = profileId,
)
schedulePendingPushRetry()
}
private suspend fun pushCurrentStateLocked(
profileId: Int,
accountId: String,
forceCurrentState: Boolean,
): Boolean {
val durableChange = ProfileSettingsWatchSourceOutbox.pendingFor(accountId, profileId)
val inMemoryChange = pendingLocalPush?.takeIf { pending ->
pending.accountId == accountId && pending.profileId == profileId
}
if (!forceCurrentState && durableChange == null && inMemoryChange == null) return true
val signature = currentObservedStateSignature()
pushToRemoteLocked(profileId, exportSettingsBlob(), accountId)
if (currentObservedStateSignature() != signature) {
pendingLocalPush = SkippedProfileSettingsPush(
signature = currentObservedStateSignature(),
accountId = accountId,
profileId = profileId,
)
schedulePendingPushRetry()
return false
}
val pushedChange = SkippedProfileSettingsPush(
signature = signature,
accountId = accountId,
profileId = profileId,
)
if (pendingLocalPush == pushedChange) {
pendingLocalPush = null
}
if (
durableChange != null &&
TraktSettingsRepository.uiState.value.watchProgressSource == durableChange.source
) {
ProfileSettingsWatchSourceOutbox.clearIfMatches(durableChange)
}
val durablePushRemains = ProfileSettingsWatchSourceOutbox.pendingFor(accountId, profileId) != null
val memoryPushRemains = pendingLocalPush?.let { pending ->
pending.accountId == accountId && pending.profileId == profileId
} == true
if (durablePushRemains || memoryPushRemains) {
schedulePendingPushRetry()
return false
}
return true
}
private fun exportSettingsBlob(): MobileProfileSettingsBlob {
ensureRepositoriesLoaded()
return MobileProfileSettingsBlob(
features = MobileProfileSettingsFeatures(
themeSettings = ThemeSettingsStorage.exportToSyncPayload(),
posterCardStyleSettingsPayload = PosterCardStyleStorage.loadPayload().orEmpty().trim(),
cardDepthStyleSettingsPayload = CardDepthStyleStorage.loadPayload().orEmpty().trim(),
playerSettings = PlayerSettingsStorage.exportToSyncPayload(),
streamBadgeSettings = StreamBadgeSettingsStorage.exportToSyncPayload(),
debridSettings = DebridSettingsStorage.exportToSyncPayload(),
@ -486,6 +248,9 @@ object ProfileSettingsSync {
PosterCardStyleStorage.savePayload(blob.features.posterCardStyleSettingsPayload)
PosterCardStyleRepository.onProfileChanged()
CardDepthStyleStorage.savePayload(blob.features.cardDepthStyleSettingsPayload)
CardDepthStyleRepository.onProfileChanged()
PlayerSettingsStorage.replaceFromSyncPayload(blob.features.playerSettings)
PlayerSettingsRepository.onProfileChanged()
@ -523,6 +288,7 @@ object ProfileSettingsSync {
private fun ensureRepositoriesLoaded() {
ThemeSettingsRepository.ensureLoaded()
PosterCardStyleRepository.ensureLoaded()
CardDepthStyleRepository.ensureLoaded()
PlayerSettingsRepository.ensureLoaded()
StreamBadgeSettingsRepository.ensureLoaded()
DebridSettingsRepository.ensureLoaded()
@ -539,14 +305,12 @@ object ProfileSettingsSync {
private fun buildSignature(blob: MobileProfileSettingsBlob): String =
json.encodeToString(MobileProfileSettingsBlob.serializer(), blob)
private fun defaultSignature(): String =
buildSignature(MobileProfileSettingsBlob())
private fun currentObservedStateSignature(): String = listOf(
"theme=${ThemeSettingsRepository.selectedTheme.value.name}",
"amoled=${ThemeSettingsRepository.amoledEnabled.value}",
"liquid_glass_tab_bar=${ThemeSettingsRepository.liquidGlassNativeTabBarEnabled.value}",
"poster_card_style=${PosterCardStyleRepository.uiState.value}",
"card_depth_style=${CardDepthStyleRepository.uiState.value}",
"player=${PlayerSettingsRepository.uiState.value}",
"stream_badges=${StreamBadgeSettingsRepository.uiState.value}",
"debrid=${DebridSettingsRepository.uiState.value}",
@ -560,13 +324,6 @@ object ProfileSettingsSync {
"episode_release_alerts=${EpisodeReleaseNotificationsRepository.uiState.value.isEnabled}",
).joinToString(separator = "||")
private fun currentCloudAccountId(): String? =
(AuthRepository.state.value as? AuthState.Authenticated)
?.takeUnless { it.isAnonymous }
?.userId
private fun isCurrentSyncTarget(profileId: Int, accountId: String): Boolean =
ProfileRepository.activeProfileId == profileId && currentCloudAccountId() == accountId
}
@Serializable
@ -579,6 +336,7 @@ private data class MobileProfileSettingsBlob(
private data class MobileProfileSettingsFeatures(
@SerialName("theme_settings") val themeSettings: JsonObject = JsonObject(emptyMap()),
@SerialName("poster_card_style_settings_payload") val posterCardStyleSettingsPayload: String = "",
@SerialName("card_depth_style_settings_payload") val cardDepthStyleSettingsPayload: String = "",
@SerialName("player_settings") val playerSettings: JsonObject = JsonObject(emptyMap()),
@SerialName("stream_badge_settings") val streamBadgeSettings: JsonObject = JsonObject(emptyMap()),
@SerialName("debrid_settings") val debridSettings: JsonObject = JsonObject(emptyMap()),

View file

@ -0,0 +1,96 @@
package com.nuvio.app.core.ui
import androidx.compose.foundation.border
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.geometry.Size
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.dp
@Composable
fun rememberCardDepthStyleUiState(): CardDepthStyleUiState {
CardDepthStyleRepository.ensureLoaded()
val uiState by CardDepthStyleRepository.uiState.collectAsState()
return uiState
}
@Composable
fun Modifier.nuvioCardDepth(
shape: Shape,
surface: NuvioCardDepthSurface,
fallbackBorderAlpha: Float = 0f,
): Modifier {
val state = rememberCardDepthStyleUiState()
if (!state.isEnabledFor(surface)) {
return if (fallbackBorderAlpha > 0f) {
border(
width = 1.dp,
color = Color.White.copy(alpha = fallbackBorderAlpha),
shape = shape,
)
} else {
this
}
}
return cardDepthVisual(
shape = shape,
edgeStrength = state.edgeStrength.toFloat(),
sheenStrength = state.sheenStrength.toFloat(),
edgeCoverage = state.edgeCoverage.toFloat(),
)
}
fun Modifier.cardDepthVisual(
shape: Shape,
edgeStrength: Float,
sheenStrength: Float,
edgeCoverage: Float = DefaultCardDepthEdgeCoverage.toFloat(),
): Modifier {
val edgeTop = edgeStrength.coerceIn(0f, 100f) / 100f
val sheen = sheenStrength.coerceIn(0f, 100f) / 100f
val coverage = edgeCoverage.coerceIn(0f, 100f) / 100f
val withEdge = if (edgeTop > 0f) {
border(
width = 1.dp,
brush = Brush.verticalGradient(
colors = listOf(
Color.White.copy(alpha = edgeTop),
Color.White.copy(alpha = edgeTop * (0.33f + 0.67f * coverage)),
Color.White.copy(alpha = edgeTop * coverage),
),
),
shape = shape,
)
} else {
this
}
return if (sheen > 0f) {
withEdge.drawWithContent {
drawContent()
val sheenHeight = size.height * 0.22f
if (sheenHeight > 0f) {
drawRect(
brush = Brush.verticalGradient(
colors = listOf(
Color.White.copy(alpha = sheen),
Color.Transparent,
),
startY = 0f,
endY = sheenHeight,
),
size = Size(size.width, sheenHeight),
)
}
}
} else {
withEdge
}
}

View file

@ -0,0 +1,175 @@
package com.nuvio.app.core.ui
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asStateFlow
import kotlinx.serialization.Serializable
import kotlinx.serialization.decodeFromString
import kotlinx.serialization.encodeToString
import kotlinx.serialization.json.Json
internal const val DefaultCardDepthEdgeStrength = 28
internal const val DefaultCardDepthSheenStrength = 10
internal const val DefaultCardDepthEdgeCoverage = 0
enum class NuvioCardDepthSurface {
Posters,
ContinueWatching,
EpisodeCards,
Cast,
Trailers,
}
@Serializable
private data class StoredCardDepthStylePreferences(
val enabled: Boolean = false,
val edgeStrength: Int = DefaultCardDepthEdgeStrength,
val sheenStrength: Int = DefaultCardDepthSheenStrength,
val edgeCoverage: Int = DefaultCardDepthEdgeCoverage,
val postersEnabled: Boolean = true,
val continueWatchingEnabled: Boolean = true,
val episodeCardsEnabled: Boolean = true,
val castEnabled: Boolean = true,
val trailersEnabled: Boolean = true,
)
data class CardDepthStyleUiState(
val enabled: Boolean = false,
val edgeStrength: Int = DefaultCardDepthEdgeStrength,
val sheenStrength: Int = DefaultCardDepthSheenStrength,
val edgeCoverage: Int = DefaultCardDepthEdgeCoverage,
val postersEnabled: Boolean = true,
val continueWatchingEnabled: Boolean = true,
val episodeCardsEnabled: Boolean = true,
val castEnabled: Boolean = true,
val trailersEnabled: Boolean = true,
) {
fun isEnabledFor(surface: NuvioCardDepthSurface): Boolean =
enabled && isSurfaceEnabled(surface)
fun isSurfaceEnabled(surface: NuvioCardDepthSurface): Boolean =
when (surface) {
NuvioCardDepthSurface.Posters -> postersEnabled
NuvioCardDepthSurface.ContinueWatching -> continueWatchingEnabled
NuvioCardDepthSurface.EpisodeCards -> episodeCardsEnabled
NuvioCardDepthSurface.Cast -> castEnabled
NuvioCardDepthSurface.Trailers -> trailersEnabled
}
}
object CardDepthStyleRepository {
private val json = Json {
ignoreUnknownKeys = true
encodeDefaults = true
}
private val _uiState = MutableStateFlow(CardDepthStyleUiState())
val uiState: StateFlow<CardDepthStyleUiState> = _uiState.asStateFlow()
private var hasLoaded = false
fun ensureLoaded() {
if (hasLoaded) return
loadFromDisk()
}
fun onProfileChanged() {
loadFromDisk()
}
fun clearLocalState() {
hasLoaded = false
_uiState.value = CardDepthStyleUiState()
}
fun setEnabled(enabled: Boolean) {
update { it.copy(enabled = enabled) }
}
fun setEdgeStrength(strength: Int) {
update { it.copy(edgeStrength = strength.coerceIn(0, 100)) }
}
fun setSheenStrength(strength: Int) {
update { it.copy(sheenStrength = strength.coerceIn(0, 100)) }
}
fun setEdgeCoverage(coverage: Int) {
update { it.copy(edgeCoverage = coverage.coerceIn(0, 100)) }
}
fun setSurfaceEnabled(surface: NuvioCardDepthSurface, enabled: Boolean) {
update {
when (surface) {
NuvioCardDepthSurface.Posters -> it.copy(postersEnabled = enabled)
NuvioCardDepthSurface.ContinueWatching -> it.copy(continueWatchingEnabled = enabled)
NuvioCardDepthSurface.EpisodeCards -> it.copy(episodeCardsEnabled = enabled)
NuvioCardDepthSurface.Cast -> it.copy(castEnabled = enabled)
NuvioCardDepthSurface.Trailers -> it.copy(trailersEnabled = enabled)
}
}
}
fun resetToDefaults() {
ensureLoaded()
if (_uiState.value == CardDepthStyleUiState()) return
_uiState.value = CardDepthStyleUiState()
persist()
}
private fun update(transform: (CardDepthStyleUiState) -> CardDepthStyleUiState) {
ensureLoaded()
val next = transform(_uiState.value)
if (_uiState.value == next) return
_uiState.value = next
persist()
}
private fun loadFromDisk() {
hasLoaded = true
val payload = CardDepthStyleStorage.loadPayload().orEmpty().trim()
if (payload.isEmpty()) {
_uiState.value = CardDepthStyleUiState()
return
}
val stored = runCatching {
json.decodeFromString<StoredCardDepthStylePreferences>(payload)
}.getOrNull()
_uiState.value = if (stored != null) {
CardDepthStyleUiState(
enabled = stored.enabled,
edgeStrength = stored.edgeStrength.coerceIn(0, 100),
sheenStrength = stored.sheenStrength.coerceIn(0, 100),
edgeCoverage = stored.edgeCoverage.coerceIn(0, 100),
postersEnabled = stored.postersEnabled,
continueWatchingEnabled = stored.continueWatchingEnabled,
episodeCardsEnabled = stored.episodeCardsEnabled,
castEnabled = stored.castEnabled,
trailersEnabled = stored.trailersEnabled,
)
} else {
CardDepthStyleUiState()
}
}
private fun persist() {
CardDepthStyleStorage.savePayload(
json.encodeToString(
StoredCardDepthStylePreferences(
enabled = _uiState.value.enabled,
edgeStrength = _uiState.value.edgeStrength,
sheenStrength = _uiState.value.sheenStrength,
edgeCoverage = _uiState.value.edgeCoverage,
postersEnabled = _uiState.value.postersEnabled,
continueWatchingEnabled = _uiState.value.continueWatchingEnabled,
episodeCardsEnabled = _uiState.value.episodeCardsEnabled,
castEnabled = _uiState.value.castEnabled,
trailersEnabled = _uiState.value.trailersEnabled,
),
),
)
}
}

View file

@ -0,0 +1,6 @@
package com.nuvio.app.core.ui
internal expect object CardDepthStyleStorage {
fun loadPayload(): String?
fun savePayload(payload: String)
}

View file

@ -148,6 +148,10 @@ fun NuvioPosterCard(
.aspectRatio(shape.aspectRatio)
.clip(cardShape)
.background(tokens.colors.surface)
.nuvioCardDepth(
shape = cardShape,
surface = NuvioCardDepthSurface.Posters,
)
.posterCardClickable(
onClick = onClick,
onLongClick = onLongClick,

View file

@ -32,6 +32,8 @@ import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import coil3.compose.LocalPlatformContext
import coil3.request.ImageRequest
import com.nuvio.app.core.ui.NuvioCardDepthSurface
import com.nuvio.app.core.ui.nuvioCardDepth
import com.nuvio.app.features.details.MetaPerson
import com.nuvio.app.features.details.castAvatarSharedTransitionKey
import nuvio.composeapp.generated.resources.*
@ -143,6 +145,10 @@ private fun CastItem(
.background(
color = MaterialTheme.colorScheme.surfaceVariant,
shape = CircleShape,
)
.nuvioCardDepth(
shape = CircleShape,
surface = NuvioCardDepthSurface.Cast,
),
contentAlignment = Alignment.Center,
) {

View file

@ -64,7 +64,9 @@ import com.nuvio.app.core.build.AppFeaturePolicy
import com.nuvio.app.core.format.formatReleaseDateForDisplay
import com.nuvio.app.core.i18n.localizedSeasonEpisodeCode
import com.nuvio.app.core.ui.NuvioAnimatedWatchedBadge
import com.nuvio.app.core.ui.NuvioCardDepthSurface
import com.nuvio.app.core.ui.NuvioProgressBar
import com.nuvio.app.core.ui.nuvioCardDepth
import com.nuvio.app.core.ui.posterCardClickable
import com.nuvio.app.features.details.MetaDetails
import com.nuvio.app.features.details.MetaEpisodeCardStyle
@ -670,10 +672,10 @@ private fun EpisodeHorizontalCard(
.height(metrics.cardHeight)
.clip(cardShape)
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.45f))
.border(
width = 1.dp,
color = Color.White.copy(alpha = 0.12f),
.nuvioCardDepth(
shape = cardShape,
surface = NuvioCardDepthSurface.EpisodeCards,
fallbackBorderAlpha = 0.12f,
)
.posterCardClickable(
onClick = onClick,
@ -699,12 +701,12 @@ private fun EpisodeHorizontalCard(
.fillMaxSize()
.background(
Brush.verticalGradient(
colors = listOf(
Color.Transparent,
Color.Black.copy(alpha = 0.10f),
Color.Black.copy(alpha = 0.42f),
Color.Black.copy(alpha = 0.78f),
),
0f to Color.Transparent,
0.42f to Color.Transparent,
0.56f to Color.Black.copy(alpha = 0.20f),
0.70f to Color.Black.copy(alpha = 0.45f),
0.84f to Color.Black.copy(alpha = 0.68f),
1f to Color.Black.copy(alpha = 0.92f),
),
),
)

View file

@ -37,6 +37,8 @@ import androidx.compose.ui.text.style.TextOverflow
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.sp
import coil3.compose.AsyncImage
import com.nuvio.app.core.ui.NuvioCardDepthSurface
import com.nuvio.app.core.ui.nuvioCardDepth
import com.nuvio.app.features.details.MetaTrailer
import nuvio.composeapp.generated.resources.*
import nuvio.composeapp.generated.resources.detail_tab_trailer
@ -193,6 +195,10 @@ private fun TrailerCard(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(cornerRadius))
.nuvioCardDepth(
shape = RoundedCornerShape(cornerRadius),
surface = NuvioCardDepthSurface.Trailers,
)
.clickable(onClick = onClick),
) {
AsyncImage(

View file

@ -48,7 +48,9 @@ import androidx.compose.ui.unit.sp
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import coil3.compose.AsyncImage
import com.nuvio.app.core.ui.DisintegratingContainer
import com.nuvio.app.core.ui.NuvioCardDepthSurface
import com.nuvio.app.core.ui.NuvioProgressBar
import com.nuvio.app.core.ui.nuvioCardDepth
import com.nuvio.app.core.ui.NuvioShelfSection
import com.nuvio.app.core.ui.NuvioTokens
import com.nuvio.app.core.ui.PosterLandscapeAspectRatio
@ -703,6 +705,10 @@ private fun ContinueWatchingCard(
.aspectRatio(PosterLandscapeAspectRatio)
.clip(RoundedCornerShape(cardMetrics.cornerRadius))
.background(MaterialTheme.colorScheme.surfaceVariant)
.nuvioCardDepth(
shape = RoundedCornerShape(cardMetrics.cornerRadius),
surface = NuvioCardDepthSurface.ContinueWatching,
)
.posterCardClickable(
onClick = onClick,
onLongClick = onLongClick,

View file

@ -8,7 +8,7 @@ import kotlinx.serialization.SerialName
import kotlinx.serialization.Serializable
import kotlinx.serialization.json.Json
private const val PARENTAL_GUIDE_BASE_URL = "https://api.imdbapi.dev"
private const val PARENTAL_GUIDE_BASE_URL = "https://api.tiffara.com"
private val imdbIdPattern = Regex("tt\\d+")
data class ParentalGuideResult(

View file

@ -13,6 +13,7 @@ import com.nuvio.app.features.downloads.DownloadsRepository
import com.nuvio.app.features.details.MetaScreenSettingsRepository
import com.nuvio.app.features.home.HomeCatalogSettingsRepository
import com.nuvio.app.features.home.HomeRepository
import com.nuvio.app.core.ui.CardDepthStyleRepository
import com.nuvio.app.core.ui.PosterCardStyleRepository
import com.nuvio.app.features.library.LibraryRepository
import com.nuvio.app.features.mdblist.MdbListSettingsRepository
@ -162,6 +163,7 @@ object ProfileRepository {
}
ThemeSettingsRepository.onProfileChanged()
PosterCardStyleRepository.onProfileChanged()
CardDepthStyleRepository.onProfileChanged()
PlayerSettingsRepository.onProfileChanged()
StreamBadgeSettingsRepository.onProfileChanged()
P2pSettingsRepository.onProfileChanged()

View file

@ -2,8 +2,12 @@ package com.nuvio.app.features.settings
import androidx.compose.animation.core.animateDpAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.border
import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectDragGestures
import androidx.compose.foundation.gestures.detectTapGestures
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@ -11,6 +15,8 @@ import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.aspectRatio
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
@ -18,23 +24,81 @@ import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.width
import androidx.compose.foundation.lazy.LazyListScope
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.rounded.Tune
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.FilterChip
import androidx.compose.material3.FilterChipDefaults
import androidx.compose.material3.Icon
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Slider
import androidx.compose.material3.SliderDefaults
import androidx.compose.material3.Switch
import androidx.compose.material3.SwitchDefaults
import androidx.compose.material3.Text
import androidx.compose.material3.rememberModalBottomSheetState
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.geometry.Offset
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.input.pointer.pointerInput
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.dp
import kotlin.math.roundToInt
import com.nuvio.app.core.ui.CardDepthStyleRepository
import com.nuvio.app.core.ui.CardDepthStyleUiState
import com.nuvio.app.core.ui.DefaultCardDepthEdgeCoverage
import com.nuvio.app.core.ui.DefaultCardDepthEdgeStrength
import com.nuvio.app.core.ui.DefaultCardDepthSheenStrength
import com.nuvio.app.core.ui.NuvioActionLabel
import com.nuvio.app.core.ui.NuvioCardDepthSurface
import com.nuvio.app.core.ui.NuvioModalBottomSheet
import com.nuvio.app.core.ui.PosterCardStyleRepository
import com.nuvio.app.core.ui.PosterCardStyleUiState
import com.nuvio.app.core.ui.cardDepthVisual
import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.action_reset
import nuvio.composeapp.generated.resources.settings_card_depth_apply_to
import nuvio.composeapp.generated.resources.settings_card_depth_description
import nuvio.composeapp.generated.resources.settings_card_depth_edge
import nuvio.composeapp.generated.resources.settings_card_depth_edge_balanced
import nuvio.composeapp.generated.resources.settings_card_depth_edge_bold
import nuvio.composeapp.generated.resources.settings_card_depth_edge_subtle
import nuvio.composeapp.generated.resources.settings_card_depth_edge_value
import nuvio.composeapp.generated.resources.settings_card_depth_edge_coverage
import nuvio.composeapp.generated.resources.settings_card_depth_coverage_full
import nuvio.composeapp.generated.resources.settings_card_depth_coverage_half
import nuvio.composeapp.generated.resources.settings_card_depth_coverage_top
import nuvio.composeapp.generated.resources.settings_card_depth_coverage_value
import nuvio.composeapp.generated.resources.settings_card_depth_enabled
import nuvio.composeapp.generated.resources.settings_card_depth_fine_tune
import nuvio.composeapp.generated.resources.settings_card_depth_fine_tune_hint
import nuvio.composeapp.generated.resources.settings_card_depth_fine_tune_title
import nuvio.composeapp.generated.resources.settings_card_depth_pad_edge_axis
import nuvio.composeapp.generated.resources.settings_card_depth_pad_sheen_axis
import nuvio.composeapp.generated.resources.settings_card_depth_preview_meta
import nuvio.composeapp.generated.resources.settings_card_depth_preview_title
import nuvio.composeapp.generated.resources.settings_card_depth_sheen
import nuvio.composeapp.generated.resources.settings_card_depth_sheen_value
import nuvio.composeapp.generated.resources.settings_card_depth_sheen_bright
import nuvio.composeapp.generated.resources.settings_card_depth_sheen_off
import nuvio.composeapp.generated.resources.settings_card_depth_sheen_soft
import nuvio.composeapp.generated.resources.settings_card_depth_surface_cast
import nuvio.composeapp.generated.resources.settings_card_depth_surface_continue_watching
import nuvio.composeapp.generated.resources.settings_card_depth_surface_episodes
import nuvio.composeapp.generated.resources.settings_card_depth_surface_posters
import nuvio.composeapp.generated.resources.settings_card_depth_surface_trailers
import nuvio.composeapp.generated.resources.settings_card_depth_title
import nuvio.composeapp.generated.resources.settings_poster_card_radius
import nuvio.composeapp.generated.resources.settings_poster_card_style
import nuvio.composeapp.generated.resources.settings_poster_card_width
@ -90,6 +154,451 @@ internal fun LazyListScope.posterCustomizationSettingsContent(
}
}
}
item {
CardDepthStyleRepository.ensureLoaded()
val cardDepthState by CardDepthStyleRepository.uiState.collectAsState()
SettingsSection(
title = stringResource(Res.string.settings_card_depth_title),
isTablet = isTablet,
actions = {
NuvioActionLabel(
text = stringResource(Res.string.action_reset),
onClick = CardDepthStyleRepository::resetToDefaults,
)
},
) {
SettingsGroup(isTablet = isTablet) {
CardDepthStyleControls(
isTablet = isTablet,
uiState = cardDepthState,
)
}
}
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CardDepthStyleControls(
isTablet: Boolean,
uiState: CardDepthStyleUiState,
) {
var showFineTune by remember { mutableStateOf(false) }
val edgeOptions = listOf(
PresetOption(stringResource(Res.string.settings_card_depth_edge_subtle), 28),
PresetOption(stringResource(Res.string.settings_card_depth_edge_balanced), 42),
PresetOption(stringResource(Res.string.settings_card_depth_edge_bold), 56),
)
val sheenOptions = listOf(
PresetOption(stringResource(Res.string.settings_card_depth_sheen_off), 0),
PresetOption(stringResource(Res.string.settings_card_depth_sheen_soft), 10),
PresetOption(stringResource(Res.string.settings_card_depth_sheen_bright), 16),
)
val coverageOptions = listOf(
PresetOption(stringResource(Res.string.settings_card_depth_coverage_top), 0),
PresetOption(stringResource(Res.string.settings_card_depth_coverage_half), 50),
PresetOption(stringResource(Res.string.settings_card_depth_coverage_full), 100),
)
val surfaceRows = listOf(
stringResource(Res.string.settings_card_depth_surface_posters) to NuvioCardDepthSurface.Posters,
stringResource(Res.string.settings_card_depth_surface_continue_watching) to NuvioCardDepthSurface.ContinueWatching,
stringResource(Res.string.settings_card_depth_surface_episodes) to NuvioCardDepthSurface.EpisodeCards,
stringResource(Res.string.settings_card_depth_surface_cast) to NuvioCardDepthSurface.Cast,
stringResource(Res.string.settings_card_depth_surface_trailers) to NuvioCardDepthSurface.Trailers,
)
Column(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = if (isTablet) 20.dp else 16.dp, vertical = if (isTablet) 18.dp else 16.dp),
verticalArrangement = Arrangement.spacedBy(14.dp),
) {
Text(
text = stringResource(Res.string.settings_card_depth_description),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
PosterToggleRow(
title = stringResource(Res.string.settings_card_depth_enabled),
checked = uiState.enabled,
onCheckedChange = CardDepthStyleRepository::setEnabled,
)
if (uiState.enabled) {
PosterStyleOptionRow(
title = stringResource(Res.string.settings_card_depth_edge),
selectedValue = uiState.edgeStrength,
options = edgeOptions,
onSelected = CardDepthStyleRepository::setEdgeStrength,
)
PosterStyleOptionRow(
title = stringResource(Res.string.settings_card_depth_sheen),
selectedValue = uiState.sheenStrength,
options = sheenOptions,
onSelected = CardDepthStyleRepository::setSheenStrength,
)
PosterStyleOptionRow(
title = stringResource(Res.string.settings_card_depth_edge_coverage),
selectedValue = uiState.edgeCoverage,
options = coverageOptions,
onSelected = CardDepthStyleRepository::setEdgeCoverage,
)
Row(
modifier = Modifier
.fillMaxWidth()
.clip(RoundedCornerShape(10.dp))
.clickable { showFineTune = true },
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(Res.string.settings_card_depth_fine_tune),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.Medium,
)
Icon(
imageVector = Icons.Rounded.Tune,
contentDescription = null,
tint = MaterialTheme.colorScheme.primary,
)
}
Text(
text = stringResource(Res.string.settings_card_depth_apply_to),
style = MaterialTheme.typography.bodyLarge,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.SemiBold,
)
surfaceRows.forEach { (title, surface) ->
PosterToggleRow(
title = title,
checked = uiState.isSurfaceEnabled(surface),
onCheckedChange = { enabled ->
CardDepthStyleRepository.setSurfaceEnabled(surface, enabled)
},
)
}
}
}
if (showFineTune) {
CardDepthFineTuneSheet(
initialEdgeStrength = uiState.edgeStrength,
initialSheenStrength = uiState.sheenStrength,
initialEdgeCoverage = uiState.edgeCoverage,
onDismiss = { showFineTune = false },
)
}
}
@OptIn(ExperimentalMaterial3Api::class)
@Composable
private fun CardDepthFineTuneSheet(
initialEdgeStrength: Int,
initialSheenStrength: Int,
initialEdgeCoverage: Int,
onDismiss: () -> Unit,
) {
val sheetState = rememberModalBottomSheetState(skipPartiallyExpanded = true)
var draftEdge by remember { mutableFloatStateOf(initialEdgeStrength.toFloat()) }
var draftSheen by remember { mutableFloatStateOf(initialSheenStrength.toFloat()) }
var draftCoverage by remember { mutableFloatStateOf(initialEdgeCoverage.toFloat()) }
fun commitDraft() {
CardDepthStyleRepository.setEdgeStrength(draftEdge.roundToInt())
CardDepthStyleRepository.setSheenStrength(draftSheen.roundToInt())
CardDepthStyleRepository.setEdgeCoverage(draftCoverage.roundToInt())
}
NuvioModalBottomSheet(
onDismissRequest = {
commitDraft()
onDismiss()
},
sheetState = sheetState,
) {
Column(
modifier = Modifier
.fillMaxWidth()
.padding(start = 20.dp, end = 20.dp, bottom = 32.dp),
verticalArrangement = Arrangement.spacedBy(16.dp),
) {
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
verticalAlignment = Alignment.CenterVertically,
) {
Text(
text = stringResource(Res.string.settings_card_depth_fine_tune_title),
style = MaterialTheme.typography.titleLarge,
color = MaterialTheme.colorScheme.onSurface,
fontWeight = FontWeight.SemiBold,
)
NuvioActionLabel(
text = stringResource(Res.string.action_reset),
onClick = {
draftEdge = DefaultCardDepthEdgeStrength.toFloat()
draftSheen = DefaultCardDepthSheenStrength.toFloat()
draftCoverage = DefaultCardDepthEdgeCoverage.toFloat()
commitDraft()
},
)
}
Text(
text = stringResource(Res.string.settings_card_depth_fine_tune_hint),
style = MaterialTheme.typography.bodyMedium,
color = MaterialTheme.colorScheme.onSurfaceVariant,
)
CardDepthPreviewCard(
edgeStrength = draftEdge,
sheenStrength = draftSheen,
edgeCoverage = draftCoverage,
)
Row(
modifier = Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween,
) {
Text(
text = "${stringResource(Res.string.settings_card_depth_edge_value)}: ${formatDepthPercentage(draftEdge)}",
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurface,
)
Text(
text = "${stringResource(Res.string.settings_card_depth_sheen_value)}: ${formatDepthPercentage(draftSheen)}",
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurface,
)
}
CardDepthTuningPad(
edgeStrength = draftEdge,
sheenStrength = draftSheen,
onChange = { edge, sheen ->
draftEdge = edge
draftSheen = sheen
},
onCommit = { edge, sheen ->
CardDepthStyleRepository.setEdgeStrength(edge.roundToInt())
CardDepthStyleRepository.setSheenStrength(sheen.roundToInt())
},
)
Text(
text = "${stringResource(Res.string.settings_card_depth_coverage_value)}: ${formatDepthPercentage(draftCoverage)}",
style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.onSurface,
)
Slider(
value = draftCoverage,
onValueChange = { draftCoverage = it },
onValueChangeFinished = {
CardDepthStyleRepository.setEdgeCoverage(draftCoverage.roundToInt())
},
valueRange = 0f..100f,
colors = SliderDefaults.colors(
thumbColor = MaterialTheme.colorScheme.primary,
activeTrackColor = MaterialTheme.colorScheme.primary,
),
modifier = Modifier.fillMaxWidth(),
)
}
}
}
private fun formatDepthPercentage(value: Float): String {
val tenths = (value.coerceIn(0f, 100f) * 10f).roundToInt()
return "${tenths / 10}.${tenths % 10}%"
}
@Composable
private fun CardDepthPreviewCard(
edgeStrength: Float,
sheenStrength: Float,
edgeCoverage: Float,
) {
val shape = RoundedCornerShape(14.dp)
Box(
modifier = Modifier
.fillMaxWidth()
.aspectRatio(16f / 9f)
.clip(shape)
.background(
Brush.linearGradient(
colors = listOf(
Color(0xFF33415C),
Color(0xFF232D42),
Color(0xFF141A28),
),
),
)
.cardDepthVisual(
shape = shape,
edgeStrength = edgeStrength,
sheenStrength = sheenStrength,
edgeCoverage = edgeCoverage,
),
) {
Box(
modifier = Modifier
.fillMaxSize()
.background(
Brush.verticalGradient(
0f to Color.Transparent,
0.42f to Color.Transparent,
0.56f to Color.Black.copy(alpha = 0.20f),
0.70f to Color.Black.copy(alpha = 0.45f),
0.84f to Color.Black.copy(alpha = 0.68f),
1f to Color.Black.copy(alpha = 0.92f),
),
),
)
Column(
modifier = Modifier
.align(Alignment.BottomStart)
.padding(14.dp),
verticalArrangement = Arrangement.spacedBy(4.dp),
) {
Text(
text = stringResource(Res.string.settings_card_depth_preview_meta),
style = MaterialTheme.typography.labelSmall,
color = Color.White.copy(alpha = 0.78f),
)
Text(
text = stringResource(Res.string.settings_card_depth_preview_title),
style = MaterialTheme.typography.titleMedium,
color = Color.White,
fontWeight = FontWeight.ExtraBold,
)
}
}
}
@Composable
private fun CardDepthTuningPad(
edgeStrength: Float,
sheenStrength: Float,
onChange: (Float, Float) -> Unit,
onCommit: (Float, Float) -> Unit,
) {
val maxEdge = 70f
val maxSheen = 25f
val shape = RoundedCornerShape(16.dp)
val thumbColor = MaterialTheme.colorScheme.primary
val gridColor = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.4f)
val currentEdge by rememberUpdatedState(edgeStrength)
val currentSheen by rememberUpdatedState(sheenStrength)
val currentOnChange by rememberUpdatedState(onChange)
val currentOnCommit by rememberUpdatedState(onCommit)
Box(
modifier = Modifier
.fillMaxWidth()
.height(200.dp)
.clip(shape)
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.35f))
.border(
width = 1.dp,
color = MaterialTheme.colorScheme.outlineVariant.copy(alpha = 0.5f),
shape = shape,
)
.pointerInput(Unit) {
fun valuesAt(position: Offset): Pair<Float, Float> {
val x = (position.x / size.width).coerceIn(0f, 1f)
val y = 1f - (position.y / size.height).coerceIn(0f, 1f)
return (x * maxEdge) to (y * maxSheen)
}
detectDragGestures(
onDragStart = { position ->
val (edge, sheen) = valuesAt(position)
currentOnChange(edge, sheen)
},
onDrag = { change, _ ->
change.consume()
val (edge, sheen) = valuesAt(change.position)
currentOnChange(edge, sheen)
},
onDragEnd = {
currentOnCommit(currentEdge, currentSheen)
},
onDragCancel = {
currentOnCommit(currentEdge, currentSheen)
},
)
}
.pointerInput(Unit) {
fun valuesAt(position: Offset): Pair<Float, Float> {
val x = (position.x / size.width).coerceIn(0f, 1f)
val y = 1f - (position.y / size.height).coerceIn(0f, 1f)
return (x * maxEdge) to (y * maxSheen)
}
detectTapGestures { position ->
val (edge, sheen) = valuesAt(position)
currentOnChange(edge, sheen)
currentOnCommit(edge, sheen)
}
},
) {
Canvas(modifier = Modifier.fillMaxSize()) {
for (step in 1..3) {
val x = size.width * step / 4f
val y = size.height * step / 4f
drawLine(
color = gridColor,
start = Offset(x, 0f),
end = Offset(x, size.height),
strokeWidth = 1.dp.toPx(),
)
drawLine(
color = gridColor,
start = Offset(0f, y),
end = Offset(size.width, y),
strokeWidth = 1.dp.toPx(),
)
}
val thumbX = size.width * (edgeStrength / maxEdge).coerceIn(0f, 1f)
val thumbY = size.height * (1f - (sheenStrength / maxSheen).coerceIn(0f, 1f))
drawLine(
color = thumbColor.copy(alpha = 0.35f),
start = Offset(thumbX, 0f),
end = Offset(thumbX, size.height),
strokeWidth = 1.dp.toPx(),
)
drawLine(
color = thumbColor.copy(alpha = 0.35f),
start = Offset(0f, thumbY),
end = Offset(size.width, thumbY),
strokeWidth = 1.dp.toPx(),
)
drawCircle(
color = thumbColor.copy(alpha = 0.25f),
radius = 16.dp.toPx(),
center = Offset(thumbX, thumbY),
)
drawCircle(
color = thumbColor,
radius = 9.dp.toPx(),
center = Offset(thumbX, thumbY),
)
drawCircle(
color = Color.White,
radius = 4.dp.toPx(),
center = Offset(thumbX, thumbY),
)
}
Text(
text = stringResource(Res.string.settings_card_depth_pad_sheen_axis),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.align(Alignment.TopStart)
.padding(10.dp),
)
Text(
text = stringResource(Res.string.settings_card_depth_pad_edge_axis),
style = MaterialTheme.typography.labelSmall,
color = MaterialTheme.colorScheme.onSurfaceVariant,
modifier = Modifier
.align(Alignment.BottomEnd)
.padding(10.dp),
)
}
}
@OptIn(ExperimentalLayoutApi::class)

View file

@ -710,6 +710,23 @@ internal fun settingsSearchEntries(
)
}
val cardDepthSection = stringResource(Res.string.settings_card_depth_title)
listOf(
PlaybackSearchRow("card-depth-effect", cardDepthSection, stringResource(Res.string.settings_card_depth_description)),
PlaybackSearchRow("card-depth-edge", stringResource(Res.string.settings_card_depth_edge)),
PlaybackSearchRow("card-depth-sheen", stringResource(Res.string.settings_card_depth_sheen)),
).forEach { row ->
addRow(
page = SettingsPage.PosterCustomization,
key = "poster-${row.key}",
title = row.title,
description = row.description,
pageLabel = posterStylePage,
section = cardDepthSection,
icon = Icons.Rounded.Tune,
)
}
val homeLayoutSection = stringResource(Res.string.settings_homescreen_section_hero)
listOf(
PlaybackSearchRow("home-hero", stringResource(Res.string.settings_homescreen_show_hero), stringResource(Res.string.settings_homescreen_show_hero_description)),

View file

@ -0,0 +1,15 @@
package com.nuvio.app.core.ui
import com.nuvio.app.core.storage.ProfileScopedKey
import platform.Foundation.NSUserDefaults
internal actual object CardDepthStyleStorage {
private const val payloadKey = "card_depth_style_payload"
actual fun loadPayload(): String? =
NSUserDefaults.standardUserDefaults.stringForKey(ProfileScopedKey.of(payloadKey))
actual fun savePayload(payload: String) {
NSUserDefaults.standardUserDefaults.setObject(payload, forKey = ProfileScopedKey.of(payloadKey))
}
}

1
enginefs Submodule

@ -0,0 +1 @@
Subproject commit 9f11922ae092e4a7120a7e1bdabb0698df1eecb8

View file

@ -1,3 +1,3 @@
CURRENT_PROJECT_VERSION=94
MARKETING_VERSION=0.2.22
CURRENT_PROJECT_VERSION=95
MARKETING_VERSION=0.2.23

@ -0,0 +1 @@
Subproject commit 9f11922ae092e4a7120a7e1bdabb0698df1eecb8