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",
+ ),
+ ),
+ ),
+ ),
+ )
}