mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-19 00:35:26 +02:00
More robust "=" logic
This commit is contained in:
parent
1537df88eb
commit
689c812c11
@ -20,6 +20,7 @@ package com.sadellie.unitto.core.ui.common.textfield
|
||||
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import com.sadellie.unitto.core.base.Token
|
||||
|
||||
fun TextFieldValue.addTokens(tokens: String): TextFieldValue {
|
||||
@ -131,6 +132,18 @@ fun TextFieldValue.deleteTokens(): TextFieldValue {
|
||||
)
|
||||
}
|
||||
|
||||
/**
|
||||
* Tries to get a [TextFieldValue]. Places cursor at the end.
|
||||
*
|
||||
* @receiver [SavedStateHandle] Where to look for.
|
||||
* @param key Key to find
|
||||
* @return [TextFieldValue] with cursor at the end.
|
||||
*/
|
||||
fun SavedStateHandle.getTextField(key: String): TextFieldValue =
|
||||
with(get(key) ?: "") {
|
||||
TextFieldValue(this, TextRange(this.length))
|
||||
}
|
||||
|
||||
/**
|
||||
* <b>!!! Recursive !!!</b> (one wrong step and you are dead 💀)
|
||||
*/
|
||||
|
@ -63,7 +63,7 @@ class CalculatorScreenTest {
|
||||
CalculatorScreen(
|
||||
uiState = CalculatorUIState.Ready(
|
||||
input = TextFieldValue(),
|
||||
output = CalculationResult.Default(),
|
||||
output = CalculationResult.Empty,
|
||||
radianMode = false,
|
||||
precision = 3,
|
||||
outputFormat = OutputFormat.PLAIN,
|
||||
@ -97,7 +97,7 @@ class CalculatorScreenTest {
|
||||
CalculatorScreen(
|
||||
uiState = CalculatorUIState.Ready(
|
||||
input = TextFieldValue(),
|
||||
output = CalculationResult.Default(),
|
||||
output = CalculationResult.Empty,
|
||||
radianMode = false,
|
||||
precision = 3,
|
||||
outputFormat = OutputFormat.PLAIN,
|
||||
|
@ -45,6 +45,8 @@ internal sealed class CalculatorUIState {
|
||||
sealed class CalculationResult {
|
||||
data class Default(val text: String = "") : CalculationResult()
|
||||
|
||||
data object Empty: CalculationResult()
|
||||
|
||||
data object DivideByZeroError : CalculationResult() {
|
||||
@StringRes
|
||||
val label: Int = R.string.calculator_divide_by_zero_error
|
||||
|
@ -28,6 +28,7 @@ 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.addTokens
|
||||
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
|
||||
import com.sadellie.unitto.core.ui.common.textfield.getTextField
|
||||
import com.sadellie.unitto.data.common.isExpression
|
||||
import com.sadellie.unitto.data.common.setMinimumRequiredScale
|
||||
import com.sadellie.unitto.data.common.stateIn
|
||||
@ -45,6 +46,7 @@ import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.mapLatest
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -55,24 +57,23 @@ internal class CalculatorViewModel @Inject constructor(
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
private val _inputKey = "CALCULATOR_INPUT"
|
||||
private val _input = MutableStateFlow(
|
||||
with(savedStateHandle[_inputKey] ?: "") {
|
||||
TextFieldValue(this, TextRange(this.length))
|
||||
}
|
||||
)
|
||||
private val _result = MutableStateFlow<CalculationResult>(CalculationResult.Default())
|
||||
private val _input = MutableStateFlow(savedStateHandle.getTextField(_inputKey))
|
||||
private val _result = MutableStateFlow<CalculationResult>(CalculationResult.Empty)
|
||||
private val _equalClicked = MutableStateFlow(false)
|
||||
private val _prefs = userPrefsRepository.calculatorPrefs
|
||||
.stateIn(viewModelScope, null)
|
||||
|
||||
val uiState: StateFlow<CalculatorUIState> = combine(
|
||||
_input,
|
||||
_result,
|
||||
userPrefsRepository.calculatorPrefs,
|
||||
_prefs,
|
||||
calculatorHistoryRepository.historyFlow,
|
||||
_equalClicked,
|
||||
) { input, result, prefs, history, showError ->
|
||||
) { input, result, prefs, history ->
|
||||
prefs ?: return@combine CalculatorUIState.Loading
|
||||
|
||||
return@combine CalculatorUIState.Ready(
|
||||
input = input,
|
||||
output = if (!showError and (result !is CalculationResult.Default)) CalculationResult.Default() else result,
|
||||
output = result,
|
||||
radianMode = prefs.radianMode,
|
||||
precision = prefs.precision,
|
||||
outputFormat = prefs.outputFormat,
|
||||
@ -85,12 +86,31 @@ internal class CalculatorViewModel @Inject constructor(
|
||||
)
|
||||
}
|
||||
.mapLatest { ui ->
|
||||
calculate(
|
||||
input = ui.input.text,
|
||||
radianMode = ui.radianMode,
|
||||
outputFormat = ui.outputFormat,
|
||||
precision = ui.precision
|
||||
)
|
||||
if (ui !is CalculatorUIState.Ready) return@mapLatest ui
|
||||
if (_equalClicked.value) return@mapLatest ui
|
||||
|
||||
if (!ui.input.text.isExpression()) {
|
||||
_result.update { CalculationResult.Empty }
|
||||
return@mapLatest ui
|
||||
}
|
||||
|
||||
_result.update {
|
||||
try {
|
||||
CalculationResult.Default(
|
||||
calculate(
|
||||
input = ui.input.text,
|
||||
radianMode = ui.radianMode,
|
||||
)
|
||||
.setMinimumRequiredScale(ui.precision)
|
||||
.trimZeros()
|
||||
.toStringWith(ui.outputFormat)
|
||||
)
|
||||
} catch (e: ExpressionException.DivideByZero) {
|
||||
CalculationResult.Empty
|
||||
} catch (e: Exception) {
|
||||
CalculationResult.Empty
|
||||
}
|
||||
}
|
||||
|
||||
ui
|
||||
}
|
||||
@ -130,9 +150,11 @@ internal class CalculatorViewModel @Inject constructor(
|
||||
}
|
||||
|
||||
fun clearInput() = _input.update {
|
||||
_equalClicked.update { false }
|
||||
savedStateHandle[_inputKey] = ""
|
||||
TextFieldValue()
|
||||
}
|
||||
|
||||
fun onCursorChange(selection: TextRange) = _input.update { it.copy(selection = selection) }
|
||||
|
||||
fun updateRadianMode(newValue: Boolean) = viewModelScope.launch {
|
||||
@ -143,56 +165,47 @@ internal class CalculatorViewModel @Inject constructor(
|
||||
calculatorHistoryRepository.clear()
|
||||
}
|
||||
|
||||
fun evaluate() = viewModelScope.launch(Dispatchers.IO) {
|
||||
when (val result = _result.value) {
|
||||
is 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 }
|
||||
}
|
||||
fun evaluate() = viewModelScope.launch {
|
||||
val prefs = _prefs.value ?: return@launch
|
||||
if (_equalClicked.value) return@launch
|
||||
if (!_input.value.text.isExpression()) return@launch
|
||||
|
||||
is CalculationResult.DivideByZeroError -> {
|
||||
_equalClicked.update { true }
|
||||
}
|
||||
|
||||
is CalculationResult.Error -> {
|
||||
// skip for generic error (bad expression and stuff
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun calculate(
|
||||
input: String,
|
||||
radianMode: Boolean,
|
||||
outputFormat: Int,
|
||||
precision: Int,
|
||||
) = viewModelScope.launch(Dispatchers.Default) {
|
||||
if (!input.isExpression()) {
|
||||
_result.update { CalculationResult.Default() }
|
||||
val result = try {
|
||||
calculate(_input.value.text, prefs.radianMode)
|
||||
} catch (e: ExpressionException.DivideByZero) {
|
||||
_equalClicked.update { true }
|
||||
_result.update { CalculationResult.DivideByZeroError }
|
||||
return@launch
|
||||
} catch (e: ExpressionException.FactorialCalculation) {
|
||||
_equalClicked.update { true }
|
||||
_result.update { CalculationResult.Error }
|
||||
return@launch
|
||||
} catch (e: Exception) {
|
||||
_equalClicked.update { true }
|
||||
_result.update { CalculationResult.Error }
|
||||
return@launch
|
||||
}
|
||||
.setMinimumRequiredScale(prefs.precision)
|
||||
.trimZeros()
|
||||
.toStringWith(prefs.outputFormat)
|
||||
|
||||
_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
|
||||
calculatorHistoryRepository.add(
|
||||
expression = _input.value.text.replace("-", Token.Operator.minus),
|
||||
result = result
|
||||
)
|
||||
|
||||
_input.update { TextFieldValue(result, TextRange(result.length)) }
|
||||
_result.update { CalculationResult.Empty }
|
||||
}
|
||||
|
||||
private suspend fun calculate(
|
||||
input: String,
|
||||
radianMode: Boolean,
|
||||
): BigDecimal = withContext(Dispatchers.Default) {
|
||||
Expression(input, radianMode)
|
||||
.calculate()
|
||||
.also {
|
||||
if (it > BigDecimal.valueOf(Double.MAX_VALUE)) throw ExpressionException.TooBig()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import android.content.res.Configuration
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
@ -83,6 +84,15 @@ fun TextBox(
|
||||
)
|
||||
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
when (output) {
|
||||
is CalculationResult.Empty -> {
|
||||
Spacer(
|
||||
modifier = Modifier
|
||||
.weight(2f)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp)
|
||||
)
|
||||
}
|
||||
|
||||
is CalculationResult.Default -> {
|
||||
var outputTF by remember(output) {
|
||||
mutableStateOf(TextFieldValue(output.text))
|
||||
|
@ -28,6 +28,7 @@ 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.addTokens
|
||||
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
|
||||
import com.sadellie.unitto.core.ui.common.textfield.getTextField
|
||||
import com.sadellie.unitto.data.common.combine
|
||||
import com.sadellie.unitto.data.common.isExpression
|
||||
import com.sadellie.unitto.data.common.stateIn
|
||||
@ -64,11 +65,7 @@ internal class ConverterViewModel @Inject constructor(
|
||||
) : ViewModel() {
|
||||
|
||||
private val converterInputKey = "CONVERTER_INPUT"
|
||||
private val _input = MutableStateFlow(
|
||||
with(savedStateHandle[converterInputKey] ?: "") {
|
||||
TextFieldValue(this, TextRange(this.length))
|
||||
}
|
||||
)
|
||||
private val _input = MutableStateFlow(savedStateHandle.getTextField(converterInputKey))
|
||||
private val _calculation = MutableStateFlow<BigDecimal?>(null)
|
||||
private val _result = MutableStateFlow<ConverterResult>(ConverterResult.Loading)
|
||||
private val _unitFrom = MutableStateFlow<AbstractUnit?>(null)
|
||||
|
Loading…
x
Reference in New Issue
Block a user