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..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 @@ -17,6 +18,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 +29,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() @@ -84,7 +94,7 @@ class Actions : Screen { } item { - footer(navigator, viewModel) + footer(navigator, viewModel, screenModel) } } @@ -114,7 +124,28 @@ class Actions : Screen { Column ( verticalArrangement = Arrangement.spacedBy(itemPadding) ) { - Text(text = "Action ${item.id}") + 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(), @@ -123,7 +154,7 @@ class Actions : 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 ) @@ -131,7 +162,7 @@ class Actions : 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 ) @@ -143,16 +174,16 @@ class Actions : 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) @@ -172,7 +203,7 @@ class Actions : Screen { contentPadding = ButtonDefaults.ButtonWithIconContentPadding, onClick = { // TODO make checks - // TODO save to database + screenModel.saveActions(inputs) navigator.pop() viewModel.procedureId = null } @@ -191,7 +222,7 @@ class Actions : Screen { val index = inputs.size val action = Action( - id = index, + id = index + 1, procedureId = procedureId, step = index, type = "", @@ -200,4 +231,13 @@ class Actions : Screen { 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) + } + } } 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..b003127 --- /dev/null +++ b/connector/composeApp/src/desktopMain/kotlin/tab/procedure/ActionsScreenModel.kt @@ -0,0 +1,47 @@ +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 + } + + 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 { + 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) },