From 07b3cfdb26f245d8b39171dc56d43aefbcf45cdd Mon Sep 17 00:00:00 2001 From: Anthony Berg Date: Fri, 26 Apr 2024 18:15:09 +0100 Subject: [PATCH 1/3] feat(connector): add ActionsScreenModel to get actions from database --- .../src/desktopMain/kotlin/di/CommonModule.kt | 10 ++++++ .../kotlin/tab/procedure/Actions.kt | 11 +++++- .../tab/procedure/ActionsScreenModel.kt | 35 +++++++++++++++++++ .../kotlin/tab/procedure/ListProcedures.kt | 30 +++++++++++----- 4 files changed, 77 insertions(+), 9 deletions(-) create mode 100644 connector/composeApp/src/desktopMain/kotlin/tab/procedure/ActionsScreenModel.kt diff --git a/connector/composeApp/src/desktopMain/kotlin/di/CommonModule.kt b/connector/composeApp/src/desktopMain/kotlin/di/CommonModule.kt index efc91d4..b57f2d8 100644 --- a/connector/composeApp/src/desktopMain/kotlin/di/CommonModule.kt +++ b/connector/composeApp/src/desktopMain/kotlin/di/CommonModule.kt @@ -1,10 +1,12 @@ package di import InterfaceState +import io.anthonyberg.connector.shared.ActionTransaction import io.anthonyberg.connector.shared.ProcedureTransaction import io.anthonyberg.connector.shared.ProjectTransaction import io.anthonyberg.connector.shared.database.DriverFactory import org.koin.dsl.module +import tab.procedure.ActionsScreenModel import tab.procedure.ProcedureScreenModel import tab.project.ProjectsScreenModel @@ -20,6 +22,10 @@ fun commonModule() = module { single { ProcedureTransaction(driverFactory = get()) } + + single { + ActionTransaction(driverFactory = get()) + } } fun viewModelModule() = module { @@ -34,4 +40,8 @@ fun viewModelModule() = module { single { ProcedureScreenModel(db = get(), interfaceState = get()) } + + single { + ActionsScreenModel(db = get(), interfaceState = get()) + } } diff --git a/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt index b86297a..d15669f 100644 --- a/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt +++ b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt @@ -17,6 +17,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.unit.dp import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.koin.getScreenModel import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow @@ -27,18 +28,26 @@ import org.jetbrains.compose.resources.ExperimentalResourceApi import org.jetbrains.compose.resources.painterResource import org.koin.compose.koinInject -class Actions : Screen { +class Actions (dbActions: List) : Screen { private val columnPadding = 24.dp private val itemPadding = 24.dp private var inputs = mutableStateListOf() + init { + inputs.addAll(dbActions) + } + @Composable override fun Content() { val navigator = LocalNavigator.currentOrThrow val viewModel = koinInject() val state = rememberLazyListState(0) + // Sends to screen model that Actions has been loaded + val screenModel = getScreenModel() + screenModel.loadedActions() + // Checks if a project has been selected before viewing contents if (viewModel.procedureId == null) { navigator.pop() diff --git a/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ActionsScreenModel.kt b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ActionsScreenModel.kt new file mode 100644 index 0000000..bfb4ab2 --- /dev/null +++ b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ActionsScreenModel.kt @@ -0,0 +1,35 @@ +package tab.procedure + +import InterfaceState +import cafe.adriel.voyager.core.model.StateScreenModel +import cafe.adriel.voyager.core.model.screenModelScope +import io.anthonyberg.connector.shared.ActionTransaction +import io.anthonyberg.connector.shared.entity.Action +import kotlinx.coroutines.launch + +class ActionsScreenModel ( + private val db: ActionTransaction, + private val interfaceState: InterfaceState +) : StateScreenModel(ActionsState.Idle) { + fun getActions() { + screenModelScope.launch { + mutableState.value = ActionsState.Loading + + val procedureId = interfaceState.procedureId ?: return@launch + + val actions = db.getActions(procedureId = procedureId) + + mutableState.value = ActionsState.Result(actions = actions) + } + } + + fun loadedActions() { + mutableState.value = ActionsState.Idle + } +} + +sealed class ActionsState { + data object Idle : ActionsState() + data object Loading : ActionsState() + data class Result(val actions: List) : ActionsState() +} diff --git a/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ListProcedures.kt b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ListProcedures.kt index fb36fc6..f32ffeb 100644 --- a/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ListProcedures.kt +++ b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ListProcedures.kt @@ -16,14 +16,17 @@ import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight import androidx.compose.material.icons.outlined.Add import androidx.compose.material3.* import androidx.compose.runtime.Composable +import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.getValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import cafe.adriel.voyager.core.screen.Screen +import cafe.adriel.voyager.koin.getScreenModel import cafe.adriel.voyager.navigator.LocalNavigator -import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.currentOrThrow import io.anthonyberg.connector.shared.entity.Procedure import org.koin.compose.koinInject +import tab.LoadingScreen class ListProcedures ( private val procedures: List @@ -32,8 +35,20 @@ class ListProcedures ( override fun Content() { val navigator = LocalNavigator.currentOrThrow val viewModel = koinInject() + val screenModel = getScreenModel() + val state by screenModel.state.collectAsState() - val state = rememberLazyListState(0) + when (val s = state) { + is ActionsState.Idle -> { } + is ActionsState.Loading -> navigator.push(LoadingScreen("Actions")) + is ActionsState.Result -> { + navigator.pop() + navigator.push(Actions(s.actions)) + } + } + + + val lazyState = rememberLazyListState(0) Scaffold ( floatingActionButton = { @@ -53,9 +68,9 @@ class ListProcedures ( Box( modifier = Modifier.fillMaxWidth(0.7F), ) { - LazyColumn(state = state) { + LazyColumn(state = lazyState) { items(procedures) { procedure -> - procedureItem(procedure, viewModel, navigator) + procedureItem(procedure, viewModel, screenModel) } } VerticalScrollbar( @@ -63,7 +78,7 @@ class ListProcedures ( .align(Alignment.CenterEnd) .fillMaxHeight(), adapter = rememberScrollbarAdapter( - scrollState = state, + scrollState = lazyState, ), ) } @@ -72,15 +87,14 @@ class ListProcedures ( } @Composable - private fun procedureItem(procedure: Procedure, viewModel: InterfaceState, navigator: Navigator) { + private fun procedureItem(procedure: Procedure, viewModel: InterfaceState, screenModel: ActionsScreenModel) { ListItem( modifier = Modifier .clickable( enabled = true, onClick = { viewModel.procedureId = procedure.id - - navigator.push(Actions()) + screenModel.getActions() } ), overlineContent = { Text(procedure.type) }, From f498c8c0c0bed2f74d49d43a11762f670aace0cc Mon Sep 17 00:00:00 2001 From: Anthony Berg Date: Sat, 27 Apr 2024 00:18:02 +0100 Subject: [PATCH 2/3] feat(connector): implement save for Action --- .../kotlin/tab/procedure/Actions.kt | 18 +++++++++--------- .../kotlin/tab/procedure/ActionsScreenModel.kt | 12 ++++++++++++ 2 files changed, 21 insertions(+), 9 deletions(-) diff --git a/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt index d15669f..1cb5bf2 100644 --- a/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt +++ b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt @@ -93,7 +93,7 @@ class Actions (dbActions: List) : Screen { } item { - footer(navigator, viewModel) + footer(navigator, viewModel, screenModel) } } @@ -123,7 +123,7 @@ class Actions (dbActions: List) : Screen { Column ( verticalArrangement = Arrangement.spacedBy(itemPadding) ) { - Text(text = "Action ${item.id}") + Text(text = "Action ${item.step + 1}") Row( Modifier.fillMaxWidth(), @@ -132,7 +132,7 @@ class Actions (dbActions: List) : Screen { OutlinedTextField( modifier = Modifier.fillMaxWidth(0.6f), value = item.type, - onValueChange = { inputs[item.id] = inputs[item.id].copy(type = it) }, + onValueChange = { inputs[item.id - 1] = inputs[item.id - 1].copy(type = it) }, label = { Text("Dataref Name") }, singleLine = true ) @@ -140,7 +140,7 @@ class Actions (dbActions: List) : Screen { OutlinedTextField( value = item.goal, modifier = Modifier.fillMaxWidth(), - onValueChange = { inputs[item.id] = inputs[item.id].copy(goal = it) }, + onValueChange = { inputs[item.id - 1] = inputs[item.id - 1].copy(goal = it) }, label = { Text("Desired State") }, singleLine = true ) @@ -152,16 +152,16 @@ class Actions (dbActions: List) : Screen { @OptIn(ExperimentalResourceApi::class) @Composable - private fun footer(navigator: Navigator, viewModel: InterfaceState) { + private fun footer(navigator: Navigator, viewModel: InterfaceState, screenModel: ActionsScreenModel) { Row ( Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.SpaceBetween ) { // Add Step + // TODO add menu to choose multiple types of items FilledTonalButton( contentPadding = ButtonDefaults.ButtonWithIconContentPadding, onClick = { - // TODO make this a proper data array for each item in checklist val procedureId = viewModel.procedureId if (procedureId != null) { inputs += createEmptyAction(procedureId) @@ -181,7 +181,7 @@ class Actions (dbActions: List) : Screen { contentPadding = ButtonDefaults.ButtonWithIconContentPadding, onClick = { // TODO make checks - // TODO save to database + screenModel.saveActions(inputs) navigator.pop() viewModel.procedureId = null } @@ -197,12 +197,12 @@ class Actions (dbActions: List) : Screen { } private fun createEmptyAction(procedureId: Int): Action { - val index = inputs.size + val index = inputs.size + 1 val action = Action( id = index, procedureId = procedureId, - step = index, + step = index - 1, type = "", goal = "" ) diff --git a/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ActionsScreenModel.kt b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ActionsScreenModel.kt index bfb4ab2..b003127 100644 --- a/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ActionsScreenModel.kt +++ b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ActionsScreenModel.kt @@ -26,6 +26,18 @@ class ActionsScreenModel ( fun loadedActions() { mutableState.value = ActionsState.Idle } + + fun saveActions(actions: List) { + screenModelScope.launch { + val procedureId = interfaceState.procedureId ?: return@launch + + // Delete all previous items of the actions from the database + db.deleteActionByProcedure(procedureId = procedureId) + + // Add the new actions to the database + db.createActionFromList(actions = actions) + } + } } sealed class ActionsState { From 1f53de5af5ac2252e36eba5d06380c8eab547a0c Mon Sep 17 00:00:00 2001 From: Anthony Berg Date: Sat, 27 Apr 2024 00:42:08 +0100 Subject: [PATCH 3/3] feat(connector): delete an action in action screen --- .../kotlin/tab/procedure/Actions.kt | 39 +++++++++++++++++-- 1 file changed, 35 insertions(+), 4 deletions(-) diff --git a/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt index 1cb5bf2..dc0d6d7 100644 --- a/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt +++ b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/Actions.kt @@ -10,6 +10,7 @@ import androidx.compose.foundation.rememberScrollbarAdapter import androidx.compose.material.icons.Icons import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.outlined.Add +import androidx.compose.material.icons.outlined.Delete import androidx.compose.material3.* import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateListOf @@ -123,7 +124,28 @@ class Actions (dbActions: List) : Screen { Column ( verticalArrangement = Arrangement.spacedBy(itemPadding) ) { - Text(text = "Action ${item.step + 1}") + Row( + Modifier.fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + Text(text = "Action ${item.step + 1}") + + IconButton( + modifier = Modifier.size(24.dp), + onClick = { + inputs.removeAt(item.step) + updateStepOrder() + } + ) { + Icon( + Icons.Outlined.Delete, + contentDescription = "Delete Action ${item.step + 1}", + modifier = Modifier.size(20.dp), + tint = MaterialTheme.colorScheme.secondary + ) + } + } Row( Modifier.fillMaxWidth(), @@ -197,16 +219,25 @@ class Actions (dbActions: List) : Screen { } private fun createEmptyAction(procedureId: Int): Action { - val index = inputs.size + 1 + val index = inputs.size val action = Action( - id = index, + id = index + 1, procedureId = procedureId, - step = index - 1, + step = index, type = "", goal = "" ) return action } + + /** + * Updates Action.step in the input list to be the same as the index in the list + */ + private fun updateStepOrder() { + for ((index, action) in inputs.withIndex()) { + inputs[index] = action.copy(step = index) + } + } }