mirror of
https://github.com/tapframe/NuvioStreaming.git
synced 2026-05-09 19:40:44 +00:00
refactor: profile ID resolution ,improve addon handling logic
This commit is contained in:
parent
13da43d303
commit
ec53965105
3 changed files with 59 additions and 16 deletions
|
|
@ -53,9 +53,10 @@ object AddonRepository {
|
||||||
private val activeRefreshJobs = mutableMapOf<String, Job>()
|
private val activeRefreshJobs = mutableMapOf<String, Job>()
|
||||||
|
|
||||||
fun initialize() {
|
fun initialize() {
|
||||||
|
val effectiveProfileId = resolveEffectiveProfileId(ProfileRepository.activeProfileId)
|
||||||
if (initialized) return
|
if (initialized) return
|
||||||
initialized = true
|
initialized = true
|
||||||
currentProfileId = ProfileRepository.activeProfileId
|
currentProfileId = effectiveProfileId
|
||||||
log.d { "initialize() — loading local addons for profile $currentProfileId" }
|
log.d { "initialize() — loading local addons for profile $currentProfileId" }
|
||||||
|
|
||||||
val storedUrls = dedupeManifestUrls(AddonStorage.loadInstalledAddonUrls(currentProfileId))
|
val storedUrls = dedupeManifestUrls(AddonStorage.loadInstalledAddonUrls(currentProfileId))
|
||||||
|
|
@ -78,9 +79,10 @@ object AddonRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun onProfileChanged(profileId: Int) {
|
fun onProfileChanged(profileId: Int) {
|
||||||
if (profileId == currentProfileId && initialized) return
|
val effectiveProfileId = resolveEffectiveProfileId(profileId)
|
||||||
|
if (effectiveProfileId == currentProfileId && initialized) return
|
||||||
cancelActiveRefreshes()
|
cancelActiveRefreshes()
|
||||||
currentProfileId = profileId
|
currentProfileId = effectiveProfileId
|
||||||
initialized = false
|
initialized = false
|
||||||
pulledFromServer = false
|
pulledFromServer = false
|
||||||
_uiState.value = AddonsUiState()
|
_uiState.value = AddonsUiState()
|
||||||
|
|
@ -95,13 +97,13 @@ object AddonRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun pullFromServer(profileId: Int) {
|
suspend fun pullFromServer(profileId: Int) {
|
||||||
currentProfileId = profileId
|
currentProfileId = resolveEffectiveProfileId(profileId)
|
||||||
log.i { "pullFromServer() — profileId=$profileId, initialized=$initialized, pulledFromServer=$pulledFromServer" }
|
log.i { "pullFromServer() — profileId=$profileId, initialized=$initialized, pulledFromServer=$pulledFromServer" }
|
||||||
runCatching {
|
runCatching {
|
||||||
val rows = SupabaseProvider.client.postgrest
|
val rows = SupabaseProvider.client.postgrest
|
||||||
.from("addons")
|
.from("addons")
|
||||||
.select {
|
.select {
|
||||||
filter { eq("profile_id", profileId) }
|
filter { eq("profile_id", currentProfileId) }
|
||||||
order("sort_order", Order.ASCENDING)
|
order("sort_order", Order.ASCENDING)
|
||||||
}
|
}
|
||||||
.decodeList<AddonRow>()
|
.decodeList<AddonRow>()
|
||||||
|
|
@ -111,10 +113,10 @@ object AddonRepository {
|
||||||
urls.forEachIndexed { i, u -> log.d { " server[$i]: $u" } }
|
urls.forEachIndexed { i, u -> log.d { " server[$i]: $u" } }
|
||||||
|
|
||||||
if (urls.isEmpty() && !pulledFromServer) {
|
if (urls.isEmpty() && !pulledFromServer) {
|
||||||
val localUrls = AddonStorage.loadInstalledAddonUrls(profileId)
|
val localUrls = AddonStorage.loadInstalledAddonUrls(currentProfileId)
|
||||||
log.i { "pullFromServer() — server empty, local has ${localUrls.size} addons" }
|
log.i { "pullFromServer() — server empty, local has ${localUrls.size} addons" }
|
||||||
if (localUrls.isNotEmpty()) {
|
if (localUrls.isNotEmpty()) {
|
||||||
log.i { "pullFromServer() — migrating local addons to server for profile $profileId" }
|
log.i { "pullFromServer() — migrating local addons to server for profile $currentProfileId" }
|
||||||
initialize()
|
initialize()
|
||||||
pulledFromServer = true
|
pulledFromServer = true
|
||||||
val addons = localUrls.mapIndexed { index, addonUrl ->
|
val addons = localUrls.mapIndexed { index, addonUrl ->
|
||||||
|
|
@ -127,7 +129,7 @@ object AddonRepository {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
val params = buildJsonObject {
|
val params = buildJsonObject {
|
||||||
put("p_profile_id", profileId)
|
put("p_profile_id", currentProfileId)
|
||||||
put("p_addons", json.encodeToJsonElement(addons))
|
put("p_addons", json.encodeToJsonElement(addons))
|
||||||
}
|
}
|
||||||
SupabaseProvider.client.postgrest.rpc("sync_push_addons", params)
|
SupabaseProvider.client.postgrest.rpc("sync_push_addons", params)
|
||||||
|
|
@ -136,6 +138,29 @@ object AddonRepository {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (urls.isEmpty()) {
|
||||||
|
val localUrls = dedupeManifestUrls(AddonStorage.loadInstalledAddonUrls(currentProfileId))
|
||||||
|
if (localUrls.isNotEmpty()) {
|
||||||
|
log.w { "pullFromServer() — remote empty while local has ${localUrls.size} addons; preserving local addons" }
|
||||||
|
val existingByUrl = _uiState.value.addons.associateBy(ManagedAddon::manifestUrl)
|
||||||
|
_uiState.value = AddonsUiState(
|
||||||
|
addons = localUrls.map { url ->
|
||||||
|
existingByUrl[url].toPendingAddon(url)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
persist()
|
||||||
|
localUrls.forEach { url ->
|
||||||
|
val existing = existingByUrl[url]
|
||||||
|
if (existing == null || (existing.manifest == null && !existing.isRefreshing)) {
|
||||||
|
refreshAddon(url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pulledFromServer = true
|
||||||
|
initialized = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
val existingByUrl = _uiState.value.addons.associateBy(ManagedAddon::manifestUrl)
|
val existingByUrl = _uiState.value.addons.associateBy(ManagedAddon::manifestUrl)
|
||||||
_uiState.value = AddonsUiState(
|
_uiState.value = AddonsUiState(
|
||||||
addons = urls.map { url ->
|
addons = urls.map { url ->
|
||||||
|
|
@ -165,6 +190,9 @@ object AddonRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun addAddon(rawUrl: String): AddAddonResult {
|
suspend fun addAddon(rawUrl: String): AddAddonResult {
|
||||||
|
if (isUsingPrimaryAddonsFromSecondaryProfile()) {
|
||||||
|
return AddAddonResult.Error("This profile uses primary addons.")
|
||||||
|
}
|
||||||
log.i { "addAddon() — rawUrl=$rawUrl" }
|
log.i { "addAddon() — rawUrl=$rawUrl" }
|
||||||
val manifestUrl = try {
|
val manifestUrl = try {
|
||||||
normalizeManifestUrl(rawUrl)
|
normalizeManifestUrl(rawUrl)
|
||||||
|
|
@ -204,6 +232,7 @@ object AddonRepository {
|
||||||
}
|
}
|
||||||
|
|
||||||
fun removeAddon(manifestUrl: String) {
|
fun removeAddon(manifestUrl: String) {
|
||||||
|
if (isUsingPrimaryAddonsFromSecondaryProfile()) return
|
||||||
log.i { "removeAddon() — $manifestUrl" }
|
log.i { "removeAddon() — $manifestUrl" }
|
||||||
_uiState.update { current ->
|
_uiState.update { current ->
|
||||||
current.copy(
|
current.copy(
|
||||||
|
|
@ -273,7 +302,10 @@ object AddonRepository {
|
||||||
private fun pushToServer() {
|
private fun pushToServer() {
|
||||||
scope.launch {
|
scope.launch {
|
||||||
runCatching {
|
runCatching {
|
||||||
val profileId = ProfileRepository.activeProfileId
|
if (isUsingPrimaryAddonsFromSecondaryProfile()) {
|
||||||
|
return@runCatching
|
||||||
|
}
|
||||||
|
val profileId = currentProfileId
|
||||||
val addons = _uiState.value.addons
|
val addons = _uiState.value.addons
|
||||||
.distinctBy { it.manifestUrl }
|
.distinctBy { it.manifestUrl }
|
||||||
.mapIndexed { index, addon ->
|
.mapIndexed { index, addon ->
|
||||||
|
|
@ -325,6 +357,16 @@ object AddonRepository {
|
||||||
activeRefreshJobs.values.forEach(Job::cancel)
|
activeRefreshJobs.values.forEach(Job::cancel)
|
||||||
activeRefreshJobs.clear()
|
activeRefreshJobs.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun resolveEffectiveProfileId(profileId: Int): Int {
|
||||||
|
val active = ProfileRepository.state.value.activeProfile
|
||||||
|
return if (active != null && active.profileIndex != 1 && active.usesPrimaryAddons) 1 else profileId
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun isUsingPrimaryAddonsFromSecondaryProfile(): Boolean {
|
||||||
|
val active = ProfileRepository.state.value.activeProfile
|
||||||
|
return active != null && active.profileIndex != 1 && active.usesPrimaryAddons
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun ManagedAddon?.toPendingAddon(manifestUrl: String): ManagedAddon =
|
private fun ManagedAddon?.toPendingAddon(manifestUrl: String): ManagedAddon =
|
||||||
|
|
|
||||||
|
|
@ -85,10 +85,7 @@ internal fun AddonsSettingsPageContent(
|
||||||
var formMessage by rememberSaveable { mutableStateOf<String?>(null) }
|
var formMessage by rememberSaveable { mutableStateOf<String?>(null) }
|
||||||
var installModalState by remember { mutableStateOf<AddonInstallModalState?>(null) }
|
var installModalState by remember { mutableStateOf<AddonInstallModalState?>(null) }
|
||||||
|
|
||||||
val sortedAddons = remember(uiState.addons) {
|
val overview = remember(uiState.addons) { uiState.addons.toOverview() }
|
||||||
uiState.addons.sortedBy { it.displayTitle.lowercase() }
|
|
||||||
}
|
|
||||||
val overview = remember(sortedAddons) { sortedAddons.toOverview() }
|
|
||||||
|
|
||||||
Column(
|
Column(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
|
|
@ -131,10 +128,10 @@ internal fun AddonsSettingsPageContent(
|
||||||
)
|
)
|
||||||
|
|
||||||
SectionHeader("INSTALLED ADDONS")
|
SectionHeader("INSTALLED ADDONS")
|
||||||
if (sortedAddons.isEmpty()) {
|
if (uiState.addons.isEmpty()) {
|
||||||
EmptyStateCard()
|
EmptyStateCard()
|
||||||
} else {
|
} else {
|
||||||
sortedAddons.forEach { addon ->
|
uiState.addons.forEach { addon ->
|
||||||
InstalledAddonCard(
|
InstalledAddonCard(
|
||||||
addon = addon,
|
addon = addon,
|
||||||
onRefreshClick = { AddonRepository.refreshAddon(addon.manifestUrl) },
|
onRefreshClick = { AddonRepository.refreshAddon(addon.manifestUrl) },
|
||||||
|
|
|
||||||
|
|
@ -125,7 +125,11 @@ internal fun PlayerControlsShell(
|
||||||
.align(Alignment.TopStart)
|
.align(Alignment.TopStart)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.windowInsetsPadding(WindowInsets.safeContent.only(WindowInsetsSides.Top))
|
.windowInsetsPadding(WindowInsets.safeContent.only(WindowInsetsSides.Top))
|
||||||
.padding(horizontal = metrics.horizontalPadding, vertical = metrics.verticalPadding),
|
.padding(
|
||||||
|
start = metrics.horizontalPadding,
|
||||||
|
end = metrics.horizontalPadding,
|
||||||
|
top = metrics.verticalPadding / 4,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
CenterControls(
|
CenterControls(
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue