feat(ui): complete ABox app experience redesign #10
+16
-4
@@ -8,6 +8,9 @@ import com.flagship.abox.manager.domain.Scene
|
||||
import com.flagship.abox.manager.domain.SceneExecutionResult
|
||||
import com.flagship.abox.manager.domain.SceneExecutor
|
||||
import com.flagship.abox.manager.domain.SceneId
|
||||
import com.flagship.abox.manager.domain.SceneExecutionId
|
||||
import com.flagship.abox.manager.domain.SceneExecutionSource
|
||||
import com.flagship.abox.manager.domain.SceneExecutionUpdate
|
||||
import com.flagship.abox.manager.domain.SceneRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import javax.inject.Inject
|
||||
@@ -15,6 +18,7 @@ import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.StateFlow
|
||||
import kotlinx.coroutines.flow.asStateFlow
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.collect
|
||||
|
||||
sealed interface SceneExecutionUiState {
|
||||
data object Loading : SceneExecutionUiState
|
||||
@@ -71,11 +75,19 @@ class SceneExecutionViewModel @Inject constructor(
|
||||
_uiState.value = SceneExecutionUiState.Failed("Active profile mismatch")
|
||||
return@launch
|
||||
}
|
||||
when (val result = sceneExecutor.execute(scene)) {
|
||||
is DomainResult.Success -> _uiState.value = SceneExecutionUiState.Completed(result.value)
|
||||
is DomainResult.Failure -> _uiState.value = SceneExecutionUiState.Failed(
|
||||
result.error.detail ?: "Execution failed",
|
||||
sceneExecutor.execute(
|
||||
scene = scene,
|
||||
executionId = SceneExecutionId("manual-${scene.id.value}"),
|
||||
source = SceneExecutionSource.USER,
|
||||
).collect { update ->
|
||||
when (update) {
|
||||
is SceneExecutionUpdate.Completed -> _uiState.value = SceneExecutionUiState.Completed(update.result)
|
||||
is SceneExecutionUpdate.Rejected -> _uiState.value = SceneExecutionUiState.Failed(
|
||||
update.error.detail ?: "Execution failed",
|
||||
)
|
||||
is SceneExecutionUpdate.Started -> _uiState.value = SceneExecutionUiState.Running(update.scene)
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -9,6 +9,8 @@ import kotlinx.coroutines.async
|
||||
import kotlinx.coroutines.awaitAll
|
||||
import kotlinx.coroutines.coroutineScope
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.flow
|
||||
|
||||
/**
|
||||
* Production implementation of [SceneExecutor].
|
||||
@@ -24,6 +26,50 @@ class DefaultSceneExecutor(
|
||||
) : SceneExecutor {
|
||||
private val runningScenes = ConcurrentHashMap<SceneId, Boolean>()
|
||||
|
||||
override fun execute(
|
||||
scene: Scene,
|
||||
executionId: SceneExecutionId,
|
||||
source: SceneExecutionSource,
|
||||
): Flow<SceneExecutionUpdate> = flow {
|
||||
val alreadyRunning = runningScenes.putIfAbsent(scene.id, true) != null
|
||||
if (alreadyRunning) {
|
||||
emit(SceneExecutionUpdate.Rejected(DomainError.OperationFailed(detail = "Scene is already running")))
|
||||
return@flow
|
||||
}
|
||||
val startTime = System.currentTimeMillis()
|
||||
try {
|
||||
emit(SceneExecutionUpdate.Started(executionId, scene))
|
||||
val results = mutableListOf<SceneActionExecutionResult>()
|
||||
var aborted = false
|
||||
for (action in scene.actions) {
|
||||
if (aborted) {
|
||||
val skipped = SceneActionExecutionResult(action, SceneActionOutcome.SKIPPED, null)
|
||||
results += skipped
|
||||
emit(SceneExecutionUpdate.StepFinished(skipped))
|
||||
continue
|
||||
}
|
||||
emit(SceneExecutionUpdate.StepPending(action))
|
||||
emit(SceneExecutionUpdate.StepRunning(action))
|
||||
val started = System.currentTimeMillis()
|
||||
val result = executeAction(scene.profileId, action).copy(
|
||||
executionDurationMillis = System.currentTimeMillis() - started,
|
||||
)
|
||||
results += result
|
||||
emit(SceneExecutionUpdate.StepFinished(result))
|
||||
if (result.outcome == SceneActionOutcome.FAILURE && scene.strategy == ExecutionStrategy.ABORT_ON_ERROR) {
|
||||
aborted = true
|
||||
}
|
||||
}
|
||||
emit(
|
||||
SceneExecutionUpdate.Completed(
|
||||
SceneExecutionResult(scene, results, System.currentTimeMillis() - startTime),
|
||||
),
|
||||
)
|
||||
} finally {
|
||||
runningScenes.remove(scene.id)
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun execute(scene: Scene): DomainResult<SceneExecutionResult> {
|
||||
val alreadyRunning = runningScenes.putIfAbsent(scene.id, true) != null
|
||||
if (alreadyRunning) {
|
||||
|
||||
Reference in New Issue
Block a user