From 6bbbd6568c3bb6383ac4982400421283dfd8191b Mon Sep 17 00:00:00 2001 From: Adit Lal Date: Wed, 18 Feb 2026 22:00:27 +0530 Subject: [PATCH] Refactor to MVI pattern with sealed AdbCommanderAction Replace individual callback parameters with a single onAction: (AdbCommanderAction) -> Unit across all composables. ViewModel dispatches actions via onAction(action) method. --- .../adbcommander/AdbCommanderViewModel.kt | 76 +++++++++++----- .../adbcommander/model/AdbCommanderAction.kt | 29 ++++++ .../adbcommander/view/AdbCommanderScreen.kt | 90 ++++--------------- .../adbcommander/view/FlowEditorDialog.kt | 62 +++++++------ .../adbcommander/view/HistoryContent.kt | 25 +++--- .../adbcommander/view/LibraryContent.kt | 44 ++++----- .../adbcommander/view/RunnerContent.kt | 62 ++++++++----- 7 files changed, 213 insertions(+), 175 deletions(-) create mode 100644 FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/model/AdbCommanderAction.kt diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/AdbCommanderViewModel.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/AdbCommanderViewModel.kt index edb8e5ea..d100adf9 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/AdbCommanderViewModel.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/AdbCommanderViewModel.kt @@ -20,6 +20,7 @@ import io.github.openflocon.domain.adbcommander.usecase.UpdateSavedCommandUseCas import io.github.openflocon.domain.common.DispatcherProvider import io.github.openflocon.domain.feedback.FeedbackDisplayer import io.github.openflocon.flocondesktop.features.adbcommander.mapper.toUiModel +import io.github.openflocon.flocondesktop.features.adbcommander.model.AdbCommanderAction import io.github.openflocon.flocondesktop.features.adbcommander.model.AdbCommanderTab import io.github.openflocon.flocondesktop.features.adbcommander.model.AdbCommanderUiState import io.github.openflocon.flocondesktop.features.adbcommander.model.ConsoleOutputEntry @@ -83,15 +84,43 @@ class AdbCommanderViewModel( initialValue = AdbCommanderUiState(), ) - fun onTabSelected(tab: AdbCommanderTab) { + fun onAction(action: AdbCommanderAction) { + when (action) { + is AdbCommanderAction.TabSelected -> onTabSelected(action.tab) + is AdbCommanderAction.CommandInputChanged -> onCommandInputChanged(action.input) + is AdbCommanderAction.ExecuteCommand -> onExecuteCommand() + is AdbCommanderAction.SaveCurrentCommand -> onSaveCurrentCommand() + is AdbCommanderAction.RunSavedCommand -> onRunSavedCommand(action.command) + is AdbCommanderAction.DeleteSavedCommand -> onDeleteSavedCommand(action.id) + is AdbCommanderAction.SaveQuickCommand -> onSaveQuickCommand(action.name, action.command) + is AdbCommanderAction.ClearHistory -> onClearHistory() + is AdbCommanderAction.RerunCommand -> onRerunCommand(action.command) + is AdbCommanderAction.ClearConsole -> onClearConsole() + is AdbCommanderAction.ShowFlowEditor -> onShowFlowEditor(action.flowId) + is AdbCommanderAction.DismissFlowEditor -> onDismissFlowEditor() + is AdbCommanderAction.FlowEditorNameChanged -> onFlowEditorNameChanged(action.name) + is AdbCommanderAction.FlowEditorDescriptionChanged -> onFlowEditorDescriptionChanged(action.description) + is AdbCommanderAction.FlowEditorStepCommandChanged -> onFlowEditorStepCommandChanged(action.index, action.command) + is AdbCommanderAction.FlowEditorStepLabelChanged -> onFlowEditorStepLabelChanged(action.index, action.label) + is AdbCommanderAction.FlowEditorStepDelayChanged -> onFlowEditorStepDelayChanged(action.index, action.delay) + is AdbCommanderAction.FlowEditorAddStep -> onFlowEditorAddStep() + is AdbCommanderAction.FlowEditorRemoveStep -> onFlowEditorRemoveStep(action.index) + is AdbCommanderAction.SaveFlow -> onSaveFlow() + is AdbCommanderAction.DeleteFlow -> onDeleteFlow(action.id) + is AdbCommanderAction.ExecuteFlow -> onExecuteFlow(action.flowId) + is AdbCommanderAction.CancelFlowExecution -> onCancelFlowExecution() + } + } + + private fun onTabSelected(tab: AdbCommanderTab) { localState.update { it.copy(selectedTab = tab) } } - fun onCommandInputChanged(input: String) { + private fun onCommandInputChanged(input: String) { localState.update { it.copy(commandInput = input) } } - fun onExecuteCommand() { + private fun onExecuteCommand() { val command = localState.value.commandInput.trim() if (command.isEmpty()) return @@ -130,12 +159,12 @@ class AdbCommanderViewModel( } } - fun onRunSavedCommand(command: String) { + private fun onRunSavedCommand(command: String) { localState.update { it.copy(commandInput = command) } onExecuteCommand() } - fun onSaveCurrentCommand() { + private fun onSaveCurrentCommand() { val command = localState.value.commandInput.trim() if (command.isEmpty()) { feedbackDisplayer.displayMessage( @@ -157,7 +186,7 @@ class AdbCommanderViewModel( } } - fun onSaveQuickCommand(name: String, command: String) { + private fun onSaveQuickCommand(name: String, command: String) { viewModelScope.launch(dispatcherProvider.viewModel) { saveCommandUseCase( AdbCommandDomainModel( @@ -171,27 +200,26 @@ class AdbCommanderViewModel( } } - fun onDeleteSavedCommand(id: Long) { + private fun onDeleteSavedCommand(id: Long) { viewModelScope.launch(dispatcherProvider.viewModel) { deleteSavedCommandUseCase(id) feedbackDisplayer.displayMessage("Command deleted") } } - fun onClearHistory() { + private fun onClearHistory() { viewModelScope.launch(dispatcherProvider.viewModel) { clearCommandHistoryUseCase() feedbackDisplayer.displayMessage("History cleared") } } - fun onRerunCommand(command: String) { + private fun onRerunCommand(command: String) { localState.update { it.copy(commandInput = command) } onExecuteCommand() } - // Flow editor - fun onShowFlowEditor(flowId: Long? = null) { + private fun onShowFlowEditor(flowId: Long? = null) { if (flowId != null) { val flow = domainFlows.value.find { it.id == flowId } localState.update { @@ -221,23 +249,23 @@ class AdbCommanderViewModel( } } - fun onDismissFlowEditor() { + private fun onDismissFlowEditor() { localState.update { it.copy(showFlowEditor = false) } } - fun onFlowEditorNameChanged(name: String) { + private fun onFlowEditorNameChanged(name: String) { localState.update { it.copy(flowEditorState = it.flowEditorState.copy(name = name)) } } - fun onFlowEditorDescriptionChanged(description: String) { + private fun onFlowEditorDescriptionChanged(description: String) { localState.update { it.copy(flowEditorState = it.flowEditorState.copy(description = description)) } } - fun onFlowEditorStepCommandChanged(index: Int, command: String) { + private fun onFlowEditorStepCommandChanged(index: Int, command: String) { localState.update { val steps = it.flowEditorState.steps.toMutableList() if (index < steps.size) { @@ -247,7 +275,7 @@ class AdbCommanderViewModel( } } - fun onFlowEditorStepLabelChanged(index: Int, label: String) { + private fun onFlowEditorStepLabelChanged(index: Int, label: String) { localState.update { val steps = it.flowEditorState.steps.toMutableList() if (index < steps.size) { @@ -257,7 +285,7 @@ class AdbCommanderViewModel( } } - fun onFlowEditorStepDelayChanged(index: Int, delay: String) { + private fun onFlowEditorStepDelayChanged(index: Int, delay: String) { localState.update { val steps = it.flowEditorState.steps.toMutableList() if (index < steps.size) { @@ -267,7 +295,7 @@ class AdbCommanderViewModel( } } - fun onFlowEditorAddStep() { + private fun onFlowEditorAddStep() { localState.update { it.copy( flowEditorState = it.flowEditorState.copy( @@ -277,7 +305,7 @@ class AdbCommanderViewModel( } } - fun onFlowEditorRemoveStep(index: Int) { + private fun onFlowEditorRemoveStep(index: Int) { localState.update { val steps = it.flowEditorState.steps.toMutableList() if (steps.size > 1 && index < steps.size) { @@ -287,7 +315,7 @@ class AdbCommanderViewModel( } } - fun onSaveFlow() { + private fun onSaveFlow() { val editor = localState.value.flowEditorState if (editor.name.isBlank()) { feedbackDisplayer.displayMessage( @@ -329,14 +357,14 @@ class AdbCommanderViewModel( } } - fun onDeleteFlow(id: Long) { + private fun onDeleteFlow(id: Long) { viewModelScope.launch(dispatcherProvider.viewModel) { deleteFlowUseCase(id) feedbackDisplayer.displayMessage("Flow deleted") } } - fun onExecuteFlow(flowId: Long) { + private fun onExecuteFlow(flowId: Long) { val flow = domainFlows.value.find { it.id == flowId } ?: return flowExecutionJob?.cancel() @@ -349,12 +377,12 @@ class AdbCommanderViewModel( } } - fun onCancelFlowExecution() { + private fun onCancelFlowExecution() { flowExecutionJob?.cancel() flowExecutionJob = null } - fun onClearConsole() { + private fun onClearConsole() { localState.update { it.copy(consoleOutput = emptyList(), flowExecution = null) } } } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/model/AdbCommanderAction.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/model/AdbCommanderAction.kt new file mode 100644 index 00000000..3fe53ce5 --- /dev/null +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/model/AdbCommanderAction.kt @@ -0,0 +1,29 @@ +package io.github.openflocon.flocondesktop.features.adbcommander.model + +sealed interface AdbCommanderAction { + data class TabSelected(val tab: AdbCommanderTab) : AdbCommanderAction + data class CommandInputChanged(val input: String) : AdbCommanderAction + data object ExecuteCommand : AdbCommanderAction + data object SaveCurrentCommand : AdbCommanderAction + data class RunSavedCommand(val command: String) : AdbCommanderAction + data class DeleteSavedCommand(val id: Long) : AdbCommanderAction + data class SaveQuickCommand(val name: String, val command: String) : AdbCommanderAction + data object ClearHistory : AdbCommanderAction + data class RerunCommand(val command: String) : AdbCommanderAction + data object ClearConsole : AdbCommanderAction + + // Flow actions + data class ShowFlowEditor(val flowId: Long? = null) : AdbCommanderAction + data object DismissFlowEditor : AdbCommanderAction + data class FlowEditorNameChanged(val name: String) : AdbCommanderAction + data class FlowEditorDescriptionChanged(val description: String) : AdbCommanderAction + data class FlowEditorStepCommandChanged(val index: Int, val command: String) : AdbCommanderAction + data class FlowEditorStepLabelChanged(val index: Int, val label: String) : AdbCommanderAction + data class FlowEditorStepDelayChanged(val index: Int, val delay: String) : AdbCommanderAction + data object FlowEditorAddStep : AdbCommanderAction + data class FlowEditorRemoveStep(val index: Int) : AdbCommanderAction + data object SaveFlow : AdbCommanderAction + data class DeleteFlow(val id: Long) : AdbCommanderAction + data class ExecuteFlow(val flowId: Long) : AdbCommanderAction + data object CancelFlowExecution : AdbCommanderAction +} diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/AdbCommanderScreen.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/AdbCommanderScreen.kt index cf1165a9..818ab642 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/AdbCommanderScreen.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/AdbCommanderScreen.kt @@ -1,9 +1,7 @@ package io.github.openflocon.flocondesktop.features.adbcommander.view import androidx.compose.foundation.background -import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.text.KeyboardActions @@ -17,9 +15,9 @@ import androidx.compose.runtime.getValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.text.input.ImeAction -import androidx.compose.ui.unit.dp import androidx.lifecycle.compose.collectAsStateWithLifecycle import io.github.openflocon.flocondesktop.features.adbcommander.AdbCommanderViewModel +import io.github.openflocon.flocondesktop.features.adbcommander.model.AdbCommanderAction import io.github.openflocon.flocondesktop.features.adbcommander.model.AdbCommanderTab import io.github.openflocon.flocondesktop.features.adbcommander.model.AdbCommanderUiState import io.github.openflocon.library.designsystem.FloconTheme @@ -30,7 +28,10 @@ import io.github.openflocon.library.designsystem.components.FloconIconButton import io.github.openflocon.library.designsystem.components.FloconPageTopBar import io.github.openflocon.library.designsystem.components.FloconTextField import io.github.openflocon.library.designsystem.components.defaultPlaceHolder +import org.jetbrains.compose.resources.stringResource import org.koin.compose.viewmodel.koinViewModel +import flocondesktop.composeapp.generated.resources.Res +import flocondesktop.composeapp.generated.resources.adb_commander_input_placeholder @Composable fun AdbCommanderScreen(modifier: Modifier = Modifier) { @@ -39,29 +40,7 @@ fun AdbCommanderScreen(modifier: Modifier = Modifier) { AdbCommanderScreen( uiState = uiState, - onTabSelected = viewModel::onTabSelected, - onCommandInputChanged = viewModel::onCommandInputChanged, - onExecuteCommand = viewModel::onExecuteCommand, - onSaveCurrentCommand = viewModel::onSaveCurrentCommand, - onRunSavedCommand = viewModel::onRunSavedCommand, - onDeleteSavedCommand = viewModel::onDeleteSavedCommand, - onSaveQuickCommand = viewModel::onSaveQuickCommand, - onClearHistory = viewModel::onClearHistory, - onRerunCommand = viewModel::onRerunCommand, - onClearConsole = viewModel::onClearConsole, - onShowFlowEditor = viewModel::onShowFlowEditor, - onDismissFlowEditor = viewModel::onDismissFlowEditor, - onFlowEditorNameChanged = viewModel::onFlowEditorNameChanged, - onFlowEditorDescriptionChanged = viewModel::onFlowEditorDescriptionChanged, - onFlowEditorStepCommandChanged = viewModel::onFlowEditorStepCommandChanged, - onFlowEditorStepLabelChanged = viewModel::onFlowEditorStepLabelChanged, - onFlowEditorStepDelayChanged = viewModel::onFlowEditorStepDelayChanged, - onFlowEditorAddStep = viewModel::onFlowEditorAddStep, - onFlowEditorRemoveStep = viewModel::onFlowEditorRemoveStep, - onSaveFlow = viewModel::onSaveFlow, - onDeleteFlow = viewModel::onDeleteFlow, - onExecuteFlow = viewModel::onExecuteFlow, - onCancelFlowExecution = viewModel::onCancelFlowExecution, + onAction = viewModel::onAction, modifier = modifier, ) } @@ -69,29 +48,7 @@ fun AdbCommanderScreen(modifier: Modifier = Modifier) { @Composable private fun AdbCommanderScreen( uiState: AdbCommanderUiState, - onTabSelected: (AdbCommanderTab) -> Unit, - onCommandInputChanged: (String) -> Unit, - onExecuteCommand: () -> Unit, - onSaveCurrentCommand: () -> Unit, - onRunSavedCommand: (String) -> Unit, - onDeleteSavedCommand: (Long) -> Unit, - onSaveQuickCommand: (String, String) -> Unit, - onClearHistory: () -> Unit, - onRerunCommand: (String) -> Unit, - onClearConsole: () -> Unit, - onShowFlowEditor: (Long?) -> Unit, - onDismissFlowEditor: () -> Unit, - onFlowEditorNameChanged: (String) -> Unit, - onFlowEditorDescriptionChanged: (String) -> Unit, - onFlowEditorStepCommandChanged: (Int, String) -> Unit, - onFlowEditorStepLabelChanged: (Int, String) -> Unit, - onFlowEditorStepDelayChanged: (Int, String) -> Unit, - onFlowEditorAddStep: () -> Unit, - onFlowEditorRemoveStep: (Int) -> Unit, - onSaveFlow: () -> Unit, - onDeleteFlow: (Long) -> Unit, - onExecuteFlow: (Long) -> Unit, - onCancelFlowExecution: () -> Unit, + onAction: (AdbCommanderAction) -> Unit, modifier: Modifier = Modifier, ) { FloconFeature(modifier = modifier) { @@ -100,7 +57,7 @@ private fun AdbCommanderScreen( selector = { AdbCommanderTab.entries.forEach { tab -> FloconButton( - onClick = { onTabSelected(tab) }, + onClick = { onAction(AdbCommanderAction.TabSelected(tab)) }, containerColor = if (tab == uiState.selectedTab) { FloconTheme.colorPalette.accent } else { @@ -114,19 +71,19 @@ private fun AdbCommanderScreen( filterBar = { FloconTextField( value = uiState.commandInput, - onValueChange = onCommandInputChanged, - placeholder = defaultPlaceHolder("Enter ADB command (e.g. shell echo hello)"), + onValueChange = { onAction(AdbCommanderAction.CommandInputChanged(it)) }, + placeholder = defaultPlaceHolder(stringResource(Res.string.adb_commander_input_placeholder)), singleLine = true, keyboardOptions = KeyboardOptions(imeAction = ImeAction.Send), - keyboardActions = KeyboardActions(onSend = { onExecuteCommand() }), + keyboardActions = KeyboardActions(onSend = { onAction(AdbCommanderAction.ExecuteCommand) }), modifier = Modifier.weight(1f), ) }, actions = { - FloconIconButton(onClick = onSaveCurrentCommand) { + FloconIconButton(onClick = { onAction(AdbCommanderAction.SaveCurrentCommand) }) { FloconIcon(imageVector = Icons.Outlined.Save) } - FloconIconButton(onClick = onExecuteCommand) { + FloconIconButton(onClick = { onAction(AdbCommanderAction.ExecuteCommand) }) { FloconIcon(imageVector = Icons.AutoMirrored.Outlined.Send) } }, @@ -143,25 +100,18 @@ private fun AdbCommanderScreen( consoleOutput = uiState.consoleOutput, flowExecution = uiState.flowExecution, isExecuting = uiState.isExecuting, - onClearConsole = onClearConsole, - onCancelFlowExecution = onCancelFlowExecution, + onAction = onAction, modifier = Modifier.fillMaxSize(), ) AdbCommanderTab.Library -> LibraryContent( savedCommands = uiState.savedCommands, flows = uiState.flows, - onRunSavedCommand = onRunSavedCommand, - onDeleteSavedCommand = onDeleteSavedCommand, - onShowFlowEditor = onShowFlowEditor, - onDeleteFlow = onDeleteFlow, - onExecuteFlow = onExecuteFlow, - onSaveQuickCommand = onSaveQuickCommand, + onAction = onAction, modifier = Modifier.fillMaxSize(), ) AdbCommanderTab.History -> HistoryContent( history = uiState.history, - onClearHistory = onClearHistory, - onRerunCommand = onRerunCommand, + onAction = onAction, modifier = Modifier.fillMaxSize(), ) } @@ -171,15 +121,7 @@ private fun AdbCommanderScreen( if (uiState.showFlowEditor) { FlowEditorDialog( state = uiState.flowEditorState, - onDismiss = onDismissFlowEditor, - onNameChanged = onFlowEditorNameChanged, - onDescriptionChanged = onFlowEditorDescriptionChanged, - onStepCommandChanged = onFlowEditorStepCommandChanged, - onStepLabelChanged = onFlowEditorStepLabelChanged, - onStepDelayChanged = onFlowEditorStepDelayChanged, - onAddStep = onFlowEditorAddStep, - onRemoveStep = onFlowEditorRemoveStep, - onSave = onSaveFlow, + onAction = onAction, ) } } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/FlowEditorDialog.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/FlowEditorDialog.kt index 12585b8b..19819a97 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/FlowEditorDialog.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/FlowEditorDialog.kt @@ -15,6 +15,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import io.github.openflocon.flocondesktop.features.adbcommander.model.AdbCommanderAction import io.github.openflocon.flocondesktop.features.adbcommander.model.FlowEditorState import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconButton @@ -25,24 +26,31 @@ import io.github.openflocon.library.designsystem.components.FloconIcon import io.github.openflocon.library.designsystem.components.FloconIconButton import io.github.openflocon.library.designsystem.components.FloconTextField import io.github.openflocon.library.designsystem.components.defaultPlaceHolder +import org.jetbrains.compose.resources.stringResource +import flocondesktop.composeapp.generated.resources.Res +import flocondesktop.composeapp.generated.resources.adb_commander_edit_flow +import flocondesktop.composeapp.generated.resources.adb_commander_new_flow +import flocondesktop.composeapp.generated.resources.adb_commander_flow_name +import flocondesktop.composeapp.generated.resources.adb_commander_flow_description +import flocondesktop.composeapp.generated.resources.adb_commander_steps +import flocondesktop.composeapp.generated.resources.adb_commander_add_step +import flocondesktop.composeapp.generated.resources.adb_commander_step_command +import flocondesktop.composeapp.generated.resources.adb_commander_step_label +import flocondesktop.composeapp.generated.resources.adb_commander_step_delay @Composable fun FlowEditorDialog( state: FlowEditorState, - onDismiss: () -> Unit, - onNameChanged: (String) -> Unit, - onDescriptionChanged: (String) -> Unit, - onStepCommandChanged: (Int, String) -> Unit, - onStepLabelChanged: (Int, String) -> Unit, - onStepDelayChanged: (Int, String) -> Unit, - onAddStep: () -> Unit, - onRemoveStep: (Int) -> Unit, - onSave: () -> Unit, + onAction: (AdbCommanderAction) -> Unit, ) { - FloconDialog(onDismissRequest = onDismiss) { + FloconDialog(onDismissRequest = { onAction(AdbCommanderAction.DismissFlowEditor) }) { Column { FloconDialogHeader( - title = if (state.flowId != null) "Edit Flow" else "New Flow", + title = if (state.flowId != null) { + stringResource(Res.string.adb_commander_edit_flow) + } else { + stringResource(Res.string.adb_commander_new_flow) + }, modifier = Modifier.fillMaxWidth(), ) @@ -54,15 +62,15 @@ fun FlowEditorDialog( ) { FloconTextField( value = state.name, - onValueChange = onNameChanged, - placeholder = defaultPlaceHolder("Flow name"), + onValueChange = { onAction(AdbCommanderAction.FlowEditorNameChanged(it)) }, + placeholder = defaultPlaceHolder(stringResource(Res.string.adb_commander_flow_name)), modifier = Modifier.fillMaxWidth(), ) FloconTextField( value = state.description, - onValueChange = onDescriptionChanged, - placeholder = defaultPlaceHolder("Description (optional)"), + onValueChange = { onAction(AdbCommanderAction.FlowEditorDescriptionChanged(it)) }, + placeholder = defaultPlaceHolder(stringResource(Res.string.adb_commander_flow_description)), modifier = Modifier.fillMaxWidth(), ) @@ -72,13 +80,13 @@ fun FlowEditorDialog( verticalAlignment = Alignment.CenterVertically, ) { Text( - text = "Steps", + text = stringResource(Res.string.adb_commander_steps), style = FloconTheme.typography.titleSmall, color = FloconTheme.colorPalette.onPrimary, ) - FloconButton(onClick = onAddStep) { + FloconButton(onClick = { onAction(AdbCommanderAction.FlowEditorAddStep) }) { FloconIcon(imageVector = Icons.Outlined.Add) - Text("Add Step") + Text(stringResource(Res.string.adb_commander_add_step)) } } @@ -91,10 +99,10 @@ fun FlowEditorDialog( command = step.command, label = step.label, delayAfterMs = step.delayAfterMs, - onCommandChanged = { onStepCommandChanged(index, it) }, - onLabelChanged = { onStepLabelChanged(index, it) }, - onDelayChanged = { onStepDelayChanged(index, it) }, - onRemove = { onRemoveStep(index) }, + onCommandChanged = { onAction(AdbCommanderAction.FlowEditorStepCommandChanged(index, it)) }, + onLabelChanged = { onAction(AdbCommanderAction.FlowEditorStepLabelChanged(index, it)) }, + onDelayChanged = { onAction(AdbCommanderAction.FlowEditorStepDelayChanged(index, it)) }, + onRemove = { onAction(AdbCommanderAction.FlowEditorRemoveStep(index)) }, canRemove = state.steps.size > 1, modifier = Modifier.fillMaxWidth(), ) @@ -102,8 +110,8 @@ fun FlowEditorDialog( } FloconDialogButtons( - onCancel = onDismiss, - onValidate = onSave, + onCancel = { onAction(AdbCommanderAction.DismissFlowEditor) }, + onValidate = { onAction(AdbCommanderAction.SaveFlow) }, modifier = Modifier.padding(top = 8.dp), ) } @@ -142,20 +150,20 @@ private fun StepEditorItem( FloconTextField( value = command, onValueChange = onCommandChanged, - placeholder = defaultPlaceHolder("ADB command"), + placeholder = defaultPlaceHolder(stringResource(Res.string.adb_commander_step_command)), modifier = Modifier.fillMaxWidth(), ) Row(horizontalArrangement = Arrangement.spacedBy(4.dp)) { FloconTextField( value = label, onValueChange = onLabelChanged, - placeholder = defaultPlaceHolder("Label (optional)"), + placeholder = defaultPlaceHolder(stringResource(Res.string.adb_commander_step_label)), modifier = Modifier.weight(1f), ) FloconTextField( value = delayAfterMs, onValueChange = onDelayChanged, - placeholder = defaultPlaceHolder("Delay (ms)"), + placeholder = defaultPlaceHolder(stringResource(Res.string.adb_commander_step_delay)), modifier = Modifier.weight(0.5f), ) } diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/HistoryContent.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/HistoryContent.kt index 228c564a..62071617 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/HistoryContent.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/HistoryContent.kt @@ -11,6 +11,8 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.DeleteSweep import androidx.compose.material.icons.outlined.Replay import androidx.compose.material3.Text @@ -23,17 +25,21 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp +import io.github.openflocon.flocondesktop.features.adbcommander.model.AdbCommanderAction import io.github.openflocon.flocondesktop.features.adbcommander.model.HistoryEntryUiModel import io.github.openflocon.library.designsystem.FloconTheme import io.github.openflocon.library.designsystem.components.FloconIcon import io.github.openflocon.library.designsystem.components.FloconIconButton import io.github.openflocon.library.designsystem.components.FloconTextButton +import org.jetbrains.compose.resources.stringResource +import flocondesktop.composeapp.generated.resources.Res +import flocondesktop.composeapp.generated.resources.adb_commander_clear_all +import flocondesktop.composeapp.generated.resources.adb_commander_no_history @Composable fun HistoryContent( history: List, - onClearHistory: () -> Unit, - onRerunCommand: (String) -> Unit, + onAction: (AdbCommanderAction) -> Unit, modifier: Modifier = Modifier, ) { Column(modifier = modifier) { @@ -41,15 +47,15 @@ fun HistoryContent( modifier = Modifier.fillMaxWidth().padding(horizontal = 8.dp, vertical = 4.dp), horizontalArrangement = Arrangement.End, ) { - FloconTextButton(onClick = onClearHistory) { + FloconTextButton(onClick = { onAction(AdbCommanderAction.ClearHistory) }) { FloconIcon(imageVector = Icons.Outlined.DeleteSweep) - Text("Clear All") + Text(stringResource(Res.string.adb_commander_clear_all)) } } if (history.isEmpty()) { Text( - text = "No command history", + text = stringResource(Res.string.adb_commander_no_history), style = FloconTheme.typography.bodySmall, color = FloconTheme.colorPalette.onPrimary.copy(alpha = 0.6f), modifier = Modifier.padding(16.dp), @@ -64,7 +70,7 @@ fun HistoryContent( items(history) { entry -> HistoryEntryView( entry = entry, - onRerun = { onRerunCommand(entry.command) }, + onRerun = { onAction(AdbCommanderAction.RerunCommand(entry.command)) }, modifier = Modifier.fillMaxWidth(), ) } @@ -90,10 +96,9 @@ private fun HistoryEntryView( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, ) { - Text( - text = if (entry.isSuccess) "+" else "x", - style = FloconTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace), - color = if (entry.isSuccess) { + FloconIcon( + imageVector = if (entry.isSuccess) Icons.Outlined.Check else Icons.Outlined.Close, + tint = if (entry.isSuccess) { FloconTheme.colorPalette.onPrimary } else { FloconTheme.colorPalette.error diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/LibraryContent.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/LibraryContent.kt index e137e34b..a60bac41 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/LibraryContent.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/LibraryContent.kt @@ -18,6 +18,7 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp +import io.github.openflocon.flocondesktop.features.adbcommander.model.AdbCommanderAction import io.github.openflocon.flocondesktop.features.adbcommander.model.FlowUiModel import io.github.openflocon.flocondesktop.features.adbcommander.model.QuickCommand import io.github.openflocon.flocondesktop.features.adbcommander.model.SavedCommandUiModel @@ -27,17 +28,20 @@ import io.github.openflocon.library.designsystem.components.FloconButton import io.github.openflocon.library.designsystem.components.FloconIcon import io.github.openflocon.library.designsystem.components.FloconIconButton import io.github.openflocon.library.designsystem.components.FloconSection +import org.jetbrains.compose.resources.stringResource +import flocondesktop.composeapp.generated.resources.Res +import flocondesktop.composeapp.generated.resources.adb_commander_saved_commands +import flocondesktop.composeapp.generated.resources.adb_commander_no_saved_commands +import flocondesktop.composeapp.generated.resources.adb_commander_automation_flows +import flocondesktop.composeapp.generated.resources.adb_commander_new_flow +import flocondesktop.composeapp.generated.resources.adb_commander_no_flows +import flocondesktop.composeapp.generated.resources.adb_commander_steps_count @Composable fun LibraryContent( savedCommands: List, flows: List, - onRunSavedCommand: (String) -> Unit, - onDeleteSavedCommand: (Long) -> Unit, - onShowFlowEditor: (Long?) -> Unit, - onDeleteFlow: (Long) -> Unit, - onExecuteFlow: (Long) -> Unit, - onSaveQuickCommand: (String, String) -> Unit, + onAction: (AdbCommanderAction) -> Unit, modifier: Modifier = Modifier, ) { val categories = defaultQuickCommands.groupBy { it.category } @@ -49,13 +53,13 @@ fun LibraryContent( ) { item { FloconSection( - title = "Saved Commands", + title = stringResource(Res.string.adb_commander_saved_commands), initialValue = true, ) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { if (savedCommands.isEmpty()) { Text( - text = "No saved commands", + text = stringResource(Res.string.adb_commander_no_saved_commands), style = FloconTheme.typography.bodySmall, color = FloconTheme.colorPalette.onPrimary.copy(alpha = 0.6f), modifier = Modifier.padding(8.dp), @@ -64,8 +68,8 @@ fun LibraryContent( savedCommands.forEach { command -> SavedCommandItem( command = command, - onRun = { onRunSavedCommand(command.command) }, - onDelete = { onDeleteSavedCommand(command.id) }, + onRun = { onAction(AdbCommanderAction.RunSavedCommand(command.command)) }, + onDelete = { onAction(AdbCommanderAction.DeleteSavedCommand(command.id)) }, modifier = Modifier.fillMaxWidth(), ) } @@ -74,19 +78,19 @@ fun LibraryContent( } item { FloconSection( - title = "Automation Flows", + title = stringResource(Res.string.adb_commander_automation_flows), initialValue = true, actions = { - FloconButton(onClick = { onShowFlowEditor(null) }) { + FloconButton(onClick = { onAction(AdbCommanderAction.ShowFlowEditor(null)) }) { FloconIcon(imageVector = Icons.Outlined.Add) - Text("New Flow") + Text(stringResource(Res.string.adb_commander_new_flow)) } }, ) { Column(verticalArrangement = Arrangement.spacedBy(4.dp)) { if (flows.isEmpty()) { Text( - text = "No automation flows", + text = stringResource(Res.string.adb_commander_no_flows), style = FloconTheme.typography.bodySmall, color = FloconTheme.colorPalette.onPrimary.copy(alpha = 0.6f), modifier = Modifier.padding(8.dp), @@ -95,9 +99,9 @@ fun LibraryContent( flows.forEach { flow -> FlowItem( flow = flow, - onRun = { onExecuteFlow(flow.id) }, - onEdit = { onShowFlowEditor(flow.id) }, - onDelete = { onDeleteFlow(flow.id) }, + onRun = { onAction(AdbCommanderAction.ExecuteFlow(flow.id)) }, + onEdit = { onAction(AdbCommanderAction.ShowFlowEditor(flow.id)) }, + onDelete = { onAction(AdbCommanderAction.DeleteFlow(flow.id)) }, modifier = Modifier.fillMaxWidth(), ) } @@ -114,8 +118,8 @@ fun LibraryContent( commands.forEach { cmd -> QuickCommandItem( command = cmd, - onRun = { onRunSavedCommand(cmd.command) }, - onSave = { onSaveQuickCommand(cmd.name, cmd.command) }, + onRun = { onAction(AdbCommanderAction.RunSavedCommand(cmd.command)) }, + onSave = { onAction(AdbCommanderAction.SaveQuickCommand(cmd.name, cmd.command)) }, modifier = Modifier.fillMaxWidth(), ) } @@ -219,7 +223,7 @@ private fun FlowItem( color = FloconTheme.colorPalette.onPrimary, ) Text( - text = "${flow.stepsCount} steps", + text = stringResource(Res.string.adb_commander_steps_count, flow.stepsCount), style = FloconTheme.typography.bodySmall, color = FloconTheme.colorPalette.onPrimary.copy(alpha = 0.7f), ) diff --git a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/RunnerContent.kt b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/RunnerContent.kt index 0b4ece1a..2ef7074c 100644 --- a/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/RunnerContent.kt +++ b/FloconDesktop/composeApp/src/commonMain/kotlin/io/github/openflocon/flocondesktop/features/adbcommander/view/RunnerContent.kt @@ -6,22 +6,35 @@ import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.rememberLazyListState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.Cancel +import androidx.compose.material.icons.outlined.Check +import androidx.compose.material.icons.outlined.Close import androidx.compose.material.icons.outlined.DeleteSweep +import androidx.compose.material.icons.outlined.HourglassEmpty +import androidx.compose.material.icons.outlined.PlayArrow +import androidx.compose.material.icons.outlined.RemoveCircle +import androidx.compose.material.icons.outlined.Schedule import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.text.font.FontFamily import androidx.compose.ui.unit.dp +import io.github.openflocon.flocondesktop.features.adbcommander.model.AdbCommanderAction import io.github.openflocon.flocondesktop.features.adbcommander.model.ConsoleOutputEntry import io.github.openflocon.flocondesktop.features.adbcommander.model.FlowExecutionUiModel import io.github.openflocon.library.designsystem.FloconTheme +import org.jetbrains.compose.resources.stringResource +import flocondesktop.composeapp.generated.resources.Res +import flocondesktop.composeapp.generated.resources.adb_commander_cancel_flow import io.github.openflocon.library.designsystem.components.FloconCircularProgressIndicator import io.github.openflocon.library.designsystem.components.FloconIcon import io.github.openflocon.library.designsystem.components.FloconIconButton @@ -32,8 +45,7 @@ fun RunnerContent( consoleOutput: List, flowExecution: FlowExecutionUiModel?, isExecuting: Boolean, - onClearConsole: () -> Unit, - onCancelFlowExecution: () -> Unit, + onAction: (AdbCommanderAction) -> Unit, modifier: Modifier = Modifier, ) { val listState = rememberLazyListState() @@ -54,12 +66,12 @@ fun RunnerContent( FloconCircularProgressIndicator() } if (flowExecution?.isRunning == true) { - FloconTextButton(onClick = onCancelFlowExecution) { + FloconTextButton(onClick = { onAction(AdbCommanderAction.CancelFlowExecution) }) { FloconIcon(imageVector = Icons.Outlined.Cancel) - Text("Cancel Flow") + Text(stringResource(Res.string.adb_commander_cancel_flow)) } } - FloconIconButton(onClick = onClearConsole) { + FloconIconButton(onClick = { onAction(AdbCommanderAction.ClearConsole) }) { FloconIcon(imageVector = Icons.Outlined.DeleteSweep) } } @@ -132,23 +144,15 @@ private fun FlowExecutionView( horizontalArrangement = Arrangement.spacedBy(8.dp), verticalAlignment = Alignment.CenterVertically, ) { - val statusIcon = when (step.status) { - "Completed" -> "+" - "Failed" -> "x" - "Running" -> ">" - "WaitingDelay" -> "~" - "Skipped" -> "-" - else -> " " - } + FloconIcon( + imageVector = step.status.toIcon(), + tint = step.status.toColor(), + modifier = Modifier.size(16.dp), + ) Text( - text = "[$statusIcon] ${step.label}", + text = step.label, style = FloconTheme.typography.bodySmall.copy(fontFamily = FontFamily.Monospace), - color = when (step.status) { - "Completed" -> FloconTheme.colorPalette.onPrimary - "Failed" -> FloconTheme.colorPalette.error - "Running", "WaitingDelay" -> FloconTheme.colorPalette.accent - else -> FloconTheme.colorPalette.onPrimary.copy(alpha = 0.5f) - }, + color = step.status.toColor(), ) if (step.isActive) { FloconCircularProgressIndicator() @@ -157,3 +161,21 @@ private fun FlowExecutionView( } } } + +@Composable +private fun String.toIcon(): ImageVector = when (this) { + "Completed" -> Icons.Outlined.Check + "Failed" -> Icons.Outlined.Close + "Running" -> Icons.Outlined.PlayArrow + "WaitingDelay" -> Icons.Outlined.HourglassEmpty + "Skipped" -> Icons.Outlined.RemoveCircle + else -> Icons.Outlined.Schedule +} + +@Composable +private fun String.toColor(): Color = when (this) { + "Completed" -> FloconTheme.colorPalette.onPrimary + "Failed" -> FloconTheme.colorPalette.error + "Running", "WaitingDelay" -> FloconTheme.colorPalette.accent + else -> FloconTheme.colorPalette.onPrimary.copy(alpha = 0.5f) +}