Merge remote-tracking branch 'origin/main'

This commit is contained in:
Anthony 2024-04-27 09:55:57 +01:00
commit fe92d6a6e9
4 changed files with 128 additions and 17 deletions

View File

@ -1,10 +1,12 @@
package di package di
import InterfaceState import InterfaceState
import io.anthonyberg.connector.shared.ActionTransaction
import io.anthonyberg.connector.shared.ProcedureTransaction import io.anthonyberg.connector.shared.ProcedureTransaction
import io.anthonyberg.connector.shared.ProjectTransaction import io.anthonyberg.connector.shared.ProjectTransaction
import io.anthonyberg.connector.shared.database.DriverFactory import io.anthonyberg.connector.shared.database.DriverFactory
import org.koin.dsl.module import org.koin.dsl.module
import tab.procedure.ActionsScreenModel
import tab.procedure.ProcedureScreenModel import tab.procedure.ProcedureScreenModel
import tab.project.ProjectsScreenModel import tab.project.ProjectsScreenModel
@ -20,6 +22,10 @@ fun commonModule() = module {
single<ProcedureTransaction> { single<ProcedureTransaction> {
ProcedureTransaction(driverFactory = get<DriverFactory>()) ProcedureTransaction(driverFactory = get<DriverFactory>())
} }
single<ActionTransaction> {
ActionTransaction(driverFactory = get<DriverFactory>())
}
} }
fun viewModelModule() = module { fun viewModelModule() = module {
@ -34,4 +40,8 @@ fun viewModelModule() = module {
single<ProcedureScreenModel> { single<ProcedureScreenModel> {
ProcedureScreenModel(db = get(), interfaceState = get()) ProcedureScreenModel(db = get(), interfaceState = get())
} }
single<ActionsScreenModel> {
ActionsScreenModel(db = get(), interfaceState = get())
}
} }

View File

@ -10,6 +10,7 @@ import androidx.compose.foundation.rememberScrollbarAdapter
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.automirrored.outlined.ArrowBack import androidx.compose.material.icons.automirrored.outlined.ArrowBack
import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Delete
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateListOf
@ -17,6 +18,7 @@ import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
@ -27,18 +29,26 @@ import org.jetbrains.compose.resources.ExperimentalResourceApi
import org.jetbrains.compose.resources.painterResource import org.jetbrains.compose.resources.painterResource
import org.koin.compose.koinInject import org.koin.compose.koinInject
class Actions : Screen { class Actions (dbActions: List<Action>) : Screen {
private val columnPadding = 24.dp private val columnPadding = 24.dp
private val itemPadding = 24.dp private val itemPadding = 24.dp
private var inputs = mutableStateListOf<Action>() private var inputs = mutableStateListOf<Action>()
init {
inputs.addAll(dbActions)
}
@Composable @Composable
override fun Content() { override fun Content() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val viewModel = koinInject<InterfaceState>() val viewModel = koinInject<InterfaceState>()
val state = rememberLazyListState(0) val state = rememberLazyListState(0)
// Sends to screen model that Actions has been loaded
val screenModel = getScreenModel<ActionsScreenModel>()
screenModel.loadedActions()
// Checks if a project has been selected before viewing contents // Checks if a project has been selected before viewing contents
if (viewModel.procedureId == null) { if (viewModel.procedureId == null) {
navigator.pop() navigator.pop()
@ -84,7 +94,7 @@ class Actions : Screen {
} }
item { item {
footer(navigator, viewModel) footer(navigator, viewModel, screenModel)
} }
} }
@ -114,7 +124,28 @@ class Actions : Screen {
Column ( Column (
verticalArrangement = Arrangement.spacedBy(itemPadding) 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( Row(
Modifier.fillMaxWidth(), Modifier.fillMaxWidth(),
@ -123,7 +154,7 @@ class Actions : Screen {
OutlinedTextField( OutlinedTextField(
modifier = Modifier.fillMaxWidth(0.6f), modifier = Modifier.fillMaxWidth(0.6f),
value = item.type, 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") }, label = { Text("Dataref Name") },
singleLine = true singleLine = true
) )
@ -131,7 +162,7 @@ class Actions : Screen {
OutlinedTextField( OutlinedTextField(
value = item.goal, value = item.goal,
modifier = Modifier.fillMaxWidth(), 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") }, label = { Text("Desired State") },
singleLine = true singleLine = true
) )
@ -143,16 +174,16 @@ class Actions : Screen {
@OptIn(ExperimentalResourceApi::class) @OptIn(ExperimentalResourceApi::class)
@Composable @Composable
private fun footer(navigator: Navigator, viewModel: InterfaceState) { private fun footer(navigator: Navigator, viewModel: InterfaceState, screenModel: ActionsScreenModel) {
Row ( Row (
Modifier.fillMaxWidth(), Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween horizontalArrangement = Arrangement.SpaceBetween
) { ) {
// Add Step // Add Step
// TODO add menu to choose multiple types of items
FilledTonalButton( FilledTonalButton(
contentPadding = ButtonDefaults.ButtonWithIconContentPadding, contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
onClick = { onClick = {
// TODO make this a proper data array for each item in checklist
val procedureId = viewModel.procedureId val procedureId = viewModel.procedureId
if (procedureId != null) { if (procedureId != null) {
inputs += createEmptyAction(procedureId) inputs += createEmptyAction(procedureId)
@ -172,7 +203,7 @@ class Actions : Screen {
contentPadding = ButtonDefaults.ButtonWithIconContentPadding, contentPadding = ButtonDefaults.ButtonWithIconContentPadding,
onClick = { onClick = {
// TODO make checks // TODO make checks
// TODO save to database screenModel.saveActions(inputs)
navigator.pop() navigator.pop()
viewModel.procedureId = null viewModel.procedureId = null
} }
@ -191,7 +222,7 @@ class Actions : Screen {
val index = inputs.size val index = inputs.size
val action = Action( val action = Action(
id = index, id = index + 1,
procedureId = procedureId, procedureId = procedureId,
step = index, step = index,
type = "", type = "",
@ -200,4 +231,13 @@ class Actions : Screen {
return action 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)
}
}
} }

View File

@ -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>(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<Action>) {
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<Action>) : ActionsState()
}

View File

@ -16,14 +16,17 @@ import androidx.compose.material.icons.automirrored.filled.KeyboardArrowRight
import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Add
import androidx.compose.material3.* import androidx.compose.material3.*
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.screen.Screen import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.koin.getScreenModel
import cafe.adriel.voyager.navigator.LocalNavigator import cafe.adriel.voyager.navigator.LocalNavigator
import cafe.adriel.voyager.navigator.Navigator
import cafe.adriel.voyager.navigator.currentOrThrow import cafe.adriel.voyager.navigator.currentOrThrow
import io.anthonyberg.connector.shared.entity.Procedure import io.anthonyberg.connector.shared.entity.Procedure
import org.koin.compose.koinInject import org.koin.compose.koinInject
import tab.LoadingScreen
class ListProcedures ( class ListProcedures (
private val procedures: List<Procedure> private val procedures: List<Procedure>
@ -32,8 +35,20 @@ class ListProcedures (
override fun Content() { override fun Content() {
val navigator = LocalNavigator.currentOrThrow val navigator = LocalNavigator.currentOrThrow
val viewModel = koinInject<InterfaceState>() val viewModel = koinInject<InterfaceState>()
val screenModel = getScreenModel<ActionsScreenModel>()
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 ( Scaffold (
floatingActionButton = { floatingActionButton = {
@ -53,9 +68,9 @@ class ListProcedures (
Box( Box(
modifier = Modifier.fillMaxWidth(0.7F), modifier = Modifier.fillMaxWidth(0.7F),
) { ) {
LazyColumn(state = state) { LazyColumn(state = lazyState) {
items(procedures) { procedure -> items(procedures) { procedure ->
procedureItem(procedure, viewModel, navigator) procedureItem(procedure, viewModel, screenModel)
} }
} }
VerticalScrollbar( VerticalScrollbar(
@ -63,7 +78,7 @@ class ListProcedures (
.align(Alignment.CenterEnd) .align(Alignment.CenterEnd)
.fillMaxHeight(), .fillMaxHeight(),
adapter = rememberScrollbarAdapter( adapter = rememberScrollbarAdapter(
scrollState = state, scrollState = lazyState,
), ),
) )
} }
@ -72,15 +87,14 @@ class ListProcedures (
} }
@Composable @Composable
private fun procedureItem(procedure: Procedure, viewModel: InterfaceState, navigator: Navigator) { private fun procedureItem(procedure: Procedure, viewModel: InterfaceState, screenModel: ActionsScreenModel) {
ListItem( ListItem(
modifier = Modifier modifier = Modifier
.clickable( .clickable(
enabled = true, enabled = true,
onClick = { onClick = {
viewModel.procedureId = procedure.id viewModel.procedureId = procedure.id
screenModel.getActions()
navigator.push(Actions())
} }
), ),
overlineContent = { Text(procedure.type) }, overlineContent = { Text(procedure.type) },