mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-23 10:12:06 +00:00
ref(torbox): short_name extraction
This commit is contained in:
parent
b0906b7b19
commit
cf1e162eb1
25 changed files with 348 additions and 72 deletions
|
|
@ -16,6 +16,7 @@ import kotlinx.serialization.json.put
|
|||
actual object DebridSettingsStorage {
|
||||
private const val preferencesName = "nuvio_debrid_settings"
|
||||
private const val enabledKey = "debrid_enabled"
|
||||
private const val cloudLibraryEnabledKey = "debrid_cloud_library_enabled"
|
||||
private const val torboxApiKeyKey = "debrid_torbox_api_key"
|
||||
private const val realDebridApiKeyKey = "debrid_real_debrid_api_key"
|
||||
private const val instantPlaybackPreparationLimitKey = "debrid_instant_playback_preparation_limit"
|
||||
|
|
@ -31,6 +32,7 @@ actual object DebridSettingsStorage {
|
|||
private fun syncKeys(): List<String> =
|
||||
listOf(
|
||||
enabledKey,
|
||||
cloudLibraryEnabledKey,
|
||||
instantPlaybackPreparationLimitKey,
|
||||
streamMaxResultsKey,
|
||||
streamSortModeKey,
|
||||
|
|
@ -55,6 +57,12 @@ actual object DebridSettingsStorage {
|
|||
saveBoolean(enabledKey, enabled)
|
||||
}
|
||||
|
||||
actual fun loadCloudLibraryEnabled(): Boolean? = loadBoolean(cloudLibraryEnabledKey)
|
||||
|
||||
actual fun saveCloudLibraryEnabled(enabled: Boolean) {
|
||||
saveBoolean(cloudLibraryEnabledKey, enabled)
|
||||
}
|
||||
|
||||
actual fun loadProviderApiKey(providerId: String): String? =
|
||||
loadString(providerApiKeyKey(providerId))
|
||||
|
||||
|
|
@ -180,6 +188,7 @@ actual object DebridSettingsStorage {
|
|||
|
||||
actual fun exportToSyncPayload(): JsonObject = buildJsonObject {
|
||||
loadEnabled()?.let { put(enabledKey, encodeSyncBoolean(it)) }
|
||||
loadCloudLibraryEnabled()?.let { put(cloudLibraryEnabledKey, encodeSyncBoolean(it)) }
|
||||
DebridProviders.all().forEach { provider ->
|
||||
loadProviderApiKey(provider.id)?.let {
|
||||
put(providerApiKeyKey(provider.id), encodeSyncString(it))
|
||||
|
|
@ -203,6 +212,7 @@ actual object DebridSettingsStorage {
|
|||
}?.apply()
|
||||
|
||||
payload.decodeSyncBoolean(enabledKey)?.let(::saveEnabled)
|
||||
payload.decodeSyncBoolean(cloudLibraryEnabledKey)?.let(::saveCloudLibraryEnabled)
|
||||
DebridProviders.all().forEach { provider ->
|
||||
payload.decodeSyncString(providerApiKeyKey(provider.id))?.let { apiKey ->
|
||||
saveProviderApiKey(provider.id, apiKey)
|
||||
|
|
|
|||
|
|
@ -591,8 +591,10 @@
|
|||
<string name="settings_integrations_debrid_description">Administrer skytjenestekontoer og tilgang til skybibliotek</string>
|
||||
<string name="settings_debrid_section_title">Skytjenester</string>
|
||||
<string name="settings_debrid_experimental_notice">Støtte for skytjenester er eksperimentell og kan endres eller fjernes senere.</string>
|
||||
<string name="settings_debrid_enable">Aktiver skytjenester</string>
|
||||
<string name="settings_debrid_enable_description">Bruk tilkoblede kontoer for spillbare lenker og tilgang til skybibliotek.</string>
|
||||
<string name="settings_debrid_cloud_library">Skybibliotek</string>
|
||||
<string name="settings_debrid_cloud_library_description">Bla gjennom og spill filer som allerede finnes i tilkoblede skytjenester.</string>
|
||||
<string name="settings_debrid_enable">Løs spillbare lenker</string>
|
||||
<string name="settings_debrid_enable_description">Be en tilkoblet tjeneste om spillbare lenker når et resultat trenger det. Dette kan legge elementet til i den tjenesten.</string>
|
||||
<string name="settings_debrid_add_key_first">Koble til en skytjenestekonto først.</string>
|
||||
<string name="settings_debrid_section_providers">Skytjenester</string>
|
||||
<string name="settings_debrid_provider_description">Koble til %1$s-kontoen din.</string>
|
||||
|
|
@ -612,7 +614,7 @@
|
|||
<string name="settings_debrid_device_auth_waiting">Venter på godkjenning...</string>
|
||||
<string name="settings_debrid_device_auth_failed">Kunne ikke starte innlogging.</string>
|
||||
<string name="settings_debrid_device_auth_expired">Denne koden er utløpt. Prøv igjen.</string>
|
||||
<string name="settings_debrid_section_instant_playback">Umiddelbar avspilling</string>
|
||||
<string name="settings_debrid_section_instant_playback">Lenkeforberedelse</string>
|
||||
<string name="settings_debrid_prepare_instant_playback">Forbered lenker</string>
|
||||
<string name="settings_debrid_prepare_instant_playback_description">Løs spillbare lenker før avspilling starter.</string>
|
||||
<string name="settings_debrid_prepare_stream_count">Lenker å forberede</string>
|
||||
|
|
|
|||
|
|
@ -592,8 +592,10 @@
|
|||
<string name="settings_integrations_debrid_description">Manage cloud service accounts and cloud library access</string>
|
||||
<string name="settings_debrid_section_title">Cloud Services</string>
|
||||
<string name="settings_debrid_experimental_notice">Cloud Services support is experimental and may be kept, changed, or removed later.</string>
|
||||
<string name="settings_debrid_enable">Enable cloud services</string>
|
||||
<string name="settings_debrid_enable_description">Use connected accounts for playable links and cloud library access.</string>
|
||||
<string name="settings_debrid_cloud_library">Cloud library</string>
|
||||
<string name="settings_debrid_cloud_library_description">Browse and play files already in your connected cloud services.</string>
|
||||
<string name="settings_debrid_enable">Resolve playable links</string>
|
||||
<string name="settings_debrid_enable_description">Ask a connected service for playable links when a result needs it. This may add the item to that service.</string>
|
||||
<string name="settings_debrid_add_key_first">Connect a cloud service account first.</string>
|
||||
<string name="settings_debrid_section_providers">Cloud Services</string>
|
||||
<string name="settings_debrid_provider_description">Connect your %1$s account.</string>
|
||||
|
|
@ -614,7 +616,7 @@
|
|||
<string name="settings_debrid_device_auth_waiting">Waiting for approval...</string>
|
||||
<string name="settings_debrid_device_auth_failed">Could not start sign-in.</string>
|
||||
<string name="settings_debrid_device_auth_expired">This code expired. Try again.</string>
|
||||
<string name="settings_debrid_section_instant_playback">Instant Playback</string>
|
||||
<string name="settings_debrid_section_instant_playback">Link Preparation</string>
|
||||
<string name="settings_debrid_prepare_instant_playback">Prepare links</string>
|
||||
<string name="settings_debrid_prepare_instant_playback_description">Resolve playable links before playback starts.</string>
|
||||
<string name="settings_debrid_prepare_stream_count">Links to prepare</string>
|
||||
|
|
@ -1330,6 +1332,9 @@
|
|||
<string name="cloud_library_connect_action">Connect account</string>
|
||||
<string name="cloud_library_connect_message">Connect Torbox in Cloud Services settings to browse playable files from your cloud library.</string>
|
||||
<string name="cloud_library_connect_title">No cloud account connected</string>
|
||||
<string name="cloud_library_disabled_action">Open Cloud Services</string>
|
||||
<string name="cloud_library_disabled_message">Turn on Cloud library in Cloud Services settings to browse files from connected accounts.</string>
|
||||
<string name="cloud_library_disabled_title">Cloud library is off</string>
|
||||
<string name="cloud_library_empty_message">No playable cloud files match the current filters.</string>
|
||||
<string name="cloud_library_empty_title">Nothing here yet</string>
|
||||
<string name="cloud_library_file_picker_title">Choose a file to play</string>
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ data class CloudLibraryProviderState(
|
|||
|
||||
data class CloudLibraryUiState(
|
||||
val isLoaded: Boolean = false,
|
||||
val isEnabled: Boolean = true,
|
||||
val isRefreshing: Boolean = false,
|
||||
val providers: List<CloudLibraryProviderState> = emptyList(),
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -87,6 +87,11 @@ object CloudLibraryRepository {
|
|||
|
||||
fun ensureLoaded() {
|
||||
DebridSettingsRepository.ensureLoaded()
|
||||
if (!DebridSettingsRepository.snapshot().cloudLibraryEnabled) {
|
||||
loadedConnectionKeys = emptyList()
|
||||
_uiState.value = CloudLibraryUiState(isLoaded = true, isEnabled = false)
|
||||
return
|
||||
}
|
||||
val current = _uiState.value
|
||||
if (current.isRefreshing) return
|
||||
val connectedKeys = connectedCloudConnectionKeys()
|
||||
|
|
@ -96,8 +101,15 @@ object CloudLibraryRepository {
|
|||
}
|
||||
|
||||
fun refresh() {
|
||||
DebridSettingsRepository.ensureLoaded()
|
||||
if (!DebridSettingsRepository.snapshot().cloudLibraryEnabled) {
|
||||
loadedConnectionKeys = emptyList()
|
||||
_uiState.value = CloudLibraryUiState(isLoaded = true, isEnabled = false)
|
||||
return
|
||||
}
|
||||
_uiState.update { current ->
|
||||
current.copy(
|
||||
isEnabled = true,
|
||||
isRefreshing = true,
|
||||
providers = current.providers.map { it.copy(isLoading = true, errorMessage = null) },
|
||||
)
|
||||
|
|
@ -112,11 +124,19 @@ object CloudLibraryRepository {
|
|||
suspend fun resolvePlayback(
|
||||
item: CloudLibraryItem,
|
||||
file: CloudLibraryFile,
|
||||
): CloudLibraryPlaybackResult =
|
||||
store.resolvePlayback(item, file)
|
||||
): CloudLibraryPlaybackResult {
|
||||
DebridSettingsRepository.ensureLoaded()
|
||||
if (!DebridSettingsRepository.snapshot().cloudLibraryEnabled) {
|
||||
return CloudLibraryPlaybackResult.Failed("Cloud library is disabled.")
|
||||
}
|
||||
return store.resolvePlayback(item, file)
|
||||
}
|
||||
|
||||
private fun connectedCloudCredentials(): List<DebridServiceCredential> =
|
||||
DebridProviders.configuredServices(DebridSettingsRepository.snapshot())
|
||||
DebridSettingsRepository.snapshot()
|
||||
.takeIf { settings -> settings.cloudLibraryEnabled }
|
||||
?.let(DebridProviders::configuredServices)
|
||||
.orEmpty()
|
||||
.filter { credential -> credential.provider.supports(DebridProviderCapability.CloudLibrary) }
|
||||
|
||||
private fun connectedCloudConnectionKeys(): List<CloudConnectionKey> =
|
||||
|
|
|
|||
|
|
@ -83,8 +83,9 @@ internal fun TorboxCloudItemDto.toCloudLibraryItem(
|
|||
val itemId = id.scalarString()
|
||||
?: hash?.trim()?.takeIf { it.isNotBlank() }
|
||||
?: return null
|
||||
val itemName = name?.trim()?.takeIf { it.isNotBlank() } ?: itemId
|
||||
val mappedFiles = files.orEmpty().mapNotNull { file ->
|
||||
file.toCloudLibraryFile()
|
||||
file.toCloudLibraryFile(parentName = itemName)
|
||||
}
|
||||
val filesSize = mappedFiles
|
||||
.mapNotNull { it.sizeBytes }
|
||||
|
|
@ -95,7 +96,7 @@ internal fun TorboxCloudItemDto.toCloudLibraryItem(
|
|||
providerName = providerName,
|
||||
id = itemId,
|
||||
type = type,
|
||||
name = name?.trim()?.takeIf { it.isNotBlank() } ?: itemId,
|
||||
name = itemName,
|
||||
status = listOf(status, downloadState, state)
|
||||
.firstNonBlank(),
|
||||
sizeBytes = size ?: totalSize ?: filesSize,
|
||||
|
|
@ -104,9 +105,8 @@ internal fun TorboxCloudItemDto.toCloudLibraryItem(
|
|||
)
|
||||
}
|
||||
|
||||
internal fun TorboxCloudFileDto.toCloudLibraryFile(): CloudLibraryFile? {
|
||||
val name = listOf(name, shortName, absolutePath)
|
||||
.firstNonBlank()
|
||||
internal fun TorboxCloudFileDto.toCloudLibraryFile(parentName: String? = null): CloudLibraryFile? {
|
||||
val name = bestCloudFileName(parentName = parentName)
|
||||
?: return null
|
||||
val fileId = id.scalarString()
|
||||
val mime = listOf(mimeType, mimeTypeAlt).firstNonBlank()
|
||||
|
|
@ -119,6 +119,32 @@ internal fun TorboxCloudFileDto.toCloudLibraryFile(): CloudLibraryFile? {
|
|||
)
|
||||
}
|
||||
|
||||
private fun TorboxCloudFileDto.bestCloudFileName(parentName: String?): String? {
|
||||
val rawName = name?.trim()?.takeIf { it.isNotBlank() }
|
||||
val short = shortName?.trim()?.takeIf { it.isNotBlank() }
|
||||
val pathName = absolutePath
|
||||
?.trim()
|
||||
?.pathBasename()
|
||||
?.takeIf { it.isNotBlank() }
|
||||
val parent = parentName?.trim()?.takeIf { it.isNotBlank() }
|
||||
val rawNameIsPath = rawName?.isPathLike() == true
|
||||
val rawNameBasename = rawName
|
||||
?.takeIf { rawNameIsPath }
|
||||
?.pathBasename()
|
||||
?.takeIf { it.isNotBlank() }
|
||||
val candidates = listOf(
|
||||
short,
|
||||
rawNameBasename,
|
||||
rawName?.takeUnless { rawNameIsPath },
|
||||
pathName,
|
||||
rawName,
|
||||
absolutePath?.trim()?.takeIf { it.isNotBlank() },
|
||||
)
|
||||
return candidates.firstOrNull { candidate ->
|
||||
candidate?.isUsableCloudFileName(parentName = parent, pathName = pathName) == true
|
||||
} ?: candidates.firstNonBlank()
|
||||
}
|
||||
|
||||
internal fun torboxRequestIdParameterName(type: CloudLibraryItemType): String =
|
||||
when (type) {
|
||||
CloudLibraryItemType.Torrent -> "torrent_id"
|
||||
|
|
@ -129,6 +155,33 @@ internal fun torboxRequestIdParameterName(type: CloudLibraryItemType): String =
|
|||
private fun List<String?>.firstNonBlank(): String? =
|
||||
firstOrNull { !it.isNullOrBlank() }?.trim()
|
||||
|
||||
private fun String.sameDisplayName(other: String?): Boolean {
|
||||
val normalized = normalizeDisplayName()
|
||||
return normalized.isNotBlank() && normalized == other?.normalizeDisplayName()
|
||||
}
|
||||
|
||||
private fun String.isUsableCloudFileName(parentName: String?, pathName: String?): Boolean {
|
||||
if (isBlank() || sameDisplayName(parentName)) return false
|
||||
val pathNameWithoutExtension = pathName?.substringBeforeLast('.', pathName)
|
||||
if (!contains('.') && sameDisplayName(pathNameWithoutExtension)) return false
|
||||
return true
|
||||
}
|
||||
|
||||
private fun String.isPathLike(): Boolean =
|
||||
contains('/') || contains('\\')
|
||||
|
||||
private fun String.pathBasename(): String =
|
||||
substringAfterLast('/').substringAfterLast('\\')
|
||||
|
||||
private fun String.normalizeDisplayName(): String =
|
||||
trim()
|
||||
.substringAfterLast('/')
|
||||
.substringAfterLast('\\')
|
||||
.substringBeforeLast('.', this)
|
||||
.lowercase()
|
||||
.replace(Regex("[^a-z0-9]+"), " ")
|
||||
.trim()
|
||||
|
||||
private fun kotlinx.serialization.json.JsonElement?.scalarString(): String? {
|
||||
val primitive = this as? JsonPrimitive ?: return null
|
||||
return primitive.content.trim().takeIf { it.isNotBlank() }
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import kotlinx.serialization.Serializable
|
|||
|
||||
data class DebridSettings(
|
||||
val enabled: Boolean = false,
|
||||
val cloudLibraryEnabled: Boolean = true,
|
||||
val providerApiKeys: Map<String, String> = emptyMap(),
|
||||
val instantPlaybackPreparationLimit: Int = 0,
|
||||
val streamMaxResults: Int = 0,
|
||||
|
|
@ -25,6 +26,19 @@ data class DebridSettings(
|
|||
val hasAnyApiKey: Boolean
|
||||
get() = DebridProviders.configuredServices(this).isNotEmpty()
|
||||
|
||||
val linkResolvingEnabled: Boolean
|
||||
get() = enabled
|
||||
|
||||
val canResolvePlayableLinks: Boolean
|
||||
get() = linkResolvingEnabled && hasAnyApiKey
|
||||
|
||||
val hasCloudLibraryProvider: Boolean
|
||||
get() = DebridProviders.configuredServices(this)
|
||||
.any { credential -> credential.provider.supports(DebridProviderCapability.CloudLibrary) }
|
||||
|
||||
val canUseCloudLibrary: Boolean
|
||||
get() = cloudLibraryEnabled && hasCloudLibraryProvider
|
||||
|
||||
val hasCustomStreamFormatting: Boolean
|
||||
get() = streamNameTemplate.isNotBlank() || streamDescriptionTemplate.isNotBlank()
|
||||
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ object DebridSettingsRepository {
|
|||
|
||||
private var hasLoaded = false
|
||||
private var enabled = false
|
||||
private var cloudLibraryEnabled = true
|
||||
private var providerApiKeys = emptyMap<String, String>()
|
||||
private var instantPlaybackPreparationLimit = 0
|
||||
private var streamMaxResults = 0
|
||||
|
|
@ -56,6 +57,19 @@ object DebridSettingsRepository {
|
|||
DebridSettingsStorage.saveEnabled(value)
|
||||
}
|
||||
|
||||
fun setLinkResolvingEnabled(value: Boolean) {
|
||||
setEnabled(value)
|
||||
}
|
||||
|
||||
fun setCloudLibraryEnabled(value: Boolean) {
|
||||
ensureLoaded()
|
||||
if (value && !hasVisibleApiKey()) return
|
||||
if (cloudLibraryEnabled == value) return
|
||||
cloudLibraryEnabled = value
|
||||
publish()
|
||||
DebridSettingsStorage.saveCloudLibraryEnabled(value)
|
||||
}
|
||||
|
||||
fun setProviderApiKey(providerId: String, value: String) {
|
||||
ensureLoaded()
|
||||
val provider = DebridProviders.byId(providerId) ?: return
|
||||
|
|
@ -217,6 +231,7 @@ object DebridSettingsRepository {
|
|||
}
|
||||
.toMap()
|
||||
enabled = (DebridSettingsStorage.loadEnabled() ?: false) && hasVisibleApiKey()
|
||||
cloudLibraryEnabled = DebridSettingsStorage.loadCloudLibraryEnabled() ?: true
|
||||
instantPlaybackPreparationLimit = normalizeDebridInstantPlaybackPreparationLimit(
|
||||
DebridSettingsStorage.loadInstantPlaybackPreparationLimit() ?: 0,
|
||||
)
|
||||
|
|
@ -264,6 +279,7 @@ object DebridSettingsRepository {
|
|||
private fun publish() {
|
||||
_uiState.value = DebridSettings(
|
||||
enabled = enabled,
|
||||
cloudLibraryEnabled = cloudLibraryEnabled,
|
||||
providerApiKeys = providerApiKeys,
|
||||
instantPlaybackPreparationLimit = instantPlaybackPreparationLimit,
|
||||
streamMaxResults = streamMaxResults,
|
||||
|
|
|
|||
|
|
@ -5,6 +5,8 @@ import kotlinx.serialization.json.JsonObject
|
|||
internal expect object DebridSettingsStorage {
|
||||
fun loadEnabled(): Boolean?
|
||||
fun saveEnabled(enabled: Boolean)
|
||||
fun loadCloudLibraryEnabled(): Boolean?
|
||||
fun saveCloudLibraryEnabled(enabled: Boolean)
|
||||
fun loadProviderApiKey(providerId: String): String?
|
||||
fun saveProviderApiKey(providerId: String, apiKey: String)
|
||||
fun loadTorboxApiKey(): String?
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ object DebridStreamPresentation {
|
|||
private val formatter = DebridStreamFormatter()
|
||||
|
||||
fun apply(groups: List<AddonStreamGroup>, settings: DebridSettings): List<AddonStreamGroup> {
|
||||
if (!settings.enabled) return groups
|
||||
if (!settings.canResolvePlayableLinks) return groups
|
||||
return groups.map { group ->
|
||||
val visibleStreams = group.streams.filterNot { stream -> stream.isUncachedDebridStream }
|
||||
val debridStreams = visibleStreams.filter { stream -> stream.isManagedDebridStream }
|
||||
|
|
|
|||
|
|
@ -111,7 +111,7 @@ object DirectDebridPlaybackResolver {
|
|||
|
||||
fun shouldResolveToPlayableStream(stream: StreamItem): Boolean {
|
||||
val settings = DebridSettingsRepository.snapshot()
|
||||
if (!settings.enabled) return false
|
||||
if (!settings.canResolvePlayableLinks) return false
|
||||
if (stream.needsLocalDebridResolve) {
|
||||
return stream.isInstalledAddonStream && localTorrentResolveCredential(settings) != null
|
||||
}
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ object DirectDebridStreamPreparer {
|
|||
) {
|
||||
val settings = DebridSettingsRepository.snapshot()
|
||||
val limit = settings.instantPlaybackPreparationLimit
|
||||
if (!settings.enabled || limit <= 0 || !settings.hasAnyApiKey) return
|
||||
if (!settings.canResolvePlayableLinks || limit <= 0) return
|
||||
|
||||
val candidates = prioritizeCandidates(
|
||||
streams = streams,
|
||||
|
|
|
|||
|
|
@ -81,7 +81,7 @@ object LocalDebridAvailabilityService {
|
|||
|
||||
private fun cacheCheckAccount(): DebridServiceCredential? {
|
||||
val settings = DebridSettingsRepository.snapshot()
|
||||
if (!settings.enabled) return null
|
||||
if (!settings.canResolvePlayableLinks) return null
|
||||
return DebridProviders.configuredServices(settings)
|
||||
.firstOrNull { credential -> credential.provider.supports(DebridProviderCapability.LocalTorrentCacheCheck) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -68,6 +68,7 @@ import com.nuvio.app.features.cloud.CloudLibraryItem
|
|||
import com.nuvio.app.features.cloud.CloudLibraryItemType
|
||||
import com.nuvio.app.features.cloud.CloudLibraryRepository
|
||||
import com.nuvio.app.features.cloud.CloudLibraryUiState
|
||||
import com.nuvio.app.features.debrid.DebridSettingsRepository
|
||||
import com.nuvio.app.features.home.components.HomeEmptyStateCard
|
||||
import com.nuvio.app.features.home.components.HomePosterCard
|
||||
import com.nuvio.app.features.home.components.HomeSkeletonRow
|
||||
|
|
@ -95,6 +96,10 @@ fun LibraryScreen(
|
|||
LibraryRepository.uiState
|
||||
}.collectAsStateWithLifecycle()
|
||||
val cloudUiState by CloudLibraryRepository.uiState.collectAsStateWithLifecycle()
|
||||
val cloudSettings by remember {
|
||||
DebridSettingsRepository.ensureLoaded()
|
||||
DebridSettingsRepository.uiState
|
||||
}.collectAsStateWithLifecycle()
|
||||
val watchedUiState by remember {
|
||||
WatchedRepository.ensureLoaded()
|
||||
WatchedRepository.uiState
|
||||
|
|
@ -151,7 +156,7 @@ fun LibraryScreen(
|
|||
}
|
||||
}
|
||||
|
||||
LaunchedEffect(sourceMode) {
|
||||
LaunchedEffect(sourceMode, cloudSettings.cloudLibraryEnabled, cloudSettings.providerApiKeys) {
|
||||
if (sourceMode == LibraryViewMode.Cloud) {
|
||||
CloudLibraryRepository.ensureLoaded()
|
||||
selectedCloudItemKey = null
|
||||
|
|
@ -307,6 +312,18 @@ private fun LazyListScope.cloudLibraryContent(
|
|||
cloudLibrarySkeletonItems()
|
||||
}
|
||||
|
||||
!uiState.isEnabled -> {
|
||||
item {
|
||||
HomeEmptyStateCard(
|
||||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
title = stringResource(Res.string.cloud_library_disabled_title),
|
||||
message = stringResource(Res.string.cloud_library_disabled_message),
|
||||
actionLabel = stringResource(Res.string.cloud_library_disabled_action),
|
||||
onActionClick = onConnectCloudClick,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
!uiState.hasConnectedProvider -> {
|
||||
item {
|
||||
HomeEmptyStateCard(
|
||||
|
|
@ -702,46 +719,59 @@ private fun CloudLibraryFileRow(
|
|||
onClick: () -> Unit,
|
||||
modifier: Modifier = Modifier,
|
||||
) {
|
||||
Row(
|
||||
Surface(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.clip(RoundedCornerShape(8.dp))
|
||||
.background(MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.58f))
|
||||
.clickable(onClick = onClick)
|
||||
.padding(horizontal = 12.dp, vertical = 10.dp),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.spacedBy(12.dp),
|
||||
.clickable(onClick = onClick),
|
||||
shape = RoundedCornerShape(8.dp),
|
||||
color = MaterialTheme.colorScheme.surfaceContainerHigh.copy(alpha = 0.58f),
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.AutoMirrored.Filled.InsertDriveFile,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Column(
|
||||
modifier = Modifier.weight(1f),
|
||||
verticalArrangement = Arrangement.spacedBy(2.dp),
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 14.dp, vertical = 12.dp),
|
||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
Text(
|
||||
text = file.name,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
file.sizeBytes?.let { size ->
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.Top,
|
||||
horizontalArrangement = Arrangement.spacedBy(10.dp),
|
||||
) {
|
||||
Icon(
|
||||
modifier = Modifier
|
||||
.padding(top = 2.dp)
|
||||
.size(18.dp),
|
||||
imageVector = Icons.AutoMirrored.Filled.InsertDriveFile,
|
||||
contentDescription = null,
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
Text(
|
||||
text = formatCloudBytes(size),
|
||||
modifier = Modifier.weight(1f),
|
||||
text = file.name,
|
||||
style = MaterialTheme.typography.bodyMedium,
|
||||
color = MaterialTheme.colorScheme.onSurface,
|
||||
fontWeight = FontWeight.SemiBold,
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
) {
|
||||
Text(
|
||||
text = file.sizeBytes?.let { size -> formatCloudBytes(size) }.orEmpty(),
|
||||
style = MaterialTheme.typography.bodySmall,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
maxLines = 1,
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.PlayArrow,
|
||||
contentDescription = stringResource(Res.string.cloud_library_play_file),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.PlayArrow,
|
||||
contentDescription = stringResource(Res.string.cloud_library_play_file),
|
||||
tint = MaterialTheme.colorScheme.primary,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -608,7 +608,7 @@ private fun EpisodeStreamsSubView(
|
|||
) { _, stream ->
|
||||
EpisodeSourceStreamRow(
|
||||
stream = stream,
|
||||
enabled = stream.isSelectableForPlayback(debridSettings.enabled),
|
||||
enabled = stream.isSelectableForPlayback(debridSettings.canResolvePlayableLinks),
|
||||
onClick = { onStreamSelected(stream, episode) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1166,7 +1166,7 @@ fun PlayerScreen(
|
|||
null
|
||||
},
|
||||
preferBingeGroupInSelection = settings.streamAutoPlayPreferBingeGroup,
|
||||
debridEnabled = DebridSettingsRepository.snapshot().enabled,
|
||||
debridEnabled = DebridSettingsRepository.snapshot().canResolvePlayableLinks,
|
||||
)
|
||||
} else null
|
||||
|
||||
|
|
|
|||
|
|
@ -221,7 +221,7 @@ fun PlayerSourcesPanel(
|
|||
SourceStreamRow(
|
||||
stream = stream,
|
||||
isCurrent = isCurrent,
|
||||
enabled = stream.isSelectableForPlayback(debridSettings.enabled),
|
||||
enabled = stream.isSelectableForPlayback(debridSettings.canResolvePlayableLinks),
|
||||
onClick = { onStreamSelected(stream) },
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -77,6 +77,8 @@ import nuvio.composeapp.generated.resources.action_reset
|
|||
import nuvio.composeapp.generated.resources.action_save
|
||||
import nuvio.composeapp.generated.resources.action_saving
|
||||
import nuvio.composeapp.generated.resources.settings_debrid_add_key_first
|
||||
import nuvio.composeapp.generated.resources.settings_debrid_cloud_library
|
||||
import nuvio.composeapp.generated.resources.settings_debrid_cloud_library_description
|
||||
import nuvio.composeapp.generated.resources.settings_debrid_connected
|
||||
import nuvio.composeapp.generated.resources.settings_debrid_connect_provider
|
||||
import nuvio.composeapp.generated.resources.settings_debrid_disconnect_provider
|
||||
|
|
@ -132,13 +134,22 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
text = stringResource(Res.string.settings_debrid_experimental_notice),
|
||||
)
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
SettingsSwitchRow(
|
||||
title = stringResource(Res.string.settings_debrid_cloud_library),
|
||||
description = stringResource(Res.string.settings_debrid_cloud_library_description),
|
||||
checked = settings.canUseCloudLibrary,
|
||||
enabled = settings.hasCloudLibraryProvider,
|
||||
isTablet = isTablet,
|
||||
onCheckedChange = DebridSettingsRepository::setCloudLibraryEnabled,
|
||||
)
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
SettingsSwitchRow(
|
||||
title = stringResource(Res.string.settings_debrid_enable),
|
||||
description = stringResource(Res.string.settings_debrid_enable_description),
|
||||
checked = settings.enabled && settings.hasAnyApiKey,
|
||||
checked = settings.canResolvePlayableLinks,
|
||||
enabled = settings.hasAnyApiKey,
|
||||
isTablet = isTablet,
|
||||
onCheckedChange = DebridSettingsRepository::setEnabled,
|
||||
onCheckedChange = DebridSettingsRepository::setLinkResolvingEnabled,
|
||||
)
|
||||
if (!settings.hasAnyApiKey) {
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
|
|
@ -220,10 +231,12 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
}
|
||||
}
|
||||
|
||||
if (!settings.canResolvePlayableLinks) return
|
||||
|
||||
item {
|
||||
var showPrepareCountDialog by rememberSaveable { mutableStateOf(false) }
|
||||
val prepareLimit = settings.instantPlaybackPreparationLimit
|
||||
val prepareEnabled = settings.enabled && prepareLimit > 0
|
||||
val prepareEnabled = prepareLimit > 0
|
||||
|
||||
SettingsSection(
|
||||
title = stringResource(Res.string.settings_debrid_section_instant_playback),
|
||||
|
|
@ -234,7 +247,7 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
title = stringResource(Res.string.settings_debrid_prepare_instant_playback),
|
||||
description = stringResource(Res.string.settings_debrid_prepare_instant_playback_description),
|
||||
checked = prepareEnabled,
|
||||
enabled = settings.enabled && settings.hasAnyApiKey,
|
||||
enabled = settings.canResolvePlayableLinks,
|
||||
isTablet = isTablet,
|
||||
onCheckedChange = { enabled ->
|
||||
DebridSettingsRepository.setInstantPlaybackPreparationLimit(
|
||||
|
|
@ -281,7 +294,7 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
title = "Max results",
|
||||
description = "Limit how many cloud-service results appear.",
|
||||
value = streamMaxResultsLabel(preferences.maxResults),
|
||||
enabled = settings.enabled,
|
||||
enabled = settings.canResolvePlayableLinks,
|
||||
onClick = { activeStreamPicker = DebridStreamPicker.MAX_RESULTS },
|
||||
)
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
|
|
@ -290,7 +303,7 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
title = "Sort results",
|
||||
description = "Choose how cloud-service results are ordered.",
|
||||
value = sortProfileLabel(preferences.sortCriteria),
|
||||
enabled = settings.enabled,
|
||||
enabled = settings.canResolvePlayableLinks,
|
||||
onClick = { activeStreamPicker = DebridStreamPicker.SORT_MODE },
|
||||
)
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
|
|
@ -299,7 +312,7 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
title = "Per resolution limit",
|
||||
description = "Cap repeated 2160p, 1080p, 720p results after sorting.",
|
||||
value = streamMaxResultsLabel(preferences.maxPerResolution),
|
||||
enabled = settings.enabled,
|
||||
enabled = settings.canResolvePlayableLinks,
|
||||
onClick = { activeStreamPicker = DebridStreamPicker.MAX_PER_RESOLUTION },
|
||||
)
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
|
|
@ -308,7 +321,7 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
title = "Per quality limit",
|
||||
description = "Cap repeated BluRay, WEB-DL, REMUX results after sorting.",
|
||||
value = streamMaxResultsLabel(preferences.maxPerQuality),
|
||||
enabled = settings.enabled,
|
||||
enabled = settings.canResolvePlayableLinks,
|
||||
onClick = { activeStreamPicker = DebridStreamPicker.MAX_PER_QUALITY },
|
||||
)
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
|
|
@ -317,7 +330,7 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
title = "Size range",
|
||||
description = "Filter cloud-service results by file size.",
|
||||
value = sizeRangeLabel(preferences),
|
||||
enabled = settings.enabled,
|
||||
enabled = settings.canResolvePlayableLinks,
|
||||
onClick = { activeStreamPicker = DebridStreamPicker.SIZE_RANGE },
|
||||
)
|
||||
rows.forEach { row ->
|
||||
|
|
@ -327,7 +340,7 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
title = row.title,
|
||||
description = row.description,
|
||||
value = row.value,
|
||||
enabled = settings.enabled,
|
||||
enabled = settings.canResolvePlayableLinks,
|
||||
onClick = { activeStreamPicker = row.picker },
|
||||
)
|
||||
}
|
||||
|
|
@ -357,7 +370,7 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
title = stringResource(Res.string.settings_debrid_name_template),
|
||||
description = stringResource(Res.string.settings_debrid_name_template_description),
|
||||
value = templatePreview(settings.streamNameTemplate),
|
||||
enabled = settings.enabled,
|
||||
enabled = settings.canResolvePlayableLinks,
|
||||
onClick = { activeTemplateField = DebridTemplateField.NAME },
|
||||
)
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
|
|
@ -366,7 +379,7 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
title = stringResource(Res.string.settings_debrid_description_template),
|
||||
description = stringResource(Res.string.settings_debrid_description_template_description),
|
||||
value = templatePreview(settings.streamDescriptionTemplate),
|
||||
enabled = settings.enabled,
|
||||
enabled = settings.canResolvePlayableLinks,
|
||||
onClick = { activeTemplateField = DebridTemplateField.DESCRIPTION },
|
||||
)
|
||||
SettingsGroupDivider(isTablet = isTablet)
|
||||
|
|
@ -375,7 +388,7 @@ internal fun LazyListScope.debridSettingsContent(
|
|||
title = stringResource(Res.string.settings_debrid_formatter_reset_title),
|
||||
description = stringResource(Res.string.settings_debrid_formatter_reset_subtitle),
|
||||
value = stringResource(Res.string.action_reset),
|
||||
enabled = settings.enabled,
|
||||
enabled = settings.canResolvePlayableLinks,
|
||||
onClick = DebridSettingsRepository::resetStreamTemplates,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -209,7 +209,7 @@ object AddonStreamWarmupRepository {
|
|||
|
||||
DebridSettingsRepository.ensureLoaded()
|
||||
val settings = DebridSettingsRepository.snapshot()
|
||||
if (!settings.enabled || settings.torboxApiKey.isBlank()) return null
|
||||
if (!settings.canResolvePlayableLinks || settings.torboxApiKey.isBlank()) return null
|
||||
|
||||
AddonRepository.initialize()
|
||||
val addonTargets = AddonRepository.uiState.value.addons
|
||||
|
|
|
|||
|
|
@ -299,7 +299,7 @@ object StreamsRepository {
|
|||
installedAddonNames = installedAddonNames,
|
||||
selectedAddons = playerSettings.streamAutoPlaySelectedAddons,
|
||||
selectedPlugins = playerSettings.streamAutoPlaySelectedPlugins,
|
||||
debridEnabled = debridSettings.enabled,
|
||||
debridEnabled = debridSettings.canResolvePlayableLinks,
|
||||
)
|
||||
_uiState.update { it.copy(autoPlayStream = selected) }
|
||||
if (selected == null) {
|
||||
|
|
@ -498,7 +498,7 @@ object StreamsRepository {
|
|||
installedAddonNames = installedAddonNames,
|
||||
selectedAddons = playerSettings.streamAutoPlaySelectedAddons,
|
||||
selectedPlugins = playerSettings.streamAutoPlaySelectedPlugins,
|
||||
debridEnabled = debridSettings.enabled,
|
||||
debridEnabled = debridSettings.canResolvePlayableLinks,
|
||||
)
|
||||
_uiState.update { it.copy(autoPlayStream = selected) }
|
||||
}
|
||||
|
|
|
|||
|
|
@ -224,8 +224,8 @@ fun StreamsScreen(
|
|||
episodeNumber = episodeNumber,
|
||||
episodeTitle = episodeTitle,
|
||||
uiState = uiState,
|
||||
debridEnabled = debridSettings.enabled,
|
||||
appendInstantServiceToDefaultName = !debridSettings.hasCustomStreamFormatting,
|
||||
debridEnabled = debridSettings.canResolvePlayableLinks,
|
||||
appendInstantServiceToDefaultName = debridSettings.canResolvePlayableLinks && !debridSettings.hasCustomStreamFormatting,
|
||||
resumePositionMs = effectiveResumePositionMs,
|
||||
resumeProgressFraction = effectiveResumeProgressFraction,
|
||||
onStreamSelected = { stream, positionMs, progressFraction ->
|
||||
|
|
@ -243,8 +243,8 @@ fun StreamsScreen(
|
|||
episodeNumber = episodeNumber,
|
||||
episodeTitle = episodeTitle,
|
||||
uiState = uiState,
|
||||
debridEnabled = debridSettings.enabled,
|
||||
appendInstantServiceToDefaultName = !debridSettings.hasCustomStreamFormatting,
|
||||
debridEnabled = debridSettings.canResolvePlayableLinks,
|
||||
appendInstantServiceToDefaultName = debridSettings.canResolvePlayableLinks && !debridSettings.hasCustomStreamFormatting,
|
||||
resumePositionMs = effectiveResumePositionMs,
|
||||
resumeProgressFraction = effectiveResumeProgressFraction,
|
||||
onStreamSelected = { stream, positionMs, progressFraction ->
|
||||
|
|
|
|||
|
|
@ -64,10 +64,72 @@ class TorboxCloudLibraryProviderApiTest {
|
|||
assertNotNull(item)
|
||||
assertEquals("abc123", item.id)
|
||||
assertEquals("abc123", item.name)
|
||||
assertEquals("/downloads/show.mp4", item.files.single().name)
|
||||
assertEquals("show.mp4", item.files.single().name)
|
||||
assertTrue(item.files.single().playable)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `mapping prefers absolute path basename when file name repeats pack name`() {
|
||||
val item = TorboxCloudItemDto(
|
||||
id = JsonPrimitive(44),
|
||||
name = "The Rookie S01",
|
||||
files = listOf(
|
||||
TorboxCloudFileDto(
|
||||
id = JsonPrimitive(1),
|
||||
name = "The Rookie S01",
|
||||
absolutePath = "/The Rookie S01/The.Rookie.S01E01.1080p.WEB-DL.mkv",
|
||||
mimeType = "video/x-matroska",
|
||||
),
|
||||
TorboxCloudFileDto(
|
||||
id = JsonPrimitive(2),
|
||||
shortName = "The Rookie S01",
|
||||
absolutePath = "/The Rookie S01/The.Rookie.S01E02.1080p.WEB-DL.mkv",
|
||||
mimeType = "video/x-matroska",
|
||||
),
|
||||
),
|
||||
).toCloudLibraryItem(
|
||||
providerId = "torbox",
|
||||
providerName = "Torbox",
|
||||
type = CloudLibraryItemType.Torrent,
|
||||
)
|
||||
|
||||
assertNotNull(item)
|
||||
assertEquals(
|
||||
listOf(
|
||||
"The.Rookie.S01E01.1080p.WEB-DL.mkv",
|
||||
"The.Rookie.S01E02.1080p.WEB-DL.mkv",
|
||||
),
|
||||
item.playableFiles.map { it.name },
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `mapping prefers short name when Torbox file name is a relative pack path`() {
|
||||
val item = TorboxCloudItemDto(
|
||||
id = JsonPrimitive(29556645),
|
||||
name = "From.The.Earth.To.The.Moon.1998.S01.2160p.MAX.WEB-DL.x265.10bit.HDR.TrueHD.7.1.Atmos-FLUX[rartv]",
|
||||
files = listOf(
|
||||
TorboxCloudFileDto(
|
||||
id = JsonPrimitive(1),
|
||||
name = "From.The.Earth.To.The.Moon.S01.2160p.MAX.WEB-DL.x265.10bit.HDR.TrueHD.7.1.Atmos-FLUX[rartv]/From.The.Earth.To.The.Moon.S01E01.2160p.MAX.WEB-DL.TrueHD.Atmos.7.1.HDR.DV.HEVC-FLUX.mkv",
|
||||
shortName = "From.The.Earth.To.The.Moon.S01E01.2160p.MAX.WEB-DL.TrueHD.Atmos.7.1.HDR.DV.HEVC-FLUX.mkv",
|
||||
absolutePath = "/completed/2c229180e129280a36ba7f3a22e2f5135a02a766/From.The.Earth.To.The.Moon.S01.2160p.MAX.WEB-DL.x265.10bit.HDR.TrueHD.7.1.Atmos-FLUX[rartv]/From.The.Earth.To.The.Moon.S01E01.2160p.MAX.WEB-DL.TrueHD.Atmos.7.1.HDR.DV.HEVC-FLUX.mkv",
|
||||
mimeType = "video/x-matroska",
|
||||
),
|
||||
),
|
||||
).toCloudLibraryItem(
|
||||
providerId = "torbox",
|
||||
providerName = "Torbox",
|
||||
type = CloudLibraryItemType.Torrent,
|
||||
)
|
||||
|
||||
assertNotNull(item)
|
||||
assertEquals(
|
||||
"From.The.Earth.To.The.Moon.S01E01.2160p.MAX.WEB-DL.TrueHD.Atmos.7.1.HDR.DV.HEVC-FLUX.mkv",
|
||||
item.playableFiles.single().name,
|
||||
)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `mapping handles missing item ids and empty file lists`() {
|
||||
assertNull(
|
||||
|
|
|
|||
|
|
@ -33,4 +33,16 @@ class DebridSettingsTest {
|
|||
assertTrue(settings.hasAnyApiKey)
|
||||
assertFalse(DebridProviders.isVisible(DebridProviders.REAL_DEBRID_ID))
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `cloud library and link resolving capabilities are independent`() {
|
||||
val settings = DebridSettings(
|
||||
enabled = false,
|
||||
cloudLibraryEnabled = true,
|
||||
providerApiKeys = mapOf(DebridProviders.TORBOX_ID to "tb_key"),
|
||||
)
|
||||
|
||||
assertTrue(settings.canUseCloudLibrary)
|
||||
assertFalse(settings.canResolvePlayableLinks)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -109,6 +109,32 @@ class DebridStreamPresentationTest {
|
|||
assertEquals(listOf("Cached"), presented.map { it.name })
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `leaves cloud-service results untouched when link resolving is off`() {
|
||||
val uncached = localTorboxStream(
|
||||
name = "Uncached",
|
||||
filename = "Movie.2160p.WEB-DL.HEVC-GRP.mkv",
|
||||
size = 20_000_000_000,
|
||||
cacheState = StreamDebridCacheState.NOT_CACHED,
|
||||
)
|
||||
|
||||
val presented = DebridStreamPresentation.apply(
|
||||
groups = listOf(
|
||||
AddonStreamGroup(
|
||||
addonName = "Addon",
|
||||
addonId = "addon:test",
|
||||
streams = listOf(uncached),
|
||||
),
|
||||
),
|
||||
settings = DebridSettings(
|
||||
enabled = false,
|
||||
providerApiKeys = mapOf(DebridProviders.TORBOX_ID to "key"),
|
||||
),
|
||||
).single().streams
|
||||
|
||||
assertEquals(listOf("Uncached"), presented.map { it.name })
|
||||
}
|
||||
|
||||
private fun localTorboxStream(
|
||||
name: String = "Torrent",
|
||||
filename: String,
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import platform.Foundation.NSUserDefaults
|
|||
|
||||
actual object DebridSettingsStorage {
|
||||
private const val enabledKey = "debrid_enabled"
|
||||
private const val cloudLibraryEnabledKey = "debrid_cloud_library_enabled"
|
||||
private const val torboxApiKeyKey = "debrid_torbox_api_key"
|
||||
private const val realDebridApiKeyKey = "debrid_real_debrid_api_key"
|
||||
private const val instantPlaybackPreparationLimitKey = "debrid_instant_playback_preparation_limit"
|
||||
|
|
@ -29,6 +30,7 @@ actual object DebridSettingsStorage {
|
|||
private fun syncKeys(): List<String> =
|
||||
listOf(
|
||||
enabledKey,
|
||||
cloudLibraryEnabledKey,
|
||||
instantPlaybackPreparationLimitKey,
|
||||
streamMaxResultsKey,
|
||||
streamSortModeKey,
|
||||
|
|
@ -47,6 +49,12 @@ actual object DebridSettingsStorage {
|
|||
saveBoolean(enabledKey, enabled)
|
||||
}
|
||||
|
||||
actual fun loadCloudLibraryEnabled(): Boolean? = loadBoolean(cloudLibraryEnabledKey)
|
||||
|
||||
actual fun saveCloudLibraryEnabled(enabled: Boolean) {
|
||||
saveBoolean(cloudLibraryEnabledKey, enabled)
|
||||
}
|
||||
|
||||
actual fun loadProviderApiKey(providerId: String): String? =
|
||||
loadString(providerApiKeyKey(providerId))
|
||||
|
||||
|
|
@ -163,6 +171,7 @@ actual object DebridSettingsStorage {
|
|||
|
||||
actual fun exportToSyncPayload(): JsonObject = buildJsonObject {
|
||||
loadEnabled()?.let { put(enabledKey, encodeSyncBoolean(it)) }
|
||||
loadCloudLibraryEnabled()?.let { put(cloudLibraryEnabledKey, encodeSyncBoolean(it)) }
|
||||
DebridProviders.all().forEach { provider ->
|
||||
loadProviderApiKey(provider.id)?.let {
|
||||
put(providerApiKeyKey(provider.id), encodeSyncString(it))
|
||||
|
|
@ -186,6 +195,7 @@ actual object DebridSettingsStorage {
|
|||
}
|
||||
|
||||
payload.decodeSyncBoolean(enabledKey)?.let(::saveEnabled)
|
||||
payload.decodeSyncBoolean(cloudLibraryEnabledKey)?.let(::saveCloudLibraryEnabled)
|
||||
DebridProviders.all().forEach { provider ->
|
||||
payload.decodeSyncString(providerApiKeyKey(provider.id))?.let { apiKey ->
|
||||
saveProviderApiKey(provider.id, apiKey)
|
||||
|
|
|
|||
Loading…
Reference in a new issue