From bf96e08a8680729d70a802d8917884cebe8b8710 Mon Sep 17 00:00:00 2001 From: Erik Eelde Date: Mon, 16 Sep 2024 19:23:05 +0200 Subject: [PATCH] Start using typesafe navigation in BooleanConfiguration --- .../booleanconfiguration/BooleanValueView.kt | 126 ++++++++++-------- .../BooleanValueViewModel.kt | 27 ++-- .../stringconfiguration/StringValueView.kt | 10 +- .../java/se/eelde/toggles/MainActivity.kt | 15 +-- 4 files changed, 100 insertions(+), 78 deletions(-) diff --git a/modules/booleanconfiguration/src/main/java/se/eelde/toggles/booleanconfiguration/BooleanValueView.kt b/modules/booleanconfiguration/src/main/java/se/eelde/toggles/booleanconfiguration/BooleanValueView.kt index a94a76e9..6f92aa4f 100644 --- a/modules/booleanconfiguration/src/main/java/se/eelde/toggles/booleanconfiguration/BooleanValueView.kt +++ b/modules/booleanconfiguration/src/main/java/se/eelde/toggles/booleanconfiguration/BooleanValueView.kt @@ -1,5 +1,6 @@ package se.eelde.toggles.booleanconfiguration +import android.annotation.SuppressLint import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Row @@ -26,22 +27,53 @@ import androidx.compose.ui.unit.dp import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle import kotlinx.coroutines.launch +import se.eelde.toggles.routes.BooleanConfiguration +import se.eelde.toggles.routes.StringConfiguration -@OptIn(ExperimentalMaterial3Api::class) +@SuppressLint("ComposeViewModelInjection") @Composable fun BooleanValueView( - modifier: Modifier = Modifier, - viewModel: FragmentBooleanValueViewModel = hiltViewModel(), + booleanConfiguration: BooleanConfiguration, back: () -> Unit, ) { + val viewModel: BooleanValueViewModel = + hiltViewModel( + creationCallback = { factory -> + factory.create(booleanConfiguration) + } + ) + val viewState by viewModel.state.collectAsStateWithLifecycle() + + val scope = rememberCoroutineScope() + + BooleanValueView( + viewState = viewState, + save = { scope.launch { viewModel.saveClick() } }, + revert = { scope.launch { viewModel.revertClick() } }, + checkedChanged = { viewModel.checkedChanged(it) }, + popBackStack = back, + ) +} + +@OptIn(ExperimentalMaterial3Api::class) +@Composable +@Suppress("LongParameterList") +fun BooleanValueView( + viewState: ViewState, + save: () -> Unit, + revert: () -> Unit, + checkedChanged: (Boolean) -> Unit, + popBackStack: () -> Unit, + modifier: Modifier = Modifier, +) { Scaffold( topBar = { TopAppBar( title = { Text("Boolean configuration") }, navigationIcon = { - IconButton(onClick = { back() }) { + IconButton(onClick = { popBackStack() }) { Icon( imageVector = Icons.Filled.ArrowBack, contentDescription = null @@ -51,63 +83,47 @@ fun BooleanValueView( ) }, ) { paddingValues -> - BooleanValueView( - uiState = viewState, - popBackStack = { back() }, - revert = { viewModel.revertClick() }, - save = { viewModel.saveClick() }, - setBooleanValue = { viewModel.checkedChanged(it) }, - modifier = modifier.padding(paddingValues), - ) - } -} - -@Composable -@Suppress("LongParameterList") -internal fun BooleanValueView( - uiState: ViewState, - popBackStack: () -> Unit, - setBooleanValue: (Boolean) -> Unit, - revert: suspend () -> Unit, - save: suspend () -> Unit, - modifier: Modifier = Modifier, -) { - val scope = rememberCoroutineScope() - - Surface(modifier = modifier.padding(16.dp)) { - Column { - Text( - modifier = Modifier.padding(8.dp), - style = MaterialTheme.typography.headlineMedium, - text = uiState.title ?: "" - ) + val scope = rememberCoroutineScope() + Surface(modifier = modifier + .padding(paddingValues) + .padding(16.dp)) { + Column { + Text( + modifier = Modifier.padding(8.dp), + style = MaterialTheme.typography.headlineMedium, + text = viewState.title ?: "" + ) - Switch( - modifier = Modifier - .padding(8.dp) - .align(End), - checked = uiState.checked ?: false, - onCheckedChange = { setBooleanValue(it) } - ) - Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { - Button(modifier = Modifier.padding(8.dp), onClick = { - scope.launch { - revert() - popBackStack() + Switch( + modifier = Modifier + .padding(8.dp) + .align(End), + checked = viewState.checked ?: false, + onCheckedChange = { + checkedChanged(it) + } + ) + Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { + Button(modifier = Modifier.padding(8.dp), onClick = { + scope.launch { + revert() + popBackStack() + } + }) { + Text("Revert") } - }) { - Text("Revert") - } - Button(modifier = Modifier.padding(8.dp), onClick = { - scope.launch { - save() - popBackStack() + Button(modifier = Modifier.padding(8.dp), onClick = { + scope.launch { + save() + popBackStack() + } + }) { + Text("Save") } - }) { - Text("Save") } } } } } + diff --git a/modules/booleanconfiguration/src/main/java/se/eelde/toggles/booleanconfiguration/BooleanValueViewModel.kt b/modules/booleanconfiguration/src/main/java/se/eelde/toggles/booleanconfiguration/BooleanValueViewModel.kt index 9389741d..a3e2a9e3 100644 --- a/modules/booleanconfiguration/src/main/java/se/eelde/toggles/booleanconfiguration/BooleanValueViewModel.kt +++ b/modules/booleanconfiguration/src/main/java/se/eelde/toggles/booleanconfiguration/BooleanValueViewModel.kt @@ -1,9 +1,11 @@ package se.eelde.toggles.booleanconfiguration import android.app.Application -import androidx.lifecycle.SavedStateHandle import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope +import dagger.assisted.Assisted +import dagger.assisted.AssistedFactory +import dagger.assisted.AssistedInject import dagger.hilt.android.lifecycle.HiltViewModel import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Job @@ -16,8 +18,8 @@ import se.eelde.toggles.database.WrenchConfigurationValue import se.eelde.toggles.database.dao.application.TogglesConfigurationDao import se.eelde.toggles.database.dao.application.TogglesConfigurationValueDao import se.eelde.toggles.provider.notifyUpdate +import se.eelde.toggles.routes.BooleanConfiguration import java.util.Date -import javax.inject.Inject data class ViewState( val title: String? = null, @@ -34,22 +36,29 @@ private sealed class PartialViewState { object Reverting : PartialViewState() } -@HiltViewModel -class FragmentBooleanValueViewModel @Inject internal constructor( - savedStateHandle: SavedStateHandle, +@HiltViewModel(assistedFactory = BooleanValueViewModel.Factory::class) +class BooleanValueViewModel @AssistedInject internal constructor( private val application: Application, private val configurationDao: TogglesConfigurationDao, - private val configurationValueDao: TogglesConfigurationValueDao + private val configurationValueDao: TogglesConfigurationValueDao, + @Assisted booleanConfiguration: BooleanConfiguration ) : ViewModel() { + @AssistedFactory + interface Factory { + fun create( + booleanConfiguration: BooleanConfiguration + ): BooleanValueViewModel + } + + private val configurationId = booleanConfiguration.configurationId + private val scopeId = booleanConfiguration.scopeId + private val _state = MutableStateFlow(reduce(ViewState(), PartialViewState.Empty)) val state: StateFlow get() = _state - private val configurationId: Long = savedStateHandle.get("configurationId")!! - private val scopeId: Long = savedStateHandle.get("scopeId")!! - private var selectedConfigurationValue: WrenchConfigurationValue? = null init { diff --git a/modules/stringconfiguration/src/main/java/se/eelde/toggles/stringconfiguration/StringValueView.kt b/modules/stringconfiguration/src/main/java/se/eelde/toggles/stringconfiguration/StringValueView.kt index 16dc6e94..c735d796 100644 --- a/modules/stringconfiguration/src/main/java/se/eelde/toggles/stringconfiguration/StringValueView.kt +++ b/modules/stringconfiguration/src/main/java/se/eelde/toggles/stringconfiguration/StringValueView.kt @@ -35,7 +35,7 @@ import se.eelde.toggles.routes.StringConfiguration private fun StringValueViewPreview() { TogglesTheme { StringValueView( - state = ViewState(title = "The title", stringValue = "This is value"), + viewState = ViewState(title = "The title", stringValue = "This is value"), setStringValue = {}, save = {}, revert = {}, @@ -62,7 +62,7 @@ fun StringValueView( val scope = rememberCoroutineScope() StringValueView( - state = viewState, + viewState = viewState, setStringValue = { viewModel.setStringValue(it) }, save = { scope.launch { viewModel.saveClick() } }, revert = { viewModel.revertClick() }, @@ -74,7 +74,7 @@ fun StringValueView( @OptIn(ExperimentalMaterial3Api::class) @Suppress("LongParameterList") internal fun StringValueView( - state: ViewState, + viewState: ViewState, setStringValue: (String) -> Unit, save: suspend () -> Unit, revert: suspend () -> Unit, @@ -104,12 +104,12 @@ internal fun StringValueView( Text( modifier = Modifier.padding(8.dp), style = MaterialTheme.typography.headlineMedium, - text = state.title ?: "" + text = viewState.title ?: "" ) OutlinedTextField( modifier = Modifier .fillMaxWidth(), - value = state.stringValue ?: "", + value = viewState.stringValue ?: "", onValueChange = { setStringValue(it) }, ) Row(modifier = Modifier.fillMaxWidth(), horizontalArrangement = Arrangement.End) { diff --git a/toggles-app/src/main/java/se/eelde/toggles/MainActivity.kt b/toggles-app/src/main/java/se/eelde/toggles/MainActivity.kt index b74b9afb..bf3c679b 100644 --- a/toggles-app/src/main/java/se/eelde/toggles/MainActivity.kt +++ b/toggles-app/src/main/java/se/eelde/toggles/MainActivity.kt @@ -34,6 +34,7 @@ import se.eelde.toggles.help.HelpView import se.eelde.toggles.integerconfiguration.IntegerValueView import se.eelde.toggles.oss.OssView import se.eelde.toggles.routes.Applications +import se.eelde.toggles.routes.BooleanConfiguration import se.eelde.toggles.routes.Configurations import se.eelde.toggles.routes.Help import se.eelde.toggles.routes.Oss @@ -63,7 +64,7 @@ fun Navigation( ) configurationsNavigations( navigateToBooleanConfiguration = { scopeId: Long, configurationId: Long -> - navController.navigate("configuration/$configurationId/$scopeId/boolean") + navController.navigate(BooleanConfiguration(configurationId, scopeId)) }, navigateToIntegerConfiguration = { scopeId: Long, configurationId: Long -> navController.navigate("configuration/$configurationId/$scopeId/integer") @@ -83,14 +84,10 @@ fun Navigation( navController.navigate("scopes/$applicationId") } ) { navController.popBackStack() } - composable( - "configuration/{configurationId}/{scopeId}/boolean", - arguments = listOf( - navArgument("configurationId") { type = NavType.LongType }, - navArgument("scopeId") { type = NavType.LongType } - ) - ) { - BooleanValueView { navController.popBackStack() } + composable { backStackEntry -> + val booleanConfiguration: BooleanConfiguration = backStackEntry.toRoute() + + BooleanValueView(booleanConfiguration) { navController.popBackStack() } } composable( "scopes/{applicationId}",