Refactor ConverterScreen

This commit is contained in:
Sad Ellie 2023-02-21 20:38:52 +04:00
parent 185f8452a8
commit 36befca5da
4 changed files with 124 additions and 113 deletions

View File

@ -27,10 +27,8 @@ import androidx.compose.material.icons.outlined.Science
import androidx.compose.material.ripple.rememberRipple import androidx.compose.material.ripple.rememberRipple
import androidx.compose.material3.Badge import androidx.compose.material3.Badge
import androidx.compose.material3.BadgedBox import androidx.compose.material3.BadgedBox
import androidx.compose.material3.CenterAlignedTopAppBar
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton import androidx.compose.material3.IconButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.TopAppBarDefaults import androidx.compose.material3.TopAppBarDefaults
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
@ -44,36 +42,60 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.semantics.Role import androidx.compose.ui.semantics.Role
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import androidx.lifecycle.viewmodel.compose.viewModel
import com.sadellie.unitto.core.ui.R import com.sadellie.unitto.core.ui.R
import com.sadellie.unitto.core.ui.common.AnimatedTopBarText import com.sadellie.unitto.core.ui.common.AnimatedTopBarText
import com.sadellie.unitto.feature.converter.components.Keyboard import com.sadellie.unitto.feature.converter.components.Keyboard
import com.sadellie.unitto.core.ui.common.PortraitLandscape 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 com.sadellie.unitto.feature.converter.components.TopScreenPart
import kotlinx.coroutines.delay import kotlinx.coroutines.delay
@Composable @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, navigateToLeftScreen: (String) -> Unit,
navigateToRightScreen: (unitFrom: String, unitTo: String, input: String) -> Unit, navigateToRightScreen: (unitFrom: String, unitTo: String, input: String) -> Unit,
navigateToSettings: () -> Unit, navigateToSettings: () -> Unit,
navigateToTools: () -> Unit, navigateToTools: () -> Unit,
viewModel: ConverterViewModel = viewModel() swapMeasurements: () -> Unit,
processInput: (String) -> Unit,
deleteDigit: () -> Unit,
clearInput: () -> Unit,
onOutputTextFieldClick: () -> Unit
) { ) {
var launched: Boolean by rememberSaveable { mutableStateOf(false) } var launched: Boolean by rememberSaveable { mutableStateOf(false) }
val uiState = viewModel.uiStateFlow.collectAsStateWithLifecycle()
val userPrefs = viewModel.userPrefs.collectAsStateWithLifecycle()
Scaffold( UnittoScreenWithTopBar(
modifier = Modifier,
topBar = {
CenterAlignedTopAppBar(
modifier = Modifier,
title = { AnimatedTopBarText(launched) }, title = { AnimatedTopBarText(launched) },
navigationIcon = { navigationIcon = {
if (uiState.value.showTools) {
BadgedBox( BadgedBox(
modifier = Modifier modifier = Modifier
.padding(horizontal = 16.dp) .padding(horizontal = 16.dp)
@ -93,7 +115,6 @@ internal fun ConverterScreen(
) )
} }
) )
}
}, },
actions = { actions = {
IconButton(onClick = navigateToSettings) { IconButton(onClick = navigateToSettings) {
@ -103,53 +124,11 @@ internal fun ConverterScreen(
) )
} }
}, },
// Makes the background of the top bar transparent, by default uses secondary color
colors = TopAppBarDefaults colors = TopAppBarDefaults
.centerAlignedTopAppBarColors(containerColor = Color.Transparent) .centerAlignedTopAppBarColors(containerColor = Color.Transparent),
)
},
content = { padding -> content = { padding ->
ConverterScreenContent(
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
)
}
)
LaunchedEffect(Unit) {
/**
* 1.5 seconds is enough for user to see "Hello" in app bar title. Also, since we are using
* Unit as key, "Hello" will be switched to app name only when composable is not getting
* recomposed for 1.5 seconds.
*/
delay(1500)
launched = true
}
}
@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( PortraitLandscape(
modifier = modifier, modifier = Modifier.padding(padding),
content1 = { content1 = {
TopScreenPart( TopScreenPart(
modifier = it, modifier = it,
@ -175,8 +154,37 @@ private fun ConverterScreenContent(
deleteDigit = deleteDigit, deleteDigit = deleteDigit,
clearInput = clearInput, clearInput = clearInput,
converterMode = uiState.mode, converterMode = uiState.mode,
allowVibration = allowVibration allowVibration = uiState.allowVibration
) )
} }
) )
}
)
LaunchedEffect(Unit) {
/**
* 1.5 seconds is enough for user to see "Hello" in app bar title. Also, since we are using
* Unit as key, "Hello" will be switched to app name only when composable is not getting
* recomposed for 1.5 seconds.
*/
delay(1500)
launched = true
}
}
@Preview
@Composable
private fun PreviewConverterScreen() {
ConverterScreen(
uiState = ConverterUIState(),
navigateToLeftScreen = {},
navigateToRightScreen = {_, _, _ -> },
navigateToSettings = {},
navigateToTools = {},
swapMeasurements = {},
processInput = {},
deleteDigit = {},
clearInput = {},
onOutputTextFieldClick = {}
)
} }

View File

@ -35,6 +35,7 @@ import com.sadellie.unitto.data.model.AbstractUnit
* @property mode * @property mode
* @property formatTime If true will format output when converting time. * @property formatTime If true will format output when converting time.
* @property showTools If true will show tools button in TopBar. * @property showTools If true will show tools button in TopBar.
* @property allowVibration When true will vibrate on button clicks.
*/ */
data class ConverterUIState( data class ConverterUIState(
val inputValue: String = KEY_0, val inputValue: String = KEY_0,
@ -46,7 +47,8 @@ data class ConverterUIState(
val unitTo: AbstractUnit? = null, val unitTo: AbstractUnit? = null,
val mode: ConverterMode = ConverterMode.DEFAULT, val mode: ConverterMode = ConverterMode.DEFAULT,
val formatTime: Boolean = true, val formatTime: Boolean = true,
val showTools: Boolean = false val showTools: Boolean = false,
val allowVibration: Boolean = false
) )
enum class ConverterMode { enum class ConverterMode {

View File

@ -83,7 +83,7 @@ class ConverterViewModel @Inject constructor(
private val allUnitsRepository: AllUnitsRepository private val allUnitsRepository: AllUnitsRepository
) : ViewModel() { ) : ViewModel() {
val userPrefs = userPrefsRepository.userPreferencesFlow.stateIn( private val _userPrefs = userPrefsRepository.userPreferencesFlow.stateIn(
viewModelScope, viewModelScope,
SharingStarted.WhileSubscribed(5000), SharingStarted.WhileSubscribed(5000),
com.sadellie.unitto.data.userprefs.UserPreferences() com.sadellie.unitto.data.userprefs.UserPreferences()
@ -143,7 +143,7 @@ class ConverterViewModel @Inject constructor(
_showLoading, _showLoading,
_showError, _showError,
_formatTime, _formatTime,
userPrefs _userPrefs
) { inputValue, unitFromValue, unitToValue, calculatedValue, resultValue, showLoadingValue, showErrorValue, formatTime, prefs -> ) { inputValue, unitFromValue, unitToValue, calculatedValue, resultValue, showLoadingValue, showErrorValue, formatTime, prefs ->
return@combine ConverterUIState( return@combine ConverterUIState(
inputValue = inputValue, inputValue = inputValue,
@ -159,7 +159,8 @@ class ConverterViewModel @Inject constructor(
*/ */
mode = if (_unitFrom.value is NumberBaseUnit) ConverterMode.BASE else ConverterMode.DEFAULT, mode = if (_unitFrom.value is NumberBaseUnit) ConverterMode.BASE else ConverterMode.DEFAULT,
formatTime = formatTime, formatTime = formatTime,
showTools = prefs.enableToolsExperiment showTools = prefs.enableToolsExperiment,
allowVibration = prefs.enableVibrations
) )
} }
.stateIn( .stateIn(
@ -445,7 +446,7 @@ class ConverterViewModel @Inject constructor(
// Now we evaluate expression in input // Now we evaluate expression in input
val evaluationResult: BigDecimal = try { val evaluationResult: BigDecimal = try {
Expressions().eval(cleanInput) Expressions().eval(cleanInput)
.setScale(userPrefs.value.digitsPrecision, RoundingMode.HALF_EVEN) .setScale(_userPrefs.value.digitsPrecision, RoundingMode.HALF_EVEN)
.trimZeros() .trimZeros()
} catch (e: Exception) { } catch (e: Exception) {
when (e) { when (e) {
@ -471,9 +472,9 @@ class ConverterViewModel @Inject constructor(
} else { } else {
_calculated.update { _calculated.update {
evaluationResult evaluationResult
.setMinimumRequiredScale(userPrefs.value.digitsPrecision) .setMinimumRequiredScale(_userPrefs.value.digitsPrecision)
.trimZeros() .trimZeros()
.toStringWith(userPrefs.value.outputFormat) .toStringWith(_userPrefs.value.outputFormat)
} }
} }
@ -482,11 +483,11 @@ class ConverterViewModel @Inject constructor(
val conversionResult: BigDecimal = unitFrom.convert( val conversionResult: BigDecimal = unitFrom.convert(
unitTo, unitTo,
evaluationResult, evaluationResult,
userPrefs.value.digitsPrecision _userPrefs.value.digitsPrecision
) )
// Converted // Converted
_result.update { conversionResult.toStringWith(userPrefs.value.outputFormat) } _result.update { conversionResult.toStringWith(_userPrefs.value.outputFormat) }
} }
private fun setInputSymbols(symbol: String, add: Boolean = true) { private fun setInputSymbols(symbol: String, add: Boolean = true) {
@ -565,7 +566,7 @@ class ConverterViewModel @Inject constructor(
private fun startObserving() { private fun startObserving() {
viewModelScope.launch(Dispatchers.Default) { viewModelScope.launch(Dispatchers.Default) {
merge(_input, _unitFrom, _unitTo, _showLoading, userPrefs).collectLatest { merge(_input, _unitFrom, _unitTo, _showLoading, _userPrefs).collectLatest {
convertInput() convertInput()
} }
} }

View File

@ -20,7 +20,7 @@ package com.sadellie.unitto.feature.converter.navigation
import androidx.navigation.NavGraphBuilder import androidx.navigation.NavGraphBuilder
import androidx.navigation.compose.composable 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 import com.sadellie.unitto.feature.converter.ConverterViewModel
const val converterRoute = "converter_route" const val converterRoute = "converter_route"
@ -33,12 +33,12 @@ fun NavGraphBuilder.converterScreen(
viewModel: ConverterViewModel viewModel: ConverterViewModel
) { ) {
composable(converterRoute) { composable(converterRoute) {
ConverterScreen( ConverterRoute(
viewModel = viewModel,
navigateToLeftScreen = navigateToLeftScreen, navigateToLeftScreen = navigateToLeftScreen,
navigateToRightScreen = navigateToRightScreen, navigateToRightScreen = navigateToRightScreen,
navigateToSettings = navigateToSettings, navigateToSettings = navigateToSettings,
navigateToTools = navigateToTools, navigateToTools = navigateToTools
viewModel = viewModel,
) )
} }
} }