From 3561f3dd4adff776df375c07eeed3bd8fdbc8017 Mon Sep 17 00:00:00 2001 From: KremeCN <3265494849@qq.com> Date: Tue, 25 Aug 2026 12:27:54 +0800 Subject: [PATCH] fix(infrared): url-encode profileId in routes and cancel underlying learning deferred --- .../abox/manager/ui/ABoxManagerApp.kt | 6 ++-- .../remote/InfraredRemoteViewModel.kt | 33 ++++++++++++++----- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/android/app/src/main/kotlin/com/flagship/abox/manager/ui/ABoxManagerApp.kt b/android/app/src/main/kotlin/com/flagship/abox/manager/ui/ABoxManagerApp.kt index 3f97c7d..257990d 100644 --- a/android/app/src/main/kotlin/com/flagship/abox/manager/ui/ABoxManagerApp.kt +++ b/android/app/src/main/kotlin/com/flagship/abox/manager/ui/ABoxManagerApp.kt @@ -100,9 +100,10 @@ private fun AuthenticatedNavHost( actionInProgress = actionInProgress, errorMessage = errorMessage, devicesSlot = { + val encodedProfileId = android.net.Uri.encode(profile.id.value) DeviceListScreen( onInfraredClick = { - navController.navigate("infrared_remote/${profile.id.value}") + navController.navigate("infrared_remote/$encodedProfileId") }, ) }, @@ -114,10 +115,11 @@ private fun AuthenticatedNavHost( navArgument("profileId") { type = NavType.StringType }, ), ) { + val encodedProfileId = android.net.Uri.encode(profile.id.value) InfraredRemoteRoute( onBack = { navController.popBackStack() }, onOpenSettings = { - navController.navigate("infrared_settings/${profile.id.value}") + navController.navigate("infrared_settings/$encodedProfileId") }, onSessionInvalid = onLogout, ) diff --git a/android/feature/infrared/src/main/kotlin/com/flagship/abox/manager/infrared/remote/InfraredRemoteViewModel.kt b/android/feature/infrared/src/main/kotlin/com/flagship/abox/manager/infrared/remote/InfraredRemoteViewModel.kt index 56d7029..4001ee4 100644 --- a/android/feature/infrared/src/main/kotlin/com/flagship/abox/manager/infrared/remote/InfraredRemoteViewModel.kt +++ b/android/feature/infrared/src/main/kotlin/com/flagship/abox/manager/infrared/remote/InfraredRemoteViewModel.kt @@ -8,6 +8,7 @@ import com.flagship.abox.manager.domain.DomainError import com.flagship.abox.manager.domain.DomainResult import com.flagship.abox.manager.domain.ProviderProfileId import com.flagship.abox.manager.domain.infrared.InfraredDeviceConfigurationRepository +import com.flagship.abox.manager.domain.infrared.InfraredKeyId import com.flagship.abox.manager.domain.infrared.InfraredKeyRef import com.flagship.abox.manager.domain.infrared.InfraredKeyRepository import com.flagship.abox.manager.domain.infrared.InfraredKeyStatus @@ -70,6 +71,7 @@ class InfraredRemoteViewModel @Inject constructor( private val activeLearningSlotFlow = MutableStateFlow(null) private val messageFlow = MutableStateFlow(null) private var activeLearningJob: Job? = null + private var underlyingLearningDeferred: kotlinx.coroutines.Deferred<*>? = null private val effectiveProfileAndMode: Flow> = runtimeModeStore.mode.flatMapLatest { mode -> @@ -151,6 +153,8 @@ class InfraredRemoteViewModel @Inject constructor( fun toggleLearningMode(enabled: Boolean) { isLearningModeFlow.value = enabled if (!enabled) { + underlyingLearningDeferred?.cancel() + underlyingLearningDeferred = null activeLearningJob?.cancel() activeLearningJob = null activeLearningSlotFlow.value = null @@ -168,7 +172,7 @@ class InfraredRemoteViewModel @Inject constructor( } if (isLearningModeFlow.value) { - if (activeLearningSlotFlow.value != null) { + if (underlyingLearningDeferred?.isActive == true || activeLearningJob?.isActive == true || activeLearningSlotFlow.value != null) { return } val existingKey = state.value.learnedSlotMap[spec.defaultSlot] @@ -176,17 +180,25 @@ class InfraredRemoteViewModel @Inject constructor( return } activeLearningSlotFlow.value = spec.defaultSlot + val deferred = if (existingKey != null) { + learningCoordinator.retry(InfraredKeyRef(currentProfile, existingKey.id)) + } else { + learningCoordinator.learn( + profileId = currentProfile, + deviceName = savedDevice, + displayName = spec.displayName, + sdkKey = spec.defaultSlot, + ) + } + underlyingLearningDeferred = deferred val job = viewModelScope.launch { try { val result = if (existingKey != null) { - learningCoordinator.retry(InfraredKeyRef(currentProfile, existingKey.id)).await() + @Suppress("UNCHECKED_CAST") + (deferred as kotlinx.coroutines.Deferred>).await() } else { - when (val res = learningCoordinator.learn( - profileId = currentProfile, - deviceName = savedDevice, - displayName = spec.displayName, - sdkKey = spec.defaultSlot, - ).await()) { + @Suppress("UNCHECKED_CAST") + when (val res = (deferred as kotlinx.coroutines.Deferred>).await()) { is DomainResult.Success -> DomainResult.Success(Unit) is DomainResult.Failure -> DomainResult.Failure(res.error) } @@ -202,6 +214,9 @@ class InfraredRemoteViewModel @Inject constructor( } catch (e: kotlinx.coroutines.CancellationException) { throw e } finally { + if (underlyingLearningDeferred === deferred) { + underlyingLearningDeferred = null + } if (activeLearningSlotFlow.value == spec.defaultSlot) { activeLearningSlotFlow.value = null } @@ -238,6 +253,8 @@ class InfraredRemoteViewModel @Inject constructor( override fun onCleared() { super.onCleared() + underlyingLearningDeferred?.cancel() + underlyingLearningDeferred = null activeLearningJob?.cancel() activeLearningJob = null }