mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-19 00:35:26 +02:00
Refactor CalculatorViewModel
This commit is contained in:
parent
48e75093bd
commit
e42fd625a5
@ -25,7 +25,10 @@ import androidx.compose.ui.test.onNodeWithText
|
|||||||
import androidx.compose.ui.test.performClick
|
import androidx.compose.ui.test.performClick
|
||||||
import androidx.compose.ui.test.performTouchInput
|
import androidx.compose.ui.test.performTouchInput
|
||||||
import androidx.compose.ui.test.swipeDown
|
import androidx.compose.ui.test.swipeDown
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import com.sadellie.unitto.core.base.OutputFormat
|
||||||
import com.sadellie.unitto.core.base.R
|
import com.sadellie.unitto.core.base.R
|
||||||
|
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
|
||||||
import org.junit.Rule
|
import org.junit.Rule
|
||||||
import org.junit.Test
|
import org.junit.Test
|
||||||
|
|
||||||
@ -58,7 +61,19 @@ class CalculatorScreenTest {
|
|||||||
fun ready_showRealKeyboard(): Unit = with(composeTestRule) {
|
fun ready_showRealKeyboard(): Unit = with(composeTestRule) {
|
||||||
setContent {
|
setContent {
|
||||||
CalculatorScreen(
|
CalculatorScreen(
|
||||||
uiState = CalculatorUIState.Ready(),
|
uiState = CalculatorUIState.Ready(
|
||||||
|
input = TextFieldValue(),
|
||||||
|
output = CalculationResult.Default(),
|
||||||
|
radianMode = false,
|
||||||
|
precision = 3,
|
||||||
|
outputFormat = OutputFormat.PLAIN,
|
||||||
|
formatterSymbols = FormatterSymbols.Spaces,
|
||||||
|
history = emptyList(),
|
||||||
|
allowVibration = false,
|
||||||
|
middleZero = false,
|
||||||
|
acButton = true,
|
||||||
|
partialHistoryView = true
|
||||||
|
),
|
||||||
navigateToMenu = {},
|
navigateToMenu = {},
|
||||||
navigateToSettings = {},
|
navigateToSettings = {},
|
||||||
addTokens = {},
|
addTokens = {},
|
||||||
@ -80,7 +95,19 @@ class CalculatorScreenTest {
|
|||||||
fun ready_swipeForHistory(): Unit = with(composeTestRule) {
|
fun ready_swipeForHistory(): Unit = with(composeTestRule) {
|
||||||
setContent {
|
setContent {
|
||||||
CalculatorScreen(
|
CalculatorScreen(
|
||||||
uiState = CalculatorUIState.Ready(),
|
uiState = CalculatorUIState.Ready(
|
||||||
|
input = TextFieldValue(),
|
||||||
|
output = CalculationResult.Default(),
|
||||||
|
radianMode = false,
|
||||||
|
precision = 3,
|
||||||
|
outputFormat = OutputFormat.PLAIN,
|
||||||
|
formatterSymbols = FormatterSymbols.Spaces,
|
||||||
|
history = emptyList(),
|
||||||
|
allowVibration = false,
|
||||||
|
middleZero = false,
|
||||||
|
acButton = true,
|
||||||
|
partialHistoryView = true
|
||||||
|
),
|
||||||
navigateToMenu = {},
|
navigateToMenu = {},
|
||||||
navigateToSettings = {},
|
navigateToSettings = {},
|
||||||
addTokens = {},
|
addTokens = {},
|
||||||
|
@ -21,6 +21,7 @@ package com.sadellie.unitto.feature.calculator
|
|||||||
import androidx.activity.compose.BackHandler
|
import androidx.activity.compose.BackHandler
|
||||||
import androidx.compose.animation.Crossfade
|
import androidx.compose.animation.Crossfade
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.gestures.AnchoredDraggableState
|
import androidx.compose.foundation.gestures.AnchoredDraggableState
|
||||||
import androidx.compose.foundation.gestures.DraggableAnchors
|
import androidx.compose.foundation.gestures.DraggableAnchors
|
||||||
import androidx.compose.foundation.gestures.Orientation
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
@ -61,11 +62,13 @@ 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.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.sadellie.unitto.core.base.OutputFormat
|
||||||
import com.sadellie.unitto.core.base.R
|
import com.sadellie.unitto.core.base.R
|
||||||
import com.sadellie.unitto.core.ui.common.MenuButton
|
import com.sadellie.unitto.core.ui.common.MenuButton
|
||||||
import com.sadellie.unitto.core.ui.common.SettingsButton
|
import com.sadellie.unitto.core.ui.common.SettingsButton
|
||||||
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
|
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
|
||||||
import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar
|
import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar
|
||||||
|
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
|
||||||
import com.sadellie.unitto.data.model.HistoryItem
|
import com.sadellie.unitto.data.model.HistoryItem
|
||||||
import com.sadellie.unitto.feature.calculator.components.CalculatorKeyboard
|
import com.sadellie.unitto.feature.calculator.components.CalculatorKeyboard
|
||||||
import com.sadellie.unitto.feature.calculator.components.HistoryItemHeight
|
import com.sadellie.unitto.feature.calculator.components.HistoryItemHeight
|
||||||
@ -91,7 +94,7 @@ internal fun CalculatorRoute(
|
|||||||
clearInput = viewModel::clearInput,
|
clearInput = viewModel::clearInput,
|
||||||
deleteTokens = viewModel::deleteTokens,
|
deleteTokens = viewModel::deleteTokens,
|
||||||
onCursorChange = viewModel::onCursorChange,
|
onCursorChange = viewModel::onCursorChange,
|
||||||
toggleCalculatorMode = viewModel::toggleCalculatorMode,
|
toggleCalculatorMode = viewModel::updateRadianMode,
|
||||||
evaluate = viewModel::evaluate,
|
evaluate = viewModel::evaluate,
|
||||||
clearHistory = viewModel::clearHistory,
|
clearHistory = viewModel::clearHistory,
|
||||||
addBracket = viewModel::addBracket
|
addBracket = viewModel::addBracket
|
||||||
@ -108,7 +111,7 @@ internal fun CalculatorScreen(
|
|||||||
clearInput: () -> Unit,
|
clearInput: () -> Unit,
|
||||||
deleteTokens: () -> Unit,
|
deleteTokens: () -> Unit,
|
||||||
onCursorChange: (TextRange) -> Unit,
|
onCursorChange: (TextRange) -> Unit,
|
||||||
toggleCalculatorMode: () -> Unit,
|
toggleCalculatorMode: (Boolean) -> Unit,
|
||||||
evaluate: () -> Unit,
|
evaluate: () -> Unit,
|
||||||
clearHistory: () -> Unit
|
clearHistory: () -> Unit
|
||||||
) {
|
) {
|
||||||
@ -122,7 +125,7 @@ internal fun CalculatorScreen(
|
|||||||
clearSymbols = clearInput,
|
clearSymbols = clearInput,
|
||||||
deleteSymbol = deleteTokens,
|
deleteSymbol = deleteTokens,
|
||||||
onCursorChange = onCursorChange,
|
onCursorChange = onCursorChange,
|
||||||
toggleAngleMode = toggleCalculatorMode,
|
toggleAngleMode = { toggleCalculatorMode(!uiState.radianMode) },
|
||||||
evaluate = evaluate,
|
evaluate = evaluate,
|
||||||
clearHistory = clearHistory,
|
clearHistory = clearHistory,
|
||||||
addBracket = addBracket
|
addBracket = addBracket
|
||||||
@ -238,6 +241,7 @@ private fun Ready(
|
|||||||
|
|
||||||
HistoryList(
|
HistoryList(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f))
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.height(historyListHeight),
|
.height(historyListHeight),
|
||||||
historyItems = uiState.history,
|
historyItems = uiState.history,
|
||||||
@ -350,7 +354,15 @@ private fun PreviewCalculatorScreen() {
|
|||||||
uiState = CalculatorUIState.Ready(
|
uiState = CalculatorUIState.Ready(
|
||||||
input = TextFieldValue("1.2345"),
|
input = TextFieldValue("1.2345"),
|
||||||
output = CalculationResult.Default("1234"),
|
output = CalculationResult.Default("1234"),
|
||||||
history = historyItems
|
radianMode = false,
|
||||||
|
precision = 3,
|
||||||
|
outputFormat = OutputFormat.PLAIN,
|
||||||
|
formatterSymbols = FormatterSymbols.Spaces,
|
||||||
|
history = historyItems,
|
||||||
|
allowVibration = false,
|
||||||
|
middleZero = false,
|
||||||
|
acButton = true,
|
||||||
|
partialHistoryView = true
|
||||||
),
|
),
|
||||||
navigateToMenu = {},
|
navigateToMenu = {},
|
||||||
navigateToSettings = {},
|
navigateToSettings = {},
|
||||||
|
@ -28,20 +28,30 @@ internal sealed class CalculatorUIState {
|
|||||||
data object Loading : CalculatorUIState()
|
data object Loading : CalculatorUIState()
|
||||||
|
|
||||||
data class Ready(
|
data class Ready(
|
||||||
val input: TextFieldValue = TextFieldValue(),
|
val input: TextFieldValue,
|
||||||
val output: CalculationResult = CalculationResult.Default(),
|
val output: CalculationResult,
|
||||||
val radianMode: Boolean = true,
|
val radianMode: Boolean,
|
||||||
val history: List<HistoryItem> = emptyList(),
|
val precision: Int,
|
||||||
val allowVibration: Boolean = false,
|
val outputFormat: Int,
|
||||||
val formatterSymbols: FormatterSymbols = FormatterSymbols.Spaces,
|
val formatterSymbols: FormatterSymbols,
|
||||||
val middleZero: Boolean = false,
|
val history: List<HistoryItem>,
|
||||||
val acButton: Boolean = false,
|
val allowVibration: Boolean,
|
||||||
val partialHistoryView: Boolean = true,
|
val middleZero: Boolean,
|
||||||
|
val acButton: Boolean,
|
||||||
|
val partialHistoryView: Boolean,
|
||||||
) : CalculatorUIState()
|
) : CalculatorUIState()
|
||||||
}
|
}
|
||||||
|
|
||||||
sealed class CalculationResult(@StringRes val label: Int? = null) {
|
sealed class CalculationResult {
|
||||||
data class Default(val text: String = "") : CalculationResult()
|
data class Default(val text: String = "") : CalculationResult()
|
||||||
data object DivideByZeroError : CalculationResult(R.string.calculator_divide_by_zero_error)
|
|
||||||
data object Error : CalculationResult(R.string.error_label)
|
data object DivideByZeroError : CalculationResult() {
|
||||||
|
@StringRes
|
||||||
|
val label: Int = R.string.calculator_divide_by_zero_error
|
||||||
|
}
|
||||||
|
|
||||||
|
data object Error : CalculationResult() {
|
||||||
|
@StringRes
|
||||||
|
val label: Int = R.string.error_label
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,10 +20,9 @@ package com.sadellie.unitto.feature.calculator
|
|||||||
|
|
||||||
import androidx.compose.ui.text.TextRange
|
import androidx.compose.ui.text.TextRange
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.lifecycle.SavedStateHandle
|
||||||
import androidx.lifecycle.ViewModel
|
import androidx.lifecycle.ViewModel
|
||||||
import androidx.lifecycle.viewModelScope
|
import androidx.lifecycle.viewModelScope
|
||||||
import com.sadellie.unitto.core.base.OutputFormat
|
|
||||||
import com.sadellie.unitto.core.base.Separator
|
|
||||||
import com.sadellie.unitto.core.base.Token
|
import com.sadellie.unitto.core.base.Token
|
||||||
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
|
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
|
||||||
import com.sadellie.unitto.core.ui.common.textfield.addBracket
|
import com.sadellie.unitto.core.ui.common.textfield.addBracket
|
||||||
@ -32,21 +31,18 @@ import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
|
|||||||
import com.sadellie.unitto.data.calculator.CalculatorHistoryRepository
|
import com.sadellie.unitto.data.calculator.CalculatorHistoryRepository
|
||||||
import com.sadellie.unitto.data.common.isExpression
|
import com.sadellie.unitto.data.common.isExpression
|
||||||
import com.sadellie.unitto.data.common.setMinimumRequiredScale
|
import com.sadellie.unitto.data.common.setMinimumRequiredScale
|
||||||
|
import com.sadellie.unitto.data.common.stateIn
|
||||||
import com.sadellie.unitto.data.common.toStringWith
|
import com.sadellie.unitto.data.common.toStringWith
|
||||||
import com.sadellie.unitto.data.common.trimZeros
|
import com.sadellie.unitto.data.common.trimZeros
|
||||||
import com.sadellie.unitto.data.userprefs.CalculatorPreferences
|
|
||||||
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
|
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
import io.github.sadellie.evaluatto.Expression
|
import io.github.sadellie.evaluatto.Expression
|
||||||
import io.github.sadellie.evaluatto.ExpressionException
|
import io.github.sadellie.evaluatto.ExpressionException
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
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.collectLatest
|
|
||||||
import kotlinx.coroutines.flow.combine
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.flow.merge
|
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 java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
@ -56,139 +52,146 @@ import javax.inject.Inject
|
|||||||
internal class CalculatorViewModel @Inject constructor(
|
internal class CalculatorViewModel @Inject constructor(
|
||||||
private val userPrefsRepository: UserPreferencesRepository,
|
private val userPrefsRepository: UserPreferencesRepository,
|
||||||
private val calculatorHistoryRepository: CalculatorHistoryRepository,
|
private val calculatorHistoryRepository: CalculatorHistoryRepository,
|
||||||
|
private val savedStateHandle: SavedStateHandle,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
private val _prefs: StateFlow<CalculatorPreferences> =
|
private val _inputKey = "CALCULATOR_INPUT"
|
||||||
userPrefsRepository.calculatorPrefs.stateIn(
|
private val _input = MutableStateFlow(
|
||||||
viewModelScope,
|
with(savedStateHandle[_inputKey] ?: "") {
|
||||||
SharingStarted.WhileSubscribed(5000L),
|
TextFieldValue(this, TextRange(this.length))
|
||||||
CalculatorPreferences(
|
}
|
||||||
radianMode = false,
|
)
|
||||||
enableVibrations = false,
|
private val _result = MutableStateFlow<CalculationResult>(CalculationResult.Default())
|
||||||
separator = Separator.SPACE,
|
|
||||||
middleZero = false,
|
|
||||||
partialHistoryView = true,
|
|
||||||
precision = 3,
|
|
||||||
outputFormat = OutputFormat.PLAIN,
|
|
||||||
acButton = false,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
private val _input: MutableStateFlow<TextFieldValue> = MutableStateFlow(TextFieldValue())
|
|
||||||
private val _output: MutableStateFlow<CalculationResult> =
|
|
||||||
MutableStateFlow(CalculationResult.Default())
|
|
||||||
private val _history = calculatorHistoryRepository.historyFlow
|
|
||||||
private val _equalClicked = MutableStateFlow(false)
|
private val _equalClicked = MutableStateFlow(false)
|
||||||
|
|
||||||
val uiState = combine(
|
val uiState: StateFlow<CalculatorUIState> = combine(
|
||||||
_input, _output, _history, _prefs
|
_input,
|
||||||
) { input, output, history, userPrefs ->
|
_result,
|
||||||
|
userPrefsRepository.calculatorPrefs,
|
||||||
|
calculatorHistoryRepository.historyFlow,
|
||||||
|
_equalClicked,
|
||||||
|
) { input, result, prefs, history, showError ->
|
||||||
return@combine CalculatorUIState.Ready(
|
return@combine CalculatorUIState.Ready(
|
||||||
input = input,
|
input = input,
|
||||||
output = output,
|
output = if (!showError and (result !is CalculationResult.Default)) CalculationResult.Default() else result,
|
||||||
radianMode = userPrefs.radianMode,
|
radianMode = prefs.radianMode,
|
||||||
|
precision = prefs.precision,
|
||||||
|
outputFormat = prefs.outputFormat,
|
||||||
|
formatterSymbols = AllFormatterSymbols.getById(prefs.separator),
|
||||||
history = history,
|
history = history,
|
||||||
allowVibration = userPrefs.enableVibrations,
|
allowVibration = prefs.enableVibrations,
|
||||||
formatterSymbols = AllFormatterSymbols.getById(userPrefs.separator),
|
middleZero = prefs.middleZero,
|
||||||
middleZero = userPrefs.middleZero,
|
acButton = prefs.acButton,
|
||||||
acButton = userPrefs.acButton,
|
partialHistoryView = prefs.partialHistoryView,
|
||||||
partialHistoryView = userPrefs.partialHistoryView,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
.stateIn(
|
.mapLatest { ui ->
|
||||||
viewModelScope, SharingStarted.WhileSubscribed(5000L), CalculatorUIState.Loading
|
calculate(
|
||||||
)
|
input = ui.input.text,
|
||||||
|
radianMode = ui.radianMode,
|
||||||
|
outputFormat = ui.outputFormat,
|
||||||
|
precision = ui.precision
|
||||||
|
)
|
||||||
|
|
||||||
|
ui
|
||||||
|
}
|
||||||
|
.stateIn(viewModelScope, CalculatorUIState.Loading)
|
||||||
|
|
||||||
fun addTokens(tokens: String) = _input.update {
|
fun addTokens(tokens: String) = _input.update {
|
||||||
if (_equalClicked.value) {
|
val newValue = if (_equalClicked.value) {
|
||||||
_equalClicked.update { false }
|
_equalClicked.update { false }
|
||||||
TextFieldValue().addTokens(tokens)
|
TextFieldValue().addTokens(tokens)
|
||||||
} else {
|
} else {
|
||||||
it.addTokens(tokens)
|
it.addTokens(tokens)
|
||||||
}
|
}
|
||||||
|
savedStateHandle[_inputKey] = newValue.text
|
||||||
|
newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
fun addBracket() = _input.update {
|
fun addBracket() = _input.update {
|
||||||
if (_equalClicked.value) {
|
val newValue = if (_equalClicked.value) {
|
||||||
_equalClicked.update { false }
|
_equalClicked.update { false }
|
||||||
TextFieldValue().addBracket()
|
TextFieldValue().addBracket()
|
||||||
} else {
|
} else {
|
||||||
it.addBracket()
|
it.addBracket()
|
||||||
}
|
}
|
||||||
|
savedStateHandle[_inputKey] = newValue.text
|
||||||
|
newValue
|
||||||
}
|
}
|
||||||
|
|
||||||
fun deleteTokens() = _input.update {
|
fun deleteTokens() = _input.update {
|
||||||
if (_equalClicked.value) {
|
val newValue = if (_equalClicked.value) {
|
||||||
_equalClicked.update { false }
|
_equalClicked.update { false }
|
||||||
TextFieldValue().deleteTokens()
|
TextFieldValue().deleteTokens()
|
||||||
} else {
|
} else {
|
||||||
it.deleteTokens()
|
it.deleteTokens()
|
||||||
}
|
}
|
||||||
|
savedStateHandle[_inputKey] = newValue.text
|
||||||
|
newValue
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearInput() = _input.update {
|
||||||
|
savedStateHandle[_inputKey] = ""
|
||||||
|
TextFieldValue()
|
||||||
}
|
}
|
||||||
fun clearInput() = _input.update { TextFieldValue() }
|
|
||||||
fun onCursorChange(selection: TextRange) = _input.update { it.copy(selection = selection) }
|
fun onCursorChange(selection: TextRange) = _input.update { it.copy(selection = selection) }
|
||||||
|
|
||||||
// Called when user clicks "=" on a keyboard
|
fun updateRadianMode(newValue: Boolean) = viewModelScope.launch {
|
||||||
fun evaluate() = viewModelScope.launch(Dispatchers.IO) {
|
userPrefsRepository.updateRadianMode(newValue)
|
||||||
when (val calculationResult = calculateInput()) {
|
|
||||||
is CalculationResult.Default -> {
|
|
||||||
if (calculationResult.text.isEmpty()) return@launch
|
|
||||||
|
|
||||||
// We can get negative number and they use ugly minus symbol
|
|
||||||
val calculationText = calculationResult.text.replace("-", Token.Operator.minus)
|
|
||||||
|
|
||||||
calculatorHistoryRepository.add(
|
|
||||||
expression = _input.value.text,
|
|
||||||
result = calculationText
|
|
||||||
)
|
|
||||||
_input.update {
|
|
||||||
TextFieldValue(calculationText, TextRange(calculationText.length))
|
|
||||||
}
|
|
||||||
_output.update { CalculationResult.Default() }
|
|
||||||
_equalClicked.update { true }
|
|
||||||
}
|
|
||||||
// Show the error
|
|
||||||
else -> _output.update { calculationResult }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun toggleCalculatorMode() = viewModelScope.launch {
|
|
||||||
userPrefsRepository.updateRadianMode(!_prefs.value.radianMode)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun clearHistory() = viewModelScope.launch(Dispatchers.IO) {
|
fun clearHistory() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
calculatorHistoryRepository.clear()
|
calculatorHistoryRepository.clear()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun calculateInput(): CalculationResult {
|
fun evaluate() = viewModelScope.launch(Dispatchers.IO) {
|
||||||
val currentInput = _input.value.text
|
when (val result = _result.value) {
|
||||||
// Input is empty or not an expression, don't calculate
|
is CalculationResult.Default -> {
|
||||||
if (!currentInput.isExpression()) return CalculationResult.Default()
|
calculatorHistoryRepository.add(
|
||||||
|
expression = _input.value.text.replace("-", Token.Operator.minus),
|
||||||
|
result = result.text
|
||||||
|
)
|
||||||
|
_input.update { TextFieldValue(result.text, TextRange(result.text.length)) }
|
||||||
|
_result.update { CalculationResult.Default() }
|
||||||
|
_equalClicked.update { true }
|
||||||
|
}
|
||||||
|
|
||||||
return try {
|
is CalculationResult.DivideByZeroError -> {
|
||||||
CalculationResult.Default(
|
_equalClicked.update { true }
|
||||||
Expression(currentInput, radianMode = _prefs.value.radianMode)
|
}
|
||||||
.calculate()
|
|
||||||
.also {
|
is CalculationResult.Error -> {
|
||||||
if (it > BigDecimal.valueOf(Double.MAX_VALUE)) throw ExpressionException.TooBig()
|
// skip for generic error (bad expression and stuff
|
||||||
}
|
}
|
||||||
.setMinimumRequiredScale(_prefs.value.precision)
|
|
||||||
.trimZeros()
|
|
||||||
.toStringWith(_prefs.value.outputFormat)
|
|
||||||
)
|
|
||||||
} catch (e: ExpressionException.DivideByZero) {
|
|
||||||
CalculationResult.DivideByZeroError
|
|
||||||
} catch (e: Exception) {
|
|
||||||
CalculationResult.Error
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
init {
|
private fun calculate(
|
||||||
// Observe and invoke calculation without UI lag.
|
input: String,
|
||||||
viewModelScope.launch(Dispatchers.Default) {
|
radianMode: Boolean,
|
||||||
merge(_prefs, _input).collectLatest {
|
outputFormat: Int,
|
||||||
val calculated = calculateInput()
|
precision: Int,
|
||||||
_output.update {
|
) = viewModelScope.launch(Dispatchers.Default) {
|
||||||
// Don't show error when simply entering stuff
|
if (!input.isExpression()) {
|
||||||
if (calculated !is CalculationResult.Default) CalculationResult.Default() else calculated
|
_result.update { CalculationResult.Default() }
|
||||||
}
|
return@launch
|
||||||
|
}
|
||||||
|
|
||||||
|
_result.update {
|
||||||
|
try {
|
||||||
|
CalculationResult.Default(
|
||||||
|
Expression(input, radianMode = radianMode)
|
||||||
|
.calculate()
|
||||||
|
.also {
|
||||||
|
if (it > BigDecimal.valueOf(Double.MAX_VALUE)) throw ExpressionException.TooBig()
|
||||||
|
}
|
||||||
|
.setMinimumRequiredScale(precision)
|
||||||
|
.trimZeros()
|
||||||
|
.toStringWith(outputFormat)
|
||||||
|
)
|
||||||
|
} catch (e: ExpressionException.DivideByZero) {
|
||||||
|
CalculationResult.DivideByZeroError
|
||||||
|
} catch (e: Exception) {
|
||||||
|
CalculationResult.Error
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -102,15 +102,27 @@ fun TextBox(
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
else -> {
|
is CalculationResult.DivideByZeroError -> {
|
||||||
val label = output.label?.let { stringResource(it) } ?: ""
|
|
||||||
|
|
||||||
UnformattedTextField(
|
UnformattedTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.weight(2f)
|
.weight(2f)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 8.dp),
|
.padding(horizontal = 8.dp),
|
||||||
value = TextFieldValue(label),
|
value = TextFieldValue(stringResource(output.label)),
|
||||||
|
minRatio = 0.8f,
|
||||||
|
onCursorChange = {},
|
||||||
|
textColor = MaterialTheme.colorScheme.error,
|
||||||
|
readOnly = true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
is CalculationResult.Error -> {
|
||||||
|
UnformattedTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(2f)
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp),
|
||||||
|
value = TextFieldValue(stringResource(output.label)),
|
||||||
minRatio = 0.8f,
|
minRatio = 0.8f,
|
||||||
onCursorChange = {},
|
onCursorChange = {},
|
||||||
textColor = MaterialTheme.colorScheme.error,
|
textColor = MaterialTheme.colorScheme.error,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user