fix(infrared): url-encode profileId in routes and cancel underlying learning deferred

This commit is contained in:
2026-08-25 12:27:54 +08:00
parent 8682f81cb8
commit 3561f3dd4a
2 changed files with 29 additions and 10 deletions
@@ -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<String?>(null)
private val messageFlow = MutableStateFlow<RemoteUiMessage?>(null)
private var activeLearningJob: Job? = null
private var underlyingLearningDeferred: kotlinx.coroutines.Deferred<*>? = null
private val effectiveProfileAndMode: Flow<Pair<ProviderProfileId?, InfraredRuntimeMode>> =
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<DomainResult<Unit>>).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<DomainResult<InfraredKeyId>>).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
}