mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-18 16:25:27 +02:00
Refactor unit selector screens
decrease duplicate logic in ConverterViewModel
This commit is contained in:
parent
9d76c168ec
commit
7cbbb846af
@ -154,5 +154,45 @@ fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, R> combine(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Suppress("UNCHECKED_CAST", "UNUSED")
|
||||||
|
fun <T1, T2, T3, T4, T5, T6, T7, T8, T9, T10, R> combine(
|
||||||
|
flow: Flow<T1>,
|
||||||
|
flow2: Flow<T2>,
|
||||||
|
flow3: Flow<T3>,
|
||||||
|
flow4: Flow<T4>,
|
||||||
|
flow5: Flow<T5>,
|
||||||
|
flow6: Flow<T6>,
|
||||||
|
flow7: Flow<T7>,
|
||||||
|
flow8: Flow<T8>,
|
||||||
|
flow9: Flow<T9>,
|
||||||
|
flow10: Flow<T10>,
|
||||||
|
transform: suspend (T1, T2, T3, T4, T5, T6, T7, T8, T9, T10) -> R,
|
||||||
|
): Flow<R> =
|
||||||
|
kotlinx.coroutines.flow.combine(
|
||||||
|
flow,
|
||||||
|
flow2,
|
||||||
|
flow3,
|
||||||
|
flow4,
|
||||||
|
flow5,
|
||||||
|
flow6,
|
||||||
|
flow7,
|
||||||
|
flow8,
|
||||||
|
flow9,
|
||||||
|
flow10
|
||||||
|
) { args: Array<*> ->
|
||||||
|
transform(
|
||||||
|
args[0] as T1,
|
||||||
|
args[1] as T2,
|
||||||
|
args[2] as T3,
|
||||||
|
args[3] as T4,
|
||||||
|
args[4] as T5,
|
||||||
|
args[5] as T6,
|
||||||
|
args[6] as T7,
|
||||||
|
args[7] as T8,
|
||||||
|
args[8] as T9,
|
||||||
|
args[9] as T10,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
fun <T> Flow<T>.stateIn(scope: CoroutineScope, initialValue: T): StateFlow<T> =
|
fun <T> Flow<T>.stateIn(scope: CoroutineScope, initialValue: T): StateFlow<T> =
|
||||||
stateIn(scope, SharingStarted.WhileSubscribed(5000L), initialValue)
|
stateIn(scope, SharingStarted.WhileSubscribed(5000L), initialValue)
|
||||||
|
@ -97,8 +97,8 @@ import java.util.Locale
|
|||||||
@Composable
|
@Composable
|
||||||
internal fun ConverterRoute(
|
internal fun ConverterRoute(
|
||||||
viewModel: ConverterViewModel = hiltViewModel(),
|
viewModel: ConverterViewModel = hiltViewModel(),
|
||||||
navigateToLeftScreen: () -> Unit,
|
navigateToLeftScreen: (uiState: UnitConverterUIState) -> Unit,
|
||||||
navigateToRightScreen: () -> Unit,
|
navigateToRightScreen: (uiState: UnitConverterUIState) -> Unit,
|
||||||
navigateToMenu: () -> Unit,
|
navigateToMenu: () -> Unit,
|
||||||
navigateToSettings: () -> Unit,
|
navigateToSettings: () -> Unit,
|
||||||
) {
|
) {
|
||||||
@ -124,8 +124,8 @@ internal fun ConverterRoute(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun ConverterScreen(
|
private fun ConverterScreen(
|
||||||
uiState: UnitConverterUIState,
|
uiState: UnitConverterUIState,
|
||||||
navigateToLeftScreen: () -> Unit,
|
navigateToLeftScreen: (uiState: UnitConverterUIState) -> Unit,
|
||||||
navigateToRightScreen: () -> Unit,
|
navigateToRightScreen: (uiState: UnitConverterUIState) -> Unit,
|
||||||
navigateToSettings: () -> Unit,
|
navigateToSettings: () -> Unit,
|
||||||
navigateToMenu: () -> Unit,
|
navigateToMenu: () -> Unit,
|
||||||
swapUnits: () -> Unit,
|
swapUnits: () -> Unit,
|
||||||
@ -207,9 +207,9 @@ private fun NumberBase(
|
|||||||
onValueChange: (TextFieldValue) -> Unit,
|
onValueChange: (TextFieldValue) -> Unit,
|
||||||
processInput: (String) -> Unit,
|
processInput: (String) -> Unit,
|
||||||
deleteDigit: () -> Unit,
|
deleteDigit: () -> Unit,
|
||||||
navigateToLeftScreen: () -> Unit,
|
navigateToLeftScreen: (uiState: UnitConverterUIState) -> Unit,
|
||||||
swapUnits: () -> Unit,
|
swapUnits: () -> Unit,
|
||||||
navigateToRightScreen: () -> Unit,
|
navigateToRightScreen: (uiState: UnitConverterUIState) -> Unit,
|
||||||
clearInput: () -> Unit,
|
clearInput: () -> Unit,
|
||||||
) {
|
) {
|
||||||
PortraitLandscape(
|
PortraitLandscape(
|
||||||
@ -239,8 +239,8 @@ private fun NumberBase(
|
|||||||
unitFromLabel = stringResource(uiState.unitFrom.displayName),
|
unitFromLabel = stringResource(uiState.unitFrom.displayName),
|
||||||
unitToLabel = stringResource(uiState.unitTo.displayName),
|
unitToLabel = stringResource(uiState.unitTo.displayName),
|
||||||
swapUnits = swapUnits,
|
swapUnits = swapUnits,
|
||||||
navigateToLeftScreen = navigateToLeftScreen,
|
navigateToLeftScreen = { navigateToLeftScreen(uiState) },
|
||||||
navigateToRightScreen = navigateToRightScreen
|
navigateToRightScreen = { navigateToRightScreen(uiState) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -263,9 +263,9 @@ private fun Default(
|
|||||||
onFocusOnInput2: (Boolean) -> Unit,
|
onFocusOnInput2: (Boolean) -> Unit,
|
||||||
processInput: (String) -> Unit,
|
processInput: (String) -> Unit,
|
||||||
deleteDigit: () -> Unit,
|
deleteDigit: () -> Unit,
|
||||||
navigateToLeftScreen: () -> Unit,
|
navigateToLeftScreen: (uiState: UnitConverterUIState) -> Unit,
|
||||||
swapUnits: () -> Unit,
|
swapUnits: () -> Unit,
|
||||||
navigateToRightScreen: () -> Unit,
|
navigateToRightScreen: (uiState: UnitConverterUIState) -> Unit,
|
||||||
clearInput: () -> Unit,
|
clearInput: () -> Unit,
|
||||||
refreshCurrencyRates: (AbstractUnit) -> Unit,
|
refreshCurrencyRates: (AbstractUnit) -> Unit,
|
||||||
addBracket: () -> Unit,
|
addBracket: () -> Unit,
|
||||||
@ -327,7 +327,9 @@ private fun Default(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
) {
|
) {
|
||||||
ExpressionTextField(
|
ExpressionTextField(
|
||||||
modifier = Modifier.fillMaxWidth().weight(1f),
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f),
|
||||||
value = uiState.input1,
|
value = uiState.input1,
|
||||||
minRatio = 0.7f,
|
minRatio = 0.7f,
|
||||||
onValueChange = onValueChange,
|
onValueChange = onValueChange,
|
||||||
@ -345,7 +347,9 @@ private fun Default(
|
|||||||
.weight(1f)
|
.weight(1f)
|
||||||
) {
|
) {
|
||||||
ExpressionTextField(
|
ExpressionTextField(
|
||||||
modifier = Modifier.fillMaxWidth().weight(1f)
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.weight(1f)
|
||||||
.onFocusEvent { state -> onFocusOnInput2(state.hasFocus) },
|
.onFocusEvent { state -> onFocusOnInput2(state.hasFocus) },
|
||||||
value = uiState.input2,
|
value = uiState.input2,
|
||||||
minRatio = 0.7f,
|
minRatio = 0.7f,
|
||||||
@ -405,8 +409,8 @@ private fun Default(
|
|||||||
unitFromLabel = stringResource(uiState.unitFrom.displayName),
|
unitFromLabel = stringResource(uiState.unitFrom.displayName),
|
||||||
unitToLabel = stringResource(uiState.unitTo.displayName),
|
unitToLabel = stringResource(uiState.unitTo.displayName),
|
||||||
swapUnits = swapUnits,
|
swapUnits = swapUnits,
|
||||||
navigateToLeftScreen = navigateToLeftScreen,
|
navigateToLeftScreen = { navigateToLeftScreen(uiState) },
|
||||||
navigateToRightScreen = navigateToRightScreen
|
navigateToRightScreen = { navigateToRightScreen(uiState) }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
@ -33,7 +33,6 @@ import com.sadellie.unitto.data.common.isExpression
|
|||||||
import com.sadellie.unitto.data.common.stateIn
|
import com.sadellie.unitto.data.common.stateIn
|
||||||
import com.sadellie.unitto.data.converter.UnitID
|
import com.sadellie.unitto.data.converter.UnitID
|
||||||
import com.sadellie.unitto.data.model.UnitGroup
|
import com.sadellie.unitto.data.model.UnitGroup
|
||||||
import com.sadellie.unitto.data.model.UnitsListSorting
|
|
||||||
import com.sadellie.unitto.data.model.repository.UnitsRepository
|
import com.sadellie.unitto.data.model.repository.UnitsRepository
|
||||||
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
||||||
import com.sadellie.unitto.data.model.unit.AbstractUnit
|
import com.sadellie.unitto.data.model.unit.AbstractUnit
|
||||||
@ -45,12 +44,10 @@ import io.github.sadellie.evaluatto.ExpressionException
|
|||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.Job
|
import kotlinx.coroutines.Job
|
||||||
import kotlinx.coroutines.flow.MutableStateFlow
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
import kotlinx.coroutines.flow.SharingStarted
|
|
||||||
import kotlinx.coroutines.flow.StateFlow
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
import kotlinx.coroutines.flow.first
|
import kotlinx.coroutines.flow.first
|
||||||
import kotlinx.coroutines.flow.getAndUpdate
|
import kotlinx.coroutines.flow.getAndUpdate
|
||||||
import kotlinx.coroutines.flow.mapLatest
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
import kotlinx.coroutines.flow.stateIn
|
|
||||||
import kotlinx.coroutines.flow.update
|
import kotlinx.coroutines.flow.update
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
@ -74,13 +71,6 @@ internal class ConverterViewModel @Inject constructor(
|
|||||||
private val _unitFrom = MutableStateFlow<AbstractUnit?>(null)
|
private val _unitFrom = MutableStateFlow<AbstractUnit?>(null)
|
||||||
private val _unitTo = MutableStateFlow<AbstractUnit?>(null)
|
private val _unitTo = MutableStateFlow<AbstractUnit?>(null)
|
||||||
|
|
||||||
private val _leftQuery = MutableStateFlow(TextFieldValue())
|
|
||||||
private val _leftUnits = MutableStateFlow<Map<UnitGroup, List<AbstractUnit>>>(emptyMap())
|
|
||||||
private val _leftUnitGroup = MutableStateFlow<UnitGroup?>(null)
|
|
||||||
|
|
||||||
private val _rightQuery = MutableStateFlow(TextFieldValue())
|
|
||||||
private val _rightUnits = MutableStateFlow<Map<UnitGroup, List<AbstractUnit>>>(emptyMap())
|
|
||||||
|
|
||||||
private val _currenciesState = MutableStateFlow<CurrencyRateUpdateState>(CurrencyRateUpdateState.Nothing)
|
private val _currenciesState = MutableStateFlow<CurrencyRateUpdateState>(CurrencyRateUpdateState.Nothing)
|
||||||
private var _loadCurrenciesJob: Job? = null
|
private var _loadCurrenciesJob: Job? = null
|
||||||
|
|
||||||
@ -160,81 +150,6 @@ internal class ConverterViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
.stateIn(viewModelScope, UnitConverterUIState.Loading)
|
.stateIn(viewModelScope, UnitConverterUIState.Loading)
|
||||||
|
|
||||||
val leftSideUIState = combine(
|
|
||||||
_unitFrom,
|
|
||||||
_leftQuery,
|
|
||||||
_leftUnits,
|
|
||||||
_leftUnitGroup,
|
|
||||||
userPrefsRepository.converterPrefs,
|
|
||||||
unitsRepo.units
|
|
||||||
) { unitFrom, query, units, unitGroup, prefs, _ ->
|
|
||||||
unitFrom ?: return@combine LeftSideUIState.Loading
|
|
||||||
|
|
||||||
return@combine LeftSideUIState.Ready(
|
|
||||||
unitFrom = unitFrom,
|
|
||||||
sorting = prefs.unitConverterSorting,
|
|
||||||
shownUnitGroups = prefs.shownUnitGroups,
|
|
||||||
favorites = prefs.unitConverterFavoritesOnly,
|
|
||||||
query = query,
|
|
||||||
units = units,
|
|
||||||
unitGroup = unitGroup
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.mapLatest {
|
|
||||||
if (it !is LeftSideUIState.Ready) return@mapLatest it
|
|
||||||
|
|
||||||
filterUnitsLeft(
|
|
||||||
query = it.query,
|
|
||||||
unitGroup = it.unitGroup,
|
|
||||||
favoritesOnly = it.favorites,
|
|
||||||
sorting = it.sorting,
|
|
||||||
shownUnitGroups = it.shownUnitGroups,
|
|
||||||
)
|
|
||||||
it
|
|
||||||
}
|
|
||||||
.stateIn(viewModelScope, SharingStarted.Lazily, LeftSideUIState.Loading)
|
|
||||||
|
|
||||||
val rightSideUIState = combine(
|
|
||||||
_unitFrom,
|
|
||||||
_unitTo,
|
|
||||||
_input1,
|
|
||||||
_calculation,
|
|
||||||
_rightQuery,
|
|
||||||
_rightUnits,
|
|
||||||
userPrefsRepository.converterPrefs,
|
|
||||||
_currenciesState,
|
|
||||||
unitsRepo.units,
|
|
||||||
) { unitFrom, unitTo, input, calculation, query, units, prefs, currenciesState, _ ->
|
|
||||||
unitFrom ?: return@combine RightSideUIState.Loading
|
|
||||||
unitTo ?: return@combine RightSideUIState.Loading
|
|
||||||
|
|
||||||
return@combine RightSideUIState.Ready(
|
|
||||||
unitFrom = unitFrom,
|
|
||||||
unitTo = unitTo,
|
|
||||||
sorting = prefs.unitConverterSorting,
|
|
||||||
favorites = prefs.unitConverterFavoritesOnly,
|
|
||||||
input = (calculation?.toPlainString() ?: input.text).replace(Token.Operator.minus, "-"),
|
|
||||||
scale = prefs.precision,
|
|
||||||
outputFormat = prefs.outputFormat,
|
|
||||||
formatterSymbols = AllFormatterSymbols.getById(prefs.separator),
|
|
||||||
currencyRateUpdateState = currenciesState,
|
|
||||||
query = query,
|
|
||||||
units = units,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
.mapLatest {
|
|
||||||
if (it !is RightSideUIState.Ready) return@mapLatest it
|
|
||||||
|
|
||||||
filterUnitsRight(
|
|
||||||
query = it.query,
|
|
||||||
unitGroup = it.unitFrom.group,
|
|
||||||
favoritesOnly = it.favorites,
|
|
||||||
sorting = it.sorting,
|
|
||||||
)
|
|
||||||
it
|
|
||||||
}
|
|
||||||
.stateIn(viewModelScope, SharingStarted.Lazily, RightSideUIState.Loading)
|
|
||||||
|
|
||||||
fun swapUnits() {
|
fun swapUnits() {
|
||||||
_unitFrom
|
_unitFrom
|
||||||
.getAndUpdate { _unitTo.value }
|
.getAndUpdate { _unitTo.value }
|
||||||
@ -380,56 +295,6 @@ internal class ConverterViewModel @Inject constructor(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fun queryChangeLeft(query: TextFieldValue) = _leftQuery.update { query }
|
|
||||||
|
|
||||||
fun queryChangeRight(query: TextFieldValue) = _rightQuery.update { query }
|
|
||||||
|
|
||||||
fun favoritesOnlyChange(enabled: Boolean) = viewModelScope.launch {
|
|
||||||
userPrefsRepository.updateUnitConverterFavoritesOnly(enabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun updateUnitGroupLeft(unitGroup: UnitGroup?) = _leftUnitGroup.update { unitGroup }
|
|
||||||
|
|
||||||
fun favoriteUnit(unit: AbstractUnit) = viewModelScope.launch {
|
|
||||||
unitsRepo.favorite(unit)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun filterUnitsLeft(
|
|
||||||
query: TextFieldValue,
|
|
||||||
unitGroup: UnitGroup?,
|
|
||||||
favoritesOnly: Boolean,
|
|
||||||
sorting: UnitsListSorting,
|
|
||||||
shownUnitGroups: List<UnitGroup>,
|
|
||||||
) = viewModelScope.launch(Dispatchers.Default) {
|
|
||||||
_leftUnits.update {
|
|
||||||
unitsRepo.filterUnits(
|
|
||||||
query = query.text,
|
|
||||||
unitGroup = unitGroup,
|
|
||||||
favoritesOnly = favoritesOnly,
|
|
||||||
hideBrokenUnits = false,
|
|
||||||
sorting = sorting,
|
|
||||||
shownUnitGroups = shownUnitGroups
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun filterUnitsRight(
|
|
||||||
query: TextFieldValue,
|
|
||||||
unitGroup: UnitGroup?,
|
|
||||||
favoritesOnly: Boolean,
|
|
||||||
sorting: UnitsListSorting,
|
|
||||||
) = viewModelScope.launch(Dispatchers.Default) {
|
|
||||||
_rightUnits.update {
|
|
||||||
unitsRepo.filterUnits(
|
|
||||||
query = query.text,
|
|
||||||
unitGroup = unitGroup,
|
|
||||||
favoritesOnly = favoritesOnly,
|
|
||||||
hideBrokenUnits = true,
|
|
||||||
sorting = sorting,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun convertDefault(
|
private fun convertDefault(
|
||||||
unitFrom: DefaultUnit,
|
unitFrom: DefaultUnit,
|
||||||
unitTo: DefaultUnit,
|
unitTo: DefaultUnit,
|
||||||
|
@ -1,38 +0,0 @@
|
|||||||
/*
|
|
||||||
* Unitto is a calculator for Android
|
|
||||||
* Copyright (c) 2023-2024 Elshan Agaev
|
|
||||||
*
|
|
||||||
* This program is free software: you can redistribute it and/or modify
|
|
||||||
* it under the terms of the GNU General Public License as published by
|
|
||||||
* the Free Software Foundation, either version 3 of the License, or
|
|
||||||
* (at your option) any later version.
|
|
||||||
*
|
|
||||||
* This program is distributed in the hope that it will be useful,
|
|
||||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
||||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
||||||
* GNU General Public License for more details.
|
|
||||||
*
|
|
||||||
* You should have received a copy of the GNU General Public License
|
|
||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package com.sadellie.unitto.feature.converter
|
|
||||||
|
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
|
||||||
import com.sadellie.unitto.data.model.UnitGroup
|
|
||||||
import com.sadellie.unitto.data.model.UnitsListSorting
|
|
||||||
import com.sadellie.unitto.data.model.unit.AbstractUnit
|
|
||||||
|
|
||||||
internal sealed class LeftSideUIState {
|
|
||||||
data object Loading : LeftSideUIState()
|
|
||||||
|
|
||||||
data class Ready(
|
|
||||||
val unitFrom: AbstractUnit,
|
|
||||||
val query: TextFieldValue,
|
|
||||||
val units: Map<UnitGroup, List<AbstractUnit>> = emptyMap(),
|
|
||||||
val favorites: Boolean,
|
|
||||||
val shownUnitGroups: List<UnitGroup>,
|
|
||||||
val unitGroup: UnitGroup?,
|
|
||||||
val sorting: UnitsListSorting,
|
|
||||||
) : LeftSideUIState()
|
|
||||||
}
|
|
@ -49,31 +49,32 @@ import com.sadellie.unitto.feature.converter.components.UnitsList
|
|||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun LeftSideRoute(
|
internal fun UnitFromSelectorRoute(
|
||||||
viewModel: ConverterViewModel,
|
unitSelectorViewModel: UnitSelectorViewModel,
|
||||||
|
converterViewModel: ConverterViewModel,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
navigateToUnitGroups: () -> Unit,
|
navigateToUnitGroups: () -> Unit,
|
||||||
) {
|
) {
|
||||||
when (
|
when (
|
||||||
val uiState = viewModel.leftSideUIState.collectAsStateWithLifecycle().value
|
val uiState = unitSelectorViewModel.unitFromUIState.collectAsStateWithLifecycle().value
|
||||||
) {
|
) {
|
||||||
is LeftSideUIState.Loading -> EmptyScreen()
|
is UnitSelectorUIState.UnitFrom -> UnitFromSelectorScreen(
|
||||||
is LeftSideUIState.Ready -> LeftSideScreen(
|
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
onQueryChange = viewModel::queryChangeLeft,
|
onQueryChange = unitSelectorViewModel::updateSelectorQuery,
|
||||||
toggleFavoritesOnly = viewModel::favoritesOnlyChange,
|
toggleFavoritesOnly = unitSelectorViewModel::updateShowFavoritesOnly,
|
||||||
updateUnitFrom = viewModel::updateUnitFrom,
|
updateUnitFrom = converterViewModel::updateUnitFrom,
|
||||||
updateUnitGroup = viewModel::updateUnitGroupLeft,
|
updateUnitGroup = unitSelectorViewModel::updateSelectedUnitGroup,
|
||||||
favoriteUnit = viewModel::favoriteUnit,
|
favoriteUnit = unitSelectorViewModel::favoriteUnit,
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
navigateToUnitGroups = navigateToUnitGroups,
|
navigateToUnitGroups = navigateToUnitGroups,
|
||||||
)
|
)
|
||||||
|
else -> EmptyScreen()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun LeftSideScreen(
|
private fun UnitFromSelectorScreen(
|
||||||
uiState: LeftSideUIState.Ready,
|
uiState: UnitSelectorUIState.UnitFrom,
|
||||||
onQueryChange: (TextFieldValue) -> Unit,
|
onQueryChange: (TextFieldValue) -> Unit,
|
||||||
toggleFavoritesOnly: (Boolean) -> Unit,
|
toggleFavoritesOnly: (Boolean) -> Unit,
|
||||||
updateUnitFrom: (AbstractUnit) -> Unit,
|
updateUnitFrom: (AbstractUnit) -> Unit,
|
||||||
@ -87,8 +88,6 @@ private fun LeftSideScreen(
|
|||||||
val chipsRowLazyListState = rememberLazyListState()
|
val chipsRowLazyListState = rememberLazyListState()
|
||||||
|
|
||||||
LaunchedEffect(uiState.unitFrom, uiState.shownUnitGroups) {
|
LaunchedEffect(uiState.unitFrom, uiState.shownUnitGroups) {
|
||||||
updateUnitGroup(uiState.unitFrom.group)
|
|
||||||
|
|
||||||
kotlin.runCatching {
|
kotlin.runCatching {
|
||||||
val groupToSelect = uiState.shownUnitGroups.indexOf(uiState.unitFrom.group)
|
val groupToSelect = uiState.shownUnitGroups.indexOf(uiState.unitFrom.group)
|
||||||
if (groupToSelect > -1) {
|
if (groupToSelect > -1) {
|
||||||
@ -108,8 +107,8 @@ private fun LeftSideScreen(
|
|||||||
onQueryChange = onQueryChange,
|
onQueryChange = onQueryChange,
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
FavoritesButton(uiState.favorites) {
|
FavoritesButton(uiState.showFavoritesOnly) {
|
||||||
toggleFavoritesOnly(!uiState.favorites)
|
toggleFavoritesOnly(!uiState.showFavoritesOnly)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
@ -119,7 +118,7 @@ private fun LeftSideScreen(
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.padding(start = 8.dp, end = 8.dp, bottom = 4.dp)
|
.padding(start = 8.dp, end = 8.dp, bottom = 4.dp)
|
||||||
.fillMaxWidth(),
|
.fillMaxWidth(),
|
||||||
chosenUnitGroup = uiState.unitGroup,
|
chosenUnitGroup = uiState.selectedUnitGroup,
|
||||||
items = uiState.shownUnitGroups,
|
items = uiState.shownUnitGroups,
|
||||||
selectAction = updateUnitGroup,
|
selectAction = updateUnitGroup,
|
||||||
navigateToSettingsAction = navigateToUnitGroups
|
navigateToSettingsAction = navigateToUnitGroups
|
||||||
@ -130,7 +129,7 @@ private fun LeftSideScreen(
|
|||||||
val resources = LocalContext.current.resources
|
val resources = LocalContext.current.resources
|
||||||
UnitsList(
|
UnitsList(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
groupedUnits = uiState.units,
|
searchResult = uiState.units,
|
||||||
navigateToUnitGroups = navigateToUnitGroups,
|
navigateToUnitGroups = navigateToUnitGroups,
|
||||||
currentUnitId = uiState.unitFrom.id,
|
currentUnitId = uiState.unitFrom.id,
|
||||||
supportLabel = { resources.getString(it.shortName) },
|
supportLabel = { resources.getString(it.shortName) },
|
||||||
@ -146,7 +145,7 @@ private fun LeftSideScreen(
|
|||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun LeftSideScreenPreview() {
|
private fun UnitFromSelectorScreenPreview() {
|
||||||
val units: Map<UnitGroup, List<AbstractUnit>> = mapOf(
|
val units: Map<UnitGroup, List<AbstractUnit>> = mapOf(
|
||||||
UnitGroup.LENGTH to listOf(
|
UnitGroup.LENGTH to listOf(
|
||||||
NormalUnit(UnitID.meter, BigDecimal.valueOf(1.0E+18), UnitGroup.LENGTH, R.string.unit_meter, R.string.unit_meter_short),
|
NormalUnit(UnitID.meter, BigDecimal.valueOf(1.0E+18), UnitGroup.LENGTH, R.string.unit_meter, R.string.unit_meter_short),
|
||||||
@ -159,14 +158,14 @@ private fun LeftSideScreenPreview() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
LeftSideScreen(
|
UnitFromSelectorScreen(
|
||||||
uiState = LeftSideUIState.Ready(
|
uiState = UnitSelectorUIState.UnitFrom(
|
||||||
unitFrom = units.values.first().first(),
|
unitFrom = units.values.first().first(),
|
||||||
units = units,
|
|
||||||
query = TextFieldValue("test"),
|
query = TextFieldValue("test"),
|
||||||
favorites = false,
|
units = UnitSearchResult.Success(units),
|
||||||
|
selectedUnitGroup = UnitGroup.SPEED,
|
||||||
shownUnitGroups = UnitGroup.entries,
|
shownUnitGroups = UnitGroup.entries,
|
||||||
unitGroup = units.keys.toList().first(),
|
showFavoritesOnly = false,
|
||||||
sorting = UnitsListSorting.USAGE,
|
sorting = UnitsListSorting.USAGE,
|
||||||
),
|
),
|
||||||
onQueryChange = {},
|
onQueryChange = {},
|
@ -1,6 +1,6 @@
|
|||||||
/*
|
/*
|
||||||
* Unitto is a calculator for Android
|
* Unitto is a calculator for Android
|
||||||
* Copyright (c) 2023-2024 Elshan Agaev
|
* Copyright (c) 2024 Elshan Agaev
|
||||||
*
|
*
|
||||||
* This program is free software: you can redistribute it and/or modify
|
* This program is free software: you can redistribute it and/or modify
|
||||||
* it under the terms of the GNU General Public License as published by
|
* it under the terms of the GNU General Public License as published by
|
||||||
@ -24,20 +24,39 @@ import com.sadellie.unitto.data.model.UnitGroup
|
|||||||
import com.sadellie.unitto.data.model.UnitsListSorting
|
import com.sadellie.unitto.data.model.UnitsListSorting
|
||||||
import com.sadellie.unitto.data.model.unit.AbstractUnit
|
import com.sadellie.unitto.data.model.unit.AbstractUnit
|
||||||
|
|
||||||
internal sealed class RightSideUIState {
|
internal sealed class UnitSelectorUIState {
|
||||||
data object Loading : RightSideUIState()
|
data object Loading : UnitSelectorUIState()
|
||||||
|
|
||||||
data class Ready(
|
data class UnitFrom(
|
||||||
|
val query: TextFieldValue,
|
||||||
|
val unitFrom: AbstractUnit,
|
||||||
|
val shownUnitGroups: List<UnitGroup>,
|
||||||
|
val showFavoritesOnly: Boolean,
|
||||||
|
val units: UnitSearchResult,
|
||||||
|
val selectedUnitGroup: UnitGroup?,
|
||||||
|
val sorting: UnitsListSorting,
|
||||||
|
) : UnitSelectorUIState()
|
||||||
|
|
||||||
|
data class UnitTo(
|
||||||
|
val query: TextFieldValue,
|
||||||
val unitFrom: AbstractUnit,
|
val unitFrom: AbstractUnit,
|
||||||
val unitTo: AbstractUnit,
|
val unitTo: AbstractUnit,
|
||||||
val query: TextFieldValue,
|
val showFavoritesOnly: Boolean,
|
||||||
val units: Map<UnitGroup, List<AbstractUnit>>,
|
val units: UnitSearchResult,
|
||||||
val favorites: Boolean,
|
val input: String?,
|
||||||
val sorting: UnitsListSorting,
|
val sorting: UnitsListSorting,
|
||||||
val input: String,
|
|
||||||
val scale: Int,
|
val scale: Int,
|
||||||
val outputFormat: Int,
|
val outputFormat: Int,
|
||||||
val formatterSymbols: FormatterSymbols,
|
val formatterSymbols: FormatterSymbols,
|
||||||
val currencyRateUpdateState: CurrencyRateUpdateState,
|
) : UnitSelectorUIState()
|
||||||
) : RightSideUIState()
|
}
|
||||||
|
|
||||||
|
internal sealed class UnitSearchResult {
|
||||||
|
data object Empty : UnitSearchResult()
|
||||||
|
|
||||||
|
data object Loading : UnitSearchResult()
|
||||||
|
|
||||||
|
data class Success(
|
||||||
|
val units: Map<UnitGroup, List<AbstractUnit>>
|
||||||
|
) : UnitSearchResult()
|
||||||
}
|
}
|
@ -0,0 +1,149 @@
|
|||||||
|
/*
|
||||||
|
* Unitto is a calculator for Android
|
||||||
|
* Copyright (c) 2024 Elshan Agaev
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sadellie.unitto.feature.converter
|
||||||
|
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
|
||||||
|
import com.sadellie.unitto.data.common.stateIn
|
||||||
|
import com.sadellie.unitto.data.model.UnitGroup
|
||||||
|
import com.sadellie.unitto.data.model.repository.UnitsRepository
|
||||||
|
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
||||||
|
import com.sadellie.unitto.data.model.unit.AbstractUnit
|
||||||
|
import com.sadellie.unitto.feature.converter.navigation.inputArg
|
||||||
|
import com.sadellie.unitto.feature.converter.navigation.unitFromIdArg
|
||||||
|
import com.sadellie.unitto.feature.converter.navigation.unitGroupArg
|
||||||
|
import com.sadellie.unitto.feature.converter.navigation.unitToIdArg
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.mapLatest
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import javax.inject.Inject
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
internal class UnitSelectorViewModel @Inject constructor(
|
||||||
|
private val userPrefsRepository: UserPreferencesRepository,
|
||||||
|
private val unitsRepo: UnitsRepository,
|
||||||
|
savedStateHandle: SavedStateHandle
|
||||||
|
) : ViewModel() {
|
||||||
|
private val _query = MutableStateFlow(TextFieldValue())
|
||||||
|
private val _searchResults = MutableStateFlow<UnitSearchResult>(UnitSearchResult.Loading)
|
||||||
|
private val _selectedUnitGroup = MutableStateFlow(savedStateHandle.get<UnitGroup>(unitGroupArg))
|
||||||
|
private val _unitFromId = savedStateHandle.get<String>(unitFromIdArg)
|
||||||
|
private val _unitToId = savedStateHandle.get<String>(unitToIdArg)
|
||||||
|
private val _input = savedStateHandle.get<String>(inputArg)
|
||||||
|
|
||||||
|
val unitFromUIState: StateFlow<UnitSelectorUIState> = combine(
|
||||||
|
_query,
|
||||||
|
_searchResults,
|
||||||
|
_selectedUnitGroup,
|
||||||
|
userPrefsRepository.converterPrefs,
|
||||||
|
) { query, searchResults, selectedUnitGroup, prefs ->
|
||||||
|
if (_unitFromId.isNullOrEmpty()) return@combine UnitSelectorUIState.Loading
|
||||||
|
|
||||||
|
return@combine UnitSelectorUIState.UnitFrom(
|
||||||
|
query = query,
|
||||||
|
unitFrom = unitsRepo.getById(_unitFromId),
|
||||||
|
shownUnitGroups = prefs.shownUnitGroups,
|
||||||
|
showFavoritesOnly = prefs.unitConverterFavoritesOnly,
|
||||||
|
units = searchResults,
|
||||||
|
selectedUnitGroup = selectedUnitGroup,
|
||||||
|
sorting = prefs.unitConverterSorting,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.mapLatest { ui ->
|
||||||
|
if (ui is UnitSelectorUIState.UnitFrom) {
|
||||||
|
_searchResults.update {
|
||||||
|
val result = unitsRepo.filterUnits(
|
||||||
|
query = ui.query.text,
|
||||||
|
unitGroup = ui.selectedUnitGroup,
|
||||||
|
favoritesOnly = ui.showFavoritesOnly,
|
||||||
|
hideBrokenUnits = false,
|
||||||
|
sorting = ui.sorting,
|
||||||
|
shownUnitGroups = ui.shownUnitGroups
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result.isEmpty()) UnitSearchResult.Empty else UnitSearchResult.Success(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
ui
|
||||||
|
}
|
||||||
|
.stateIn(viewModelScope, UnitSelectorUIState.Loading)
|
||||||
|
|
||||||
|
val unitToUIState: StateFlow<UnitSelectorUIState> = combine(
|
||||||
|
_query,
|
||||||
|
_searchResults,
|
||||||
|
userPrefsRepository.converterPrefs,
|
||||||
|
unitsRepo.units,
|
||||||
|
) { query, searchResults, prefs, _ ->
|
||||||
|
if (_unitFromId.isNullOrEmpty()) return@combine UnitSelectorUIState.Loading
|
||||||
|
if (_unitToId.isNullOrEmpty()) return@combine UnitSelectorUIState.Loading
|
||||||
|
|
||||||
|
UnitSelectorUIState.UnitTo(
|
||||||
|
query = query,
|
||||||
|
unitFrom = unitsRepo.getById(_unitFromId),
|
||||||
|
unitTo = unitsRepo.getById(_unitToId),
|
||||||
|
showFavoritesOnly = prefs.unitConverterFavoritesOnly,
|
||||||
|
units = searchResults,
|
||||||
|
input = _input,
|
||||||
|
sorting = prefs.unitConverterSorting,
|
||||||
|
scale = prefs.precision,
|
||||||
|
outputFormat = prefs.outputFormat,
|
||||||
|
formatterSymbols = AllFormatterSymbols.getById(prefs.separator),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
.mapLatest { ui ->
|
||||||
|
if (ui is UnitSelectorUIState.UnitTo) {
|
||||||
|
_searchResults.update {
|
||||||
|
if (ui.unitFrom.group == UnitGroup.CURRENCY) unitsRepo.updateRates(ui.unitFrom)
|
||||||
|
|
||||||
|
val result = unitsRepo.filterUnits(
|
||||||
|
query = ui.query.text,
|
||||||
|
unitGroup = ui.unitFrom.group,
|
||||||
|
favoritesOnly = ui.showFavoritesOnly,
|
||||||
|
hideBrokenUnits = true,
|
||||||
|
sorting = ui.sorting,
|
||||||
|
)
|
||||||
|
|
||||||
|
if (result.isEmpty()) UnitSearchResult.Empty else UnitSearchResult.Success(result)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ui
|
||||||
|
}
|
||||||
|
.stateIn(viewModelScope, UnitSelectorUIState.Loading)
|
||||||
|
|
||||||
|
fun updateSelectorQuery(value: TextFieldValue) = _query.update { value }
|
||||||
|
|
||||||
|
fun updateShowFavoritesOnly(value: Boolean) = viewModelScope.launch {
|
||||||
|
userPrefsRepository.updateUnitConverterFavoritesOnly(value)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun updateSelectedUnitGroup(value: UnitGroup?) = _selectedUnitGroup.update { value }
|
||||||
|
|
||||||
|
fun favoriteUnit(unit: AbstractUnit) = viewModelScope.launch(Dispatchers.IO) {
|
||||||
|
unitsRepo.favorite(unit)
|
||||||
|
}
|
||||||
|
}
|
@ -47,31 +47,31 @@ import com.sadellie.unitto.feature.converter.components.UnitsList
|
|||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun RightSideRoute(
|
internal fun UnitToSelectorRoute(
|
||||||
viewModel: ConverterViewModel,
|
unitSelectorViewModel: UnitSelectorViewModel,
|
||||||
|
converterViewModel: ConverterViewModel,
|
||||||
navigateUp: () -> Unit,
|
navigateUp: () -> Unit,
|
||||||
navigateToUnitGroups: () -> Unit,
|
navigateToUnitGroups: () -> Unit,
|
||||||
) {
|
) {
|
||||||
when (
|
when (
|
||||||
val uiState = viewModel.rightSideUIState.collectAsStateWithLifecycle().value
|
val uiState = unitSelectorViewModel.unitToUIState.collectAsStateWithLifecycle().value
|
||||||
) {
|
) {
|
||||||
is RightSideUIState.Loading -> EmptyScreen()
|
is UnitSelectorUIState.UnitTo -> UnitToSelectorScreen(
|
||||||
is RightSideUIState.Ready ->
|
uiState = uiState,
|
||||||
RightSideScreen(
|
onQueryChange = unitSelectorViewModel::updateSelectorQuery,
|
||||||
uiState = uiState,
|
toggleFavoritesOnly = unitSelectorViewModel::updateShowFavoritesOnly,
|
||||||
onQueryChange = viewModel::queryChangeRight,
|
updateUnitTo = converterViewModel::updateUnitTo,
|
||||||
toggleFavoritesOnly = viewModel::favoritesOnlyChange,
|
favoriteUnit = unitSelectorViewModel::favoriteUnit,
|
||||||
updateUnitTo = viewModel::updateUnitTo,
|
navigateUp = navigateUp,
|
||||||
favoriteUnit = viewModel::favoriteUnit,
|
navigateToUnitGroups = navigateToUnitGroups,
|
||||||
navigateUp = navigateUp,
|
)
|
||||||
navigateToUnitGroups = navigateToUnitGroups,
|
else -> EmptyScreen()
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
private fun RightSideScreen(
|
private fun UnitToSelectorScreen(
|
||||||
uiState: RightSideUIState.Ready,
|
uiState: UnitSelectorUIState.UnitTo,
|
||||||
onQueryChange: (TextFieldValue) -> Unit,
|
onQueryChange: (TextFieldValue) -> Unit,
|
||||||
toggleFavoritesOnly: (Boolean) -> Unit,
|
toggleFavoritesOnly: (Boolean) -> Unit,
|
||||||
updateUnitTo: (AbstractUnit) -> Unit,
|
updateUnitTo: (AbstractUnit) -> Unit,
|
||||||
@ -89,8 +89,8 @@ private fun RightSideScreen(
|
|||||||
onQueryChange = onQueryChange,
|
onQueryChange = onQueryChange,
|
||||||
navigateUp = navigateUp,
|
navigateUp = navigateUp,
|
||||||
trailingIcon = {
|
trailingIcon = {
|
||||||
FavoritesButton(uiState.favorites) {
|
FavoritesButton(uiState.showFavoritesOnly) {
|
||||||
toggleFavoritesOnly(!uiState.favorites)
|
toggleFavoritesOnly(!uiState.showFavoritesOnly)
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
scrollBehavior = scrollBehavior
|
scrollBehavior = scrollBehavior
|
||||||
@ -100,7 +100,7 @@ private fun RightSideScreen(
|
|||||||
val resources = LocalContext.current.resources
|
val resources = LocalContext.current.resources
|
||||||
UnitsList(
|
UnitsList(
|
||||||
modifier = Modifier.padding(paddingValues),
|
modifier = Modifier.padding(paddingValues),
|
||||||
groupedUnits = uiState.units,
|
searchResult = uiState.units,
|
||||||
navigateToUnitGroups = navigateToUnitGroups,
|
navigateToUnitGroups = navigateToUnitGroups,
|
||||||
currentUnitId = uiState.unitTo.id,
|
currentUnitId = uiState.unitTo.id,
|
||||||
supportLabel = {
|
supportLabel = {
|
||||||
@ -112,7 +112,6 @@ private fun RightSideScreen(
|
|||||||
scale = uiState.scale,
|
scale = uiState.scale,
|
||||||
outputFormat = uiState.outputFormat,
|
outputFormat = uiState.outputFormat,
|
||||||
formatterSymbols = uiState.formatterSymbols,
|
formatterSymbols = uiState.formatterSymbols,
|
||||||
readyCurrencies = uiState.currencyRateUpdateState is CurrencyRateUpdateState.Ready,
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
onClick = {
|
onClick = {
|
||||||
@ -128,15 +127,13 @@ private fun RightSideScreen(
|
|||||||
private fun formatUnitToSupportLabel(
|
private fun formatUnitToSupportLabel(
|
||||||
unitFrom: AbstractUnit?,
|
unitFrom: AbstractUnit?,
|
||||||
unitTo: AbstractUnit?,
|
unitTo: AbstractUnit?,
|
||||||
input: String,
|
input: String?,
|
||||||
shortName: String,
|
shortName: String,
|
||||||
scale: Int,
|
scale: Int,
|
||||||
outputFormat: Int,
|
outputFormat: Int,
|
||||||
formatterSymbols: FormatterSymbols,
|
formatterSymbols: FormatterSymbols,
|
||||||
readyCurrencies: Boolean,
|
|
||||||
): String {
|
): String {
|
||||||
if ((unitFrom?.group == UnitGroup.CURRENCY) and !readyCurrencies) return shortName
|
if (input.isNullOrEmpty()) return shortName
|
||||||
if (input.isEmpty()) return shortName
|
|
||||||
|
|
||||||
try {
|
try {
|
||||||
if ((unitFrom is DefaultUnit) and (unitTo is DefaultUnit)) {
|
if ((unitFrom is DefaultUnit) and (unitTo is DefaultUnit)) {
|
||||||
@ -168,7 +165,7 @@ private fun formatUnitToSupportLabel(
|
|||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun RightSideScreenPreview() {
|
private fun UnitToSelectorPreview() {
|
||||||
val units: Map<UnitGroup, List<AbstractUnit>> = mapOf(
|
val units: Map<UnitGroup, List<AbstractUnit>> = mapOf(
|
||||||
UnitGroup.LENGTH to listOf(
|
UnitGroup.LENGTH to listOf(
|
||||||
NormalUnit(UnitID.meter, BigDecimal.valueOf(1.0E+18), UnitGroup.LENGTH, R.string.unit_meter, R.string.unit_meter_short),
|
NormalUnit(UnitID.meter, BigDecimal.valueOf(1.0E+18), UnitGroup.LENGTH, R.string.unit_meter, R.string.unit_meter_short),
|
||||||
@ -181,19 +178,18 @@ private fun RightSideScreenPreview() {
|
|||||||
)
|
)
|
||||||
)
|
)
|
||||||
|
|
||||||
RightSideScreen(
|
UnitToSelectorScreen(
|
||||||
uiState = RightSideUIState.Ready(
|
uiState = UnitSelectorUIState.UnitTo(
|
||||||
unitFrom = units.values.first().first(),
|
unitFrom = units.values.first().first(),
|
||||||
units = units,
|
unitTo = units.values.first().first(),
|
||||||
query = TextFieldValue(),
|
query = TextFieldValue("test"),
|
||||||
favorites = false,
|
units = UnitSearchResult.Success(units),
|
||||||
|
showFavoritesOnly = false,
|
||||||
sorting = UnitsListSorting.USAGE,
|
sorting = UnitsListSorting.USAGE,
|
||||||
unitTo = units.values.first()[1],
|
|
||||||
input = "100",
|
input = "100",
|
||||||
scale = 3,
|
scale = 3,
|
||||||
outputFormat = OutputFormat.PLAIN,
|
outputFormat = OutputFormat.PLAIN,
|
||||||
formatterSymbols = FormatterSymbols.Spaces,
|
formatterSymbols = FormatterSymbols.Spaces,
|
||||||
currencyRateUpdateState = CurrencyRateUpdateState.Nothing
|
|
||||||
),
|
),
|
||||||
onQueryChange = {},
|
onQueryChange = {},
|
||||||
toggleFavoritesOnly = {},
|
toggleFavoritesOnly = {},
|
@ -33,12 +33,13 @@ import com.sadellie.unitto.data.converter.UnitID
|
|||||||
import com.sadellie.unitto.data.model.UnitGroup
|
import com.sadellie.unitto.data.model.UnitGroup
|
||||||
import com.sadellie.unitto.data.model.unit.AbstractUnit
|
import com.sadellie.unitto.data.model.unit.AbstractUnit
|
||||||
import com.sadellie.unitto.data.model.unit.NormalUnit
|
import com.sadellie.unitto.data.model.unit.NormalUnit
|
||||||
|
import com.sadellie.unitto.feature.converter.UnitSearchResult
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun UnitsList(
|
internal fun UnitsList(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
groupedUnits: Map<UnitGroup, List<AbstractUnit>>,
|
searchResult: UnitSearchResult,
|
||||||
navigateToUnitGroups: () -> Unit,
|
navigateToUnitGroups: () -> Unit,
|
||||||
currentUnitId: String,
|
currentUnitId: String,
|
||||||
supportLabel: (AbstractUnit) -> String,
|
supportLabel: (AbstractUnit) -> String,
|
||||||
@ -47,14 +48,14 @@ internal fun UnitsList(
|
|||||||
) {
|
) {
|
||||||
Crossfade(
|
Crossfade(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
targetState = groupedUnits.isNotEmpty(),
|
targetState = searchResult,
|
||||||
label = "Units list"
|
label = "Units list"
|
||||||
) { hasUnits ->
|
) { result ->
|
||||||
when (hasUnits) {
|
when (result) {
|
||||||
true -> LazyColumn(
|
is UnitSearchResult.Success -> LazyColumn(
|
||||||
modifier = Modifier.fillMaxSize()
|
modifier = Modifier.fillMaxSize()
|
||||||
) {
|
) {
|
||||||
groupedUnits.forEach { (group, units) ->
|
result.units.forEach { (group, units) ->
|
||||||
item(group.name) {
|
item(group.name) {
|
||||||
UnitGroupHeader(Modifier.animateItemPlacement(), group)
|
UnitGroupHeader(Modifier.animateItemPlacement(), group)
|
||||||
}
|
}
|
||||||
@ -73,11 +74,13 @@ internal fun UnitsList(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
false -> SearchPlaceholder(
|
UnitSearchResult.Empty -> SearchPlaceholder(
|
||||||
onButtonClick = navigateToUnitGroups,
|
onButtonClick = navigateToUnitGroups,
|
||||||
supportText = stringResource(R.string.converter_no_results_support),
|
supportText = stringResource(R.string.converter_no_results_support),
|
||||||
buttonLabel = stringResource(R.string.open_settings_label)
|
buttonLabel = stringResource(R.string.open_settings_label)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
UnitSearchResult.Loading -> Unit
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -100,7 +103,7 @@ private fun PreviewUnitsList() {
|
|||||||
|
|
||||||
UnitsList(
|
UnitsList(
|
||||||
modifier = Modifier.fillMaxSize(),
|
modifier = Modifier.fillMaxSize(),
|
||||||
groupedUnits = groupedUnits,
|
searchResult = UnitSearchResult.Success(units = groupedUnits),
|
||||||
navigateToUnitGroups = {},
|
navigateToUnitGroups = {},
|
||||||
currentUnitId = UnitID.mile,
|
currentUnitId = UnitID.mile,
|
||||||
supportLabel = { resources.getString(it.shortName) },
|
supportLabel = { resources.getString(it.shortName) },
|
||||||
|
@ -22,19 +22,42 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.navigation.NavGraphBuilder
|
import androidx.navigation.NavGraphBuilder
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
|
import androidx.navigation.NavType
|
||||||
|
import androidx.navigation.navArgument
|
||||||
import androidx.navigation.navDeepLink
|
import androidx.navigation.navDeepLink
|
||||||
import com.sadellie.unitto.core.ui.model.DrawerItem
|
import com.sadellie.unitto.core.ui.model.DrawerItem
|
||||||
import com.sadellie.unitto.core.ui.unittoComposable
|
import com.sadellie.unitto.core.ui.unittoComposable
|
||||||
import com.sadellie.unitto.core.ui.unittoNavigation
|
import com.sadellie.unitto.core.ui.unittoNavigation
|
||||||
|
import com.sadellie.unitto.data.model.UnitGroup
|
||||||
import com.sadellie.unitto.feature.converter.ConverterRoute
|
import com.sadellie.unitto.feature.converter.ConverterRoute
|
||||||
import com.sadellie.unitto.feature.converter.ConverterViewModel
|
import com.sadellie.unitto.feature.converter.ConverterViewModel
|
||||||
import com.sadellie.unitto.feature.converter.LeftSideRoute
|
import com.sadellie.unitto.feature.converter.CurrencyRateUpdateState
|
||||||
import com.sadellie.unitto.feature.converter.RightSideRoute
|
import com.sadellie.unitto.feature.converter.UnitConverterUIState
|
||||||
|
import com.sadellie.unitto.feature.converter.UnitFromSelectorRoute
|
||||||
|
import com.sadellie.unitto.feature.converter.UnitToSelectorRoute
|
||||||
|
|
||||||
private val graph = DrawerItem.Converter.graph
|
private val graph = DrawerItem.Converter.graph
|
||||||
private val start = DrawerItem.Converter.start
|
private val start = DrawerItem.Converter.start
|
||||||
private const val LEFT = "left"
|
|
||||||
private const val RIGHT = "right"
|
private const val UNIT_FROM = "unitFromSelector"
|
||||||
|
private const val UNIT_TO = "unitToSelector"
|
||||||
|
internal const val unitGroupArg = "unitGroupArg"
|
||||||
|
internal const val unitFromIdArg = "unitFromId"
|
||||||
|
internal const val unitToIdArg = "unitToIdArg"
|
||||||
|
internal const val inputArg = "inputArg"
|
||||||
|
|
||||||
|
private const val UNIT_FROM_ROUTE = "$UNIT_FROM/{$unitFromIdArg}/{$unitGroupArg}"
|
||||||
|
private const val UNIT_TO_ROUTE = "$UNIT_TO/{$unitFromIdArg}/{$unitToIdArg}/{$inputArg}"
|
||||||
|
private fun NavHostController.navigateLeft(
|
||||||
|
unitFromId: String,
|
||||||
|
unitGroup: UnitGroup,
|
||||||
|
) = navigate("$UNIT_FROM/$unitFromId/$unitGroup")
|
||||||
|
|
||||||
|
private fun NavHostController.navigateRight(
|
||||||
|
unitFromId: String,
|
||||||
|
unitToId: String,
|
||||||
|
input: String?,
|
||||||
|
) = navigate("$UNIT_TO/$unitFromId/$unitToId/$input")
|
||||||
|
|
||||||
fun NavGraphBuilder.converterGraph(
|
fun NavGraphBuilder.converterGraph(
|
||||||
openDrawer: () -> Unit,
|
openDrawer: () -> Unit,
|
||||||
@ -58,36 +81,107 @@ fun NavGraphBuilder.converterGraph(
|
|||||||
|
|
||||||
ConverterRoute(
|
ConverterRoute(
|
||||||
viewModel = parentViewModel,
|
viewModel = parentViewModel,
|
||||||
navigateToLeftScreen = { navController.navigate(LEFT) },
|
// Navigation logic is here, but should actually be in ConverterScreen
|
||||||
navigateToRightScreen = { navController.navigate(RIGHT) },
|
navigateToLeftScreen = { uiState: UnitConverterUIState ->
|
||||||
|
when (uiState) {
|
||||||
|
is UnitConverterUIState.Default -> navController
|
||||||
|
.navigateLeft(uiState.unitFrom.id, uiState.unitFrom.group)
|
||||||
|
|
||||||
|
is UnitConverterUIState.NumberBase -> navController
|
||||||
|
.navigateLeft(uiState.unitFrom.id, uiState.unitFrom.group)
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
},
|
||||||
|
navigateToRightScreen = { uiState: UnitConverterUIState ->
|
||||||
|
when (uiState) {
|
||||||
|
is UnitConverterUIState.Default -> {
|
||||||
|
// Don't allow converting if still loading currencies
|
||||||
|
val convertingCurrencies = uiState.unitFrom.group == UnitGroup.CURRENCY
|
||||||
|
val currenciesReady =
|
||||||
|
uiState.currencyRateUpdateState is CurrencyRateUpdateState.Ready
|
||||||
|
|
||||||
|
val input: String? = if (convertingCurrencies and !currenciesReady) {
|
||||||
|
null
|
||||||
|
} else {
|
||||||
|
(uiState.calculation?.toPlainString() ?: uiState.input1.text)
|
||||||
|
.ifEmpty { null }
|
||||||
|
}
|
||||||
|
|
||||||
|
navController.navigateRight(
|
||||||
|
uiState.unitFrom.id,
|
||||||
|
uiState.unitTo.id,
|
||||||
|
input
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is UnitConverterUIState.NumberBase -> {
|
||||||
|
val input = uiState.input.text.ifEmpty { null }
|
||||||
|
navController.navigateRight(
|
||||||
|
uiState.unitFrom.id,
|
||||||
|
uiState.unitTo.id,
|
||||||
|
input
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
UnitConverterUIState.Loading -> Unit
|
||||||
|
}
|
||||||
|
},
|
||||||
navigateToSettings = navigateToSettings,
|
navigateToSettings = navigateToSettings,
|
||||||
navigateToMenu = openDrawer
|
navigateToMenu = openDrawer
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
unittoComposable(LEFT) { backStackEntry ->
|
unittoComposable(
|
||||||
|
route = UNIT_FROM_ROUTE,
|
||||||
|
arguments = listOf(
|
||||||
|
navArgument(unitFromIdArg) {
|
||||||
|
type = NavType.StringType
|
||||||
|
},
|
||||||
|
navArgument(unitGroupArg) {
|
||||||
|
type = NavType.EnumType(UnitGroup::class.java)
|
||||||
|
},
|
||||||
|
)
|
||||||
|
) { backStackEntry ->
|
||||||
val parentEntry = remember(backStackEntry) {
|
val parentEntry = remember(backStackEntry) {
|
||||||
navController.getBackStackEntry(graph)
|
navController.getBackStackEntry(graph)
|
||||||
}
|
}
|
||||||
|
|
||||||
val parentViewModel = hiltViewModel<ConverterViewModel>(parentEntry)
|
val parentViewModel = hiltViewModel<ConverterViewModel>(parentEntry)
|
||||||
|
|
||||||
LeftSideRoute(
|
UnitFromSelectorRoute(
|
||||||
viewModel = parentViewModel,
|
unitSelectorViewModel = hiltViewModel(),
|
||||||
|
converterViewModel = parentViewModel,
|
||||||
navigateUp = navController::navigateUp,
|
navigateUp = navController::navigateUp,
|
||||||
navigateToUnitGroups = navigateToUnitGroups
|
navigateToUnitGroups = navigateToUnitGroups
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
unittoComposable(RIGHT) { backStackEntry ->
|
unittoComposable(
|
||||||
|
route = UNIT_TO_ROUTE,
|
||||||
|
arguments = listOf(
|
||||||
|
navArgument(unitFromIdArg) {
|
||||||
|
type = NavType.StringType
|
||||||
|
},
|
||||||
|
navArgument(unitToIdArg) {
|
||||||
|
type = NavType.StringType
|
||||||
|
},
|
||||||
|
navArgument(inputArg) {
|
||||||
|
type = NavType.StringType
|
||||||
|
nullable = true
|
||||||
|
defaultValue = null
|
||||||
|
},
|
||||||
|
)
|
||||||
|
) { backStackEntry ->
|
||||||
val parentEntry = remember(backStackEntry) {
|
val parentEntry = remember(backStackEntry) {
|
||||||
navController.getBackStackEntry(graph)
|
navController.getBackStackEntry(graph)
|
||||||
}
|
}
|
||||||
|
|
||||||
val parentViewModel = hiltViewModel<ConverterViewModel>(parentEntry)
|
val parentViewModel = hiltViewModel<ConverterViewModel>(parentEntry)
|
||||||
|
|
||||||
RightSideRoute(
|
UnitToSelectorRoute(
|
||||||
viewModel = parentViewModel,
|
unitSelectorViewModel = hiltViewModel(),
|
||||||
|
converterViewModel = parentViewModel,
|
||||||
navigateUp = navController::navigateUp,
|
navigateUp = navController::navigateUp,
|
||||||
navigateToUnitGroups = navigateToUnitGroups
|
navigateToUnitGroups = navigateToUnitGroups
|
||||||
)
|
)
|
||||||
|
Loading…
x
Reference in New Issue
Block a user