feat(infrared): add infrared control, learning, and virtual remote module #9
@@ -100,9 +100,10 @@ private fun AuthenticatedNavHost(
|
|||||||
actionInProgress = actionInProgress,
|
actionInProgress = actionInProgress,
|
||||||
errorMessage = errorMessage,
|
errorMessage = errorMessage,
|
||||||
devicesSlot = {
|
devicesSlot = {
|
||||||
|
val encodedProfileId = android.net.Uri.encode(profile.id.value)
|
||||||
DeviceListScreen(
|
DeviceListScreen(
|
||||||
onInfraredClick = {
|
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 },
|
navArgument("profileId") { type = NavType.StringType },
|
||||||
),
|
),
|
||||||
) {
|
) {
|
||||||
|
val encodedProfileId = android.net.Uri.encode(profile.id.value)
|
||||||
InfraredRemoteRoute(
|
InfraredRemoteRoute(
|
||||||
onBack = { navController.popBackStack() },
|
onBack = { navController.popBackStack() },
|
||||||
onOpenSettings = {
|
onOpenSettings = {
|
||||||
navController.navigate("infrared_settings/${profile.id.value}")
|
navController.navigate("infrared_settings/$encodedProfileId")
|
||||||
},
|
},
|
||||||
onSessionInvalid = onLogout,
|
onSessionInvalid = onLogout,
|
||||||
)
|
)
|
||||||
|
|||||||
+24
-7
@@ -8,6 +8,7 @@ import com.flagship.abox.manager.domain.DomainError
|
|||||||
import com.flagship.abox.manager.domain.DomainResult
|
import com.flagship.abox.manager.domain.DomainResult
|
||||||
import com.flagship.abox.manager.domain.ProviderProfileId
|
import com.flagship.abox.manager.domain.ProviderProfileId
|
||||||
import com.flagship.abox.manager.domain.infrared.InfraredDeviceConfigurationRepository
|
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.InfraredKeyRef
|
||||||
import com.flagship.abox.manager.domain.infrared.InfraredKeyRepository
|
import com.flagship.abox.manager.domain.infrared.InfraredKeyRepository
|
||||||
import com.flagship.abox.manager.domain.infrared.InfraredKeyStatus
|
import com.flagship.abox.manager.domain.infrared.InfraredKeyStatus
|
||||||
@@ -70,6 +71,7 @@ class InfraredRemoteViewModel @Inject constructor(
|
|||||||
private val activeLearningSlotFlow = MutableStateFlow<String?>(null)
|
private val activeLearningSlotFlow = MutableStateFlow<String?>(null)
|
||||||
private val messageFlow = MutableStateFlow<RemoteUiMessage?>(null)
|
private val messageFlow = MutableStateFlow<RemoteUiMessage?>(null)
|
||||||
private var activeLearningJob: Job? = null
|
private var activeLearningJob: Job? = null
|
||||||
|
private var underlyingLearningDeferred: kotlinx.coroutines.Deferred<*>? = null
|
||||||
|
|
||||||
private val effectiveProfileAndMode: Flow<Pair<ProviderProfileId?, InfraredRuntimeMode>> =
|
private val effectiveProfileAndMode: Flow<Pair<ProviderProfileId?, InfraredRuntimeMode>> =
|
||||||
runtimeModeStore.mode.flatMapLatest { mode ->
|
runtimeModeStore.mode.flatMapLatest { mode ->
|
||||||
@@ -151,6 +153,8 @@ class InfraredRemoteViewModel @Inject constructor(
|
|||||||
fun toggleLearningMode(enabled: Boolean) {
|
fun toggleLearningMode(enabled: Boolean) {
|
||||||
isLearningModeFlow.value = enabled
|
isLearningModeFlow.value = enabled
|
||||||
if (!enabled) {
|
if (!enabled) {
|
||||||
|
underlyingLearningDeferred?.cancel()
|
||||||
|
underlyingLearningDeferred = null
|
||||||
activeLearningJob?.cancel()
|
activeLearningJob?.cancel()
|
||||||
activeLearningJob = null
|
activeLearningJob = null
|
||||||
activeLearningSlotFlow.value = null
|
activeLearningSlotFlow.value = null
|
||||||
@@ -168,7 +172,7 @@ class InfraredRemoteViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (isLearningModeFlow.value) {
|
if (isLearningModeFlow.value) {
|
||||||
if (activeLearningSlotFlow.value != null) {
|
if (underlyingLearningDeferred?.isActive == true || activeLearningJob?.isActive == true || activeLearningSlotFlow.value != null) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
val existingKey = state.value.learnedSlotMap[spec.defaultSlot]
|
val existingKey = state.value.learnedSlotMap[spec.defaultSlot]
|
||||||
@@ -176,17 +180,25 @@ class InfraredRemoteViewModel @Inject constructor(
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
activeLearningSlotFlow.value = spec.defaultSlot
|
activeLearningSlotFlow.value = spec.defaultSlot
|
||||||
val job = viewModelScope.launch {
|
val deferred = if (existingKey != null) {
|
||||||
try {
|
learningCoordinator.retry(InfraredKeyRef(currentProfile, existingKey.id))
|
||||||
val result = if (existingKey != null) {
|
|
||||||
learningCoordinator.retry(InfraredKeyRef(currentProfile, existingKey.id)).await()
|
|
||||||
} else {
|
} else {
|
||||||
when (val res = learningCoordinator.learn(
|
learningCoordinator.learn(
|
||||||
profileId = currentProfile,
|
profileId = currentProfile,
|
||||||
deviceName = savedDevice,
|
deviceName = savedDevice,
|
||||||
displayName = spec.displayName,
|
displayName = spec.displayName,
|
||||||
sdkKey = spec.defaultSlot,
|
sdkKey = spec.defaultSlot,
|
||||||
).await()) {
|
)
|
||||||
|
}
|
||||||
|
underlyingLearningDeferred = deferred
|
||||||
|
val job = viewModelScope.launch {
|
||||||
|
try {
|
||||||
|
val result = if (existingKey != null) {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
(deferred as kotlinx.coroutines.Deferred<DomainResult<Unit>>).await()
|
||||||
|
} else {
|
||||||
|
@Suppress("UNCHECKED_CAST")
|
||||||
|
when (val res = (deferred as kotlinx.coroutines.Deferred<DomainResult<InfraredKeyId>>).await()) {
|
||||||
is DomainResult.Success -> DomainResult.Success(Unit)
|
is DomainResult.Success -> DomainResult.Success(Unit)
|
||||||
|
|
|||||||
is DomainResult.Failure -> DomainResult.Failure(res.error)
|
is DomainResult.Failure -> DomainResult.Failure(res.error)
|
||||||
}
|
}
|
||||||
@@ -202,6 +214,9 @@ class InfraredRemoteViewModel @Inject constructor(
|
|||||||
} catch (e: kotlinx.coroutines.CancellationException) {
|
} catch (e: kotlinx.coroutines.CancellationException) {
|
||||||
throw e
|
throw e
|
||||||
} finally {
|
} finally {
|
||||||
|
if (underlyingLearningDeferred === deferred) {
|
||||||
|
underlyingLearningDeferred = null
|
||||||
|
}
|
||||||
if (activeLearningSlotFlow.value == spec.defaultSlot) {
|
if (activeLearningSlotFlow.value == spec.defaultSlot) {
|
||||||
activeLearningSlotFlow.value = null
|
activeLearningSlotFlow.value = null
|
||||||
}
|
}
|
||||||
@@ -238,6 +253,8 @@ class InfraredRemoteViewModel @Inject constructor(
|
|||||||
|
|
||||||
override fun onCleared() {
|
override fun onCleared() {
|
||||||
super.onCleared()
|
super.onCleared()
|
||||||
|
underlyingLearningDeferred?.cancel()
|
||||||
|
underlyingLearningDeferred = null
|
||||||
activeLearningJob?.cancel()
|
activeLearningJob?.cancel()
|
||||||
activeLearningJob = null
|
activeLearningJob = null
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user
设备错绑
配置尚未加载时,
savedDeviceName为null,这里会直接回退到ir-1,但遥控按键仍可操作。若该配置实际保存了其他设备名,冷启动后快速开启学习并点击按键会将按键绑定到错误设备,配置加载完成后该按键还会从当前列表消失。应在设备配置解析完成前禁用学习操作。