mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-22 17:52:06 +00:00
feat: add support for addon enable/disable flag
This commit is contained in:
parent
ada9bc00f7
commit
ba5f460002
25 changed files with 285 additions and 62 deletions
|
|
@ -19,6 +19,7 @@ import java.util.concurrent.TimeUnit
|
|||
actual object AddonStorage {
|
||||
private const val preferencesName = "nuvio_addons"
|
||||
private const val addonUrlsKey = "installed_manifest_urls"
|
||||
private const val addonEnabledStatesKey = "installed_manifest_enabled_states"
|
||||
|
||||
private var preferences: SharedPreferences? = null
|
||||
|
||||
|
|
@ -41,6 +42,34 @@ actual object AddonStorage {
|
|||
?.putString("${addonUrlsKey}_$profileId", urls.joinToString(separator = "\n"))
|
||||
?.apply()
|
||||
}
|
||||
|
||||
actual fun loadAddonEnabledStates(profileId: Int): Map<String, Boolean> =
|
||||
preferences
|
||||
?.getString("${addonEnabledStatesKey}_$profileId", null)
|
||||
.orEmpty()
|
||||
.lineSequence()
|
||||
.mapNotNull(::parseEnabledStateLine)
|
||||
.toMap()
|
||||
|
||||
actual fun saveAddonEnabledStates(profileId: Int, states: Map<String, Boolean>) {
|
||||
val payload = states.entries.joinToString(separator = "\n") { (url, enabled) ->
|
||||
"$url\t$enabled"
|
||||
}
|
||||
preferences
|
||||
?.edit()
|
||||
?.putString("${addonEnabledStatesKey}_$profileId", payload)
|
||||
?.apply()
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseEnabledStateLine(line: String): Pair<String, Boolean>? {
|
||||
val url = line.substringBefore("\t").trim().takeIf { it.isNotEmpty() } ?: return null
|
||||
val rawEnabled = line.substringAfter("\t", "true").trim().lowercase()
|
||||
val enabled = when (rawEnabled) {
|
||||
"false" -> false
|
||||
else -> true
|
||||
}
|
||||
return url to enabled
|
||||
}
|
||||
|
||||
private val addonHttpClient = OkHttpClient.Builder()
|
||||
|
|
|
|||
|
|
@ -25,6 +25,7 @@
|
|||
<string name="addons_badge_active">Active</string>
|
||||
<string name="addons_badge_catalogs">%1$d catalogs</string>
|
||||
<string name="addons_badge_configurable">Configurable</string>
|
||||
<string name="addons_badge_disabled">Disabled</string>
|
||||
<string name="addons_badge_refreshing">Refreshing</string>
|
||||
<string name="addons_badge_resources">%1$d resources</string>
|
||||
<string name="addons_badge_unavailable">Unavailable</string>
|
||||
|
|
|
|||
|
|
@ -103,6 +103,7 @@ import com.nuvio.app.core.ui.isLiquidGlassNativeTabBarSupported
|
|||
import com.nuvio.app.core.ui.localizedContinueWatchingSubtitle
|
||||
import com.nuvio.app.features.auth.AuthScreen
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.catalog.CatalogRepository
|
||||
import com.nuvio.app.features.catalog.CatalogScreen
|
||||
import com.nuvio.app.features.catalog.INTERNAL_LIBRARY_MANIFEST_URL
|
||||
|
|
@ -622,6 +623,7 @@ private fun MainAppContent(
|
|||
|
||||
val addonProbeTargets = remember(addonsUiState.addons) {
|
||||
addonsUiState.addons
|
||||
.enabledAddons()
|
||||
.mapNotNull { it.manifest?.transportUrl }
|
||||
.distinct()
|
||||
.sorted()
|
||||
|
|
|
|||
|
|
@ -50,11 +50,12 @@ data class ManagedAddon(
|
|||
val manifestUrl: String,
|
||||
val manifest: AddonManifest? = null,
|
||||
val userSetName: String? = null,
|
||||
val enabled: Boolean = true,
|
||||
val isRefreshing: Boolean = false,
|
||||
val errorMessage: String? = null,
|
||||
) {
|
||||
val isActive: Boolean
|
||||
get() = manifest != null
|
||||
get() = enabled && manifest != null
|
||||
|
||||
val displayTitle: String
|
||||
get() = userSetName?.takeIf { it.isNotBlank() && it != manifest?.name }
|
||||
|
|
@ -78,9 +79,12 @@ internal fun List<ManagedAddon>.toOverview(): AddonOverview =
|
|||
AddonOverview(
|
||||
totalAddons = size,
|
||||
activeAddons = count { it.isActive },
|
||||
totalCatalogs = sumOf { it.manifest?.catalogs?.size ?: 0 },
|
||||
totalCatalogs = filter { it.enabled }.sumOf { it.manifest?.catalogs?.size ?: 0 },
|
||||
)
|
||||
|
||||
internal fun List<ManagedAddon>.enabledAddons(): List<ManagedAddon> =
|
||||
filter { it.enabled }
|
||||
|
||||
sealed interface AddAddonResult {
|
||||
data class Success(val manifest: AddonManifest) : AddAddonResult
|
||||
data class Error(val message: String) : AddAddonResult
|
||||
|
|
|
|||
|
|
@ -3,6 +3,8 @@ package com.nuvio.app.features.addons
|
|||
internal expect object AddonStorage {
|
||||
fun loadInstalledAddonUrls(profileId: Int): List<String>
|
||||
fun saveInstalledAddonUrls(profileId: Int, urls: List<String>)
|
||||
fun loadAddonEnabledStates(profileId: Int): Map<String, Boolean>
|
||||
fun saveAddonEnabledStates(profileId: Int, states: Map<String, Boolean>)
|
||||
}
|
||||
|
||||
data class RawHttpResponse(
|
||||
|
|
|
|||
|
|
@ -62,19 +62,24 @@ object AddonRepository {
|
|||
log.d { "initialize() — loading local addons for profile $currentProfileId" }
|
||||
|
||||
val storedUrls = dedupeManifestUrls(AddonStorage.loadInstalledAddonUrls(currentProfileId))
|
||||
val enabledByUrl = loadLocalEnabledStates()
|
||||
log.d { "initialize() — local addon count: ${storedUrls.size}" }
|
||||
if (storedUrls.isEmpty()) return
|
||||
|
||||
val existingByUrl = _uiState.value.addons.associateBy(ManagedAddon::manifestUrl)
|
||||
_uiState.value = AddonsUiState(
|
||||
addons = storedUrls.map { manifestUrl ->
|
||||
existingByUrl[manifestUrl].toPendingAddon(manifestUrl)
|
||||
existingByUrl[manifestUrl].toPendingAddon(
|
||||
manifestUrl = manifestUrl,
|
||||
enabled = enabledByUrl[manifestUrl],
|
||||
)
|
||||
},
|
||||
)
|
||||
|
||||
storedUrls.forEach { manifestUrl ->
|
||||
val existing = existingByUrl[manifestUrl]
|
||||
if (existing == null || (existing.manifest == null && !existing.isRefreshing)) {
|
||||
val addon = _uiState.value.addons.firstOrNull { it.manifestUrl == manifestUrl }
|
||||
if (addon?.enabled == true && (existing == null || (addon.manifest == null && !addon.isRefreshing))) {
|
||||
refreshAddon(manifestUrl)
|
||||
}
|
||||
}
|
||||
|
|
@ -110,30 +115,35 @@ object AddonRepository {
|
|||
}
|
||||
.decodeList<AddonRow>()
|
||||
|
||||
val namesByUrl = mutableMapOf<String, String>()
|
||||
val rowsByUrl = linkedMapOf<String, AddonRow>()
|
||||
rows.forEach { row ->
|
||||
if (!row.name.isNullOrBlank()) {
|
||||
namesByUrl[ensureManifestSuffix(row.url)] = row.name
|
||||
val manifestUrl = ensureManifestSuffix(row.url)
|
||||
if (!rowsByUrl.containsKey(manifestUrl)) {
|
||||
rowsByUrl[manifestUrl] = row.copy(url = manifestUrl)
|
||||
}
|
||||
}
|
||||
|
||||
val urls = dedupeManifestUrls(rows.map { it.url })
|
||||
val urls = rowsByUrl.keys.toList()
|
||||
log.i { "pullFromServer() — server returned ${rows.size} addons" }
|
||||
urls.forEachIndexed { i, u -> log.d { " server[$i]: $u" } }
|
||||
|
||||
if (urls.isEmpty() && !pulledFromServer) {
|
||||
val localUrls = AddonStorage.loadInstalledAddonUrls(currentProfileId)
|
||||
val localUrls = dedupeManifestUrls(AddonStorage.loadInstalledAddonUrls(currentProfileId))
|
||||
log.i { "pullFromServer() — server empty, local has ${localUrls.size} addons" }
|
||||
if (localUrls.isNotEmpty()) {
|
||||
log.i { "pullFromServer() — migrating local addons to server for profile $currentProfileId" }
|
||||
initialize()
|
||||
pulledFromServer = true
|
||||
val enabledByUrl = loadLocalEnabledStates()
|
||||
val addons = localUrls.mapIndexed { index, addonUrl ->
|
||||
val manifestUrl = ensureManifestSuffix(addonUrl)
|
||||
AddonPushItem(
|
||||
url = addonUrl,
|
||||
url = manifestUrl,
|
||||
name = _uiState.value.addons
|
||||
.find { it.manifestUrl == addonUrl }?.manifest?.name ?: "",
|
||||
enabled = true,
|
||||
.find { it.manifestUrl == manifestUrl }?.manifest?.name ?: "",
|
||||
enabled = enabledByUrl[manifestUrl]
|
||||
?: _uiState.value.addons.find { it.manifestUrl == manifestUrl }?.enabled
|
||||
?: true,
|
||||
sortOrder = index,
|
||||
)
|
||||
}
|
||||
|
|
@ -151,16 +161,21 @@ object AddonRepository {
|
|||
val localUrls = dedupeManifestUrls(AddonStorage.loadInstalledAddonUrls(currentProfileId))
|
||||
if (localUrls.isNotEmpty()) {
|
||||
log.w { "pullFromServer() — remote empty while local has ${localUrls.size} addons; preserving local addons" }
|
||||
val enabledByUrl = loadLocalEnabledStates()
|
||||
val existingByUrl = _uiState.value.addons.associateBy(ManagedAddon::manifestUrl)
|
||||
_uiState.value = AddonsUiState(
|
||||
addons = localUrls.map { url ->
|
||||
existingByUrl[url].toPendingAddon(url)
|
||||
existingByUrl[url].toPendingAddon(
|
||||
manifestUrl = url,
|
||||
enabled = enabledByUrl[url],
|
||||
)
|
||||
},
|
||||
)
|
||||
persist()
|
||||
localUrls.forEach { url ->
|
||||
val existing = existingByUrl[url]
|
||||
if (existing == null || (existing.manifest == null && !existing.isRefreshing)) {
|
||||
val addon = _uiState.value.addons.firstOrNull { it.manifestUrl == url }
|
||||
if (addon?.enabled == true && (existing == null || (addon.manifest == null && !addon.isRefreshing))) {
|
||||
refreshAddon(url)
|
||||
}
|
||||
}
|
||||
|
|
@ -173,13 +188,19 @@ object AddonRepository {
|
|||
val existingByUrl = _uiState.value.addons.associateBy(ManagedAddon::manifestUrl)
|
||||
_uiState.value = AddonsUiState(
|
||||
addons = urls.map { url ->
|
||||
existingByUrl[url].toPendingAddon(url, namesByUrl[url])
|
||||
val row = rowsByUrl[url]
|
||||
existingByUrl[url].toPendingAddon(
|
||||
manifestUrl = url,
|
||||
userSetName = row?.name?.takeIf { it.isNotBlank() },
|
||||
enabled = row?.enabled,
|
||||
)
|
||||
},
|
||||
)
|
||||
persist()
|
||||
urls.forEach { url ->
|
||||
val existing = existingByUrl[url]
|
||||
if (existing == null || (existing.manifest == null && !existing.isRefreshing)) {
|
||||
val addon = _uiState.value.addons.firstOrNull { it.manifestUrl == url }
|
||||
if (addon?.enabled == true && (existing == null || (addon.manifest == null && !addon.isRefreshing))) {
|
||||
refreshAddon(url)
|
||||
}
|
||||
}
|
||||
|
|
@ -194,7 +215,9 @@ object AddonRepository {
|
|||
suspend fun awaitManifestsLoaded() {
|
||||
if (_uiState.value.addons.isEmpty()) return
|
||||
uiState.first { state ->
|
||||
state.addons.isEmpty() || state.addons.any { it.manifest != null }
|
||||
state.addons.isEmpty() ||
|
||||
state.addons.any { it.manifest != null } ||
|
||||
state.addons.none { it.isRefreshing }
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -273,8 +296,30 @@ object AddonRepository {
|
|||
pushToServer()
|
||||
}
|
||||
|
||||
fun setAddonEnabled(manifestUrl: String, enabled: Boolean) {
|
||||
if (isUsingPrimaryAddonsFromSecondaryProfile()) return
|
||||
var shouldRefresh = false
|
||||
_uiState.update { current ->
|
||||
current.copy(
|
||||
addons = current.addons.map { addon ->
|
||||
if (addon.manifestUrl != manifestUrl || addon.enabled == enabled) {
|
||||
addon
|
||||
} else {
|
||||
shouldRefresh = enabled && addon.manifest == null && !addon.isRefreshing
|
||||
addon.copy(enabled = enabled)
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
persist()
|
||||
pushToServer()
|
||||
if (shouldRefresh) {
|
||||
refreshAddon(manifestUrl)
|
||||
}
|
||||
}
|
||||
|
||||
fun refreshAll() {
|
||||
_uiState.value.addons.distinctBy { it.manifestUrl }.forEach { addon ->
|
||||
_uiState.value.addons.filter { it.enabled }.distinctBy { it.manifestUrl }.forEach { addon ->
|
||||
refreshAddon(addon.manifestUrl)
|
||||
}
|
||||
}
|
||||
|
|
@ -339,13 +384,13 @@ object AddonRepository {
|
|||
val addons = _uiState.value.addons
|
||||
.distinctBy { it.manifestUrl }
|
||||
.mapIndexed { index, addon ->
|
||||
AddonPushItem(
|
||||
url = addon.manifestUrl,
|
||||
name = addon.userSetName?.takeIf { it.isNotBlank() } ?: addon.manifest?.name ?: "",
|
||||
enabled = true,
|
||||
sortOrder = index,
|
||||
)
|
||||
}
|
||||
AddonPushItem(
|
||||
url = addon.manifestUrl,
|
||||
name = addon.userSetName?.takeIf { it.isNotBlank() } ?: addon.manifest?.name ?: "",
|
||||
enabled = addon.enabled,
|
||||
sortOrder = index,
|
||||
)
|
||||
}
|
||||
log.d { "pushToServer() — profileId=$profileId, pushing ${addons.size} addons" }
|
||||
val params = buildJsonObject {
|
||||
put("p_profile_id", profileId)
|
||||
|
|
@ -377,12 +422,21 @@ object AddonRepository {
|
|||
}
|
||||
|
||||
private fun persist() {
|
||||
val addons = _uiState.value.addons
|
||||
AddonStorage.saveInstalledAddonUrls(
|
||||
currentProfileId,
|
||||
dedupeManifestUrls(_uiState.value.addons.map { it.manifestUrl }),
|
||||
dedupeManifestUrls(addons.map { it.manifestUrl }),
|
||||
)
|
||||
AddonStorage.saveAddonEnabledStates(
|
||||
currentProfileId,
|
||||
addons.associate { it.manifestUrl to it.enabled },
|
||||
)
|
||||
}
|
||||
|
||||
private fun loadLocalEnabledStates(): Map<String, Boolean> =
|
||||
AddonStorage.loadAddonEnabledStates(currentProfileId)
|
||||
.mapKeys { (url, _) -> ensureManifestSuffix(url) }
|
||||
|
||||
private fun cancelActiveRefreshes() {
|
||||
activeRefreshJobs.values.forEach(Job::cancel)
|
||||
activeRefreshJobs.clear()
|
||||
|
|
@ -399,27 +453,35 @@ object AddonRepository {
|
|||
}
|
||||
}
|
||||
|
||||
private fun ManagedAddon?.toPendingAddon(manifestUrl: String, userSetName: String? = null): ManagedAddon =
|
||||
private fun ManagedAddon?.toPendingAddon(
|
||||
manifestUrl: String,
|
||||
userSetName: String? = null,
|
||||
enabled: Boolean? = null,
|
||||
): ManagedAddon =
|
||||
when {
|
||||
this == null -> ManagedAddon(
|
||||
manifestUrl = manifestUrl,
|
||||
isRefreshing = true,
|
||||
isRefreshing = enabled ?: true,
|
||||
userSetName = userSetName,
|
||||
enabled = enabled ?: true,
|
||||
)
|
||||
manifest != null -> copy(
|
||||
manifestUrl = manifestUrl,
|
||||
isRefreshing = false,
|
||||
userSetName = userSetName ?: this.userSetName,
|
||||
enabled = enabled ?: this.enabled,
|
||||
)
|
||||
isRefreshing -> copy(
|
||||
manifestUrl = manifestUrl,
|
||||
userSetName = userSetName ?: this.userSetName,
|
||||
enabled = enabled ?: this.enabled,
|
||||
)
|
||||
else -> copy(
|
||||
manifestUrl = manifestUrl,
|
||||
isRefreshing = true,
|
||||
isRefreshing = enabled ?: this.enabled,
|
||||
errorMessage = null,
|
||||
userSetName = userSetName ?: this.userSetName,
|
||||
enabled = enabled ?: this.enabled,
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -23,6 +23,8 @@ import androidx.compose.material.icons.rounded.Settings
|
|||
import androidx.compose.material3.HorizontalDivider
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Switch
|
||||
import androidx.compose.material3.SwitchDefaults
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
|
|
@ -157,6 +159,9 @@ internal fun AddonsSettingsPageContent(
|
|||
null
|
||||
},
|
||||
onRefreshClick = { AddonRepository.refreshAddon(addon.manifestUrl) },
|
||||
onEnabledChange = { enabled ->
|
||||
AddonRepository.setAddonEnabled(addon.manifestUrl, enabled)
|
||||
},
|
||||
onConfigureClick = if (showConfigureAction && !configureUrl.isNullOrBlank()) {
|
||||
{
|
||||
runCatching {
|
||||
|
|
@ -347,6 +352,7 @@ private fun InstalledAddonCard(
|
|||
onMoveUpClick: (() -> Unit)?,
|
||||
onMoveDownClick: (() -> Unit)?,
|
||||
onRefreshClick: () -> Unit,
|
||||
onEnabledChange: (Boolean) -> Unit,
|
||||
onConfigureClick: (() -> Unit)?,
|
||||
onDeleteClick: () -> Unit,
|
||||
) {
|
||||
|
|
@ -380,6 +386,17 @@ private fun InstalledAddonCard(
|
|||
)
|
||||
}
|
||||
}
|
||||
Spacer(modifier = Modifier.width(12.dp))
|
||||
Switch(
|
||||
checked = addon.enabled,
|
||||
onCheckedChange = onEnabledChange,
|
||||
colors = SwitchDefaults.colors(
|
||||
checkedThumbColor = MaterialTheme.colorScheme.onPrimary,
|
||||
checkedTrackColor = MaterialTheme.colorScheme.primary,
|
||||
uncheckedThumbColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
uncheckedTrackColor = MaterialTheme.colorScheme.outlineVariant,
|
||||
),
|
||||
)
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(12.dp))
|
||||
|
|
@ -438,6 +455,7 @@ private fun InstalledAddonCard(
|
|||
) {
|
||||
NuvioInfoBadge(
|
||||
text = when {
|
||||
!addon.enabled -> stringResource(Res.string.addons_badge_disabled)
|
||||
addon.isRefreshing -> stringResource(Res.string.addons_badge_refreshing)
|
||||
manifest != null -> stringResource(Res.string.addons_badge_active)
|
||||
else -> stringResource(Res.string.addons_badge_unavailable)
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.nuvio.app.features.collection
|
|||
|
||||
import com.nuvio.app.features.addons.AddonCatalog
|
||||
import com.nuvio.app.features.addons.ManagedAddon
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
|
||||
internal data class ResolvedCollectionCatalog(
|
||||
val addon: ManagedAddon,
|
||||
|
|
@ -11,13 +12,14 @@ internal data class ResolvedCollectionCatalog(
|
|||
internal fun List<ManagedAddon>.findCollectionCatalog(
|
||||
source: CollectionCatalogSource,
|
||||
): ResolvedCollectionCatalog? {
|
||||
val declaredAddon = firstOrNull { it.manifest?.id == source.addonId }
|
||||
val activeAddons = enabledAddons()
|
||||
val declaredAddon = activeAddons.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 ->
|
||||
return activeAddons.firstNotNullOfOrNull { addon ->
|
||||
val catalog = addon.manifest?.catalogs?.find {
|
||||
it.id == source.catalogId && it.type == source.type
|
||||
} ?: return@firstNotNullOfOrNull null
|
||||
|
|
@ -40,4 +42,3 @@ private fun List<AddonCatalog>.findSourceCatalog(source: CollectionCatalogSource
|
|||
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 }
|
||||
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.nuvio.app.features.collection
|
|||
import co.touchlab.kermit.Logger
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.ManagedAddon
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.MutableSharedFlow
|
||||
import kotlinx.coroutines.flow.SharedFlow
|
||||
|
|
@ -240,7 +241,7 @@ object CollectionRepository {
|
|||
fun generateId(): String = Uuid.random().toString()
|
||||
|
||||
fun getAvailableCatalogs(): List<AvailableCatalog> {
|
||||
val addons = AddonRepository.uiState.value.addons
|
||||
val addons = AddonRepository.uiState.value.addons.enabledAddons()
|
||||
return addons.mapNotNull { addon ->
|
||||
val manifest = addon.manifest ?: return@mapNotNull null
|
||||
addon to manifest
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import co.touchlab.kermit.Logger
|
|||
import com.nuvio.app.features.addons.AddonManifest
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.buildAddonResourceUrl
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.addons.httpGetText
|
||||
import com.nuvio.app.features.home.HomeCatalogSettingsRepository
|
||||
import com.nuvio.app.features.home.filterReleasedItems
|
||||
|
|
@ -261,6 +262,7 @@ object MetaDetailsRepository {
|
|||
|
||||
private fun findMetaManifests(type: String, id: String): List<AddonManifest> =
|
||||
AddonRepository.uiState.value.addons
|
||||
.enabledAddons()
|
||||
.mapNotNull { it.manifest }
|
||||
.filter { manifest ->
|
||||
manifest.resources.any { resource ->
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.nuvio.app.features.home
|
|||
|
||||
import com.nuvio.app.core.i18n.localizedMediaTypeLabel
|
||||
import com.nuvio.app.features.addons.ManagedAddon
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.catalog.supportsPagination
|
||||
import kotlinx.coroutines.runBlocking
|
||||
import nuvio.composeapp.generated.resources.Res
|
||||
|
|
@ -19,7 +20,7 @@ data class HomeCatalogDefinition(
|
|||
)
|
||||
|
||||
fun buildHomeCatalogDefinitions(addons: List<ManagedAddon>): List<HomeCatalogDefinition> =
|
||||
addons.mapNotNull { addon ->
|
||||
addons.enabledAddons().mapNotNull { addon ->
|
||||
val manifest = addon.manifest ?: return@mapNotNull null
|
||||
addon to manifest
|
||||
}.flatMap { (addon, manifest) ->
|
||||
|
|
|
|||
|
|
@ -2,6 +2,7 @@ package com.nuvio.app.features.home
|
|||
|
||||
import com.nuvio.app.features.addons.ManagedAddon
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.catalog.fetchCatalogPage
|
||||
import com.nuvio.app.features.collection.Collection
|
||||
import com.nuvio.app.features.collection.CollectionRepository
|
||||
|
|
@ -41,7 +42,8 @@ object HomeRepository {
|
|||
private var lastErrorMessage: String? = null
|
||||
|
||||
fun refresh(addons: List<ManagedAddon>, force: Boolean = false) {
|
||||
val requests = buildHomeCatalogDefinitions(addons)
|
||||
val activeAddons = addons.enabledAddons()
|
||||
val requests = buildHomeCatalogDefinitions(activeAddons)
|
||||
currentDefinitions = requests
|
||||
val requestKeys = requests.mapTo(mutableSetOf(), HomeCatalogDefinition::key)
|
||||
cachedSections = cachedSections.filterKeys(requestKeys::contains)
|
||||
|
|
@ -71,7 +73,7 @@ object HomeRepository {
|
|||
requestKey = requestKey,
|
||||
)
|
||||
ensureCollectionHeroFallback(
|
||||
addons = addons,
|
||||
addons = activeAddons,
|
||||
force = force,
|
||||
requestKey = requestKey,
|
||||
)
|
||||
|
|
@ -135,7 +137,7 @@ object HomeRepository {
|
|||
requestKey = requestKey,
|
||||
)
|
||||
ensureCollectionHeroFallback(
|
||||
addons = addons,
|
||||
addons = activeAddons,
|
||||
force = force,
|
||||
requestKey = requestKey,
|
||||
)
|
||||
|
|
@ -148,7 +150,7 @@ object HomeRepository {
|
|||
requestKey = activeRequestKey ?: lastRequestKey,
|
||||
)
|
||||
ensureCollectionHeroFallback(
|
||||
addons = AddonRepository.uiState.value.addons,
|
||||
addons = AddonRepository.uiState.value.addons.enabledAddons(),
|
||||
force = false,
|
||||
requestKey = activeRequestKey ?: lastRequestKey,
|
||||
)
|
||||
|
|
|
|||
|
|
@ -21,6 +21,7 @@ import com.nuvio.app.core.ui.NuvioScreen
|
|||
import com.nuvio.app.core.ui.NuvioNetworkOfflineCard
|
||||
import com.nuvio.app.core.ui.nuvioSafeBottomPadding
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.cloud.CloudLibraryContentType
|
||||
import com.nuvio.app.features.cloud.CloudLibraryRepository
|
||||
import com.nuvio.app.features.cloud.CloudLibraryUiState
|
||||
|
|
@ -144,7 +145,7 @@ fun HomeScreen(
|
|||
NetworkCondition.Online -> {
|
||||
if (observedOfflineState) {
|
||||
observedOfflineState = false
|
||||
HomeRepository.refresh(addonsUiState.addons, force = true)
|
||||
HomeRepository.refresh(addonsUiState.addons.enabledAddons(), force = true)
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -367,8 +368,11 @@ fun HomeScreen(
|
|||
cloudLibraryUiState = cloudLibraryUiState,
|
||||
)
|
||||
}
|
||||
val availableManifests = remember(addonsUiState.addons) {
|
||||
addonsUiState.addons.mapNotNull { addon -> addon.manifest }
|
||||
val enabledAddons = remember(addonsUiState.addons) {
|
||||
addonsUiState.addons.enabledAddons()
|
||||
}
|
||||
val availableManifests = remember(enabledAddons) {
|
||||
enabledAddons.mapNotNull { addon -> addon.manifest }
|
||||
}
|
||||
|
||||
val metaProviderKey = remember(availableManifests) {
|
||||
|
|
@ -394,8 +398,8 @@ fun HomeScreen(
|
|||
|
||||
LaunchedEffect(catalogRefreshKey) {
|
||||
if (catalogRefreshKey.isEmpty()) return@LaunchedEffect
|
||||
HomeCatalogSettingsRepository.syncCatalogs(addonsUiState.addons)
|
||||
HomeRepository.refresh(addonsUiState.addons)
|
||||
HomeCatalogSettingsRepository.syncCatalogs(enabledAddons)
|
||||
HomeRepository.refresh(enabledAddons)
|
||||
}
|
||||
|
||||
LaunchedEffect(collections) {
|
||||
|
|
@ -498,9 +502,9 @@ fun HomeScreen(
|
|||
)
|
||||
}
|
||||
|
||||
val hasActiveAddons = addonsUiState.addons.any { it.manifest != null }
|
||||
val hasActiveAddons = enabledAddons.any { it.manifest != null }
|
||||
val showHeroSlot = homeSettingsUiState.heroEnabled
|
||||
val isResolvingHeroSources = addonsUiState.addons.any { it.isRefreshing } || homeUiState.isLoading
|
||||
val isResolvingHeroSources = enabledAddons.any { it.isRefreshing } || homeUiState.isLoading
|
||||
val showHeroSkeleton = showHeroSlot &&
|
||||
homeUiState.heroItems.isEmpty() &&
|
||||
isResolvingHeroSources
|
||||
|
|
@ -591,7 +595,7 @@ fun HomeScreen(
|
|||
}
|
||||
|
||||
when {
|
||||
addonsUiState.addons.none { it.manifest != null } && !hasRenderableCollectionRows -> {
|
||||
!hasActiveAddons && !hasRenderableCollectionRows -> {
|
||||
if (continueWatchingPreferences.isVisible && continueWatchingItems.isNotEmpty()) {
|
||||
item {
|
||||
HomeContinueWatchingSection(
|
||||
|
|
@ -650,7 +654,7 @@ fun HomeScreen(
|
|||
modifier = Modifier.padding(horizontal = 16.dp),
|
||||
onRetry = {
|
||||
NetworkStatusRepository.requestRefresh(force = true)
|
||||
HomeRepository.refresh(addonsUiState.addons, force = true)
|
||||
HomeRepository.refresh(addonsUiState.addons.enabledAddons(), force = true)
|
||||
},
|
||||
)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -46,6 +46,7 @@ import com.nuvio.app.features.debrid.toastMessage
|
|||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.AddonResource
|
||||
import com.nuvio.app.features.addons.ManagedAddon
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.details.MetaDetailsRepository
|
||||
import com.nuvio.app.features.details.MetaScreenSettingsRepository
|
||||
import com.nuvio.app.features.details.MetaVideo
|
||||
|
|
@ -1166,6 +1167,7 @@ fun PlayerScreen(
|
|||
)
|
||||
|
||||
val installedAddonNames = AddonRepository.uiState.value.addons
|
||||
.enabledAddons()
|
||||
.map { it.displayTitle }
|
||||
.toSet()
|
||||
val debridSettings = DebridSettingsRepository.snapshot()
|
||||
|
|
@ -2289,7 +2291,7 @@ private fun buildAddonSubtitleFetchKey(
|
|||
): String? {
|
||||
val normalizedType = type?.takeIf { it.isNotBlank() } ?: return null
|
||||
val normalizedVideoId = videoId?.takeIf { it.isNotBlank() } ?: return null
|
||||
val compatibleSubtitleAddons = addons.mapNotNull { addon ->
|
||||
val compatibleSubtitleAddons = addons.enabledAddons().mapNotNull { addon ->
|
||||
val manifest = addon.manifest ?: return@mapNotNull null
|
||||
val supportsSubtitles = manifest.resources.any { resource ->
|
||||
resource.isCompatibleSubtitleResource(
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import co.touchlab.kermit.Logger
|
|||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.buildAddonResourceUrl
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.addons.httpGetText
|
||||
import com.nuvio.app.features.debrid.DebridSettingsRepository
|
||||
import com.nuvio.app.features.debrid.DebridStreamPresentation
|
||||
|
|
@ -158,7 +159,7 @@ object PlayerStreamsRepository {
|
|||
return
|
||||
}
|
||||
|
||||
val installedAddons = AddonRepository.uiState.value.addons
|
||||
val installedAddons = AddonRepository.uiState.value.addons.enabledAddons()
|
||||
val installedAddonNames = installedAddons.map { it.displayTitle }.toSet()
|
||||
PlayerSettingsRepository.ensureLoaded()
|
||||
val playerSettings = PlayerSettingsRepository.uiState.value
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.nuvio.app.features.player
|
|||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.AddonResource
|
||||
import com.nuvio.app.features.addons.buildAddonResourceUrl
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.addons.httpGetText
|
||||
import kotlinx.coroutines.CancellationException
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
|
|
@ -49,7 +50,7 @@ object SubtitleRepository {
|
|||
_error.value = null
|
||||
_addonSubtitles.value = emptyList()
|
||||
|
||||
val addons = AddonRepository.uiState.value.addons
|
||||
val addons = AddonRepository.uiState.value.addons.enabledAddons()
|
||||
val allSubs = mutableListOf<AddonSubtitle>()
|
||||
|
||||
for (addon in addons) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.nuvio.app.core.i18n.localizedMediaTypeLabel
|
|||
import com.nuvio.app.features.addons.AddonCatalog
|
||||
import com.nuvio.app.features.addons.AddonExtraProperty
|
||||
import com.nuvio.app.features.addons.ManagedAddon
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.catalog.CatalogPage
|
||||
import com.nuvio.app.features.catalog.buildCatalogUrl
|
||||
import com.nuvio.app.features.catalog.fetchCatalogPage
|
||||
|
|
@ -50,7 +51,7 @@ object SearchRepository {
|
|||
return
|
||||
}
|
||||
|
||||
val activeAddons = addons.filter { it.manifest != null }
|
||||
val activeAddons = addons.enabledAddons().filter { it.manifest != null }
|
||||
if (activeAddons.isEmpty()) {
|
||||
activeJob?.cancel()
|
||||
lastRequestKey = null
|
||||
|
|
@ -173,7 +174,7 @@ object SearchRepository {
|
|||
}
|
||||
|
||||
fun refreshDiscover(addons: List<ManagedAddon>) {
|
||||
val activeAddons = addons.filter { it.manifest != null }
|
||||
val activeAddons = addons.enabledAddons().filter { it.manifest != null }
|
||||
if (activeAddons.isEmpty()) {
|
||||
activeDiscoverJob?.cancel()
|
||||
discoverSources = emptyList()
|
||||
|
|
|
|||
|
|
@ -49,6 +49,7 @@ import com.nuvio.app.core.ui.NuvioScreenHeader
|
|||
import com.nuvio.app.core.ui.nuvioBlockPointerPassthrough
|
||||
import com.nuvio.app.core.ui.withDuplicateSafeLazyKeys
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.home.HomeCatalogSettingsRepository
|
||||
import com.nuvio.app.features.home.MetaPreview
|
||||
import com.nuvio.app.features.home.components.HomeCatalogRowSection
|
||||
|
|
@ -128,7 +129,7 @@ fun SearchScreen(
|
|||
}
|
||||
|
||||
val addonRefreshKey = remember(addonsUiState.addons) {
|
||||
addonsUiState.addons.mapNotNull { addon ->
|
||||
addonsUiState.addons.enabledAddons().mapNotNull { addon ->
|
||||
val manifest = addon.manifest ?: return@mapNotNull null
|
||||
buildString {
|
||||
append(manifest.transportUrl)
|
||||
|
|
|
|||
|
|
@ -51,6 +51,7 @@ import androidx.compose.ui.text.style.TextOverflow
|
|||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.player.AudioLanguageOption
|
||||
import com.nuvio.app.features.player.AvailableLanguageOptions
|
||||
import com.nuvio.app.features.player.ExternalPlayerApp
|
||||
|
|
@ -999,6 +1000,7 @@ private fun PlaybackSettingsSection(
|
|||
|
||||
if (showAutoPlayAddonSelectionDialog) {
|
||||
val addonNames = addonUiState.addons
|
||||
.enabledAddons()
|
||||
.mapNotNull { it.manifest }
|
||||
.filter { manifest -> manifest.resources.any { resource -> resource.name == "stream" } }
|
||||
.map { it.name }
|
||||
|
|
|
|||
|
|
@ -11,6 +11,7 @@ import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
|||
import com.nuvio.app.core.ui.NuvioScreen
|
||||
import com.nuvio.app.core.ui.NuvioScreenHeader
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.collection.CollectionRepository
|
||||
import com.nuvio.app.features.details.MetaScreenSettingsRepository
|
||||
import com.nuvio.app.features.plugins.PluginRepository
|
||||
|
|
@ -31,10 +32,11 @@ fun HomescreenSettingsScreen(
|
|||
) {
|
||||
val addonsUiState by AddonRepository.uiState.collectAsStateWithLifecycle()
|
||||
val homescreenCatalogRefreshKey = remember(addonsUiState.addons) {
|
||||
val allManifestsSettled = addonsUiState.addons.isNotEmpty() &&
|
||||
addonsUiState.addons.none { it.isRefreshing }
|
||||
val enabledAddons = addonsUiState.addons.enabledAddons()
|
||||
val allManifestsSettled = enabledAddons.isNotEmpty() &&
|
||||
enabledAddons.none { it.isRefreshing }
|
||||
if (!allManifestsSettled) return@remember emptyList<String>()
|
||||
addonsUiState.addons.mapNotNull { addon ->
|
||||
enabledAddons.mapNotNull { addon ->
|
||||
val manifest = addon.manifest ?: return@mapNotNull null
|
||||
buildString {
|
||||
append(manifest.transportUrl)
|
||||
|
|
@ -58,7 +60,7 @@ fun HomescreenSettingsScreen(
|
|||
|
||||
LaunchedEffect(homescreenCatalogRefreshKey) {
|
||||
if (homescreenCatalogRefreshKey.isEmpty()) return@LaunchedEffect
|
||||
HomeCatalogSettingsRepository.syncCatalogs(addonsUiState.addons)
|
||||
HomeCatalogSettingsRepository.syncCatalogs(addonsUiState.addons.enabledAddons())
|
||||
}
|
||||
|
||||
LaunchedEffect(collections) {
|
||||
|
|
|
|||
|
|
@ -59,6 +59,7 @@ import com.nuvio.app.features.details.MetaScreenSettingsUiState
|
|||
import com.nuvio.app.core.ui.PosterCardStyleRepository
|
||||
import com.nuvio.app.core.ui.PosterCardStyleUiState
|
||||
import com.nuvio.app.features.collection.CollectionRepository
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.debrid.DebridSettings
|
||||
import com.nuvio.app.features.debrid.DebridSettingsRepository
|
||||
import com.nuvio.app.features.home.HomeCatalogSettingsItem
|
||||
|
|
@ -157,10 +158,11 @@ fun SettingsScreen(
|
|||
AddonRepository.uiState
|
||||
}.collectAsStateWithLifecycle()
|
||||
val homescreenCatalogRefreshKey = remember(addonsUiState.addons) {
|
||||
val allManifestsSettled = addonsUiState.addons.isNotEmpty() &&
|
||||
addonsUiState.addons.none { it.isRefreshing }
|
||||
val enabledAddons = addonsUiState.addons.enabledAddons()
|
||||
val allManifestsSettled = enabledAddons.isNotEmpty() &&
|
||||
enabledAddons.none { it.isRefreshing }
|
||||
if (!allManifestsSettled) return@remember emptyList<String>()
|
||||
addonsUiState.addons.mapNotNull { addon ->
|
||||
enabledAddons.mapNotNull { addon ->
|
||||
val manifest = addon.manifest ?: return@mapNotNull null
|
||||
buildString {
|
||||
append(manifest.transportUrl)
|
||||
|
|
@ -195,7 +197,7 @@ fun SettingsScreen(
|
|||
|
||||
LaunchedEffect(homescreenCatalogRefreshKey) {
|
||||
if (homescreenCatalogRefreshKey.isEmpty()) return@LaunchedEffect
|
||||
HomeCatalogSettingsRepository.syncCatalogs(addonsUiState.addons)
|
||||
HomeCatalogSettingsRepository.syncCatalogs(addonsUiState.addons.enabledAddons())
|
||||
}
|
||||
|
||||
LaunchedEffect(Unit) {
|
||||
|
|
|
|||
|
|
@ -5,6 +5,7 @@ import com.nuvio.app.features.addons.AddonManifest
|
|||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.ManagedAddon
|
||||
import com.nuvio.app.features.addons.buildAddonResourceUrl
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.addons.httpGetText
|
||||
import com.nuvio.app.features.debrid.DebridSettings
|
||||
import com.nuvio.app.features.debrid.DebridSettingsRepository
|
||||
|
|
@ -213,6 +214,7 @@ object AddonStreamWarmupRepository {
|
|||
|
||||
AddonRepository.initialize()
|
||||
val addonTargets = AddonRepository.uiState.value.addons
|
||||
.enabledAddons()
|
||||
.mapNotNull { addon -> addon.toWarmupTarget(normalizedType, normalizedVideoId) }
|
||||
if (addonTargets.isEmpty()) return null
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import co.touchlab.kermit.Logger
|
|||
import com.nuvio.app.core.build.AppFeaturePolicy
|
||||
import com.nuvio.app.features.addons.AddonRepository
|
||||
import com.nuvio.app.features.addons.buildAddonResourceUrl
|
||||
import com.nuvio.app.features.addons.enabledAddons
|
||||
import com.nuvio.app.features.addons.httpGetText
|
||||
import com.nuvio.app.features.debrid.DirectDebridStreamPreparer
|
||||
import com.nuvio.app.features.debrid.DebridSettingsRepository
|
||||
|
|
@ -152,7 +153,7 @@ object StreamsRepository {
|
|||
return
|
||||
}
|
||||
|
||||
val installedAddons = AddonRepository.uiState.value.addons
|
||||
val installedAddons = AddonRepository.uiState.value.addons.enabledAddons()
|
||||
val pluginScrapers = if (AppFeaturePolicy.pluginsEnabled) {
|
||||
PluginRepository.getEnabledScrapersForType(type)
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package com.nuvio.app.features.addons
|
||||
|
||||
import kotlin.test.Test
|
||||
import kotlin.test.assertEquals
|
||||
import kotlin.test.assertFalse
|
||||
import kotlin.test.assertTrue
|
||||
|
||||
class AddonModelsTest {
|
||||
|
||||
@Test
|
||||
fun `disabled addon is installed but not active`() {
|
||||
val addon = ManagedAddon(
|
||||
manifestUrl = "https://example.test/manifest.json",
|
||||
manifest = manifest(),
|
||||
enabled = false,
|
||||
)
|
||||
|
||||
assertFalse(addon.isActive)
|
||||
assertEquals(0, listOf(addon).toOverview().activeAddons)
|
||||
assertEquals(0, listOf(addon).toOverview().totalCatalogs)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun `enabled addons helper filters disabled addons`() {
|
||||
val enabled = ManagedAddon(
|
||||
manifestUrl = "https://enabled.example/manifest.json",
|
||||
manifest = manifest(id = "enabled"),
|
||||
enabled = true,
|
||||
)
|
||||
val disabled = ManagedAddon(
|
||||
manifestUrl = "https://disabled.example/manifest.json",
|
||||
manifest = manifest(id = "disabled"),
|
||||
enabled = false,
|
||||
)
|
||||
|
||||
assertEquals(listOf(enabled), listOf(enabled, disabled).enabledAddons())
|
||||
assertTrue(enabled.isActive)
|
||||
}
|
||||
}
|
||||
|
||||
private fun manifest(id: String = "addon") = AddonManifest(
|
||||
id = id,
|
||||
name = id,
|
||||
description = "",
|
||||
version = "1.0.0",
|
||||
resources = listOf(AddonResource(name = "catalog", types = listOf("movie"))),
|
||||
types = listOf("movie"),
|
||||
catalogs = listOf(AddonCatalog(type = "movie", id = "popular", name = "Popular")),
|
||||
transportUrl = "https://$id.example/manifest.json",
|
||||
)
|
||||
|
|
@ -19,6 +19,7 @@ import platform.Foundation.NSUserDefaults
|
|||
|
||||
actual object AddonStorage {
|
||||
private const val addonUrlsKey = "installed_manifest_urls"
|
||||
private const val addonEnabledStatesKey = "installed_manifest_enabled_states"
|
||||
|
||||
actual fun loadInstalledAddonUrls(profileId: Int): List<String> =
|
||||
NSUserDefaults.standardUserDefaults
|
||||
|
|
@ -35,6 +36,34 @@ actual object AddonStorage {
|
|||
forKey = "${addonUrlsKey}_$profileId",
|
||||
)
|
||||
}
|
||||
|
||||
actual fun loadAddonEnabledStates(profileId: Int): Map<String, Boolean> =
|
||||
NSUserDefaults.standardUserDefaults
|
||||
.stringForKey("${addonEnabledStatesKey}_$profileId")
|
||||
.orEmpty()
|
||||
.lineSequence()
|
||||
.mapNotNull(::parseEnabledStateLine)
|
||||
.toMap()
|
||||
|
||||
actual fun saveAddonEnabledStates(profileId: Int, states: Map<String, Boolean>) {
|
||||
val payload = states.entries.joinToString(separator = "\n") { (url, enabled) ->
|
||||
"$url\t$enabled"
|
||||
}
|
||||
NSUserDefaults.standardUserDefaults.setObject(
|
||||
payload,
|
||||
forKey = "${addonEnabledStatesKey}_$profileId",
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private fun parseEnabledStateLine(line: String): Pair<String, Boolean>? {
|
||||
val url = line.substringBefore("\t").trim().takeIf { it.isNotEmpty() } ?: return null
|
||||
val rawEnabled = line.substringAfter("\t", "true").trim().lowercase()
|
||||
val enabled = when (rawEnabled) {
|
||||
"false" -> false
|
||||
else -> true
|
||||
}
|
||||
return url to enabled
|
||||
}
|
||||
|
||||
private val addonHttpClient = HttpClient(Darwin) {
|
||||
|
|
|
|||
Loading…
Reference in a new issue