Merge branch 'NuvioMedia:cmp-rewrite' into cmp-rewrite

This commit is contained in:
skoruppa 2026-04-30 10:43:20 +02:00 committed by GitHub
commit 7a0cc2c03b
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
30 changed files with 2742 additions and 130 deletions

View file

@ -272,7 +272,7 @@ kotlin {
afterEvaluate { afterEvaluate {
dependencies { dependencies {
add("fullImplementation", libs.quickjs.kt) add("fullImplementation", files("libs/quickjs-kt-android-1.0.5-nuvio.aar"))
add("fullImplementation", libs.ksoup) add("fullImplementation", libs.ksoup)
} }
} }

Binary file not shown.

View file

@ -18,7 +18,8 @@ actual object ThemeSettingsStorage {
private const val selectedThemeKey = "selected_theme" private const val selectedThemeKey = "selected_theme"
private const val amoledEnabledKey = "amoled_enabled" private const val amoledEnabledKey = "amoled_enabled"
private const val selectedAppLanguageKey = "selected_app_language" private const val selectedAppLanguageKey = "selected_app_language"
private val syncKeys = listOf(selectedThemeKey, amoledEnabledKey, selectedAppLanguageKey) private val profileScopedSyncKeys = listOf(selectedThemeKey, amoledEnabledKey)
private val globalSyncKeys = listOf(selectedAppLanguageKey)
private var preferences: SharedPreferences? = null private var preferences: SharedPreferences? = null
@ -50,13 +51,18 @@ actual object ThemeSettingsStorage {
?.apply() ?.apply()
} }
actual fun loadSelectedAppLanguage(): String? = actual fun loadSelectedAppLanguage(): String? {
preferences?.getString(ProfileScopedKey.of(selectedAppLanguageKey), null) val value = preferences?.getString(selectedAppLanguageKey, null)
if (value != null) return value
val legacy = preferences?.getString(ProfileScopedKey.of(selectedAppLanguageKey), null)
if (legacy != null) saveSelectedAppLanguage(legacy)
return legacy
}
actual fun saveSelectedAppLanguage(languageCode: String) { actual fun saveSelectedAppLanguage(languageCode: String) {
preferences preferences
?.edit() ?.edit()
?.putString(ProfileScopedKey.of(selectedAppLanguageKey), languageCode) ?.putString(selectedAppLanguageKey, languageCode)
?.apply() ?.apply()
} }
@ -74,7 +80,8 @@ actual object ThemeSettingsStorage {
actual fun replaceFromSyncPayload(payload: JsonObject) { actual fun replaceFromSyncPayload(payload: JsonObject) {
preferences?.edit()?.apply { preferences?.edit()?.apply {
syncKeys.forEach { remove(ProfileScopedKey.of(it)) } profileScopedSyncKeys.forEach { remove(ProfileScopedKey.of(it)) }
globalSyncKeys.forEach { remove(it) }
}?.apply() }?.apply()
payload.decodeSyncString(selectedThemeKey)?.let(::saveSelectedTheme) payload.decodeSyncString(selectedThemeKey)?.let(::saveSelectedTheme)

View file

@ -1,7 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<locale-config xmlns:android="http://schemas.android.com/apk/res/android"> <locale-config xmlns:android="http://schemas.android.com/apk/res/android">
<locale android:name="en"/> <locale android:name="en"/>
<locale android:name="fr"/>
<locale android:name="es"/> <locale android:name="es"/>
<locale android:name="pt-PT"/>
<locale android:name="tr"/> <locale android:name="tr"/>
<locale android:name="it"/> <locale android:name="it"/>
<locale android:name="el"/> <locale android:name="el"/>

View file

@ -569,7 +569,7 @@
<string name="settings_playback_threshold_mode_percentage">Ποσοστό</string> <string name="settings_playback_threshold_mode_percentage">Ποσοστό</string>
<string name="settings_playback_threshold_percentage">Ποσοστό κατωφλίου</string> <string name="settings_playback_threshold_percentage">Ποσοστό κατωφλίου</string>
<string name="settings_playback_threshold_percentage_description">Εμφάνιση κάρτας επόμενου επεισοδίου όταν η αναπαραγωγή φτάσει σε αυτό το ποσοστό.</string> <string name="settings_playback_threshold_percentage_description">Εμφάνιση κάρτας επόμενου επεισοδίου όταν η αναπαραγωγή φτάσει σε αυτό το ποσοστό.</string>
<string name="settings_playback_threshold_percentage_value">%1$d%%</string> <string name="settings_playback_threshold_percentage_value">%1$d%</string>
<string name="settings_playback_timeout_instant">Άμεσα</string> <string name="settings_playback_timeout_instant">Άμεσα</string>
<string name="settings_playback_timeout_seconds">%1$ds</string> <string name="settings_playback_timeout_seconds">%1$ds</string>
<string name="settings_playback_timeout_unlimited">Απεριόριστο</string> <string name="settings_playback_timeout_unlimited">Απεριόριστο</string>
@ -774,7 +774,7 @@
<string name="episode_mark_unwatched">Σήμανση ως μη παρακολουθηθέν</string> <string name="episode_mark_unwatched">Σήμανση ως μη παρακολουθηθέν</string>
<string name="episode_mark_watched">Σήμανση ως παρακολουθηθέν</string> <string name="episode_mark_watched">Σήμανση ως παρακολουθηθέν</string>
<string name="home_continue_watching_up_next">Επόμενο</string> <string name="home_continue_watching_up_next">Επόμενο</string>
<string name="home_continue_watching_watched">%1$d%% παρακολουθήθηκε</string> <string name="home_continue_watching_watched">%1$s παρακολουθήθηκε</string>
<string name="home_empty_no_active_addons_message">Εγκαταστήστε και επικυρώστε τουλάχιστον ένα πρόσθετο πριν φορτώσετε γραμμές καταλόγου στην Αρχική.</string> <string name="home_empty_no_active_addons_message">Εγκαταστήστε και επικυρώστε τουλάχιστον ένα πρόσθετο πριν φορτώσετε γραμμές καταλόγου στην Αρχική.</string>
<string name="home_empty_no_rows_message">Τα εγκατεστημένα πρόσθετα δεν παρέχουν αυτή τη στιγμή καταλόγους συμβατούς με πίνακα χωρίς απαιτούμενα extras.</string> <string name="home_empty_no_rows_message">Τα εγκατεστημένα πρόσθετα δεν παρέχουν αυτή τη στιγμή καταλόγους συμβατούς με πίνακα χωρίς απαιτούμενα extras.</string>
<string name="home_empty_no_rows_title">Δεν υπάρχουν διαθέσιμες γραμμές αρχικής</string> <string name="home_empty_no_rows_title">Δεν υπάρχουν διαθέσιμες γραμμές αρχικής</string>
@ -866,7 +866,7 @@
<string name="streams_no_direct_link">Δεν υπάρχει διαθέσιμος άμεσος σύνδεσμος ροής</string> <string name="streams_no_direct_link">Δεν υπάρχει διαθέσιμος άμεσος σύνδεσμος ροής</string>
<string name="streams_no_metadata">Δεν υπάρχουν διαθέσιμα μεταδεδομένα</string> <string name="streams_no_metadata">Δεν υπάρχουν διαθέσιμα μεταδεδομένα</string>
<string name="streams_refresh">Ανανέωση ροών</string> <string name="streams_refresh">Ανανέωση ροών</string>
<string name="streams_resume_from_percent">Συνέχεια από %1$d%%</string> <string name="streams_resume_from_percent">Συνέχεια από %1$d%</string>
<string name="streams_resume_from_time">Συνέχεια από %1$s</string> <string name="streams_resume_from_time">Συνέχεια από %1$s</string>
<string name="streams_size">ΜΕΓΕΘΟΣ %1$s</string> <string name="streams_size">ΜΕΓΕΘΟΣ %1$s</string>
<string name="trailer_close">Κλείσιμο τρέιλερ</string> <string name="trailer_close">Κλείσιμο τρέιλερ</string>
@ -876,7 +876,7 @@
<string name="updates_asset_line">%1$s • %2$s</string> <string name="updates_asset_line">%1$s • %2$s</string>
<string name="updates_check_failed">Ο έλεγχος ενημερώσεων απέτυχε</string> <string name="updates_check_failed">Ο έλεγχος ενημερώσεων απέτυχε</string>
<string name="updates_download_failed">Η λήψη απέτυχε</string> <string name="updates_download_failed">Η λήψη απέτυχε</string>
<string name="updates_downloading_progress">Λήψη %1$d%%</string> <string name="updates_downloading_progress">Λήψη %1$d%</string>
<string name="updates_install_failed">Αδυναμία εκκίνησης εγκατάστασης</string> <string name="updates_install_failed">Αδυναμία εκκίνησης εγκατάστασης</string>
<string name="updates_latest_version">Χρησιμοποιείτε την τελευταία έκδοση.</string> <string name="updates_latest_version">Χρησιμοποιείτε την τελευταία έκδοση.</string>
<string name="updates_message_allow_installs">Ενεργοποιήστε τις εγκαταστάσεις εφαρμογών για το Nuvio και επιστρέψτε για να συνεχίσετε.</string> <string name="updates_message_allow_installs">Ενεργοποιήστε τις εγκαταστάσεις εφαρμογών για το Nuvio και επιστρέψτε για να συνεχίσετε.</string>

View file

@ -219,7 +219,7 @@
<string name="collections_editor_tmdb_subtitle_movie_collection">Colección de películas de TMDB</string> <string name="collections_editor_tmdb_subtitle_movie_collection">Colección de películas de TMDB</string>
<string name="collections_editor_tmdb_subtitle_production">Producción</string> <string name="collections_editor_tmdb_subtitle_production">Producción</string>
<string name="collections_editor_tmdb_subtitle_network">Cadena</string> <string name="collections_editor_tmdb_subtitle_network">Cadena</string>
<string name="collections_editor_tmdb_subtitle_discover">Discover de TMDB</string> <string name="collections_editor_tmdb_subtitle_discover">Descubrir de TMDB</string>
<string name="collections_empty_subtitle">Crea una para organizar tus catálogos.</string> <string name="collections_empty_subtitle">Crea una para organizar tus catálogos.</string>
<string name="collections_empty_title">Aún no hay colecciones</string> <string name="collections_empty_title">Aún no hay colecciones</string>
<string name="collections_folder_count">%1$d carpeta(s)</string> <string name="collections_folder_count">%1$d carpeta(s)</string>
@ -298,7 +298,7 @@
<string name="compose_player_seek_forward_10">Avanzar 10 segundos</string> <string name="compose_player_seek_forward_10">Avanzar 10 segundos</string>
<string name="compose_player_sources">Fuentes</string> <string name="compose_player_sources">Fuentes</string>
<string name="compose_player_style">Estilo</string> <string name="compose_player_style">Estilo</string>
<string name="compose_player_subs">Subs</string> <string name="compose_player_subs">Subtítulos</string>
<string name="compose_player_subtitles">Subtítulos</string> <string name="compose_player_subtitles">Subtítulos</string>
<string name="compose_player_brightness_level">Brillo %1$s</string> <string name="compose_player_brightness_level">Brillo %1$s</string>
<string name="compose_player_volume_level">Volumen %1$s</string> <string name="compose_player_volume_level">Volumen %1$s</string>
@ -424,12 +424,12 @@
<string name="settings_homescreen_display_name">Nombre visible</string> <string name="settings_homescreen_display_name">Nombre visible</string>
<string name="settings_homescreen_empty_message">Instala un complemento con catálogos compatibles con tableros para configurar las filas de la pantalla de inicio.</string> <string name="settings_homescreen_empty_message">Instala un complemento con catálogos compatibles con tableros para configurar las filas de la pantalla de inicio.</string>
<string name="settings_homescreen_empty_title">No hay catálogos de inicio</string> <string name="settings_homescreen_empty_title">No hay catálogos de inicio</string>
<string name="settings_homescreen_hero_source">Fuente del Hero</string> <string name="settings_homescreen_hero_source">Fuente de Destacado</string>
<string name="settings_homescreen_hidden">Oculto</string> <string name="settings_homescreen_hidden">Oculto</string>
<string name="settings_homescreen_keep_home_focused">Mantener Inicio enfocado</string> <string name="settings_homescreen_keep_home_focused">Mantener Inicio enfocado</string>
<string name="settings_homescreen_limit_reached">%1$s • Límite alcanzado (máx. %2$d)</string> <string name="settings_homescreen_limit_reached">%1$s • Límite alcanzado (máx. %2$d)</string>
<string name="settings_homescreen_no_sources_selected">No hay fuentes Hero seleccionadas</string> <string name="settings_homescreen_no_sources_selected">No hay fuentes de Destacado seleccionadas</string>
<string name="settings_homescreen_not_in_hero">No está en Hero</string> <string name="settings_homescreen_not_in_hero">No está en Destacado</string>
<string name="settings_homescreen_pin_to_move_toast">Quita fijar arriba de la colección para moverla</string> <string name="settings_homescreen_pin_to_move_toast">Quita fijar arriba de la colección para moverla</string>
<string name="settings_homescreen_pinned">Fijado</string> <string name="settings_homescreen_pinned">Fijado</string>
<string name="settings_homescreen_pinned_to_top">Fijado arriba</string> <string name="settings_homescreen_pinned_to_top">Fijado arriba</string>
@ -437,12 +437,12 @@
<string name="settings_homescreen_section_catalogs">CATÁLOGOS</string> <string name="settings_homescreen_section_catalogs">CATÁLOGOS</string>
<string name="settings_homescreen_section_catalogs_collections">CATÁLOGOS Y COLECCIONES</string> <string name="settings_homescreen_section_catalogs_collections">CATÁLOGOS Y COLECCIONES</string>
<string name="settings_homescreen_section_collections">COLECCIONES</string> <string name="settings_homescreen_section_collections">COLECCIONES</string>
<string name="settings_homescreen_section_hero">HERO</string> <string name="settings_homescreen_section_hero">DESTACADO</string>
<string name="settings_homescreen_section_hero_sources">FUENTES DEL HERO</string> <string name="settings_homescreen_section_hero_sources">FUENTES DE DESTACADO</string>
<string name="settings_homescreen_selected_count">%1$d de %2$d seleccionados</string> <string name="settings_homescreen_selected_count">%1$d de %2$d seleccionados</string>
<string name="settings_homescreen_show_hero">Mostrar Hero</string> <string name="settings_homescreen_show_hero">Mostrar Destacado</string>
<string name="settings_homescreen_show_hero_description">Mostrar un carrusel Hero destacado en la parte superior del inicio. Elige hasta 2 catálogos fuente abajo.</string> <string name="settings_homescreen_show_hero_description">Mostrar un carrusel destacado en la parte superior del inicio. Elige hasta 2 catálogos de origen abajo.</string>
<string name="settings_homescreen_summary">%1$d de %2$d catálogos visibles • %3$d fuentes Hero seleccionadas</string> <string name="settings_homescreen_summary">%1$d de %2$d catálogos visibles • %3$d fuentes de Destacado seleccionadas</string>
<string name="settings_homescreen_summary_hint">Abre un catálogo solo cuando necesites cambiarle el nombre o reordenarlo.</string> <string name="settings_homescreen_summary_hint">Abre un catálogo solo cuando necesites cambiarle el nombre o reordenarlo.</string>
<string name="settings_homescreen_visible">Visible</string> <string name="settings_homescreen_visible">Visible</string>
<string name="settings_playback_subtitle">Reproductor, subtítulos y reproducción automática</string> <string name="settings_playback_subtitle">Reproductor, subtítulos y reproducción automática</string>
@ -488,7 +488,7 @@
<string name="settings_content_discovery_addons_description">Instala, elimina, actualiza y ordena tus fuentes de contenido.</string> <string name="settings_content_discovery_addons_description">Instala, elimina, actualiza y ordena tus fuentes de contenido.</string>
<string name="settings_content_discovery_plugins_description">Instala repositorios de scrapers en JavaScript y prueba proveedores internamente.</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_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 Hero.</string> <string name="settings_content_discovery_meta_screen_description">Desactiva secciones de detalles y reordena todo debajo del Destacado.</string>
<string name="settings_content_discovery_collections_description">Crea agrupaciones de catálogos personalizadas con carpetas mostradas en Inicio.</string> <string name="settings_content_discovery_collections_description">Crea agrupaciones de catálogos personalizadas con carpetas mostradas en Inicio.</string>
<string name="settings_integrations_section_title">INTEGRACIONES</string> <string name="settings_integrations_section_title">INTEGRACIONES</string>
<string name="settings_integrations_tmdb_description">Mejora las páginas de detalles con arte, créditos, metadatos de episodios y más desde TMDB.</string> <string name="settings_integrations_tmdb_description">Mejora las páginas de detalles con arte, créditos, metadatos de episodios y más desde TMDB.</string>
@ -641,7 +641,7 @@
<string name="settings_playback_regex_preset_quality_720p_smaller">720p / más pequeño</string> <string name="settings_playback_regex_preset_quality_720p_smaller">720p / más pequeño</string>
<string name="settings_playback_regex_preset_web_sources">Fuentes WEB</string> <string name="settings_playback_regex_preset_web_sources">Fuentes WEB</string>
<string name="settings_playback_render_type">Tipo de renderizado</string> <string name="settings_playback_render_type">Tipo de renderizado</string>
<string name="settings_playback_render_type_cues">Estándar (Cues)</string> <string name="settings_playback_render_type_cues">Estándar (marcas)</string>
<string name="settings_playback_render_type_effects_canvas">Canvas con efectos</string> <string name="settings_playback_render_type_effects_canvas">Canvas con efectos</string>
<string name="settings_playback_render_type_effects_opengl">OpenGL con efectos</string> <string name="settings_playback_render_type_effects_opengl">OpenGL con efectos</string>
<string name="settings_playback_render_type_overlay_canvas">Canvas superpuesto</string> <string name="settings_playback_render_type_overlay_canvas">Canvas superpuesto</string>
@ -687,7 +687,7 @@
<string name="settings_playback_threshold_mode_percentage">Porcentaje</string> <string name="settings_playback_threshold_mode_percentage">Porcentaje</string>
<string name="settings_playback_threshold_percentage">Porcentaje de umbral</string> <string name="settings_playback_threshold_percentage">Porcentaje de umbral</string>
<string name="settings_playback_threshold_percentage_description">Mostrar la tarjeta del siguiente episodio cuando la reproducción alcance este porcentaje.</string> <string name="settings_playback_threshold_percentage_description">Mostrar la tarjeta del siguiente episodio cuando la reproducción alcance este porcentaje.</string>
<string name="settings_playback_threshold_percentage_value">%1$d%%</string> <string name="settings_playback_threshold_percentage_value">%1$d%</string>
<string name="settings_playback_timeout_instant">Instantáneo</string> <string name="settings_playback_timeout_instant">Instantáneo</string>
<string name="settings_playback_timeout_seconds">%1$ds</string> <string name="settings_playback_timeout_seconds">%1$ds</string>
<string name="settings_playback_timeout_unlimited">Ilimitado</string> <string name="settings_playback_timeout_unlimited">Ilimitado</string>
@ -877,7 +877,7 @@
<string name="discover_empty_load_failed_message">El catálogo seleccionado no devolvió elementos de descubrimiento.</string> <string name="discover_empty_load_failed_message">El catálogo seleccionado no devolvió elementos de descubrimiento.</string>
<string name="discover_empty_load_failed_title">No se pudo cargar Descubrir</string> <string name="discover_empty_load_failed_title">No se pudo cargar Descubrir</string>
<string name="discover_empty_no_catalogs_message">Los addons instalados no exponen catálogos compatibles con el tablero para Descubrir.</string> <string name="discover_empty_no_catalogs_message">Los addons instalados no exponen catálogos compatibles con el tablero para Descubrir.</string>
<string name="discover_empty_no_catalogs_title">No hay catálogos de descubrir</string> <string name="discover_empty_no_catalogs_title">No hay catálogos de Descubrir</string>
<string name="discover_empty_no_results_message">El catálogo y los filtros seleccionados no devolvieron ningún elemento.</string> <string name="discover_empty_no_results_message">El catálogo y los filtros seleccionados no devolvieron ningún elemento.</string>
<string name="discover_empty_no_results_title">No se encontraron títulos</string> <string name="discover_empty_no_results_title">No se encontraron títulos</string>
<string name="discover_empty_no_active_addons_message">Instala y valida al menos un addon antes de explorar catálogos en Descubrir.</string> <string name="discover_empty_no_active_addons_message">Instala y valida al menos un addon antes de explorar catálogos en Descubrir.</string>
@ -892,7 +892,7 @@
<string name="episode_mark_unwatched">Marcar como no visto</string> <string name="episode_mark_unwatched">Marcar como no visto</string>
<string name="episode_mark_watched">Marcar como visto</string> <string name="episode_mark_watched">Marcar como visto</string>
<string name="home_continue_watching_up_next">Siguiente</string> <string name="home_continue_watching_up_next">Siguiente</string>
<string name="home_continue_watching_watched">%1$d%% visto</string> <string name="home_continue_watching_watched">%1$s visto</string>
<string name="home_empty_no_active_addons_message">Instala y valida al menos un addon antes de cargar filas de catálogo en Inicio.</string> <string name="home_empty_no_active_addons_message">Instala y valida al menos un addon antes de cargar filas de catálogo en Inicio.</string>
<string name="home_empty_no_rows_message">Los addons instalados no exponen actualmente catálogos compatibles con el tablero sin extras requeridos.</string> <string name="home_empty_no_rows_message">Los addons instalados no exponen actualmente catálogos compatibles con el tablero sin extras requeridos.</string>
<string name="home_empty_no_rows_title">No hay filas de inicio disponibles</string> <string name="home_empty_no_rows_title">No hay filas de inicio disponibles</string>
@ -948,8 +948,8 @@
<string name="profile_manage_profiles">Gestionar perfiles</string> <string name="profile_manage_profiles">Gestionar perfiles</string>
<string name="profile_name_placeholder">Nombre del perfil</string> <string name="profile_name_placeholder">Nombre del perfil</string>
<string name="profile_new">Perfil nuevo</string> <string name="profile_new">Perfil nuevo</string>
<string name="profile_primary_addons_off">Addons principales desactivados</string> <string name="profile_primary_addons_off">Complementos principales desactivados</string>
<string name="profile_primary_addons_on">Addons principales activados</string> <string name="profile_primary_addons_on">Complementos principales activados</string>
<string name="profile_remove_pin_for">Quitar PIN para %1$s</string> <string name="profile_remove_pin_for">Quitar PIN para %1$s</string>
<string name="profile_remove_pin_lock">Quitar bloqueo PIN</string> <string name="profile_remove_pin_lock">Quitar bloqueo PIN</string>
<string name="profile_saving">Guardando...</string> <string name="profile_saving">Guardando...</string>
@ -984,7 +984,7 @@
<string name="streams_no_direct_link">No hay enlace directo del stream disponible</string> <string name="streams_no_direct_link">No hay enlace directo del stream disponible</string>
<string name="streams_no_metadata">No hay metadatos disponibles</string> <string name="streams_no_metadata">No hay metadatos disponibles</string>
<string name="streams_refresh">Actualizar streams</string> <string name="streams_refresh">Actualizar streams</string>
<string name="streams_resume_from_percent">Reanudar desde %1$d%%</string> <string name="streams_resume_from_percent">Reanudar desde %1$d%</string>
<string name="streams_resume_from_time">Reanudar desde %1$s</string> <string name="streams_resume_from_time">Reanudar desde %1$s</string>
<string name="streams_size">TAMAÑO %1$s</string> <string name="streams_size">TAMAÑO %1$s</string>
<string name="trailer_close">Cerrar tráiler</string> <string name="trailer_close">Cerrar tráiler</string>
@ -994,7 +994,7 @@
<string name="updates_asset_line">%1$s • %2$s</string> <string name="updates_asset_line">%1$s • %2$s</string>
<string name="updates_check_failed">Falló la comprobación de actualizaciones</string> <string name="updates_check_failed">Falló la comprobación de actualizaciones</string>
<string name="updates_download_failed">La descarga falló</string> <string name="updates_download_failed">La descarga falló</string>
<string name="updates_downloading_progress">Descargando %1$d%%</string> <string name="updates_downloading_progress">Descargando %1$d%</string>
<string name="updates_install_failed">No se pudo iniciar la instalación</string> <string name="updates_install_failed">No se pudo iniciar la instalación</string>
<string name="updates_latest_version">Estás usando la versión más reciente.</string> <string name="updates_latest_version">Estás usando la versión más reciente.</string>
<string name="updates_message_allow_installs">Activa la instalación de apps para Nuvio y luego vuelve para continuar.</string> <string name="updates_message_allow_installs">Activa la instalación de apps para Nuvio y luego vuelve para continuar.</string>
@ -1021,7 +1021,7 @@
<string name="detail_logo_content_description">logotipo de %1$s</string> <string name="detail_logo_content_description">logotipo de %1$s</string>
<string name="details_comments_load_failed">No se pudieron cargar los comentarios</string> <string name="details_comments_load_failed">No se pudieron cargar los comentarios</string>
<string name="details_load_failed_all_addons">No se pudieron cargar los detalles desde ningún complemento.</string> <string name="details_load_failed_all_addons">No se pudieron cargar los detalles desde ningún complemento.</string>
<string name="details_networks">Redes</string> <string name="details_networks">Cadenas</string>
<string name="details_no_addon_meta">Ningún complemento proporciona metadatos para este contenido.</string> <string name="details_no_addon_meta">Ningún complemento proporciona metadatos para este contenido.</string>
<string name="download_failed">Descarga fallida</string> <string name="download_failed">Descarga fallida</string>
<string name="downloads_channel_description">Muestra el progreso en vivo y los controles de descarga.</string> <string name="downloads_channel_description">Muestra el progreso en vivo y los controles de descarga.</string>
@ -1039,7 +1039,7 @@
<string name="notifications_test_preview_body">Vista previa de la alerta de estreno de episodio.</string> <string name="notifications_test_preview_body">Vista previa de la alerta de estreno de episodio.</string>
<string name="notifications_test_send_failed">No se pudo enviar una notificación de prueba.</string> <string name="notifications_test_send_failed">No se pudo enviar una notificación de prueba.</string>
<string name="notifications_test_sent_for">Notificación de prueba enviada para %1$s.</string> <string name="notifications_test_sent_for">Notificación de prueba enviada para %1$s.</string>
<string name="player_unable_to_play_stream">No se puede reproducir esta transmisión.</string> <string name="player_unable_to_play_stream">No se puede reproducir este stream.</string>
<string name="profile_pin_changed_requires_refresh">El PIN de este perfil cambió. Conéctate una vez para actualizar el bloqueo en este dispositivo.</string> <string name="profile_pin_changed_requires_refresh">El PIN de este perfil cambió. Conéctate una vez para actualizar el bloqueo en este dispositivo.</string>
<string name="profile_pin_clear_failed">No se pudo quitar el bloqueo por PIN. Inténtalo de nuevo.</string> <string name="profile_pin_clear_failed">No se pudo quitar el bloqueo por PIN. Inténtalo de nuevo.</string>
<string name="profile_pin_clear_requires_internet">Conéctate a internet para quitar el bloqueo por PIN.</string> <string name="profile_pin_clear_requires_internet">Conéctate a internet para quitar el bloqueo por PIN.</string>
@ -1107,7 +1107,7 @@
<string name="details_browse_rail_popular">Popular</string> <string name="details_browse_rail_popular">Popular</string>
<string name="details_browse_rail_recent">Reciente</string> <string name="details_browse_rail_recent">Reciente</string>
<string name="details_browse_rail_title">%1$s • %2$s</string> <string name="details_browse_rail_title">%1$s • %2$s</string>
<string name="details_browse_rail_top_rated">Mejor valorado</string> <string name="details_browse_rail_top_rated">Mejor valorados</string>
<string name="details_certification">Clasificación</string> <string name="details_certification">Clasificación</string>
<string name="details_movie_details">Detalles de la película</string> <string name="details_movie_details">Detalles de la película</string>
<string name="details_original_language">Idioma original</string> <string name="details_original_language">Idioma original</string>

File diff suppressed because it is too large Load diff

View file

@ -29,11 +29,11 @@
<string name="addons_delete">Cancella addon</string> <string name="addons_delete">Cancella addon</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_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_empty_title">Nessun addon installato ancora.</string>
<string name="addons_error_enter_url">Inserisci l\'URL dell\'addon.</string> <string name="addons_error_enter_url">Inserisci l'URL dell'addon.</string>
<string name="addons_input_placeholder">URL Addon</string> <string name="addons_input_placeholder">URL Addon</string>
<string name="addons_install_button">Installa Addon</string> <string name="addons_install_button">Installa Addon</string>
<string name="addons_loading_manifest_details">Caricamento dettagli manifest...</string> <string name="addons_loading_manifest_details">Caricamento dettagli manifest...</string>
<string name="addons_modal_checking_message">Validazione dell\'URL del manifest e caricamento dei dettagli dell\'addon prima dell\'installazione.</string> <string name="addons_modal_checking_message">Validazione dell'URL del manifest e caricamento dei dettagli dell'addon prima dell'installazione.</string>
<string name="addons_modal_checking_title">Verifica Addon</string> <string name="addons_modal_checking_title">Verifica Addon</string>
<string name="addons_modal_failure_title">Installazione Fallita</string> <string name="addons_modal_failure_title">Installazione Fallita</string>
<string name="addons_modal_success_message">%1$s è stato validato e aggiunto con successo.</string> <string name="addons_modal_success_message">%1$s è stato validato e aggiunto con successo.</string>
@ -52,7 +52,7 @@
<string name="cd_selected">Selezionato</string> <string name="cd_selected">Selezionato</string>
<string name="collections_copy_json">Copia JSON</string> <string name="collections_copy_json">Copia JSON</string>
<string name="collections_count_summary">%1$d collezioni, %2$d cartelle</string> <string name="collections_count_summary">%1$d collezioni, %2$d cartelle</string>
<string name="collections_delete_message">Eliminare "%1$s"? L\'azione è irreversibile.</string> <string name="collections_delete_message">Eliminare "%1$s"? L'azione è irreversibile.</string>
<string name="collections_delete_title">Elimina Collezione</string> <string name="collections_delete_title">Elimina Collezione</string>
<string name="collections_editor_add_catalog">Aggiungi Catalogo</string> <string name="collections_editor_add_catalog">Aggiungi Catalogo</string>
<string name="collections_editor_add_folder">Aggiungi Cartella</string> <string name="collections_editor_add_folder">Aggiungi Cartella</string>
@ -68,15 +68,15 @@
<string name="collections_editor_done">Fatto</string> <string name="collections_editor_done">Fatto</string>
<string name="collections_editor_edit_collection">Modifica Collezione</string> <string name="collections_editor_edit_collection">Modifica Collezione</string>
<string name="collections_editor_edit_folder">Modifica Cartella</string> <string name="collections_editor_edit_folder">Modifica Cartella</string>
<string name="collections_editor_folder_editor_help">Imposta l\'identità della cartella, la presentazione e le sorgenti del catalogo con la stessa struttura dell\'editor principale delle collezioni.</string> <string name="collections_editor_folder_editor_help">Imposta l'identità della cartella, la presentazione e le sorgenti del catalogo con la stessa struttura dell'editor principale delle collezioni.</string>
<string name="collections_editor_folder_empty_subtitle">Aggiungine una per iniziare.</string> <string name="collections_editor_folder_empty_subtitle">Aggiungine una per iniziare.</string>
<string name="collections_editor_folder_empty_title">Ancora nessuna cartella</string> <string name="collections_editor_folder_empty_title">Ancora nessuna cartella</string>
<string name="collections_editor_folders">Cartelle</string> <string name="collections_editor_folders">Cartelle</string>
<string name="collections_editor_genre_filter">Filtro Genere</string> <string name="collections_editor_genre_filter">Filtro Genere</string>
<string name="collections_editor_hide_title_desc">Mostra solo l\'immagine di copertina</string> <string name="collections_editor_hide_title_desc">Mostra solo l'immagine di copertina</string>
<string name="collections_editor_hide_title">Nascondi Titolo</string> <string name="collections_editor_hide_title">Nascondi Titolo</string>
<string name="collections_editor_new_folder">Nuova Cartella</string> <string name="collections_editor_new_folder">Nuova Cartella</string>
<string name="collections_editor_pin_above_desc">Mostra questa collezione sopra tutti i normali cataloghi della home. In presenza di multiple collezioni fissate si seguirà l\'ordine di creazione.</string> <string name="collections_editor_pin_above_desc">Mostra questa collezione sopra tutti i normali cataloghi della home. In presenza di multiple collezioni fissate si seguirà l'ordine di creazione.</string>
<string name="collections_editor_pin_above">Fissa sopra i cataloghi</string> <string name="collections_editor_pin_above">Fissa sopra i cataloghi</string>
<string name="collections_editor_placeholder_backdrop">URL backdrop (opzionale)</string> <string name="collections_editor_placeholder_backdrop">URL backdrop (opzionale)</string>
<string name="collections_editor_placeholder_folder">Nome cartella</string> <string name="collections_editor_placeholder_folder">Nome cartella</string>
@ -229,14 +229,14 @@
<string name="compose_settings_root_account_description">Gestisci il tuo account, disconnettiti o eliminalo.</string> <string name="compose_settings_root_account_description">Gestisci il tuo account, disconnettiti o eliminalo.</string>
<string name="compose_settings_root_account_section">ACCOUNT</string> <string name="compose_settings_root_account_section">ACCOUNT</string>
<string name="compose_settings_root_appearance_description">Regola la presentazione della home e le preferenze visive.</string> <string name="compose_settings_root_appearance_description">Regola la presentazione della home e le preferenze visive.</string>
<string name="compose_settings_root_check_updates_description">Controlla se ci sono nuove versioni dell\'app.</string> <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_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">Gestisci gli addon e le sorgenti di scoperta.</string>
<string name="compose_settings_root_downloads_description">Gestisci i film e gli episodi scaricati.</string> <string name="compose_settings_root_downloads_description">Gestisci i film e gli episodi scaricati.</string>
<string name="compose_settings_root_downloads_title">Download</string> <string name="compose_settings_root_downloads_title">Download</string>
<string name="compose_settings_root_general_section">GENERALI</string> <string name="compose_settings_root_general_section">GENERALI</string>
<string name="compose_settings_root_integrations_description">Collega i servizi TMDB e MDBList.</string> <string name="compose_settings_root_integrations_description">Collega i servizi TMDB e MDBList.</string>
<string name="compose_settings_root_notifications_description">Gestisci gli avvisi per l\'uscita di nuovi episodi e invia una notifica di test.</string> <string name="compose_settings_root_notifications_description">Gestisci gli avvisi per l'uscita di nuovi episodi e invia una notifica di test.</string>
<string name="compose_settings_root_switch_profile_description">Passa a un profilo diverso.</string> <string name="compose_settings_root_switch_profile_description">Passa a un profilo diverso.</string>
<string name="compose_settings_root_switch_profile_title">Cambia profilo</string> <string name="compose_settings_root_switch_profile_title">Cambia profilo</string>
<string name="compose_settings_root_trakt_description">Collega Trakt, sincronizza la lista dei desideri e salva i titoli direttamente su Trakt.</string> <string name="compose_settings_root_trakt_description">Collega Trakt, sincronizza la lista dei desideri e salva i titoli direttamente su Trakt.</string>
@ -245,7 +245,7 @@
<string name="action_donate">Dona</string> <string name="action_donate">Dona</string>
<string name="cw_action_go_to_details">Vai ai dettagli</string> <string name="cw_action_go_to_details">Vai ai dettagli</string>
<string name="cw_action_remove">Rimuovi</string> <string name="cw_action_remove">Rimuovi</string>
<string name="cw_action_start_from_beginning">Riproduci dall\'inizio</string> <string name="cw_action_start_from_beginning">Riproduci dall'inizio</string>
<string name="detail_btn_play">Riproduci</string> <string name="detail_btn_play">Riproduci</string>
<string name="detail_comments_badge_rating">%1$d/10</string> <string name="detail_comments_badge_rating">%1$d/10</string>
<string name="detail_comments_badge_review">Recensione</string> <string name="detail_comments_badge_review">Recensione</string>
@ -284,7 +284,7 @@
<string name="settings_account_delete_account">Elimina account</string> <string name="settings_account_delete_account">Elimina account</string>
<string name="settings_account_delete_account_description">Questo eliminerà permanentemente il tuo account e tutti i dati associati.</string> <string name="settings_account_delete_account_description">Questo eliminerà permanentemente il tuo account e tutti i dati associati.</string>
<string name="settings_account_delete_confirm_message">Questa azione non può essere annullata. Tutti i tuoi dati, profili e la cronologia di sincronizzazione saranno rimossi per sempre.</string> <string name="settings_account_delete_confirm_message">Questa azione non può essere annullata. Tutti i tuoi dati, profili e la cronologia di sincronizzazione saranno rimossi per sempre.</string>
<string name="settings_account_delete_confirm_title">Eliminare l\'account?</string> <string name="settings_account_delete_confirm_title">Eliminare l'account?</string>
<string name="settings_account_email">Email</string> <string name="settings_account_email">Email</string>
<string name="settings_account_not_signed_in">Accesso non effettuato</string> <string name="settings_account_not_signed_in">Accesso non effettuato</string>
<string name="settings_account_sign_out">Disconnetti</string> <string name="settings_account_sign_out">Disconnetti</string>
@ -332,7 +332,7 @@
<string name="settings_poster_card_style">STILE LOCANDINA</string> <string name="settings_poster_card_style">STILE LOCANDINA</string>
<string name="settings_poster_card_width">Larghezza locandina</string> <string name="settings_poster_card_width">Larghezza locandina</string>
<string name="settings_poster_custom">Personalizzato</string> <string name="settings_poster_custom">Personalizzato</string>
<string name="settings_poster_description">Personalizza la larghezza e il raggio degli angoli delle locandine in tutta l\'app.</string> <string name="settings_poster_description">Personalizza la larghezza e il raggio degli angoli delle locandine in tutta l'app.</string>
<string name="settings_poster_hide_labels">Nascondi etichette</string> <string name="settings_poster_hide_labels">Nascondi etichette</string>
<string name="settings_poster_landscape_mode">Modalità orizzontale per le locandine della riga</string> <string name="settings_poster_landscape_mode">Modalità orizzontale per le locandine della riga</string>
<string name="settings_poster_live_preview">Anteprima in tempo reale</string> <string name="settings_poster_live_preview">Anteprima in tempo reale</string>
@ -351,10 +351,10 @@
<string name="settings_poster_width_dense">Denso</string> <string name="settings_poster_width_dense">Denso</string>
<string name="settings_poster_width_large">Grande</string> <string name="settings_poster_width_large">Grande</string>
<string name="settings_poster_width_standard">Standard</string> <string name="settings_poster_width_standard">Standard</string>
<string name="settings_continue_watching_resume_prompt_description">Mostra un popup per riprendere la visione all\'apertura dell\'app se eri uscito dal player.</string> <string name="settings_continue_watching_resume_prompt_description">Mostra un popup per riprendere la visione all'apertura dell'app se eri uscito dal player.</string>
<string name="settings_continue_watching_resume_prompt_title">Richiesta ripresa all\'avvio</string> <string name="settings_continue_watching_resume_prompt_title">Richiesta ripresa all'avvio</string>
<string name="settings_continue_watching_section_card_style">STILE SCHEDA</string> <string name="settings_continue_watching_section_card_style">STILE SCHEDA</string>
<string name="settings_continue_watching_section_on_launch">ALL\'AVVIO</string> <string name="settings_continue_watching_section_on_launch">ALL'AVVIO</string>
<string name="settings_continue_watching_section_up_next_behavior">COMPORTAMENTO \"PROSSIMO EPISODIO\"</string> <string name="settings_continue_watching_section_up_next_behavior">COMPORTAMENTO \"PROSSIMO EPISODIO\"</string>
<string name="settings_continue_watching_section_visibility">VISIBILITÀ</string> <string name="settings_continue_watching_section_visibility">VISIBILITÀ</string>
<string name="settings_continue_watching_show_description">Mostra la riga \"Continua a guardare\" nella schermata Home.</string> <string name="settings_continue_watching_show_description">Mostra la riga \"Continua a guardare\" nella schermata Home.</string>
@ -363,14 +363,14 @@
<string name="settings_continue_watching_style_poster_description">Scheda focalizzata sulla locandina</string> <string name="settings_continue_watching_style_poster_description">Scheda focalizzata sulla locandina</string>
<string name="settings_continue_watching_style_wide">Orizzontale</string> <string name="settings_continue_watching_style_wide">Orizzontale</string>
<string name="settings_continue_watching_style_wide_description">Scheda orizzontale ricca di informazioni</string> <string name="settings_continue_watching_style_wide_description">Scheda orizzontale ricca di informazioni</string>
<string name="settings_continue_watching_up_next_description">Se abilitato, \"Prossimo episodio\" continua sempre dall\'ultimo episodio visto. Se disabilitato, segue l\'episodio visto più di recente. Utile se riguardi spesso episodi precedenti.</string> <string name="settings_continue_watching_up_next_description">Se abilitato, \"Prossimo episodio\" continua sempre dall'ultimo episodio visto. Se disabilitato, segue l'episodio visto più di recente. Utile se riguardi spesso episodi precedenti.</string>
<string name="settings_continue_watching_up_next_title">Prossimo episodio dall\'ultimo visto</string> <string name="settings_continue_watching_up_next_title">Prossimo episodio dall'ultimo visto</string>
<string name="settings_content_discovery_section_home">HOME</string> <string name="settings_content_discovery_section_home">HOME</string>
<string name="settings_content_discovery_section_sources">SORGENTI</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">Installa, rimuovi, aggiorna e ordina le tue sorgenti di contenuto.</string>
<string name="settings_content_discovery_plugins_description">Installa repository di scraper JavaScript e testa i provider internamente.</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_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> <string name="settings_content_discovery_meta_screen_description">Disabilita le sezioni dei dettagli e riordina tutto ciò che sta sotto l'elemento Hero.</string>
<string name="settings_content_discovery_collections_description">Crea raggruppamenti di cataloghi personalizzati con cartelle mostrate in Home.</string> <string name="settings_content_discovery_collections_description">Crea raggruppamenti di cataloghi personalizzati con cartelle mostrate in Home.</string>
<string name="settings_integrations_section_title">INTEGRAZIONI</string> <string name="settings_integrations_section_title">INTEGRAZIONI</string>
<string name="settings_integrations_tmdb_description">Migliora le pagine dei dettagli con immagini, crediti, metadati degli episodi di TMDB e altro ancora.</string> <string name="settings_integrations_tmdb_description">Migliora le pagine dei dettagli con immagini, crediti, metadati degli episodi di TMDB e altro ancora.</string>
@ -416,7 +416,7 @@
<string name="settings_meta_section_sections">SEZIONI</string> <string name="settings_meta_section_sections">SEZIONI</string>
<string name="settings_meta_tab_group_format">Gruppo schede %1$d</string> <string name="settings_meta_tab_group_format">Gruppo schede %1$d</string>
<string name="settings_meta_tab_layout">Layout a schede</string> <string name="settings_meta_tab_layout">Layout a schede</string>
<string name="settings_meta_tab_layout_description">Raggruppa le sezioni in schede (tab) come nell\'app TV. Assegna fino a 3 sezioni per ogni gruppo.</string> <string name="settings_meta_tab_layout_description">Raggruppa le sezioni in schede (tab) come nell'app TV. Assegna fino a 3 sezioni per ogni gruppo.</string>
<string name="settings_meta_trailers">Trailer</string> <string name="settings_meta_trailers">Trailer</string>
<string name="settings_meta_trailers_description">Riga dei trailer e scorciatoie di riproduzione.</string> <string name="settings_meta_trailers_description">Riga dei trailer e scorciatoie di riproduzione.</string>
<string name="settings_notifications_disabled_in_app">Le notifiche sono attualmente disabilitate in Nuvio.</string> <string name="settings_notifications_disabled_in_app">Le notifiche sono attualmente disabilitate in Nuvio.</string>
@ -433,7 +433,7 @@
<string name="settings_notifications_test_title">Notifica di test</string> <string name="settings_notifications_test_title">Notifica di test</string>
<string name="community_section_title">Community</string> <string name="community_section_title">Community</string>
<string name="community_section_description">Scopri le persone che sviluppano e supportano Nuvio su Mobile, TV e Web.</string> <string name="community_section_description">Scopri le persone che sviluppano e supportano Nuvio su Mobile, TV e Web.</string>
<string name="community_supporters_not_configured">L\'API dei sostenitori non è configurata. Aggiungi DONATIONS_BASE_URL a local.properties.</string> <string name="community_supporters_not_configured">L'API dei sostenitori non è configurata. Aggiungi DONATIONS_BASE_URL a local.properties.</string>
<string name="community_tab_contributors">Collaboratori</string> <string name="community_tab_contributors">Collaboratori</string>
<string name="community_tab_supporters">Sostenitori</string> <string name="community_tab_supporters">Sostenitori</string>
<string name="community_open_github">Apri GitHub</string> <string name="community_open_github">Apri GitHub</string>
@ -472,7 +472,7 @@
<string name="settings_playback_anime_skip_client_id_description">Inserisci il tuo ID client API AnimeSkip. Ottienine uno su anime-skip.com.</string> <string name="settings_playback_anime_skip_client_id_description">Inserisci il tuo ID client API AnimeSkip. Ottienine uno su anime-skip.com.</string>
<string name="settings_playback_anime_skip_description">Cerca anche su AnimeSkip i timestamp per saltare le sigle (richiede ID client).</string> <string name="settings_playback_anime_skip_description">Cerca anche su AnimeSkip i timestamp per saltare le sigle (richiede ID client).</string>
<string name="settings_playback_auto_play_next_episode">Riproduzione automatica prossimo episodio</string> <string name="settings_playback_auto_play_next_episode">Riproduzione automatica prossimo episodio</string>
<string name="settings_playback_auto_play_next_episode_description">Trova e riproduce automaticamente l\'episodio successivo al raggiungimento della soglia.</string> <string name="settings_playback_auto_play_next_episode_description">Trova e riproduce automaticamente l'episodio successivo al raggiungimento della soglia.</string>
<string name="settings_playback_decoder_device_only">Solo dispositivo</string> <string name="settings_playback_decoder_device_only">Solo dispositivo</string>
<string name="settings_playback_decoder_prefer_app">Preferisci App (FFmpeg)</string> <string name="settings_playback_decoder_prefer_app">Preferisci App (FFmpeg)</string>
<string name="settings_playback_decoder_prefer_device">Preferisci dispositivo</string> <string name="settings_playback_decoder_prefer_device">Preferisci dispositivo</string>
@ -493,7 +493,7 @@
<string name="settings_playback_map_dv7_to_hevc">Mappa DV7 su HEVC</string> <string name="settings_playback_map_dv7_to_hevc">Mappa DV7 su HEVC</string>
<string name="settings_playback_map_dv7_to_hevc_description">Fallback da Dolby Vision Profile 7 a HEVC per i dispositivi non supportati.</string> <string name="settings_playback_map_dv7_to_hevc_description">Fallback da Dolby Vision Profile 7 a HEVC per i dispositivi non supportati.</string>
<string name="settings_playback_minutes_before_end">Minuti prima della fine</string> <string name="settings_playback_minutes_before_end">Minuti prima della fine</string>
<string name="settings_playback_minutes_before_end_description">Mostra la scheda dell\'episodio successivo questo numero di minuti prima della fine.</string> <string name="settings_playback_minutes_before_end_description">Mostra la scheda dell'episodio successivo questo numero di minuti prima della fine.</string>
<string name="settings_playback_minutes_value">%1$d min</string> <string name="settings_playback_minutes_value">%1$d min</string>
<string name="settings_playback_no_items_available">Nessun elemento disponibile</string> <string name="settings_playback_no_items_available">Nessun elemento disponibile</string>
<string name="settings_playback_not_set">Non impostato</string> <string name="settings_playback_not_set">Non impostato</string>
@ -528,8 +528,8 @@
<string name="settings_playback_render_type_effects_opengl">Effetti OpenGL</string> <string name="settings_playback_render_type_effects_opengl">Effetti OpenGL</string>
<string name="settings_playback_render_type_overlay_canvas">Overlay Canvas</string> <string name="settings_playback_render_type_overlay_canvas">Overlay Canvas</string>
<string name="settings_playback_render_type_overlay_opengl">Overlay OpenGL</string> <string name="settings_playback_render_type_overlay_opengl">Overlay OpenGL</string>
<string name="settings_playback_reuse_last_link">Riusa l\'ultimo link</string> <string name="settings_playback_reuse_last_link">Riusa l'ultimo link</string>
<string name="settings_playback_reuse_last_link_description">Riproduci automaticamente l\'ultimo flusso funzionante per lo stesso film/episodio se la cache è ancora valida.</string> <string name="settings_playback_reuse_last_link_description">Riproduci automaticamente l'ultimo flusso funzionante per lo stesso film/episodio se la cache è ancora valida.</string>
<string name="settings_playback_secondary_audio_language">Lingua audio secondaria</string> <string name="settings_playback_secondary_audio_language">Lingua audio secondaria</string>
<string name="settings_playback_secondary_subtitle_language">Lingua sottotitoli secondaria</string> <string name="settings_playback_secondary_subtitle_language">Lingua sottotitoli secondaria</string>
<string name="settings_playback_section_decoder">DECODER</string> <string name="settings_playback_section_decoder">DECODER</string>
@ -542,7 +542,7 @@
<string name="settings_playback_section_subtitle_rendering">RENDERING SOTTOTITOLI</string> <string name="settings_playback_section_subtitle_rendering">RENDERING SOTTOTITOLI</string>
<string name="settings_playback_selected_count">%1$d selezionati</string> <string name="settings_playback_selected_count">%1$d selezionati</string>
<string name="settings_playback_show_loading_overlay">Mostra overlay di caricamento</string> <string name="settings_playback_show_loading_overlay">Mostra overlay di caricamento</string>
<string name="settings_playback_show_loading_overlay_description">Mostra una schermata di caricamento all\'avvio della riproduzione di un flusso.</string> <string name="settings_playback_show_loading_overlay_description">Mostra una schermata di caricamento all'avvio della riproduzione di un flusso.</string>
<string name="settings_playback_skip_intro_outro_recap">Salta Intro/Outro/Recap</string> <string name="settings_playback_skip_intro_outro_recap">Salta Intro/Outro/Recap</string>
<string name="settings_playback_skip_intro_outro_recap_description">Mostra il pulsante \"salta\" durante i segmenti rilevati di introduzione, chiusura e riassunto.</string> <string name="settings_playback_skip_intro_outro_recap_description">Mostra il pulsante \"salta\" durante i segmenti rilevati di introduzione, chiusura e riassunto.</string>
<string name="settings_playback_source_scope">Ambito sorgente</string> <string name="settings_playback_source_scope">Ambito sorgente</string>
@ -568,14 +568,14 @@
<string name="settings_playback_threshold_mode_minutes_before_end">Minuti prima della fine</string> <string name="settings_playback_threshold_mode_minutes_before_end">Minuti prima della fine</string>
<string name="settings_playback_threshold_mode_percentage">Percentuale</string> <string name="settings_playback_threshold_mode_percentage">Percentuale</string>
<string name="settings_playback_threshold_percentage">Percentuale di soglia</string> <string name="settings_playback_threshold_percentage">Percentuale di soglia</string>
<string name="settings_playback_threshold_percentage_description">Mostra la scheda dell\'episodio successivo quando la riproduzione raggiunge questa percentuale.</string> <string name="settings_playback_threshold_percentage_description">Mostra la scheda dell'episodio successivo quando la riproduzione raggiunge questa percentuale.</string>
<string name="settings_playback_threshold_percentage_value">%1$d%%</string> <string name="settings_playback_threshold_percentage_value">%1$d%</string>
<string name="settings_playback_timeout_instant">Istantaneo</string> <string name="settings_playback_timeout_instant">Istantaneo</string>
<string name="settings_playback_timeout_seconds">%1$ds</string> <string name="settings_playback_timeout_seconds">%1$ds</string>
<string name="settings_playback_timeout_unlimited">Illimitato</string> <string name="settings_playback_timeout_unlimited">Illimitato</string>
<string name="settings_playback_tunneled_playback">Riproduzione Tunneled</string> <string name="settings_playback_tunneled_playback">Riproduzione Tunneled</string>
<string name="settings_playback_tunneled_playback_description">Abilita la riproduzione tunneled per una minore latenza nella sincronizzazione audio/video.</string> <string name="settings_playback_tunneled_playback_description">Abilita la riproduzione tunneled per una minore latenza nella sincronizzazione audio/video.</string>
<string name="settings_tmdb_add_api_key_first">Aggiungi la tua chiave API TMDB qui sotto prima di attivare l\'arricchimento.</string> <string name="settings_tmdb_add_api_key_first">Aggiungi la tua chiave API TMDB qui sotto prima di attivare l'arricchimento.</string>
<string name="settings_tmdb_api_key_label">Chiave API TMDB</string> <string name="settings_tmdb_api_key_label">Chiave API TMDB</string>
<string name="settings_tmdb_enable_enrichment">Abilita arricchimento TMDB</string> <string name="settings_tmdb_enable_enrichment">Abilita arricchimento TMDB</string>
<string name="settings_tmdb_enable_enrichment_description">Usa la tua chiave API TMDB per arricchire i metadati degli addon nella schermata dei dettagli quando è disponibile un ID TMDB o IMDb.</string> <string name="settings_tmdb_enable_enrichment_description">Usa la tua chiave API TMDB per arricchire i metadati degli addon nella schermata dei dettagli quando è disponibile un ID TMDB o IMDb.</string>
@ -590,7 +590,7 @@
<string name="settings_tmdb_module_credits">Crediti</string> <string name="settings_tmdb_module_credits">Crediti</string>
<string name="settings_tmdb_module_credits_description">Usa creatori, registi, sceneggiatori e foto del cast di TMDB.</string> <string name="settings_tmdb_module_credits_description">Usa creatori, registi, sceneggiatori e foto del cast di TMDB.</string>
<string name="settings_tmdb_module_details">Dettagli</string> <string name="settings_tmdb_module_details">Dettagli</string>
<string name="settings_tmdb_module_details_description">Usa info su rilascio, durata, classificazione d\'età, stato, paese e lingua di TMDB.</string> <string name="settings_tmdb_module_details_description">Usa info su rilascio, durata, classificazione d'età, stato, paese e lingua di TMDB.</string>
<string name="settings_tmdb_module_episodes">Episodi</string> <string name="settings_tmdb_module_episodes">Episodi</string>
<string name="settings_tmdb_module_episodes_description">Usa titoli, miniature, descrizioni e durate degli episodi di TMDB per le serie.</string> <string name="settings_tmdb_module_episodes_description">Usa titoli, miniature, descrizioni e durate degli episodi di TMDB per le serie.</string>
<string name="settings_tmdb_module_more_like_this">Altri titoli simili</string> <string name="settings_tmdb_module_more_like_this">Altri titoli simili</string>
@ -610,7 +610,7 @@
<string name="settings_tmdb_section_localization">LOCALIZZAZIONE</string> <string name="settings_tmdb_section_localization">LOCALIZZAZIONE</string>
<string name="settings_tmdb_section_modules">MODULI</string> <string name="settings_tmdb_section_modules">MODULI</string>
<string name="settings_tmdb_section_title">TMDB</string> <string name="settings_tmdb_section_title">TMDB</string>
<string name="settings_trakt_approval_redirect">Dopo l\'approvazione, verrai reindirizzato automaticamente.</string> <string name="settings_trakt_approval_redirect">Dopo l'approvazione, verrai reindirizzato automaticamente.</string>
<string name="settings_trakt_authentication">AUTENTICAZIONE</string> <string name="settings_trakt_authentication">AUTENTICAZIONE</string>
<string name="settings_trakt_comments">Commenti</string> <string name="settings_trakt_comments">Commenti</string>
<string name="settings_trakt_comments_description">Mostra i commenti di Trakt nei dettagli di film e serie TV.</string> <string name="settings_trakt_comments_description">Mostra i commenti di Trakt nei dettagli di film e serie TV.</string>
@ -620,7 +620,7 @@
<string name="settings_trakt_disconnect">Disconnetti</string> <string name="settings_trakt_disconnect">Disconnetti</string>
<string name="settings_trakt_failed_open_browser">Impossibile aprire il browser</string> <string name="settings_trakt_failed_open_browser">Impossibile aprire il browser</string>
<string name="settings_trakt_features">FUNZIONALITÀ</string> <string name="settings_trakt_features">FUNZIONALITÀ</string>
<string name="settings_trakt_finish_sign_in">Completa l\'accesso a Trakt nel tuo browser</string> <string name="settings_trakt_finish_sign_in">Completa l'accesso a Trakt nel tuo browser</string>
<string name="settings_trakt_intro_description">Tieni traccia di ciò che guardi, salva contenuti nella watchlist o in liste personalizzate e mantieni la tua libreria sincronizzata con Trakt.</string> <string name="settings_trakt_intro_description">Tieni traccia di ciò che guardi, salva contenuti nella watchlist o in liste personalizzate e mantieni la tua libreria sincronizzata con Trakt.</string>
<string name="settings_trakt_missing_credentials">Credenziali Trakt mancanti in local.properties (TRAKT_CLIENT_ID / TRAKT_CLIENT_SECRET).</string> <string name="settings_trakt_missing_credentials">Credenziali Trakt mancanti in local.properties (TRAKT_CLIENT_ID / TRAKT_CLIENT_SECRET).</string>
<string name="settings_trakt_open_login">Apri Login Trakt</string> <string name="settings_trakt_open_login">Apri Login Trakt</string>
@ -737,8 +737,8 @@
<string name="action_no">No</string> <string name="action_no">No</string>
<string name="action_update">Aggiorna</string> <string name="action_update">Aggiorna</string>
<string name="action_yes"></string> <string name="action_yes"></string>
<string name="app_exit_message">Vuoi uscire dall\'app?</string> <string name="app_exit_message">Vuoi uscire dall'app?</string>
<string name="app_exit_title">Esci dall\'app</string> <string name="app_exit_title">Esci dall'app</string>
<string name="catalog_empty_message">Questo catalogo non ha restituito alcun elemento.</string> <string name="catalog_empty_message">Questo catalogo non ha restituito alcun elemento.</string>
<string name="catalog_empty_title">Nessun titolo trovato</string> <string name="catalog_empty_title">Nessun titolo trovato</string>
<string name="details_check_connection">Controlla la tua connessione Wi-Fi o dati mobili e riprova.</string> <string name="details_check_connection">Controlla la tua connessione Wi-Fi o dati mobili e riprova.</string>
@ -746,8 +746,8 @@
<string name="details_failed_to_load">Caricamento fallito</string> <string name="details_failed_to_load">Caricamento fallito</string>
<string name="details_more_like_this">Altri titoli simili</string> <string name="details_more_like_this">Altri titoli simili</string>
<string name="details_seasons">Stagioni</string> <string name="details_seasons">Stagioni</string>
<string name="details_series_missing_numbers">L\'addon ha restituito i video per questa serie, ma nessuno include numeri di stagione o episodio.</string> <string name="details_series_missing_numbers">L'addon ha restituito i video per questa serie, ma nessuno include numeri di stagione o episodio.</string>
<string name="details_series_no_metadata">L\'addon non ha fornito metadati sugli episodi per questa serie.</string> <string name="details_series_no_metadata">L'addon non ha fornito metadati sugli episodi per questa serie.</string>
<string name="details_series_unpublished">Gli episodi non sono ancora stati pubblicati da questo addon.</string> <string name="details_series_unpublished">Gli episodi non sono ancora stati pubblicati da questo addon.</string>
<string name="details_servers_unreachable">Il dispositivo è online, ma Nuvio non è riuscito a raggiungere i server richiesti.</string> <string name="details_servers_unreachable">Il dispositivo è online, ma Nuvio non è riuscito a raggiungere i server richiesti.</string>
<string name="details_show_less">Mostra meno</string> <string name="details_show_less">Mostra meno</string>
@ -774,7 +774,7 @@
<string name="episode_mark_unwatched">Segna come non visto</string> <string name="episode_mark_unwatched">Segna come non visto</string>
<string name="episode_mark_watched">Segna come visto</string> <string name="episode_mark_watched">Segna come visto</string>
<string name="home_continue_watching_up_next">Prossimo episodio</string> <string name="home_continue_watching_up_next">Prossimo episodio</string>
<string name="home_continue_watching_watched">%1$d%% visto</string> <string name="home_continue_watching_watched">%1$s visto</string>
<string name="home_empty_no_active_addons_message">Installa e convalida almeno un addon prima di caricare le righe del catalogo in Home.</string> <string name="home_empty_no_active_addons_message">Installa e convalida almeno un addon prima di caricare le righe del catalogo in Home.</string>
<string name="home_empty_no_rows_message">Gli addon installati non espongono attualmente cataloghi compatibili con la bacheca senza gli extra richiesti.</string> <string name="home_empty_no_rows_message">Gli addon installati non espongono attualmente cataloghi compatibili con la bacheca senza gli extra richiesti.</string>
<string name="home_empty_no_rows_title">Nessuna riga disponibile in Home</string> <string name="home_empty_no_rows_title">Nessuna riga disponibile in Home</string>
@ -866,7 +866,7 @@
<string name="streams_no_direct_link">Nessun link diretto disponibile</string> <string name="streams_no_direct_link">Nessun link diretto disponibile</string>
<string name="streams_no_metadata">Nessun metadato disponibile</string> <string name="streams_no_metadata">Nessun metadato disponibile</string>
<string name="streams_refresh">Aggiorna flussi</string> <string name="streams_refresh">Aggiorna flussi</string>
<string name="streams_resume_from_percent">Riprendi dal %1$d%%</string> <string name="streams_resume_from_percent">Riprendi dal %1$d%</string>
<string name="streams_resume_from_time">Riprendi da %1$s</string> <string name="streams_resume_from_time">Riprendi da %1$s</string>
<string name="streams_size">DIMENSIONE %1$s</string> <string name="streams_size">DIMENSIONE %1$s</string>
<string name="trailer_close">Chiudi trailer</string> <string name="trailer_close">Chiudi trailer</string>
@ -876,13 +876,13 @@
<string name="updates_asset_line">%1$s • %2$s</string> <string name="updates_asset_line">%1$s • %2$s</string>
<string name="updates_check_failed">Controllo aggiornamenti fallito</string> <string name="updates_check_failed">Controllo aggiornamenti fallito</string>
<string name="updates_download_failed">Download fallito</string> <string name="updates_download_failed">Download fallito</string>
<string name="updates_downloading_progress">Download in corso: %1$d%%</string> <string name="updates_downloading_progress">Download in corso: %1$d%</string>
<string name="updates_install_failed">Impossibile avviare l\'installazione</string> <string name="updates_install_failed">Impossibile avviare l'installazione</string>
<string name="updates_latest_version">Stai utilizzando l\'ultima versione.</string> <string name="updates_latest_version">Stai utilizzando l'ultima versione.</string>
<string name="updates_message_allow_installs">Abilita l\'installazione di app per Nuvio, quindi torna qui e continua.</string> <string name="updates_message_allow_installs">Abilita l'installazione di app per Nuvio, quindi torna qui e continua.</string>
<string name="updates_message_downloading">Download aggiornamento in corso...</string> <string name="updates_message_downloading">Download aggiornamento in corso...</string>
<string name="updates_message_no_updates">Nessun aggiornamento trovato.</string> <string name="updates_message_no_updates">Nessun aggiornamento trovato.</string>
<string name="updates_message_ready">Una nuova versione è pronta per l\'installazione.</string> <string name="updates_message_ready">Una nuova versione è pronta per l'installazione.</string>
<string name="updates_not_available">Gli aggiornamenti in-app non sono disponibili in questa versione.</string> <string name="updates_not_available">Gli aggiornamenti in-app non sono disponibili in questa versione.</string>
<string name="updates_preparing_download">Preparazione del download</string> <string name="updates_preparing_download">Preparazione del download</string>
<string name="updates_release_notes">Note di rilascio</string> <string name="updates_release_notes">Note di rilascio</string>
@ -918,7 +918,7 @@
<string name="library_remove_title">Rimuovere dalla libreria?</string> <string name="library_remove_title">Rimuovere dalla libreria?</string>
<string name="media_movie">Film</string> <string name="media_movie">Film</string>
<string name="notifications_channel_episode_releases_description">Avvisi quando viene rilasciato un nuovo episodio di una serie salvata.</string> <string name="notifications_channel_episode_releases_description">Avvisi quando viene rilasciato un nuovo episodio di una serie salvata.</string>
<string name="notifications_test_preview_body">Anteprima dell\'avviso di uscita episodio.</string> <string name="notifications_test_preview_body">Anteprima dell'avviso di uscita episodio.</string>
<string name="notifications_test_send_failed">Impossibile inviare la notifica di test.</string> <string name="notifications_test_send_failed">Impossibile inviare la notifica di test.</string>
<string name="notifications_test_sent_for">Notifica di test inviata per %1$s.</string> <string name="notifications_test_sent_for">Notifica di test inviata per %1$s.</string>
<string name="player_unable_to_play_stream">Impossibile riprodurre questo flusso.</string> <string name="player_unable_to_play_stream">Impossibile riprodurre questo flusso.</string>
@ -933,7 +933,7 @@
<string name="stream_default_name">Flusso</string> <string name="stream_default_name">Flusso</string>
<string name="source_embedded">Incorporato (Embedded)</string> <string name="source_embedded">Incorporato (Embedded)</string>
<string name="trakt_authorization_denied">Autorizzazione negata</string> <string name="trakt_authorization_denied">Autorizzazione negata</string>
<string name="trakt_complete_sign_in_browser">Completa l\'accesso a Trakt nel tuo browser</string> <string name="trakt_complete_sign_in_browser">Completa l'accesso a Trakt nel tuo browser</string>
<string name="trakt_invalid_callback">Callback Trakt non valido</string> <string name="trakt_invalid_callback">Callback Trakt non valido</string>
<string name="trakt_invalid_callback_state">Stato callback Trakt non valido</string> <string name="trakt_invalid_callback_state">Stato callback Trakt non valido</string>
<string name="trakt_invalid_token_response">Risposta token Trakt non valida</string> <string name="trakt_invalid_token_response">Risposta token Trakt non valida</string>
@ -942,7 +942,7 @@
<string name="trakt_missing_auth_code">Trakt non ha restituito un codice di autorizzazione</string> <string name="trakt_missing_auth_code">Trakt non ha restituito un codice di autorizzazione</string>
<string name="trakt_missing_credentials">Credenziali Trakt mancanti</string> <string name="trakt_missing_credentials">Credenziali Trakt mancanti</string>
<string name="trakt_progress_load_failed">Impossibile caricare i progressi di Trakt</string> <string name="trakt_progress_load_failed">Impossibile caricare i progressi di Trakt</string>
<string name="trakt_sign_in_complete_failed">Impossibile completare l\'accesso a Trakt</string> <string name="trakt_sign_in_complete_failed">Impossibile completare l'accesso a Trakt</string>
<string name="trakt_user_fallback">Utente Trakt</string> <string name="trakt_user_fallback">Utente Trakt</string>
<string name="trakt_watchlist">Watchlist</string> <string name="trakt_watchlist">Watchlist</string>
<string name="generic_trailer">Trailer</string> <string name="generic_trailer">Trailer</string>
@ -953,10 +953,10 @@
<string name="action_resume_episode">Riprendi %1$s</string> <string name="action_resume_episode">Riprendi %1$s</string>
<string name="collections_import_error_empty_json">Il file JSON è vuoto.</string> <string name="collections_import_error_empty_json">Il file JSON è vuoto.</string>
<string name="collections_import_error_collection_blank_id">La collezione %1$d ha un ID vuoto.</string> <string name="collections_import_error_collection_blank_id">La collezione %1$d ha un ID vuoto.</string>
<string name="collections_import_error_collection_blank_title">La collezione \'%1$s\' ha un titolo vuoto.</string> <string name="collections_import_error_collection_blank_title">La collezione '%1$s' ha un titolo vuoto.</string>
<string name="collections_import_error_folder_blank_id">La cartella %1$d in \'%2$s\' ha un ID vuoto.</string> <string name="collections_import_error_folder_blank_id">La cartella %1$d in '%2$s' ha un ID vuoto.</string>
<string name="collections_import_error_folder_blank_title">La cartella \'%1$s\' in \'%2$s\' ha un titolo vuoto.</string> <string name="collections_import_error_folder_blank_title">La cartella '%1$s' in '%2$s' ha un titolo vuoto.</string>
<string name="collections_import_error_source_blank_fields">La sorgente %1$d nella cartella \'%2$s\' presenta campi vuoti.</string> <string name="collections_import_error_source_blank_fields">La sorgente %1$d nella cartella '%2$s' presenta campi vuoti.</string>
<string name="collections_import_error_invalid_json">JSON non valido: %1$s</string> <string name="collections_import_error_invalid_json">JSON non valido: %1$s</string>
<string name="collections_folder_addon_not_found">Addon non trovato: %1$s</string> <string name="collections_folder_addon_not_found">Addon non trovato: %1$s</string>
<string name="date_month_january">Gennaio</string> <string name="date_month_january">Gennaio</string>
@ -993,7 +993,7 @@
<string name="details_certification">Certificazione</string> <string name="details_certification">Certificazione</string>
<string name="details_movie_details">Dettagli film</string> <string name="details_movie_details">Dettagli film</string>
<string name="details_original_language">Lingua originale</string> <string name="details_original_language">Lingua originale</string>
<string name="details_origin_country">Paese d\'origine</string> <string name="details_origin_country">Paese d'origine</string>
<string name="details_release_info">Info rilascio</string> <string name="details_release_info">Info rilascio</string>
<string name="details_runtime">Durata</string> <string name="details_runtime">Durata</string>
<string name="details_season_view_posters">Locandine</string> <string name="details_season_view_posters">Locandine</string>

View file

@ -687,7 +687,7 @@
<string name="settings_playback_threshold_mode_percentage">Procent</string> <string name="settings_playback_threshold_mode_percentage">Procent</string>
<string name="settings_playback_threshold_percentage">Próg procentowy</string> <string name="settings_playback_threshold_percentage">Próg procentowy</string>
<string name="settings_playback_threshold_percentage_description">Pokaż kartę następnego odcinka, gdy odtwarzanie osiągnie ten procent.</string> <string name="settings_playback_threshold_percentage_description">Pokaż kartę następnego odcinka, gdy odtwarzanie osiągnie ten procent.</string>
<string name="settings_playback_threshold_percentage_value">%1$d%%</string> <string name="settings_playback_threshold_percentage_value">%1$d%</string>
<string name="settings_playback_timeout_instant">Natychmiast</string> <string name="settings_playback_timeout_instant">Natychmiast</string>
<string name="settings_playback_timeout_seconds">%1$ds</string> <string name="settings_playback_timeout_seconds">%1$ds</string>
<string name="settings_playback_timeout_unlimited">Bez limitu</string> <string name="settings_playback_timeout_unlimited">Bez limitu</string>
@ -892,7 +892,7 @@
<string name="episode_mark_unwatched">Oznacz jako nieobejrzane</string> <string name="episode_mark_unwatched">Oznacz jako nieobejrzane</string>
<string name="episode_mark_watched">Oznacz jako obejrzane</string> <string name="episode_mark_watched">Oznacz jako obejrzane</string>
<string name="home_continue_watching_up_next">Następny</string> <string name="home_continue_watching_up_next">Następny</string>
<string name="home_continue_watching_watched">%1$d%% obejrzane</string> <string name="home_continue_watching_watched">%1$s obejrzane</string>
<string name="home_empty_no_active_addons_message">Zainstaluj i sprawdź co najmniej jeden dodatek przed ładowaniem wierszy katalogów na ekranie głównym.</string> <string name="home_empty_no_active_addons_message">Zainstaluj i sprawdź co najmniej jeden dodatek przed ładowaniem wierszy katalogów na ekranie głównym.</string>
<string name="home_empty_no_rows_message">Zainstalowane dodatki nie udostępniają obecnie katalogów kompatybilnych z tablicą bez wymaganych dodatków.</string> <string name="home_empty_no_rows_message">Zainstalowane dodatki nie udostępniają obecnie katalogów kompatybilnych z tablicą bez wymaganych dodatków.</string>
<string name="home_empty_no_rows_title">Brak dostępnych wierszy ekranu głównego</string> <string name="home_empty_no_rows_title">Brak dostępnych wierszy ekranu głównego</string>
@ -984,7 +984,7 @@
<string name="streams_no_direct_link">Brak bezpośredniego linku strumienia</string> <string name="streams_no_direct_link">Brak bezpośredniego linku strumienia</string>
<string name="streams_no_metadata">Brak dostępnych metadanych</string> <string name="streams_no_metadata">Brak dostępnych metadanych</string>
<string name="streams_refresh">Odśwież strumienie</string> <string name="streams_refresh">Odśwież strumienie</string>
<string name="streams_resume_from_percent">Wznów od %1$d%%</string> <string name="streams_resume_from_percent">Wznów od %1$d%</string>
<string name="streams_resume_from_time">Wznów od %1$s</string> <string name="streams_resume_from_time">Wznów od %1$s</string>
<string name="streams_size">ROZMIAR %1$s</string> <string name="streams_size">ROZMIAR %1$s</string>
<string name="trailer_close">Zamknij zwiastun</string> <string name="trailer_close">Zamknij zwiastun</string>
@ -994,7 +994,7 @@
<string name="updates_asset_line">%1$s • %2$s</string> <string name="updates_asset_line">%1$s • %2$s</string>
<string name="updates_check_failed">Sprawdzanie aktualizacji nie powiodło się</string> <string name="updates_check_failed">Sprawdzanie aktualizacji nie powiodło się</string>
<string name="updates_download_failed">Pobieranie nie powiodło się</string> <string name="updates_download_failed">Pobieranie nie powiodło się</string>
<string name="updates_downloading_progress">Pobieranie %1$d%%</string> <string name="updates_downloading_progress">Pobieranie %1$d%</string>
<string name="updates_install_failed">Nie można rozpocząć instalacji</string> <string name="updates_install_failed">Nie można rozpocząć instalacji</string>
<string name="updates_latest_version">Używasz najnowszej wersji.</string> <string name="updates_latest_version">Używasz najnowszej wersji.</string>
<string name="updates_message_allow_installs">Zezwól na instalację aplikacji dla Nuvio, a następnie wróć i kontynuuj.</string> <string name="updates_message_allow_installs">Zezwól na instalację aplikacji dla Nuvio, a następnie wróć i kontynuuj.</string>
@ -1158,4 +1158,4 @@
<string name="unit_bytes_kb">KB</string> <string name="unit_bytes_kb">KB</string>
<string name="unit_bytes_mb">MB</string> <string name="unit_bytes_mb">MB</string>
<string name="unit_bytes_gb">GB</string> <string name="unit_bytes_gb">GB</string>
</resources> </resources>

File diff suppressed because it is too large Load diff

View file

@ -569,7 +569,7 @@
<string name="settings_playback_threshold_mode_percentage">Yüzde</string> <string name="settings_playback_threshold_mode_percentage">Yüzde</string>
<string name="settings_playback_threshold_percentage">Eşik yüzdesi</string> <string name="settings_playback_threshold_percentage">Eşik yüzdesi</string>
<string name="settings_playback_threshold_percentage_description">Oynatma bu yüzdeye ulaşınca sonraki bölüm kartını göster.</string> <string name="settings_playback_threshold_percentage_description">Oynatma bu yüzdeye ulaşınca sonraki bölüm kartını göster.</string>
<string name="settings_playback_threshold_percentage_value">%1$d%%</string> <string name="settings_playback_threshold_percentage_value">%1$d%</string>
<string name="settings_playback_timeout_instant">Hemen</string> <string name="settings_playback_timeout_instant">Hemen</string>
<string name="settings_playback_timeout_seconds">%1$dsn</string> <string name="settings_playback_timeout_seconds">%1$dsn</string>
<string name="settings_playback_timeout_unlimited">Sınırsız</string> <string name="settings_playback_timeout_unlimited">Sınırsız</string>
@ -774,7 +774,7 @@
<string name="episode_mark_unwatched">İzlenmedi olarak işaretle</string> <string name="episode_mark_unwatched">İzlenmedi olarak işaretle</string>
<string name="episode_mark_watched">İzlendi olarak işaretle</string> <string name="episode_mark_watched">İzlendi olarak işaretle</string>
<string name="home_continue_watching_up_next">Sıradaki</string> <string name="home_continue_watching_up_next">Sıradaki</string>
<string name="home_continue_watching_watched">%%%1$d izlendi</string> <string name="home_continue_watching_watched">%1$s izlendi</string>
<string name="home_empty_no_active_addons_message">Ana sayfada katalog satırlarını yüklemeden önce en az bir eklenti kurup doğrula.</string> <string name="home_empty_no_active_addons_message">Ana sayfada katalog satırlarını yüklemeden önce en az bir eklenti kurup doğrula.</string>
<string name="home_empty_no_rows_message">Kurulu eklentiler şu anda gerekli ek bilgiler olmadan ana sayfaya uyumlu katalog sunmuyor.</string> <string name="home_empty_no_rows_message">Kurulu eklentiler şu anda gerekli ek bilgiler olmadan ana sayfaya uyumlu katalog sunmuyor.</string>
<string name="home_empty_no_rows_title">Ana sayfa satırı yok</string> <string name="home_empty_no_rows_title">Ana sayfa satırı yok</string>
@ -866,7 +866,7 @@
<string name="streams_no_direct_link">Doğrudan yayın bağlantısı yok</string> <string name="streams_no_direct_link">Doğrudan yayın bağlantısı yok</string>
<string name="streams_no_metadata">Meta veri yok</string> <string name="streams_no_metadata">Meta veri yok</string>
<string name="streams_refresh">Yayınları yenile</string> <string name="streams_refresh">Yayınları yenile</string>
<string name="streams_resume_from_percent">%%%1$d konumundan devam et</string> <string name="streams_resume_from_percent">%1$d% konumundan devam et</string>
<string name="streams_resume_from_time">%1$s konumundan devam et</string> <string name="streams_resume_from_time">%1$s konumundan devam et</string>
<string name="streams_size">BOYUT %1$s</string> <string name="streams_size">BOYUT %1$s</string>
<string name="trailer_close">Fragmanı kapat</string> <string name="trailer_close">Fragmanı kapat</string>
@ -876,7 +876,7 @@
<string name="updates_asset_line">%1$s • %2$s</string> <string name="updates_asset_line">%1$s • %2$s</string>
<string name="updates_check_failed">Güncelleme kontrolü olmadı</string> <string name="updates_check_failed">Güncelleme kontrolü olmadı</string>
<string name="updates_download_failed">İndirme olmadı</string> <string name="updates_download_failed">İndirme olmadı</string>
<string name="updates_downloading_progress">İndiriliyor %%%1$d</string> <string name="updates_downloading_progress">İndiriliyor %1$d%</string>
<string name="updates_install_failed">Kurulum başlatılamadı</string> <string name="updates_install_failed">Kurulum başlatılamadı</string>
<string name="updates_latest_version">En güncel sürümü kullanıyorsun.</string> <string name="updates_latest_version">En güncel sürümü kullanıyorsun.</string>
<string name="updates_message_allow_installs">Nuvio için uygulama kurulumlarına izin ver, sonra geri gelip devam et.</string> <string name="updates_message_allow_installs">Nuvio için uygulama kurulumlarına izin ver, sonra geri gelip devam et.</string>

View file

@ -110,29 +110,38 @@
<string name="collections_editor_tmdb_production_mode">Production</string> <string name="collections_editor_tmdb_production_mode">Production</string>
<string name="collections_editor_tmdb_network_mode">Network</string> <string name="collections_editor_tmdb_network_mode">Network</string>
<string name="collections_editor_tmdb_collection_mode">Collection</string> <string name="collections_editor_tmdb_collection_mode">Collection</string>
<string name="collections_editor_tmdb_person_mode">Person</string>
<string name="collections_editor_tmdb_director_mode">Director</string>
<string name="collections_editor_tmdb_custom_mode">Custom</string> <string name="collections_editor_tmdb_custom_mode">Custom</string>
<string name="collections_editor_tmdb_help_presets">Pick a ready-made source. You can edit or remove it after adding.</string> <string name="collections_editor_tmdb_help_presets">Pick a ready-made source. You can edit or remove it after adding.</string>
<string name="collections_editor_tmdb_help_list">Paste a public TMDB list URL or only the number from the URL.</string> <string name="collections_editor_tmdb_help_list">Paste a public TMDB list URL or only the number from the URL.</string>
<string name="collections_editor_tmdb_help_production">Search by studio name, or paste a TMDB company ID/URL and add it directly.</string> <string name="collections_editor_tmdb_help_production">Search by studio name, or paste a TMDB company ID/URL and add it directly.</string>
<string name="collections_editor_tmdb_help_network">Enter a network ID. Common networks are available in Presets and quick filters.</string> <string name="collections_editor_tmdb_help_network">Enter a network ID. Common networks are available in Presets and quick filters.</string>
<string name="collections_editor_tmdb_help_collection">Search a movie collection name or paste the collection ID from TMDB.</string> <string name="collections_editor_tmdb_help_collection">Search a movie collection name or paste the collection ID from TMDB.</string>
<string name="collections_editor_tmdb_help_person">Enter a TMDB person ID or URL to build a row from cast credits.</string>
<string name="collections_editor_tmdb_help_director">Enter a TMDB person ID or URL to build a row from director credits.</string>
<string name="collections_editor_tmdb_help_discover">Build a live TMDB row using optional filters. Leave fields empty when you do not need that filter.</string> <string name="collections_editor_tmdb_help_discover">Build a live TMDB row using optional filters. Leave fields empty when you do not need that filter.</string>
<string name="collections_editor_tmdb_public_list">Public TMDB list</string> <string name="collections_editor_tmdb_public_list">Public TMDB list</string>
<string name="collections_editor_tmdb_network_id">Network ID</string> <string name="collections_editor_tmdb_network_id">Network ID</string>
<string name="collections_editor_tmdb_collection_id">Collection ID</string> <string name="collections_editor_tmdb_collection_id">Collection ID</string>
<string name="collections_editor_tmdb_person_id">Person ID</string>
<string name="collections_editor_tmdb_company_search">Production company name, ID, or URL</string> <string name="collections_editor_tmdb_company_search">Production company name, ID, or URL</string>
<string name="collections_editor_tmdb_id_or_url">TMDB ID or URL</string> <string name="collections_editor_tmdb_id_or_url">TMDB ID or URL</string>
<string name="collections_editor_tmdb_list_placeholder">https://www.themoviedb.org/list/8504994 or 8504994</string> <string name="collections_editor_tmdb_list_placeholder">https://www.themoviedb.org/list/8504994 or 8504994</string>
<string name="collections_editor_tmdb_network_placeholder">213 for Netflix, 49 for HBO, 2739 for Disney+</string> <string name="collections_editor_tmdb_network_placeholder">213 for Netflix, 49 for HBO, 2739 for Disney+</string>
<string name="collections_editor_tmdb_collection_placeholder">10 for Star Wars Collection</string> <string name="collections_editor_tmdb_collection_placeholder">10 for Star Wars Collection</string>
<string name="collections_editor_tmdb_company_placeholder">Marvel Studios, 420, or company URL</string> <string name="collections_editor_tmdb_company_placeholder">Marvel Studios, 420, or company URL</string>
<string name="collections_editor_tmdb_person_placeholder">31 for Tom Hanks, or person URL</string>
<string name="collections_editor_tmdb_search_helper">Examples: Marvel Studios, 420, or https://www.themoviedb.org/company/420.</string> <string name="collections_editor_tmdb_search_helper">Examples: Marvel Studios, 420, or https://www.themoviedb.org/company/420.</string>
<string name="collections_editor_tmdb_collection_helper">Example: Star Wars Collection, Harry Potter Collection, or a collection URL.</string> <string name="collections_editor_tmdb_collection_helper">Example: Star Wars Collection, Harry Potter Collection, or a collection URL.</string>
<string name="collections_editor_tmdb_network_helper">Example IDs: Netflix 213, HBO 49, Disney+ 2739.</string> <string name="collections_editor_tmdb_network_helper">Example IDs: Netflix 213, HBO 49, Disney+ 2739.</string>
<string name="collections_editor_tmdb_list_helper">Example: https://www.themoviedb.org/list/8504994 or 8504994.</string> <string name="collections_editor_tmdb_list_helper">Example: https://www.themoviedb.org/list/8504994 or 8504994.</string>
<string name="collections_editor_tmdb_person_helper">Example: https://www.themoviedb.org/person/31-tom-hanks or 31.</string>
<string name="collections_editor_tmdb_display_title">Display title</string> <string name="collections_editor_tmdb_display_title">Display title</string>
<string name="collections_editor_tmdb_title_helper">Shown as the row/tab name. If blank, Nuvio creates one from the source.</string> <string name="collections_editor_tmdb_title_helper">Shown as the row/tab name. If blank, Nuvio creates one from the source.</string>
<string name="collections_editor_tmdb_title_placeholder">Marvel Movies, Netflix Originals, Pixar</string> <string name="collections_editor_tmdb_title_placeholder">Marvel Movies, Netflix Originals, Pixar</string>
<string name="collections_editor_tmdb_person_title_placeholder">Tom Hanks Movies, Favorite Actors</string>
<string name="collections_editor_tmdb_director_title_placeholder">Christopher Nolan Movies, Favorite Directors</string>
<string name="collections_editor_tmdb_discover_title_placeholder">Best Action Movies, Korean Dramas, 2024 Animation</string> <string name="collections_editor_tmdb_discover_title_placeholder">Best Action Movies, Korean Dramas, 2024 Animation</string>
<string name="collections_editor_tmdb_search_results">Search Results</string> <string name="collections_editor_tmdb_search_results">Search Results</string>
<string name="collections_editor_tmdb_collection">TMDB Collection</string> <string name="collections_editor_tmdb_collection">TMDB Collection</string>
@ -212,6 +221,7 @@
<string name="collections_editor_tmdb_network_disney_plus">Disney+</string> <string name="collections_editor_tmdb_network_disney_plus">Disney+</string>
<string name="collections_editor_tmdb_network_prime_video">Prime Video</string> <string name="collections_editor_tmdb_network_prime_video">Prime Video</string>
<string name="collections_editor_tmdb_network_hulu">Hulu</string> <string name="collections_editor_tmdb_network_hulu">Hulu</string>
<string name="collections_editor_tmdb_sort_original">Original</string>
<string name="collections_editor_tmdb_sort_popular">Popular</string> <string name="collections_editor_tmdb_sort_popular">Popular</string>
<string name="collections_editor_tmdb_sort_top_rated">Top Rated</string> <string name="collections_editor_tmdb_sort_top_rated">Top Rated</string>
<string name="collections_editor_tmdb_sort_recent">Recent</string> <string name="collections_editor_tmdb_sort_recent">Recent</string>
@ -219,6 +229,8 @@
<string name="collections_editor_tmdb_subtitle_movie_collection">TMDB Movie Collection</string> <string name="collections_editor_tmdb_subtitle_movie_collection">TMDB Movie Collection</string>
<string name="collections_editor_tmdb_subtitle_production">Production</string> <string name="collections_editor_tmdb_subtitle_production">Production</string>
<string name="collections_editor_tmdb_subtitle_network">Network</string> <string name="collections_editor_tmdb_subtitle_network">Network</string>
<string name="collections_editor_tmdb_subtitle_person">Person</string>
<string name="collections_editor_tmdb_subtitle_director">Director</string>
<string name="collections_editor_tmdb_subtitle_discover">TMDB Discover</string> <string name="collections_editor_tmdb_subtitle_discover">TMDB Discover</string>
<string name="collections_empty_subtitle">Create one to organize your catalogs.</string> <string name="collections_empty_subtitle">Create one to organize your catalogs.</string>
<string name="collections_empty_title">No collections yet</string> <string name="collections_empty_title">No collections yet</string>
@ -687,7 +699,7 @@
<string name="settings_playback_threshold_mode_percentage">Percentage</string> <string name="settings_playback_threshold_mode_percentage">Percentage</string>
<string name="settings_playback_threshold_percentage">Threshold Percentage</string> <string name="settings_playback_threshold_percentage">Threshold Percentage</string>
<string name="settings_playback_threshold_percentage_description">Show next episode card when playback reaches this percentage.</string> <string name="settings_playback_threshold_percentage_description">Show next episode card when playback reaches this percentage.</string>
<string name="settings_playback_threshold_percentage_value">%1$d%%</string> <string name="settings_playback_threshold_percentage_value">%1$d%</string>
<string name="settings_playback_timeout_instant">Instant</string> <string name="settings_playback_timeout_instant">Instant</string>
<string name="settings_playback_timeout_seconds">%1$ds</string> <string name="settings_playback_timeout_seconds">%1$ds</string>
<string name="settings_playback_timeout_unlimited">Unlimited</string> <string name="settings_playback_timeout_unlimited">Unlimited</string>
@ -892,7 +904,7 @@
<string name="episode_mark_unwatched">Mark as unwatched</string> <string name="episode_mark_unwatched">Mark as unwatched</string>
<string name="episode_mark_watched">Mark as watched</string> <string name="episode_mark_watched">Mark as watched</string>
<string name="home_continue_watching_up_next">Up next</string> <string name="home_continue_watching_up_next">Up next</string>
<string name="home_continue_watching_watched">%1$d%% watched</string> <string name="home_continue_watching_watched">%1$s watched</string>
<string name="home_empty_no_active_addons_message">Install and validate at least one addon before loading catalog rows on Home.</string> <string name="home_empty_no_active_addons_message">Install and validate at least one addon before loading catalog rows on Home.</string>
<string name="home_empty_no_rows_message">Installed addons do not currently expose board-compatible catalogs without required extras.</string> <string name="home_empty_no_rows_message">Installed addons do not currently expose board-compatible catalogs without required extras.</string>
<string name="home_empty_no_rows_title">No home rows available</string> <string name="home_empty_no_rows_title">No home rows available</string>
@ -984,7 +996,7 @@
<string name="streams_no_direct_link">No direct stream link available</string> <string name="streams_no_direct_link">No direct stream link available</string>
<string name="streams_no_metadata">No metadata available</string> <string name="streams_no_metadata">No metadata available</string>
<string name="streams_refresh">Refresh streams</string> <string name="streams_refresh">Refresh streams</string>
<string name="streams_resume_from_percent">Resume from %1$d%%</string> <string name="streams_resume_from_percent">Resume from %1$d%</string>
<string name="streams_resume_from_time">Resume from %1$s</string> <string name="streams_resume_from_time">Resume from %1$s</string>
<string name="streams_size">SIZE %1$s</string> <string name="streams_size">SIZE %1$s</string>
<string name="trailer_close">Close trailer</string> <string name="trailer_close">Close trailer</string>
@ -994,7 +1006,7 @@
<string name="updates_asset_line">%1$s • %2$s</string> <string name="updates_asset_line">%1$s • %2$s</string>
<string name="updates_check_failed">Update check failed</string> <string name="updates_check_failed">Update check failed</string>
<string name="updates_download_failed">Download failed</string> <string name="updates_download_failed">Download failed</string>
<string name="updates_downloading_progress">Downloading %1$d%%</string> <string name="updates_downloading_progress">Downloading %1$d%</string>
<string name="updates_install_failed">Unable to start installation</string> <string name="updates_install_failed">Unable to start installation</string>
<string name="updates_latest_version">You&apos;re using the latest version.</string> <string name="updates_latest_version">You&apos;re using the latest version.</string>
<string name="updates_message_allow_installs">Enable app installs for Nuvio, then come back and continue.</string> <string name="updates_message_allow_installs">Enable app installs for Nuvio, then come back and continue.</string>

View file

@ -0,0 +1,43 @@
package com.nuvio.app.features.collection
import com.nuvio.app.features.addons.AddonCatalog
import com.nuvio.app.features.addons.ManagedAddon
internal data class ResolvedCollectionCatalog(
val addon: ManagedAddon,
val catalog: AddonCatalog,
)
internal fun List<ManagedAddon>.findCollectionCatalog(
source: CollectionCatalogSource,
): ResolvedCollectionCatalog? {
val declaredAddon = firstOrNull { it.manifest?.id == source.addonId }
val declaredCatalog = declaredAddon?.manifest?.catalogs?.findSourceCatalog(source)
if (declaredAddon != null && declaredCatalog != null) {
return ResolvedCollectionCatalog(addon = declaredAddon, catalog = declaredCatalog)
}
return firstNotNullOfOrNull { addon ->
val catalog = addon.manifest?.catalogs?.find {
it.id == source.catalogId && it.type == source.type
} ?: return@firstNotNullOfOrNull null
ResolvedCollectionCatalog(addon = addon, catalog = catalog)
}
}
internal fun List<AvailableCatalog>.findAvailableCatalog(
source: CollectionCatalogSource,
): AvailableCatalog? {
val declaredCatalogs = filter { it.addonId == source.addonId }
return declaredCatalogs.findSourceCatalog(source)
?: firstOrNull { it.catalogId == source.catalogId && it.type == source.type }
}
private fun List<AddonCatalog>.findSourceCatalog(source: CollectionCatalogSource): AddonCatalog? =
find { it.id == source.catalogId && it.type == source.type }
?: find { it.id == source.catalogId.substringBefore(",") && it.type == source.type }
private fun List<AvailableCatalog>.findSourceCatalog(source: CollectionCatalogSource): AvailableCatalog? =
find { it.catalogId == source.catalogId && it.type == source.type }
?: find { it.catalogId == source.catalogId.substringBefore(",") && it.type == source.type }

View file

@ -46,6 +46,8 @@ enum class TmdbBuilderMode {
PRODUCTION, PRODUCTION,
NETWORK, NETWORK,
COLLECTION, COLLECTION,
PERSON,
DIRECTOR,
DISCOVER, DISCOVER,
} }
@ -340,9 +342,15 @@ object CollectionEditorRepository {
} else { } else {
_uiState.value.tmdbMediaType _uiState.value.tmdbMediaType
} }
val sortBy = when (mode) {
TmdbBuilderMode.LIST,
TmdbBuilderMode.COLLECTION -> TmdbCollectionSort.ORIGINAL.value
else -> TmdbCollectionSort.POPULAR_DESC.value
}
_uiState.value = _uiState.value.copy( _uiState.value = _uiState.value.copy(
tmdbBuilderMode = mode, tmdbBuilderMode = mode,
tmdbMediaType = mediaType, tmdbMediaType = mediaType,
tmdbSortBy = sortBy,
tmdbMediaBoth = if ( tmdbMediaBoth = if (
mode == TmdbBuilderMode.NETWORK || mode == TmdbBuilderMode.NETWORK ||
mode == TmdbBuilderMode.LIST || mode == TmdbBuilderMode.LIST ||
@ -459,6 +467,8 @@ object CollectionEditorRepository {
TmdbBuilderMode.COLLECTION -> TmdbCollectionSourceType.COLLECTION TmdbBuilderMode.COLLECTION -> TmdbCollectionSourceType.COLLECTION
TmdbBuilderMode.PRODUCTION -> TmdbCollectionSourceType.COMPANY TmdbBuilderMode.PRODUCTION -> TmdbCollectionSourceType.COMPANY
TmdbBuilderMode.NETWORK -> TmdbCollectionSourceType.NETWORK TmdbBuilderMode.NETWORK -> TmdbCollectionSourceType.NETWORK
TmdbBuilderMode.PERSON -> TmdbCollectionSourceType.PERSON
TmdbBuilderMode.DIRECTOR -> TmdbCollectionSourceType.DIRECTOR
TmdbBuilderMode.DISCOVER -> TmdbCollectionSourceType.DISCOVER TmdbBuilderMode.DISCOVER -> TmdbCollectionSourceType.DISCOVER
} }
val id = TmdbCollectionSourceResolver.parseTmdbId(state.tmdbInput) val id = TmdbCollectionSourceResolver.parseTmdbId(state.tmdbInput)
@ -473,6 +483,8 @@ object CollectionEditorRepository {
TmdbCollectionSourceType.COLLECTION -> "TMDB Collection ${id ?: ""}".trim() TmdbCollectionSourceType.COLLECTION -> "TMDB Collection ${id ?: ""}".trim()
TmdbCollectionSourceType.COMPANY -> "TMDB Production ${id ?: ""}".trim() TmdbCollectionSourceType.COMPANY -> "TMDB Production ${id ?: ""}".trim()
TmdbCollectionSourceType.NETWORK -> "TMDB Network ${id ?: ""}".trim() TmdbCollectionSourceType.NETWORK -> "TMDB Network ${id ?: ""}".trim()
TmdbCollectionSourceType.PERSON -> "TMDB Person ${id ?: ""}".trim()
TmdbCollectionSourceType.DIRECTOR -> "TMDB Director ${id ?: ""}".trim()
TmdbCollectionSourceType.DISCOVER -> "TMDB Discover" TmdbCollectionSourceType.DISCOVER -> "TMDB Discover"
} }
} }
@ -561,6 +573,8 @@ private val coverMetadataSourceTypes = setOf(
TmdbCollectionSourceType.COLLECTION, TmdbCollectionSourceType.COLLECTION,
TmdbCollectionSourceType.COMPANY, TmdbCollectionSourceType.COMPANY,
TmdbCollectionSourceType.NETWORK, TmdbCollectionSourceType.NETWORK,
TmdbCollectionSourceType.PERSON,
TmdbCollectionSourceType.DIRECTOR,
) )
private fun CollectionCatalogSource.toCollectionSource(): CollectionSource = private fun CollectionCatalogSource.toCollectionSource(): CollectionSource =
@ -591,6 +605,8 @@ private fun selectedMediaTypes(
): List<TmdbCollectionMediaType> = ): List<TmdbCollectionMediaType> =
when (sourceType) { when (sourceType) {
TmdbCollectionSourceType.COMPANY, TmdbCollectionSourceType.COMPANY,
TmdbCollectionSourceType.PERSON,
TmdbCollectionSourceType.DIRECTOR,
TmdbCollectionSourceType.DISCOVER -> if (state.tmdbMediaBoth) { TmdbCollectionSourceType.DISCOVER -> if (state.tmdbMediaBoth) {
listOf(TmdbCollectionMediaType.MOVIE, TmdbCollectionMediaType.TV) listOf(TmdbCollectionMediaType.MOVIE, TmdbCollectionMediaType.TV)
} else { } else {

View file

@ -111,9 +111,7 @@ fun CollectionEditorScreen(
val genrePickerSource = genrePickerIndex?.let { editingFolder.resolvedSources.getOrNull(it) } val genrePickerSource = genrePickerIndex?.let { editingFolder.resolvedSources.getOrNull(it) }
val genrePickerCatalogSource = genrePickerSource?.addonCatalogSource() val genrePickerCatalogSource = genrePickerSource?.addonCatalogSource()
val genrePickerCatalog = genrePickerCatalogSource?.let { source -> val genrePickerCatalog = genrePickerCatalogSource?.let { source ->
state.availableCatalogs.find { state.availableCatalogs.findAvailableCatalog(source)
it.addonId == source.addonId && it.type == source.type && it.catalogId == source.catalogId
}
} }
FolderEditorPage( FolderEditorPage(
@ -757,11 +755,7 @@ private fun FolderEditorPage(
} else if (addonSource != null) { } else if (addonSource != null) {
FolderCatalogSourceCard( FolderCatalogSourceCard(
source = addonSource, source = addonSource,
matchingCatalog = state.availableCatalogs.find { matchingCatalog = state.availableCatalogs.findAvailableCatalog(addonSource),
it.addonId == addonSource.addonId &&
it.type == addonSource.type &&
it.catalogId == addonSource.catalogId
},
onRemove = { CollectionEditorRepository.removeCatalogSource(index) }, onRemove = { CollectionEditorRepository.removeCatalogSource(index) },
onOpenGenrePicker = { CollectionEditorRepository.showGenrePicker(index) }, onOpenGenrePicker = { CollectionEditorRepository.showGenrePicker(index) },
) )
@ -897,13 +891,19 @@ private fun TmdbSourcePickerScreen(
TmdbBuilderMode.COLLECTION -> TmdbCollectionSourceType.COLLECTION TmdbBuilderMode.COLLECTION -> TmdbCollectionSourceType.COLLECTION
TmdbBuilderMode.PRODUCTION -> TmdbCollectionSourceType.COMPANY TmdbBuilderMode.PRODUCTION -> TmdbCollectionSourceType.COMPANY
TmdbBuilderMode.NETWORK -> TmdbCollectionSourceType.NETWORK TmdbBuilderMode.NETWORK -> TmdbCollectionSourceType.NETWORK
TmdbBuilderMode.PERSON -> TmdbCollectionSourceType.PERSON
TmdbBuilderMode.DIRECTOR -> TmdbCollectionSourceType.DIRECTOR
TmdbBuilderMode.DISCOVER -> TmdbCollectionSourceType.DISCOVER TmdbBuilderMode.DISCOVER -> TmdbCollectionSourceType.DISCOVER
} }
val requiresId = sourceType != TmdbCollectionSourceType.DISCOVER val requiresId = sourceType != TmdbCollectionSourceType.DISCOVER
val showMediaControls = state.tmdbBuilderMode == TmdbBuilderMode.PRODUCTION || val showMediaControls = state.tmdbBuilderMode == TmdbBuilderMode.PRODUCTION ||
state.tmdbBuilderMode == TmdbBuilderMode.PERSON ||
state.tmdbBuilderMode == TmdbBuilderMode.DIRECTOR ||
state.tmdbBuilderMode == TmdbBuilderMode.DISCOVER state.tmdbBuilderMode == TmdbBuilderMode.DISCOVER
val showSortControls = state.tmdbBuilderMode == TmdbBuilderMode.PRODUCTION || val showSortControls = state.tmdbBuilderMode == TmdbBuilderMode.PRODUCTION ||
state.tmdbBuilderMode == TmdbBuilderMode.NETWORK || state.tmdbBuilderMode == TmdbBuilderMode.NETWORK ||
state.tmdbBuilderMode == TmdbBuilderMode.PERSON ||
state.tmdbBuilderMode == TmdbBuilderMode.DIRECTOR ||
state.tmdbBuilderMode == TmdbBuilderMode.DISCOVER state.tmdbBuilderMode == TmdbBuilderMode.DISCOVER
val showFilterControls = state.tmdbBuilderMode == TmdbBuilderMode.DISCOVER val showFilterControls = state.tmdbBuilderMode == TmdbBuilderMode.DISCOVER
@ -1892,6 +1892,8 @@ private fun tmdbBuilderModeLabel(mode: TmdbBuilderMode): String =
TmdbBuilderMode.PRODUCTION -> stringResource(Res.string.collections_editor_tmdb_production_mode) TmdbBuilderMode.PRODUCTION -> stringResource(Res.string.collections_editor_tmdb_production_mode)
TmdbBuilderMode.NETWORK -> stringResource(Res.string.collections_editor_tmdb_network_mode) TmdbBuilderMode.NETWORK -> stringResource(Res.string.collections_editor_tmdb_network_mode)
TmdbBuilderMode.COLLECTION -> stringResource(Res.string.collections_editor_tmdb_collection_mode) TmdbBuilderMode.COLLECTION -> stringResource(Res.string.collections_editor_tmdb_collection_mode)
TmdbBuilderMode.PERSON -> stringResource(Res.string.collections_editor_tmdb_person_mode)
TmdbBuilderMode.DIRECTOR -> stringResource(Res.string.collections_editor_tmdb_director_mode)
TmdbBuilderMode.DISCOVER -> stringResource(Res.string.collections_editor_tmdb_custom_mode) TmdbBuilderMode.DISCOVER -> stringResource(Res.string.collections_editor_tmdb_custom_mode)
} }
@ -1903,6 +1905,8 @@ private fun tmdbModeHelpText(mode: TmdbBuilderMode): String =
TmdbBuilderMode.PRODUCTION -> stringResource(Res.string.collections_editor_tmdb_help_production) TmdbBuilderMode.PRODUCTION -> stringResource(Res.string.collections_editor_tmdb_help_production)
TmdbBuilderMode.NETWORK -> stringResource(Res.string.collections_editor_tmdb_help_network) TmdbBuilderMode.NETWORK -> stringResource(Res.string.collections_editor_tmdb_help_network)
TmdbBuilderMode.COLLECTION -> stringResource(Res.string.collections_editor_tmdb_help_collection) TmdbBuilderMode.COLLECTION -> stringResource(Res.string.collections_editor_tmdb_help_collection)
TmdbBuilderMode.PERSON -> stringResource(Res.string.collections_editor_tmdb_help_person)
TmdbBuilderMode.DIRECTOR -> stringResource(Res.string.collections_editor_tmdb_help_director)
TmdbBuilderMode.DISCOVER -> stringResource(Res.string.collections_editor_tmdb_help_discover) TmdbBuilderMode.DISCOVER -> stringResource(Res.string.collections_editor_tmdb_help_discover)
} }
@ -1913,6 +1917,8 @@ private fun tmdbInputLabel(mode: TmdbBuilderMode): String =
TmdbBuilderMode.NETWORK -> stringResource(Res.string.collections_editor_tmdb_network_id) TmdbBuilderMode.NETWORK -> stringResource(Res.string.collections_editor_tmdb_network_id)
TmdbBuilderMode.COLLECTION -> stringResource(Res.string.collections_editor_tmdb_collection_id) TmdbBuilderMode.COLLECTION -> stringResource(Res.string.collections_editor_tmdb_collection_id)
TmdbBuilderMode.PRODUCTION -> stringResource(Res.string.collections_editor_tmdb_company_search) TmdbBuilderMode.PRODUCTION -> stringResource(Res.string.collections_editor_tmdb_company_search)
TmdbBuilderMode.PERSON,
TmdbBuilderMode.DIRECTOR -> stringResource(Res.string.collections_editor_tmdb_person_id)
else -> stringResource(Res.string.collections_editor_tmdb_id_or_url) else -> stringResource(Res.string.collections_editor_tmdb_id_or_url)
} }
@ -1923,6 +1929,8 @@ private fun tmdbInputPlaceholder(mode: TmdbBuilderMode): String =
TmdbBuilderMode.NETWORK -> stringResource(Res.string.collections_editor_tmdb_network_placeholder) TmdbBuilderMode.NETWORK -> stringResource(Res.string.collections_editor_tmdb_network_placeholder)
TmdbBuilderMode.COLLECTION -> stringResource(Res.string.collections_editor_tmdb_collection_placeholder) TmdbBuilderMode.COLLECTION -> stringResource(Res.string.collections_editor_tmdb_collection_placeholder)
TmdbBuilderMode.PRODUCTION -> stringResource(Res.string.collections_editor_tmdb_company_placeholder) TmdbBuilderMode.PRODUCTION -> stringResource(Res.string.collections_editor_tmdb_company_placeholder)
TmdbBuilderMode.PERSON,
TmdbBuilderMode.DIRECTOR -> stringResource(Res.string.collections_editor_tmdb_person_placeholder)
else -> stringResource(Res.string.collections_editor_tmdb_id_or_url) else -> stringResource(Res.string.collections_editor_tmdb_id_or_url)
} }
@ -1933,6 +1941,8 @@ private fun tmdbInputHelper(mode: TmdbBuilderMode): String =
TmdbBuilderMode.COLLECTION -> stringResource(Res.string.collections_editor_tmdb_collection_helper) TmdbBuilderMode.COLLECTION -> stringResource(Res.string.collections_editor_tmdb_collection_helper)
TmdbBuilderMode.NETWORK -> stringResource(Res.string.collections_editor_tmdb_network_helper) TmdbBuilderMode.NETWORK -> stringResource(Res.string.collections_editor_tmdb_network_helper)
TmdbBuilderMode.LIST -> stringResource(Res.string.collections_editor_tmdb_list_helper) TmdbBuilderMode.LIST -> stringResource(Res.string.collections_editor_tmdb_list_helper)
TmdbBuilderMode.PERSON,
TmdbBuilderMode.DIRECTOR -> stringResource(Res.string.collections_editor_tmdb_person_helper)
else -> "" else -> ""
} }
@ -1940,12 +1950,15 @@ private fun tmdbInputHelper(mode: TmdbBuilderMode): String =
private fun tmdbTitlePlaceholder(mode: TmdbBuilderMode): String = private fun tmdbTitlePlaceholder(mode: TmdbBuilderMode): String =
when (mode) { when (mode) {
TmdbBuilderMode.DISCOVER -> stringResource(Res.string.collections_editor_tmdb_discover_title_placeholder) TmdbBuilderMode.DISCOVER -> stringResource(Res.string.collections_editor_tmdb_discover_title_placeholder)
TmdbBuilderMode.PERSON -> stringResource(Res.string.collections_editor_tmdb_person_title_placeholder)
TmdbBuilderMode.DIRECTOR -> stringResource(Res.string.collections_editor_tmdb_director_title_placeholder)
else -> stringResource(Res.string.collections_editor_tmdb_title_placeholder) else -> stringResource(Res.string.collections_editor_tmdb_title_placeholder)
} }
@Composable @Composable
private fun tmdbSortLabel(sort: TmdbCollectionSort): String = private fun tmdbSortLabel(sort: TmdbCollectionSort): String =
when (sort) { when (sort) {
TmdbCollectionSort.ORIGINAL -> stringResource(Res.string.collections_editor_tmdb_sort_original)
TmdbCollectionSort.POPULAR_DESC -> stringResource(Res.string.collections_editor_tmdb_sort_popular) TmdbCollectionSort.POPULAR_DESC -> stringResource(Res.string.collections_editor_tmdb_sort_popular)
TmdbCollectionSort.VOTE_AVERAGE_DESC -> stringResource(Res.string.collections_editor_tmdb_sort_top_rated) TmdbCollectionSort.VOTE_AVERAGE_DESC -> stringResource(Res.string.collections_editor_tmdb_sort_top_rated)
TmdbCollectionSort.RELEASE_DATE_DESC -> stringResource(Res.string.collections_editor_tmdb_sort_recent) TmdbCollectionSort.RELEASE_DATE_DESC -> stringResource(Res.string.collections_editor_tmdb_sort_recent)
@ -1979,6 +1992,16 @@ private fun tmdbSourceSubtitle(source: CollectionSource): String {
stringResource(Res.string.collections_editor_tmdb_series), stringResource(Res.string.collections_editor_tmdb_series),
sort, sort,
).joinToString("") ).joinToString("")
TmdbCollectionSourceType.PERSON -> listOf(
stringResource(Res.string.collections_editor_tmdb_subtitle_person),
media,
sort,
).joinToString("")
TmdbCollectionSourceType.DIRECTOR -> listOf(
stringResource(Res.string.collections_editor_tmdb_subtitle_director),
media,
sort,
).joinToString("")
TmdbCollectionSourceType.DISCOVER -> listOf( TmdbCollectionSourceType.DISCOVER -> listOf(
stringResource(Res.string.collections_editor_tmdb_subtitle_discover), stringResource(Res.string.collections_editor_tmdb_subtitle_discover),
media, media,

View file

@ -69,6 +69,8 @@ enum class TmdbCollectionSourceType {
COMPANY, COMPANY,
NETWORK, NETWORK,
DISCOVER, DISCOVER,
PERSON,
DIRECTOR,
} }
@Serializable @Serializable
@ -86,6 +88,7 @@ enum class TmdbCollectionMediaType(val value: String) {
} }
enum class TmdbCollectionSort(val value: String) { enum class TmdbCollectionSort(val value: String) {
ORIGINAL("original"),
POPULAR_DESC("popularity.desc"), POPULAR_DESC("popularity.desc"),
VOTE_AVERAGE_DESC("vote_average.desc"), VOTE_AVERAGE_DESC("vote_average.desc"),
RELEASE_DATE_DESC("primary_release_date.desc"), RELEASE_DATE_DESC("primary_release_date.desc"),
@ -133,6 +136,7 @@ data class CollectionFolder(
val sources: List<CollectionSource> = emptyList(), val sources: List<CollectionSource> = emptyList(),
val catalogSources: List<CollectionCatalogSource> = emptyList(), val catalogSources: List<CollectionCatalogSource> = emptyList(),
val heroBackdropUrl: String? = null, val heroBackdropUrl: String? = null,
val heroVideoUrl: String? = null,
val titleLogoUrl: String? = null, val titleLogoUrl: String? = null,
) { ) {
val posterShape: PosterShape val posterShape: PosterShape

View file

@ -4,7 +4,10 @@ import co.touchlab.kermit.Logger
import com.nuvio.app.features.addons.AddonRepository import com.nuvio.app.features.addons.AddonRepository
import com.nuvio.app.features.addons.ManagedAddon import com.nuvio.app.features.addons.ManagedAddon
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.MutableSharedFlow
import kotlinx.coroutines.flow.SharedFlow
import kotlinx.coroutines.flow.StateFlow import kotlinx.coroutines.flow.StateFlow
import kotlinx.coroutines.flow.asSharedFlow
import kotlinx.coroutines.flow.asStateFlow import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.runBlocking import kotlinx.coroutines.runBlocking
import kotlinx.serialization.decodeFromString import kotlinx.serialization.decodeFromString
@ -33,6 +36,8 @@ object CollectionRepository {
private val _collections = MutableStateFlow<List<Collection>>(emptyList()) private val _collections = MutableStateFlow<List<Collection>>(emptyList())
val collections: StateFlow<List<Collection>> = _collections.asStateFlow() val collections: StateFlow<List<Collection>> = _collections.asStateFlow()
private val _localChangeEvents = MutableSharedFlow<Unit>(extraBufferCapacity = 1)
internal val localChangeEvents: SharedFlow<Unit> = _localChangeEvents.asSharedFlow()
private var rawCollectionsJson: JsonElement = JsonArray(emptyList()) private var rawCollectionsJson: JsonElement = JsonArray(emptyList())
private var hasLoaded = false private var hasLoaded = false
@ -244,16 +249,19 @@ object CollectionRepository {
internal fun applyFromRemote(collections: List<Collection>, rawJson: JsonElement) { internal fun applyFromRemote(collections: List<Collection>, rawJson: JsonElement) {
rawCollectionsJson = rawJson rawCollectionsJson = rawJson
_collections.value = collections _collections.value = collections
persist() persist(sync = false)
} }
private fun ensureLoaded() { private fun ensureLoaded() {
if (!hasLoaded) initialize() if (!hasLoaded) initialize()
} }
private fun persist() { private fun persist(sync: Boolean = true) {
runCatching { runCatching {
CollectionStorage.savePayload(mergedCollectionsJson().toString()) CollectionStorage.savePayload(mergedCollectionsJson().toString())
if (sync) {
_localChangeEvents.tryEmit(Unit)
}
}.onFailure { e -> }.onFailure { e ->
log.e(e) { "Failed to persist collections" } log.e(e) { "Failed to persist collections" }
} }

View file

@ -15,8 +15,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.debounce import kotlinx.coroutines.flow.debounce
import kotlinx.coroutines.flow.distinctUntilChanged
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlinx.serialization.json.Json import kotlinx.serialization.json.Json
import kotlinx.serialization.json.JsonArray import kotlinx.serialization.json.JsonArray
@ -125,9 +123,7 @@ object CollectionSyncService {
@OptIn(FlowPreview::class) @OptIn(FlowPreview::class)
private fun observeLocalChangesAndPush() { private fun observeLocalChangesAndPush() {
observeJob = scope.launch { observeJob = scope.launch {
CollectionRepository.collections CollectionRepository.localChangeEvents
.drop(1)
.distinctUntilChanged()
.debounce(PUSH_DEBOUNCE_MS) .debounce(PUSH_DEBOUNCE_MS)
.collect { .collect {
if (isSyncingFromRemote) return@collect if (isSyncingFromRemote) return@collect

View file

@ -140,16 +140,19 @@ object FolderDetailRepository {
source = source, source = source,
type = type, type = type,
catalogId = tmdbCatalogId(source), catalogId = tmdbCatalogId(source),
supportsPagination = source.tmdbSourceType != TmdbCollectionSourceType.COLLECTION.name, supportsPagination = source.tmdbSourceType !in setOf(
TmdbCollectionSourceType.COLLECTION.name,
TmdbCollectionSourceType.PERSON.name,
TmdbCollectionSourceType.DIRECTOR.name,
),
isLoading = true, isLoading = true,
), ),
) )
} else { } else {
val catalogSource = source.addonCatalogSource() ?: return@forEach val catalogSource = source.addonCatalogSource() ?: return@forEach
val addon = addons.find { it.manifest?.id == catalogSource.addonId } val resolvedCatalog = addons.findCollectionCatalog(catalogSource)
val catalog = addon?.manifest?.catalogs?.find { val addon = resolvedCatalog?.addon
it.id == catalogSource.catalogId && it.type == catalogSource.type val catalog = resolvedCatalog?.catalog
}
val label = catalog?.name ?: catalogSource.catalogId val label = catalog?.name ?: catalogSource.catalogId
val typeLabel = localizedMediaTypeLabel(catalogSource.type) val typeLabel = localizedMediaTypeLabel(catalogSource.type)
val genreSuffix = if (catalogSource.genre != null) " · ${catalogSource.genre}" else "" val genreSuffix = if (catalogSource.genre != null) " · ${catalogSource.genre}" else ""
@ -184,8 +187,8 @@ object FolderDetailRepository {
sources.forEachIndexed { sourceIndex, source -> sources.forEachIndexed { sourceIndex, source ->
val tabIndex = if (showAll) sourceIndex + 1 else sourceIndex val tabIndex = if (showAll) sourceIndex + 1 else sourceIndex
val catalogSource = source.addonCatalogSource() val catalogSource = source.addonCatalogSource()
val addon = catalogSource?.let { value -> addons.find { it.manifest?.id == value.addonId } } val resolvedCatalog = catalogSource?.let { addons.findCollectionCatalog(it) }
if (!source.isTmdb && addon == null) { if (!source.isTmdb && resolvedCatalog == null) {
updateTab(tabIndex) { updateTab(tabIndex) {
it.copy( it.copy(
isLoading = false, isLoading = false,

View file

@ -29,6 +29,8 @@ object TmdbCollectionSourceResolver {
when (sourceType) { when (sourceType) {
TmdbCollectionSourceType.LIST -> resolveList(source, apiKey, language, page) TmdbCollectionSourceType.LIST -> resolveList(source, apiKey, language, page)
TmdbCollectionSourceType.COLLECTION -> resolveCollection(source, apiKey, language) TmdbCollectionSourceType.COLLECTION -> resolveCollection(source, apiKey, language)
TmdbCollectionSourceType.PERSON,
TmdbCollectionSourceType.DIRECTOR -> resolvePersonCredits(source, apiKey, language)
TmdbCollectionSourceType.COMPANY, TmdbCollectionSourceType.COMPANY,
TmdbCollectionSourceType.NETWORK, TmdbCollectionSourceType.NETWORK,
TmdbCollectionSourceType.DISCOVER -> resolveDiscover(source, apiKey, language, page) TmdbCollectionSourceType.DISCOVER -> resolveDiscover(source, apiKey, language, page)
@ -85,6 +87,19 @@ object TmdbCollectionSourceResolver {
) )
} }
TmdbCollectionSourceType.PERSON,
TmdbCollectionSourceType.DIRECTOR -> {
val body = fetch<TmdbPersonResponse>(
endpoint = "person/$id",
apiKey = apiKey,
query = mapOf("language" to language),
) ?: error("TMDB person not found")
TmdbSourceImportMetadata(
title = body.name?.takeIf { it.isNotBlank() },
coverImageUrl = imageUrl(body.profilePath, "w500"),
)
}
TmdbCollectionSourceType.DISCOVER -> TmdbSourceImportMetadata(title = "TMDB Discover") TmdbCollectionSourceType.DISCOVER -> TmdbSourceImportMetadata(title = "TMDB Discover")
} }
} }
@ -153,7 +168,7 @@ object TmdbCollectionSourceResolver {
fun parseTmdbId(input: String): Int? { fun parseTmdbId(input: String): Int? {
val trimmed = input.trim() val trimmed = input.trim()
trimmed.toIntOrNull()?.let { return it } trimmed.toIntOrNull()?.let { return it }
return Regex("""(?:list|collection|company|network)/(\d+)""") return Regex("""(?:list|collection|company|network|person)/(\d+)""")
.find(trimmed) .find(trimmed)
?.groupValues ?.groupValues
?.getOrNull(1) ?.getOrNull(1)
@ -193,6 +208,7 @@ object TmdbCollectionSourceResolver {
) ?: error("TMDB list not found") ) ?: error("TMDB list not found")
val items = body.items.orEmpty() val items = body.items.orEmpty()
.mapNotNull { it.toPreview() } .mapNotNull { it.toPreview() }
.sortedFor(source.sortBy)
.distinctBy { "${it.type}:${it.id}" } .distinctBy { "${it.type}:${it.id}" }
return CatalogPage( return CatalogPage(
items = items, items = items,
@ -213,12 +229,35 @@ object TmdbCollectionSourceResolver {
query = mapOf("language" to language), query = mapOf("language" to language),
) ?: error("TMDB collection not found") ) ?: error("TMDB collection not found")
val items = body.parts.orEmpty() val items = body.parts.orEmpty()
.sortedBy { it.releaseDate ?: "9999" }
.mapNotNull { it.toPreview(TmdbCollectionMediaType.MOVIE) } .mapNotNull { it.toPreview(TmdbCollectionMediaType.MOVIE) }
.sortedFor(source.sortBy)
.distinctBy { it.id } .distinctBy { it.id }
return CatalogPage(items = items, rawItemCount = items.size, nextSkip = null) return CatalogPage(items = items, rawItemCount = items.size, nextSkip = null)
} }
private suspend fun resolvePersonCredits(
source: CollectionSource,
apiKey: String,
language: String,
): CatalogPage {
val id = source.tmdbId ?: error("Missing TMDB person ID")
val mediaType = source.tmdbMediaType()
val body = fetch<TmdbPersonCreditsResponse>(
endpoint = "person/$id/combined_credits",
apiKey = apiKey,
query = mapOf("language" to language),
) ?: error("TMDB person credits not found")
val items = when (source.tmdbType()) {
TmdbCollectionSourceType.DIRECTOR -> body.crew.orEmpty()
.filter { it.job.equals("Director", ignoreCase = true) }
.mapNotNull { it.toPreview(mediaType) }
else -> body.cast.orEmpty().mapNotNull { it.toPreview(mediaType) }
}
.distinctBy { "${it.type}:${it.id}" }
.sortedFor(source.sortBy)
return CatalogPage(items = items, rawItemCount = items.size, nextSkip = null)
}
private suspend fun resolveDiscover( private suspend fun resolveDiscover(
source: CollectionSource, source: CollectionSource,
apiKey: String, apiKey: String,
@ -312,6 +351,21 @@ object TmdbCollectionSourceResolver {
}.getOrNull() }.getOrNull()
} }
private fun List<MetaPreview>.sortedFor(sortBy: String?): List<MetaPreview> =
when (sortBy) {
TmdbCollectionSort.ORIGINAL.value -> this
TmdbCollectionSort.VOTE_AVERAGE_DESC.value -> sortedWith(
compareByDescending<MetaPreview> { it.imdbRating?.toDoubleOrNull() ?: -1.0 }
.thenByDescending { it.rawReleaseDate ?: it.releaseInfo.orEmpty() },
)
TmdbCollectionSort.RELEASE_DATE_DESC.value,
TmdbCollectionSort.FIRST_AIR_DATE_DESC.value -> sortedByDescending { it.rawReleaseDate ?: it.releaseInfo.orEmpty() }
TmdbCollectionSort.POPULAR_DESC.value,
null,
"" -> this
else -> this
}
private fun TmdbListItem.toPreview(): MetaPreview? { private fun TmdbListItem.toPreview(): MetaPreview? {
val media = mediaType?.lowercase() val media = mediaType?.lowercase()
val contentType = if (media == "tv") TmdbCollectionMediaType.TV else TmdbCollectionMediaType.MOVIE val contentType = if (media == "tv") TmdbCollectionMediaType.TV else TmdbCollectionMediaType.MOVIE
@ -362,6 +416,62 @@ object TmdbCollectionSourceResolver {
) )
} }
private fun TmdbPersonCreditCast.toPreview(mediaType: TmdbCollectionMediaType): MetaPreview? {
if (!matchesMediaType(mediaType, this.mediaType)) return null
val title = title?.takeIf { it.isNotBlank() }
?: name?.takeIf { it.isNotBlank() }
?: originalTitle?.takeIf { it.isNotBlank() }
?: originalName?.takeIf { it.isNotBlank() }
?: return null
return MetaPreview(
id = "tmdb:$id",
type = if (mediaType == TmdbCollectionMediaType.TV) "series" else "movie",
name = title,
poster = imageUrl(posterPath, "w500") ?: imageUrl(backdropPath, "w780"),
banner = imageUrl(backdropPath, "w1280"),
posterShape = PosterShape.Poster,
description = overview?.takeIf { it.isNotBlank() },
releaseInfo = when (mediaType) {
TmdbCollectionMediaType.MOVIE -> releaseDate?.take(4)
TmdbCollectionMediaType.TV -> firstAirDate?.take(4)
},
rawReleaseDate = when (mediaType) {
TmdbCollectionMediaType.MOVIE -> releaseDate
TmdbCollectionMediaType.TV -> firstAirDate
},
popularity = popularity,
imdbRating = voteAverage?.let { ((it * 10).roundToInt() / 10.0).toString() },
)
}
private fun TmdbPersonCreditCrew.toPreview(mediaType: TmdbCollectionMediaType): MetaPreview? {
if (!matchesMediaType(mediaType, this.mediaType)) return null
val title = title?.takeIf { it.isNotBlank() }
?: name?.takeIf { it.isNotBlank() }
?: originalTitle?.takeIf { it.isNotBlank() }
?: originalName?.takeIf { it.isNotBlank() }
?: return null
return MetaPreview(
id = "tmdb:$id",
type = if (mediaType == TmdbCollectionMediaType.TV) "series" else "movie",
name = title,
poster = imageUrl(posterPath, "w500") ?: imageUrl(backdropPath, "w780"),
banner = imageUrl(backdropPath, "w1280"),
posterShape = PosterShape.Poster,
description = overview?.takeIf { it.isNotBlank() },
releaseInfo = when (mediaType) {
TmdbCollectionMediaType.MOVIE -> releaseDate?.take(4)
TmdbCollectionMediaType.TV -> firstAirDate?.take(4)
},
rawReleaseDate = when (mediaType) {
TmdbCollectionMediaType.MOVIE -> releaseDate
TmdbCollectionMediaType.TV -> firstAirDate
},
popularity = popularity,
imdbRating = voteAverage?.let { ((it * 10).roundToInt() / 10.0).toString() },
)
}
private fun CollectionSource.tmdbType(): TmdbCollectionSourceType = private fun CollectionSource.tmdbType(): TmdbCollectionSourceType =
tmdbSourceType tmdbSourceType
?.let { raw -> runCatching { TmdbCollectionSourceType.valueOf(raw.uppercase()) }.getOrNull() } ?.let { raw -> runCatching { TmdbCollectionSourceType.valueOf(raw.uppercase()) }.getOrNull() }
@ -370,6 +480,12 @@ object TmdbCollectionSourceResolver {
private fun CollectionSource.tmdbMediaType(): TmdbCollectionMediaType = private fun CollectionSource.tmdbMediaType(): TmdbCollectionMediaType =
TmdbCollectionMediaType.fromString(mediaType) TmdbCollectionMediaType.fromString(mediaType)
private fun matchesMediaType(expected: TmdbCollectionMediaType, actual: String?): Boolean =
when (expected) {
TmdbCollectionMediaType.MOVIE -> actual == "movie"
TmdbCollectionMediaType.TV -> actual == "tv"
}
private fun company(title: String, id: Int) = CollectionSource( private fun company(title: String, id: Int) = CollectionSource(
provider = "tmdb", provider = "tmdb",
tmdbSourceType = TmdbCollectionSourceType.COMPANY.name, tmdbSourceType = TmdbCollectionSourceType.COMPANY.name,
@ -391,6 +507,7 @@ object TmdbCollectionSourceResolver {
private fun movieSort(sortBy: String?): String = private fun movieSort(sortBy: String?): String =
when (sortBy) { when (sortBy) {
TmdbCollectionSort.FIRST_AIR_DATE_DESC.value -> TmdbCollectionSort.RELEASE_DATE_DESC.value TmdbCollectionSort.FIRST_AIR_DATE_DESC.value -> TmdbCollectionSort.RELEASE_DATE_DESC.value
TmdbCollectionSort.ORIGINAL.value -> TmdbCollectionSort.POPULAR_DESC.value
null, "" -> TmdbCollectionSort.POPULAR_DESC.value null, "" -> TmdbCollectionSort.POPULAR_DESC.value
else -> sortBy else -> sortBy
} }
@ -398,6 +515,7 @@ object TmdbCollectionSourceResolver {
private fun tvSort(sortBy: String?): String = private fun tvSort(sortBy: String?): String =
when (sortBy) { when (sortBy) {
TmdbCollectionSort.RELEASE_DATE_DESC.value -> TmdbCollectionSort.FIRST_AIR_DATE_DESC.value TmdbCollectionSort.RELEASE_DATE_DESC.value -> TmdbCollectionSort.FIRST_AIR_DATE_DESC.value
TmdbCollectionSort.ORIGINAL.value -> TmdbCollectionSort.POPULAR_DESC.value
null, "" -> TmdbCollectionSort.POPULAR_DESC.value null, "" -> TmdbCollectionSort.POPULAR_DESC.value
else -> sortBy else -> sortBy
} }
@ -449,6 +567,12 @@ private data class TmdbNetworkResponse(
@SerialName("logo_path") val logoPath: String? = null, @SerialName("logo_path") val logoPath: String? = null,
) )
@Serializable
private data class TmdbPersonResponse(
val name: String? = null,
@SerialName("profile_path") val profilePath: String? = null,
)
@Serializable @Serializable
data class TmdbCompanySearchResult( data class TmdbCompanySearchResult(
val id: Int, val id: Int,
@ -496,6 +620,47 @@ private data class TmdbGenreItem(
val name: String, val name: String,
) )
@Serializable
private data class TmdbPersonCreditsResponse(
val cast: List<TmdbPersonCreditCast>? = null,
val crew: List<TmdbPersonCreditCrew>? = null,
)
@Serializable
private data class TmdbPersonCreditCast(
val id: Int,
@SerialName("media_type") val mediaType: String? = null,
val title: String? = null,
val name: String? = null,
@SerialName("original_title") val originalTitle: String? = null,
@SerialName("original_name") val originalName: String? = null,
val overview: String? = null,
@SerialName("poster_path") val posterPath: String? = null,
@SerialName("backdrop_path") val backdropPath: String? = null,
@SerialName("release_date") val releaseDate: String? = null,
@SerialName("first_air_date") val firstAirDate: String? = null,
@SerialName("vote_average") val voteAverage: Double? = null,
val popularity: Double? = null,
)
@Serializable
private data class TmdbPersonCreditCrew(
val id: Int,
@SerialName("media_type") val mediaType: String? = null,
val title: String? = null,
val name: String? = null,
@SerialName("original_title") val originalTitle: String? = null,
@SerialName("original_name") val originalName: String? = null,
val overview: String? = null,
@SerialName("poster_path") val posterPath: String? = null,
@SerialName("backdrop_path") val backdropPath: String? = null,
@SerialName("release_date") val releaseDate: String? = null,
@SerialName("first_air_date") val firstAirDate: String? = null,
val job: String? = null,
@SerialName("vote_average") val voteAverage: Double? = null,
val popularity: Double? = null,
)
@Serializable @Serializable
private data class TmdbListItem( private data class TmdbListItem(
val id: Int, val id: Int,

View file

@ -365,7 +365,7 @@ private fun ContinueWatchingWideCard(
Text( Text(
text = stringResource( text = stringResource(
Res.string.home_continue_watching_watched, Res.string.home_continue_watching_watched,
continueWatchingProgressPercent(item.progressFraction), "${continueWatchingProgressPercent(item.progressFraction)}%",
), ),
style = MaterialTheme.typography.labelSmall.copy( style = MaterialTheme.typography.labelSmall.copy(
fontSize = layout.progressLabelSize, fontSize = layout.progressLabelSize,

View file

@ -657,7 +657,6 @@ fun PlayerScreen(
} }
} }
playerController?.seekTo(targetPositionMs) playerController?.seekTo(targetPositionMs)
controlsVisible = true
showSeekFeedback(direction, nextState.amountMs) showSeekFeedback(direction, nextState.amountMs)
accumulatedSeekResetJob?.cancel() accumulatedSeekResetJob?.cancel()

View file

@ -70,6 +70,7 @@ object ProfileRepository {
val stored = decodeStoredPayload() ?: return false val stored = decodeStoredPayload() ?: return false
loadedCacheForUserId = stored.userId loadedCacheForUserId = stored.userId
applyStoredPayload(stored) applyStoredPayload(stored)
ThemeSettingsRepository.onProfileChanged()
return _state.value.profiles.isNotEmpty() return _state.value.profiles.isNotEmpty()
} }

View file

@ -2,7 +2,9 @@ package com.nuvio.app.features.settings
import nuvio.composeapp.generated.resources.Res import nuvio.composeapp.generated.resources.Res
import nuvio.composeapp.generated.resources.lang_english import nuvio.composeapp.generated.resources.lang_english
import nuvio.composeapp.generated.resources.lang_french
import nuvio.composeapp.generated.resources.lang_spanish import nuvio.composeapp.generated.resources.lang_spanish
import nuvio.composeapp.generated.resources.lang_portuguese_portugal
import nuvio.composeapp.generated.resources.lang_turkish import nuvio.composeapp.generated.resources.lang_turkish
import nuvio.composeapp.generated.resources.lang_italian import nuvio.composeapp.generated.resources.lang_italian
import nuvio.composeapp.generated.resources.lang_greek import nuvio.composeapp.generated.resources.lang_greek
@ -14,6 +16,7 @@ enum class AppLanguage(
val labelRes: StringResource, val labelRes: StringResource,
) { ) {
ENGLISH("en", Res.string.lang_english), ENGLISH("en", Res.string.lang_english),
FRENCH("fr", Res.string.lang_french),
SPANISH("es", Res.string.lang_spanish), SPANISH("es", Res.string.lang_spanish),
TURKISH("tr", Res.string.lang_turkish), TURKISH("tr", Res.string.lang_turkish),
ITALIAN("it", Res.string.lang_italian), ITALIAN("it", Res.string.lang_italian),

View file

@ -14,7 +14,8 @@ actual object ThemeSettingsStorage {
private const val selectedThemeKey = "selected_theme" private const val selectedThemeKey = "selected_theme"
private const val amoledEnabledKey = "amoled_enabled" private const val amoledEnabledKey = "amoled_enabled"
private const val selectedAppLanguageKey = "selected_app_language" private const val selectedAppLanguageKey = "selected_app_language"
private val syncKeys = listOf(selectedThemeKey, amoledEnabledKey, selectedAppLanguageKey) private val profileScopedSyncKeys = listOf(selectedThemeKey, amoledEnabledKey)
private val globalSyncKeys = listOf(selectedAppLanguageKey)
actual fun loadSelectedTheme(): String? = actual fun loadSelectedTheme(): String? =
NSUserDefaults.standardUserDefaults.stringForKey(ProfileScopedKey.of(selectedThemeKey)) NSUserDefaults.standardUserDefaults.stringForKey(ProfileScopedKey.of(selectedThemeKey))
@ -37,11 +38,16 @@ actual object ThemeSettingsStorage {
NSUserDefaults.standardUserDefaults.setBool(enabled, forKey = ProfileScopedKey.of(amoledEnabledKey)) NSUserDefaults.standardUserDefaults.setBool(enabled, forKey = ProfileScopedKey.of(amoledEnabledKey))
} }
actual fun loadSelectedAppLanguage(): String? = actual fun loadSelectedAppLanguage(): String? {
NSUserDefaults.standardUserDefaults.stringForKey(ProfileScopedKey.of(selectedAppLanguageKey)) val value = NSUserDefaults.standardUserDefaults.stringForKey(selectedAppLanguageKey)
if (value != null) return value
val legacy = NSUserDefaults.standardUserDefaults.stringForKey(ProfileScopedKey.of(selectedAppLanguageKey))
if (legacy != null) saveSelectedAppLanguage(legacy)
return legacy
}
actual fun saveSelectedAppLanguage(languageCode: String) { actual fun saveSelectedAppLanguage(languageCode: String) {
NSUserDefaults.standardUserDefaults.setObject(languageCode, forKey = ProfileScopedKey.of(selectedAppLanguageKey)) NSUserDefaults.standardUserDefaults.setObject(languageCode, forKey = selectedAppLanguageKey)
} }
actual fun applySelectedAppLanguage(languageCode: String) = Unit actual fun applySelectedAppLanguage(languageCode: String) = Unit
@ -53,9 +59,12 @@ actual object ThemeSettingsStorage {
} }
actual fun replaceFromSyncPayload(payload: JsonObject) { actual fun replaceFromSyncPayload(payload: JsonObject) {
syncKeys.forEach { key -> profileScopedSyncKeys.forEach { key ->
NSUserDefaults.standardUserDefaults.removeObjectForKey(ProfileScopedKey.of(key)) NSUserDefaults.standardUserDefaults.removeObjectForKey(ProfileScopedKey.of(key))
} }
globalSyncKeys.forEach { key ->
NSUserDefaults.standardUserDefaults.removeObjectForKey(key)
}
payload.decodeSyncString(selectedThemeKey)?.let(::saveSelectedTheme) payload.decodeSyncString(selectedThemeKey)?.let(::saveSelectedTheme)
payload.decodeSyncBoolean(amoledEnabledKey)?.let(::saveAmoledEnabled) payload.decodeSyncBoolean(amoledEnabledKey)?.let(::saveAmoledEnabled)

View file

@ -22,7 +22,7 @@ ktor = "3.4.1"
material3 = "1.11.0-alpha07" material3 = "1.11.0-alpha07"
androidx-media3 = "1.8.0" androidx-media3 = "1.8.0"
supabase = "3.4.1" supabase = "3.4.1"
quickjsKt = "1.0.1" quickjsKt = "1.0.5"
ksoup = "0.2.6" ksoup = "0.2.6"
reorderable = "3.0.0" reorderable = "3.0.0"
desugarJdkLibs = "2.1.5" desugarJdkLibs = "2.1.5"

View file

@ -1,3 +1,3 @@
CURRENT_PROJECT_VERSION=40 CURRENT_PROJECT_VERSION=42
MARKETING_VERSION=0.1.9 MARKETING_VERSION=0.1.10

@ -1 +0,0 @@
Subproject commit df33966d7fbc6eb14e43fb1892e062417d76e7f5

@ -1 +0,0 @@
Subproject commit 8a8ddddf430555878273da13006fc57e182b0c0c