fix: clean up infrared learning cancellation

This commit is contained in:
2026-08-26 19:58:51 +08:00
parent 26ab5095e5
commit 176a70cf7f
2 changed files with 57 additions and 1 deletions
@@ -24,6 +24,8 @@ import com.flagship.abox.manager.infrared.remote.UiText
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.ExperimentalCoroutinesApi
import kotlinx.coroutines.Job
import kotlinx.coroutines.NonCancellable
import kotlinx.coroutines.withContext
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.SharingStarted
@@ -59,6 +61,7 @@ class RemotePanelViewModel @Inject constructor(
private val localAcStateFlow = MutableStateFlow<AcStateSnapshot?>(null)
private val messageFlow = MutableStateFlow<PanelUiMessage?>(null)
private var acDebounceJob: Job? = null
private var learningJob: Job? = null
private val remoteFlow = if (profileId == null || remoteId == null) {
flowOf(null)
@@ -128,6 +131,7 @@ class RemotePanelViewModel @Inject constructor(
fun toggleLearnMode() {
val newMode = !isLearnModeFlow.value
isLearnModeFlow.value = newMode
if (!newMode) cancelLearning()
if (newMode) {
messageFlow.value = PanelUiMessage.Info(UiText.StringResource(R.string.infrared_msg_learning_mode_enabled))
}
@@ -167,7 +171,7 @@ class RemotePanelViewModel @Inject constructor(
if (state.value.learningButtonId != null) return
learningButtonIdFlow.value = buttonId
viewModelScope.launch {
learningJob = viewModelScope.launch {
try {
when (val res = learnVirtualRemoteKey(currentProfile, currentRemoteId, buttonId)) {
is DomainResult.Success -> {
@@ -175,12 +179,39 @@ class RemotePanelViewModel @Inject constructor(
}
is DomainResult.Failure -> handleDomainError(res.error)
}
} catch (cancelled: kotlinx.coroutines.CancellationException) {
val binding = state.value.bindings.firstOrNull { it.buttonId == buttonId }
if (binding != null) {
withContext(NonCancellable) {
virtualRemoteRepository.updateKeyBinding(
binding.copy(
status = com.flagship.abox.manager.domain.infrared.InfraredKeyStatus.FAILED,
lastErrorCode = null,
lastErrorDetail = "学习已取消,需要重新学习",
),
)
}
}
throw cancelled
} finally {
learningButtonIdFlow.value = null
}
}
}
fun cancelLearning() {
learningJob?.cancel()
learningJob = null
learningButtonIdFlow.value = null
isLearnModeFlow.value = false
}
override fun onCleared() {
cancelLearning()
acDebounceJob?.cancel()
super.onCleared()
}
fun addCustomButton(displayName: String) {
val currentProfile = profileId ?: return
val currentRemoteId = remoteId ?: return
@@ -226,4 +226,29 @@ class RemotePanelViewModelTest {
job.cancel()
}
@Test
fun cancellingLearningModeClearsActiveLearningState() = testScope.runTest {
val res = createRemote(
profileId = profileId,
deviceName = device,
displayName = "TV",
type = RemoteDeviceType.TV,
brandModel = "generic_tv",
transmissionMode = TransmissionMode.STATIC_SLOTS,
)
val remoteId = (res as com.flagship.abox.manager.domain.DomainResult.Success).value
val vm = createViewModel(remoteId.value)
val job = launch { vm.state.collect {} }
advanceUntilIdle()
vm.toggleLearnMode()
runCurrent()
vm.cancelLearning()
runCurrent()
assertEquals(false, vm.state.value.isLearnMode)
assertEquals(null, vm.state.value.learningButtonId)
job.cancel()
}
}