diff --git a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterScreen.kt b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterScreen.kt index cd908a03..f4c35ee3 100644 --- a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterScreen.kt +++ b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterScreen.kt @@ -27,10 +27,8 @@ import androidx.compose.material.icons.outlined.Science import androidx.compose.material.ripple.rememberRipple import androidx.compose.material3.Badge import androidx.compose.material3.BadgedBox -import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.Icon import androidx.compose.material3.IconButton -import androidx.compose.material3.Scaffold import androidx.compose.material3.Text import androidx.compose.material3.TopAppBarDefaults import androidx.compose.runtime.Composable @@ -44,82 +42,121 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.semantics.Role +import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp +import androidx.hilt.navigation.compose.hiltViewModel import androidx.lifecycle.compose.collectAsStateWithLifecycle -import androidx.lifecycle.viewmodel.compose.viewModel import com.sadellie.unitto.core.ui.R import com.sadellie.unitto.core.ui.common.AnimatedTopBarText import com.sadellie.unitto.feature.converter.components.Keyboard import com.sadellie.unitto.core.ui.common.PortraitLandscape +import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar import com.sadellie.unitto.feature.converter.components.TopScreenPart import kotlinx.coroutines.delay @Composable -internal fun ConverterScreen( +internal fun ConverterRoute( + viewModel: ConverterViewModel = hiltViewModel(), + navigateToLeftScreen: (String) -> Unit, + navigateToRightScreen: (unitFrom: String, unitTo: String, input: String) -> Unit, + navigateToTools: () -> Unit, + navigateToSettings: () -> Unit +) { + val uiState = viewModel.uiStateFlow.collectAsStateWithLifecycle() + + ConverterScreen( + uiState = uiState.value, + navigateToLeftScreen = navigateToLeftScreen, + navigateToRightScreen = navigateToRightScreen, + navigateToSettings = navigateToSettings, + navigateToTools = navigateToTools, + swapMeasurements = viewModel::swapUnits, + processInput = viewModel::processInput, + deleteDigit = viewModel::deleteDigit, + clearInput = viewModel::clearInput, + onOutputTextFieldClick = viewModel::toggleFormatTime + ) +} + +@Composable +private fun ConverterScreen( + uiState: ConverterUIState, navigateToLeftScreen: (String) -> Unit, navigateToRightScreen: (unitFrom: String, unitTo: String, input: String) -> Unit, navigateToSettings: () -> Unit, navigateToTools: () -> Unit, - viewModel: ConverterViewModel = viewModel() + swapMeasurements: () -> Unit, + processInput: (String) -> Unit, + deleteDigit: () -> Unit, + clearInput: () -> Unit, + onOutputTextFieldClick: () -> Unit ) { var launched: Boolean by rememberSaveable { mutableStateOf(false) } - val uiState = viewModel.uiStateFlow.collectAsStateWithLifecycle() - val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle() - Scaffold( - modifier = Modifier, - topBar = { - CenterAlignedTopAppBar( - modifier = Modifier, - title = { AnimatedTopBarText(launched) }, - navigationIcon = { - if (uiState.value.showTools) { - BadgedBox( - modifier = Modifier - .padding(horizontal = 16.dp) - .clickable( - onClick = navigateToTools, - interactionSource = remember { MutableInteractionSource() }, - indication = rememberRipple(false), - role = Role.Button - ), - badge = { - Badge { Text("1") } - }, - content = { - Icon( - Icons.Outlined.Science, - contentDescription = stringResource(R.string.tools_screen) - ) - } - ) - } + UnittoScreenWithTopBar( + title = { AnimatedTopBarText(launched) }, + navigationIcon = { + BadgedBox( + modifier = Modifier + .padding(horizontal = 16.dp) + .clickable( + onClick = navigateToTools, + interactionSource = remember { MutableInteractionSource() }, + indication = rememberRipple(false), + role = Role.Button + ), + badge = { + Badge { Text("1") } }, - actions = { - IconButton(onClick = navigateToSettings) { - Icon( - Icons.Outlined.MoreVert, - contentDescription = stringResource(R.string.open_settings_description) - ) - } - }, - // Makes the background of the top bar transparent, by default uses secondary color - colors = TopAppBarDefaults - .centerAlignedTopAppBarColors(containerColor = Color.Transparent) + content = { + Icon( + Icons.Outlined.Science, + contentDescription = stringResource(R.string.tools_screen) + ) + } ) }, + actions = { + IconButton(onClick = navigateToSettings) { + Icon( + Icons.Outlined.MoreVert, + contentDescription = stringResource(R.string.open_settings_description) + ) + } + }, + colors = TopAppBarDefaults + .centerAlignedTopAppBarColors(containerColor = Color.Transparent), content = { padding -> - ConverterScreenContent( + PortraitLandscape( modifier = Modifier.padding(padding), - uiState = uiState.value, - navigateToLeftScreen = navigateToLeftScreen, - navigateToRightScreen = navigateToRightScreen, - swapMeasurements = { viewModel.swapUnits() }, - processInput = { viewModel.processInput(it) }, - deleteDigit = { viewModel.deleteDigit() }, - clearInput = { viewModel.clearInput() }, - onOutputTextFieldClick = { viewModel.toggleFormatTime() }, - allowVibration = userPrefs.value.enableVibrations + content1 = { + TopScreenPart( + modifier = it, + inputValue = uiState.inputValue, + calculatedValue = uiState.calculatedValue, + outputValue = uiState.resultValue, + unitFrom = uiState.unitFrom, + unitTo = uiState.unitTo, + networkLoading = uiState.showLoading, + networkError = uiState.showError, + navigateToLeftScreen = navigateToLeftScreen, + navigateToRightScreen = navigateToRightScreen, + swapUnits = swapMeasurements, + converterMode = uiState.mode, + formatTime = uiState.formatTime, + onOutputTextFieldClick = onOutputTextFieldClick + ) + }, + content2 = { + Keyboard( + modifier = it, + addDigit = processInput, + deleteDigit = deleteDigit, + clearInput = clearInput, + converterMode = uiState.mode, + allowVibration = uiState.allowVibration + ) + } ) } ) @@ -135,48 +172,19 @@ internal fun ConverterScreen( } } +@Preview @Composable -private fun ConverterScreenContent( - modifier: Modifier, - uiState: ConverterUIState, - navigateToLeftScreen: (String) -> Unit, - navigateToRightScreen: (unitFrom: String, unitTo: String, input: String) -> Unit, - swapMeasurements: () -> Unit = {}, - processInput: (String) -> Unit = {}, - deleteDigit: () -> Unit = {}, - clearInput: () -> Unit = {}, - onOutputTextFieldClick: () -> Unit, - allowVibration: Boolean -) { - PortraitLandscape( - modifier = modifier, - content1 = { - TopScreenPart( - modifier = it, - inputValue = uiState.inputValue, - calculatedValue = uiState.calculatedValue, - outputValue = uiState.resultValue, - unitFrom = uiState.unitFrom, - unitTo = uiState.unitTo, - networkLoading = uiState.showLoading, - networkError = uiState.showError, - navigateToLeftScreen = navigateToLeftScreen, - navigateToRightScreen = navigateToRightScreen, - swapUnits = swapMeasurements, - converterMode = uiState.mode, - formatTime = uiState.formatTime, - onOutputTextFieldClick = onOutputTextFieldClick - ) - }, - content2 = { - Keyboard( - modifier = it, - addDigit = processInput, - deleteDigit = deleteDigit, - clearInput = clearInput, - converterMode = uiState.mode, - allowVibration = allowVibration - ) - } +private fun PreviewConverterScreen() { + ConverterScreen( + uiState = ConverterUIState(), + navigateToLeftScreen = {}, + navigateToRightScreen = {_, _, _ -> }, + navigateToSettings = {}, + navigateToTools = {}, + swapMeasurements = {}, + processInput = {}, + deleteDigit = {}, + clearInput = {}, + onOutputTextFieldClick = {} ) -} +} \ No newline at end of file diff --git a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterUIState.kt b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterUIState.kt index a03e1378..01db9187 100644 --- a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterUIState.kt +++ b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterUIState.kt @@ -35,6 +35,7 @@ import com.sadellie.unitto.data.model.AbstractUnit * @property mode * @property formatTime If true will format output when converting time. * @property showTools If true will show tools button in TopBar. + * @property allowVibration When true will vibrate on button clicks. */ data class ConverterUIState( val inputValue: String = KEY_0, @@ -46,7 +47,8 @@ data class ConverterUIState( val unitTo: AbstractUnit? = null, val mode: ConverterMode = ConverterMode.DEFAULT, val formatTime: Boolean = true, - val showTools: Boolean = false + val showTools: Boolean = false, + val allowVibration: Boolean = false ) enum class ConverterMode { diff --git a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterViewModel.kt b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterViewModel.kt index 7590c24e..fb7df6a7 100644 --- a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterViewModel.kt +++ b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterViewModel.kt @@ -83,7 +83,7 @@ class ConverterViewModel @Inject constructor( private val allUnitsRepository: AllUnitsRepository ) : ViewModel() { - val userPrefs = userPrefsRepository.userPreferencesFlow.stateIn( + private val _userPrefs = userPrefsRepository.userPreferencesFlow.stateIn( viewModelScope, SharingStarted.WhileSubscribed(5000), com.sadellie.unitto.data.userprefs.UserPreferences() @@ -143,7 +143,7 @@ class ConverterViewModel @Inject constructor( _showLoading, _showError, _formatTime, - userPrefs + _userPrefs ) { inputValue, unitFromValue, unitToValue, calculatedValue, resultValue, showLoadingValue, showErrorValue, formatTime, prefs -> return@combine ConverterUIState( inputValue = inputValue, @@ -159,7 +159,8 @@ class ConverterViewModel @Inject constructor( */ mode = if (_unitFrom.value is NumberBaseUnit) ConverterMode.BASE else ConverterMode.DEFAULT, formatTime = formatTime, - showTools = prefs.enableToolsExperiment + showTools = prefs.enableToolsExperiment, + allowVibration = prefs.enableVibrations ) } .stateIn( @@ -445,7 +446,7 @@ class ConverterViewModel @Inject constructor( // Now we evaluate expression in input val evaluationResult: BigDecimal = try { Expressions().eval(cleanInput) - .setScale(userPrefs.value.digitsPrecision, RoundingMode.HALF_EVEN) + .setScale(_userPrefs.value.digitsPrecision, RoundingMode.HALF_EVEN) .trimZeros() } catch (e: Exception) { when (e) { @@ -471,9 +472,9 @@ class ConverterViewModel @Inject constructor( } else { _calculated.update { evaluationResult - .setMinimumRequiredScale(userPrefs.value.digitsPrecision) + .setMinimumRequiredScale(_userPrefs.value.digitsPrecision) .trimZeros() - .toStringWith(userPrefs.value.outputFormat) + .toStringWith(_userPrefs.value.outputFormat) } } @@ -482,11 +483,11 @@ class ConverterViewModel @Inject constructor( val conversionResult: BigDecimal = unitFrom.convert( unitTo, evaluationResult, - userPrefs.value.digitsPrecision + _userPrefs.value.digitsPrecision ) // Converted - _result.update { conversionResult.toStringWith(userPrefs.value.outputFormat) } + _result.update { conversionResult.toStringWith(_userPrefs.value.outputFormat) } } private fun setInputSymbols(symbol: String, add: Boolean = true) { @@ -565,7 +566,7 @@ class ConverterViewModel @Inject constructor( private fun startObserving() { viewModelScope.launch(Dispatchers.Default) { - merge(_input, _unitFrom, _unitTo, _showLoading, userPrefs).collectLatest { + merge(_input, _unitFrom, _unitTo, _showLoading, _userPrefs).collectLatest { convertInput() } } diff --git a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/navigation/ConverterNavigation.kt b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/navigation/ConverterNavigation.kt index 2c6c5972..0ebe7ebf 100644 --- a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/navigation/ConverterNavigation.kt +++ b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/navigation/ConverterNavigation.kt @@ -20,7 +20,7 @@ package com.sadellie.unitto.feature.converter.navigation import androidx.navigation.NavGraphBuilder import androidx.navigation.compose.composable -import com.sadellie.unitto.feature.converter.ConverterScreen +import com.sadellie.unitto.feature.converter.ConverterRoute import com.sadellie.unitto.feature.converter.ConverterViewModel const val converterRoute = "converter_route" @@ -33,12 +33,12 @@ fun NavGraphBuilder.converterScreen( viewModel: ConverterViewModel ) { composable(converterRoute) { - ConverterScreen( + ConverterRoute( + viewModel = viewModel, navigateToLeftScreen = navigateToLeftScreen, navigateToRightScreen = navigateToRightScreen, navigateToSettings = navigateToSettings, - navigateToTools = navigateToTools, - viewModel = viewModel, + navigateToTools = navigateToTools ) } }