diff --git a/composeApp/src/commonMain/composeResources/values-no/strings.xml b/composeApp/src/commonMain/composeResources/values-no/strings.xml index c80318d5..15e482d2 100644 --- a/composeApp/src/commonMain/composeResources/values-no/strings.xml +++ b/composeApp/src/commonMain/composeResources/values-no/strings.xml @@ -379,7 +379,7 @@ Utseende Innhold & oppdagelse Fortsett å se - Skytjenester + Tilkoblede tjenester Hjemmeoppsett Integrasjoner Lisenser & attribusjon @@ -588,17 +588,17 @@ Integrasjoner Metadata-berikelse-kontroller Eksterne vurderingsleverandører - Administrer skytjenestekontoer og tilgang til skybibliotek - Skytjenester - Støtte for skytjenester er eksperimentell og kan endres eller fjernes senere. + Koble til kontoer for lenker og bibliotektilgang + Tilkoblede tjenester + Disse integrasjonene er eksperimentelle og kan endres eller fjernes senere. Skybibliotek - Bla gjennom og spill filer som allerede finnes i tilkoblede skytjenester. + Bla gjennom og spill filer som allerede finnes i tilkoblede kontoer. Løs spillbare lenker Be en tilkoblet tjeneste om spillbare lenker når et resultat trenger det. Dette kan legge elementet til i den tjenesten. Løs med - Velg hvilken tilkoblet skytjeneste som håndterer spillbare lenker. - Koble til en skytjenestekonto først. - Skytjenester + Velg hvilken tilkoblet konto som håndterer spillbare lenker. + Koble til en konto først. + Kontoer Koble til %1$s-kontoen din. Koble til %1$s-kontoen din i nettleseren. %1$s API-nøkkel @@ -625,9 +625,9 @@ %1$d lenker Formatering Navnemal - Styrer hvordan navn på skyresultater vises. + Styrer hvordan resultatnavn vises. Beskrivelsesmal - Styrer metadata vist under hvert skyresultat. + Styrer metadata vist under hvert resultat. API-nøkkel validert. Kunne ikke validere denne API-nøkkelen. Legg til MDBList API-nøkkel før du skrur på vurderinger. @@ -1165,9 +1165,9 @@ Gjenoppta fra %1$s STØRRELSE %1$s Denne strømtypen støttes ikke - Koble til en skytjenestekonto i Innstillinger. - Denne skytjenestelenken er utgått. Oppdaterer resultater. - Kunne ikke åpne denne skytjenestelenken. + Koble til en konto i Innstillinger. + Denne lenken er utgått. Oppdaterer resultater. + Kunne ikke åpne denne lenken. Kunne ikke åpne ekstern avspiller Velg en ekstern avspiller i innstillinger først Ingen ekstern avspiller er tilgjengelig diff --git a/composeApp/src/commonMain/composeResources/values/strings.xml b/composeApp/src/commonMain/composeResources/values/strings.xml index e75ecbdf..ab3b8ab1 100644 --- a/composeApp/src/commonMain/composeResources/values/strings.xml +++ b/composeApp/src/commonMain/composeResources/values/strings.xml @@ -380,7 +380,7 @@ Layout Content & Discovery Continue Watching - Cloud Services + Connected Services Home Layout Integrations Licenses & Attribution @@ -589,17 +589,17 @@ Integrations Metadata enrichment controls External ratings providers - Manage cloud service accounts and cloud library access - Cloud Services - Cloud Services support is experimental and may be kept, changed, or removed later. + Connect accounts for links and library access + Connected Services + These integrations are experimental and may be kept, changed, or removed later. Cloud library - Browse and play files already in your connected cloud services. + Browse and play files already in your connected accounts. Resolve playable links Ask a connected service for playable links when a result needs it. This may add the item to that service. Resolve with - Choose which connected cloud service manages playable links. - Connect a cloud service account first. - Cloud Services + Choose which connected account handles playable links. + Connect an account first. + Accounts Connect your %1$s account. Link your %1$s account in the browser. %1$s API Key @@ -623,16 +623,16 @@ Prepare links Resolve playable links before playback starts. Links to prepare - Use a lower count when possible. Cloud services may rate-limit how many links can be resolved in a time period. Opening a movie or episode can count toward those limits even if you do not press Watch, because the links are prepared ahead of time. + Use a lower count when possible. Connected services may rate-limit how many links can be resolved in a time period. Opening a movie or episode can count toward those limits even if you do not press Watch, because the links are prepared ahead of time. 1 link %1$d links Formatting Name template - Controls how cloud result names appear. + Controls how result names appear. Description template - Controls the metadata shown under each cloud result. + Controls the metadata shown under each result. Reset formatting - Restore default cloud result formatting. + Restore default result formatting. API key validated. Could not validate this API key. Add your MDBList API key below before turning ratings on. @@ -1170,10 +1170,10 @@ Resume from %1$s SIZE %1$s This stream type is not supported - Connect a cloud service account in Settings. + Connect an account in Settings. Not cached on Torbox. - This cloud service link expired. Refreshing results. - Could not open this cloud service link. + This link expired. Refreshing results. + Could not open this link. Couldn't open external player Choose an external player in settings first No external player is available @@ -1333,10 +1333,10 @@ Couldn't load Trakt library Trakt Library Connect account - Connect a cloud service in Cloud Services settings to browse playable files from your cloud library. + Connect an account in Connected Services settings to browse playable files from your cloud library. No cloud account connected - Open Cloud Services - Turn on Cloud library in Cloud Services settings to browse files from connected accounts. + Open Connected Services + Turn on Cloud library in Connected Services settings to browse files from connected accounts. Cloud library is off No playable cloud files match the current filters. Nothing here yet diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridSettings.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridSettings.kt index 472cdbfa..8774316f 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridSettings.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridSettings.kt @@ -56,7 +56,10 @@ data class DebridSettings( get() = cloudLibraryEnabled && hasCloudLibraryProvider val hasCustomStreamFormatting: Boolean - get() = streamNameTemplate.isNotBlank() || streamDescriptionTemplate.isNotBlank() + get() = DebridStreamFormatterDefaults.NAME_TEMPLATE.isNotBlank() || + DebridStreamFormatterDefaults.DESCRIPTION_TEMPLATE.isNotBlank() || + streamNameTemplate.isNotBlank() || + streamDescriptionTemplate.isNotBlank() fun apiKeyFor(providerId: String?): String { val normalized = DebridProviders.byId(providerId)?.id diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridStreamFormatter.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridStreamFormatter.kt index c4880bc1..a148c9de 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridStreamFormatter.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridStreamFormatter.kt @@ -12,12 +12,14 @@ class DebridStreamFormatter( fun format(stream: StreamItem, settings: DebridSettings): StreamItem { if (!stream.isManagedDebridStream) return stream val values = buildValues(stream, settings) - val formattedName = engine.render(settings.streamNameTemplate, values) + val nameTemplate = settings.streamNameTemplate.ifBlank { DebridStreamFormatterDefaults.NAME_TEMPLATE } + val descriptionTemplate = settings.streamDescriptionTemplate.ifBlank { DebridStreamFormatterDefaults.DESCRIPTION_TEMPLATE } + val formattedName = engine.render(nameTemplate, values) .lineSequence() .joinToString(" ") { it.trim() } .replace(Regex("\\s+"), " ") .trim() - val formattedDescription = engine.render(settings.streamDescriptionTemplate, values) + val formattedDescription = engine.render(descriptionTemplate, values) .lineSequence() .map { it.trim() } .filter { it.isNotBlank() } diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridStreamFormatterDefaults.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridStreamFormatterDefaults.kt index d6637fd4..0129ae2e 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridStreamFormatterDefaults.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/debrid/DebridStreamFormatterDefaults.kt @@ -1,7 +1,7 @@ package com.nuvio.app.features.debrid object DebridStreamFormatterDefaults { - const val NAME_TEMPLATE = "" + const val NAME_TEMPLATE = "{stream.resolution::exists[\"{stream.resolution} \"||\"\"]}{service.shortName::exists[\"{service.shortName}\"||\"Cloud\"]} Instant" const val DESCRIPTION_TEMPLATE = "" diff --git a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/DebridSettingsPage.kt b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/DebridSettingsPage.kt index 8a5a23b5..bef71870 100644 --- a/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/DebridSettingsPage.kt +++ b/composeApp/src/commonMain/kotlin/com/nuvio/app/features/settings/DebridSettingsPage.kt @@ -321,7 +321,7 @@ internal fun LazyListScope.debridSettingsContent( DebridPreferenceRow( isTablet = isTablet, title = "Max results", - description = "Limit how many cloud-service results appear.", + description = "Limit how many results appear.", value = streamMaxResultsLabel(preferences.maxResults), enabled = settings.canResolvePlayableLinks, onClick = { activeStreamPicker = DebridStreamPicker.MAX_RESULTS }, @@ -330,7 +330,7 @@ internal fun LazyListScope.debridSettingsContent( DebridPreferenceRow( isTablet = isTablet, title = "Sort results", - description = "Choose how cloud-service results are ordered.", + description = "Choose how results are ordered.", value = sortProfileLabel(preferences.sortCriteria), enabled = settings.canResolvePlayableLinks, onClick = { activeStreamPicker = DebridStreamPicker.SORT_MODE }, @@ -357,7 +357,7 @@ internal fun LazyListScope.debridSettingsContent( DebridPreferenceRow( isTablet = isTablet, title = "Size range", - description = "Filter cloud-service results by file size.", + description = "Filter results by file size.", value = sizeRangeLabel(preferences), enabled = settings.canResolvePlayableLinks, onClick = { activeStreamPicker = DebridStreamPicker.SIZE_RANGE }, @@ -398,7 +398,10 @@ internal fun LazyListScope.debridSettingsContent( isTablet = isTablet, title = stringResource(Res.string.settings_debrid_name_template), description = stringResource(Res.string.settings_debrid_name_template_description), - value = templatePreview(settings.streamNameTemplate), + value = templatePreview( + value = settings.streamNameTemplate, + defaultValue = DebridStreamFormatterDefaults.NAME_TEMPLATE, + ), enabled = settings.canResolvePlayableLinks, onClick = { activeTemplateField = DebridTemplateField.NAME }, ) @@ -407,7 +410,10 @@ internal fun LazyListScope.debridSettingsContent( isTablet = isTablet, title = stringResource(Res.string.settings_debrid_description_template), description = stringResource(Res.string.settings_debrid_description_template_description), - value = templatePreview(settings.streamDescriptionTemplate), + value = templatePreview( + value = settings.streamDescriptionTemplate, + defaultValue = DebridStreamFormatterDefaults.DESCRIPTION_TEMPLATE, + ), enabled = settings.canResolvePlayableLinks, onClick = { activeTemplateField = DebridTemplateField.DESCRIPTION }, ) @@ -450,7 +456,8 @@ private enum class DebridTemplateField { DESCRIPTION, } -private fun templatePreview(value: String): String { +private fun templatePreview(value: String, defaultValue: String): String { + if (value.trim().isBlank() || value.trim() == defaultValue.trim()) return "Default format" val firstLine = value .lineSequence() .map { it.trim() } diff --git a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/debrid/DebridStreamPresentationTest.kt b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/debrid/DebridStreamPresentationTest.kt index b23a17c5..d97f29c5 100644 --- a/composeApp/src/commonTest/kotlin/com/nuvio/app/features/debrid/DebridStreamPresentationTest.kt +++ b/composeApp/src/commonTest/kotlin/com/nuvio/app/features/debrid/DebridStreamPresentationTest.kt @@ -2,12 +2,17 @@ package com.nuvio.app.features.debrid import com.nuvio.app.features.streams.AddonStreamGroup import com.nuvio.app.features.streams.StreamBehaviorHints +import com.nuvio.app.features.streams.StreamClientResolve +import com.nuvio.app.features.streams.StreamClientResolveParsed +import com.nuvio.app.features.streams.StreamClientResolveRaw +import com.nuvio.app.features.streams.StreamClientResolveStream import com.nuvio.app.features.streams.StreamDebridCacheState import com.nuvio.app.features.streams.StreamDebridCacheStatus import com.nuvio.app.features.streams.StreamItem import kotlin.test.Test import kotlin.test.assertContains import kotlin.test.assertEquals +import kotlin.test.assertFalse class DebridStreamPresentationTest { @Test @@ -34,6 +39,36 @@ class DebridStreamPresentationTest { assertContains(description, "Lost.S01E01.2160p.WEB-DL.H265.AAC-NAKSU.mkv") } + @Test + fun `default formatter replaces addon source labels for managed streams`() { + val stream = premiumizeDirectStream( + name = "[P2P] Torrentio 2160p - PM Instant", + filename = "The.Boys.S03E01.Payback.2160p.WEB-DL.H265.mkv", + size = 12_000_000_000, + ) + + val presented = DebridStreamPresentation.apply( + groups = listOf( + AddonStreamGroup( + addonName = "Torrentio", + addonId = "addon:torrentio", + streams = listOf(stream), + ), + ), + settings = DebridSettings( + enabled = true, + providerApiKeys = mapOf(DebridProviders.PREMIUMIZE_ID to "pm_key"), + ), + ).single().streams.single() + + val name = presented.name.orEmpty() + assertEquals("2160p PM Instant", name) + assertFalse(name.contains("P2P", ignoreCase = true)) + assertFalse(name.contains("torrent", ignoreCase = true)) + assertFalse(name.contains("Torrentio", ignoreCase = true)) + assertFalse(name.contains("Comet", ignoreCase = true)) + } + @Test fun `applies debrid sort filters and limits without removing normal urls`() { val low = localTorboxStream( @@ -75,7 +110,7 @@ class DebridStreamPresentationTest { ), ).single().streams - assertEquals(listOf("Large", "Mid", "Resolved addon URL"), presented.map { it.name }) + assertEquals(listOf("2160p TB Instant", "1080p TB Instant", "Resolved addon URL"), presented.map { it.name }) } @Test @@ -106,7 +141,7 @@ class DebridStreamPresentationTest { ), ).single().streams - assertEquals(listOf("Cached"), presented.map { it.name }) + assertEquals(listOf("1080p TB Instant"), presented.map { it.name }) } @Test @@ -158,4 +193,30 @@ class DebridStreamPresentationTest { cachedSize = size, ), ) + + private fun premiumizeDirectStream( + name: String, + filename: String, + size: Long, + ): StreamItem = + StreamItem( + name = name, + addonName = "Torrentio", + addonId = "addon:torrentio", + clientResolve = StreamClientResolve( + type = "debrid", + service = DebridProviders.PREMIUMIZE_ID, + filename = filename, + isCached = true, + stream = StreamClientResolveStream( + raw = StreamClientResolveRaw( + filename = filename, + size = size, + parsed = StreamClientResolveParsed( + resolution = "2160p", + ), + ), + ), + ), + ) }