mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-18 16:25:27 +02:00
Remove RPN experiment
This commit is contained in:
parent
4a499b8fd3
commit
b2030ad9dc
@ -112,7 +112,6 @@ internal fun ComponentActivity.App(prefs: AppPreferences?) {
|
||||
navController = navController,
|
||||
themmoController = it,
|
||||
startDestination = prefs.startingScreen,
|
||||
rpnMode = prefs.rpnMode,
|
||||
openDrawer = { drawerScope.launch { drawerState.open() } }
|
||||
)
|
||||
}
|
||||
|
@ -42,7 +42,6 @@ internal fun UnittoNavigation(
|
||||
themmoController: ThemmoController,
|
||||
startDestination: String,
|
||||
openDrawer: () -> Unit,
|
||||
rpnMode: Boolean,
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
@ -65,7 +64,6 @@ internal fun UnittoNavigation(
|
||||
|
||||
calculatorGraph(
|
||||
openDrawer = openDrawer,
|
||||
rpnMode = rpnMode,
|
||||
navigateToSettings = navController::navigateToSettings
|
||||
)
|
||||
|
||||
|
@ -1,37 +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 io.github.sadellie.evaluatto
|
||||
|
||||
sealed class RPNCalculation {
|
||||
data object Negate : RPNCalculation()
|
||||
|
||||
data object Clear : RPNCalculation()
|
||||
data object Enter : RPNCalculation()
|
||||
data object RotateUp : RPNCalculation()
|
||||
data object RotateDown : RPNCalculation()
|
||||
data object Swap : RPNCalculation()
|
||||
data object Pop : RPNCalculation()
|
||||
|
||||
data object Plus : RPNCalculation()
|
||||
data object Minus : RPNCalculation()
|
||||
data object Multiply : RPNCalculation()
|
||||
data object Divide : RPNCalculation()
|
||||
data object Percent : RPNCalculation()
|
||||
data object Power : RPNCalculation() // unused
|
||||
}
|
@ -1,191 +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 io.github.sadellie.evaluatto
|
||||
|
||||
import com.sadellie.unitto.core.base.MAX_PRECISION
|
||||
import java.math.BigDecimal
|
||||
import java.math.RoundingMode
|
||||
|
||||
sealed class RPNResult {
|
||||
|
||||
/**
|
||||
* Both input and stack were changed.
|
||||
*
|
||||
* @property input New input. Empty when `null`.
|
||||
* @property stack New stack.
|
||||
*/
|
||||
data class Result(
|
||||
val input: BigDecimal?,
|
||||
val stack: List<BigDecimal>,
|
||||
) : RPNResult()
|
||||
|
||||
/**
|
||||
* Only input has been changed.
|
||||
*
|
||||
* @property input New input.
|
||||
*/
|
||||
data class NewInput(
|
||||
val input: BigDecimal,
|
||||
) : RPNResult()
|
||||
|
||||
/**
|
||||
* Only stack has been changed.
|
||||
*
|
||||
* @property stack New stack.
|
||||
*/
|
||||
data class NewStack(
|
||||
val stack: List<BigDecimal>,
|
||||
) : RPNResult()
|
||||
|
||||
/**
|
||||
* Something is wrong. Input/stack is empty or there ane not enough stack objects.
|
||||
*/
|
||||
data object BadInput : RPNResult()
|
||||
|
||||
/**
|
||||
* Dividing by zero, duh
|
||||
*/
|
||||
data object DivideByZero : RPNResult()
|
||||
}
|
||||
|
||||
// vroom vroom mfs
|
||||
// overdose on early returns
|
||||
fun RPNCalculation.perform(
|
||||
input: String,
|
||||
stack: List<BigDecimal>,
|
||||
): RPNResult {
|
||||
when (this) {
|
||||
RPNCalculation.Clear -> {
|
||||
return RPNResult.Result(null, emptyList())
|
||||
}
|
||||
|
||||
RPNCalculation.Enter -> {
|
||||
val inputBD = input.toBigDecimalOrNull() ?: return RPNResult.BadInput
|
||||
return RPNResult.Result(null, stack + inputBD)
|
||||
}
|
||||
|
||||
RPNCalculation.Negate -> {
|
||||
val inputBD = input.toBigDecimalOrNull() ?: return RPNResult.BadInput
|
||||
val result = inputBD.negate()
|
||||
return RPNResult.NewInput(result)
|
||||
}
|
||||
|
||||
RPNCalculation.RotateUp -> {
|
||||
if (stack.size < 2) return RPNResult.BadInput
|
||||
return RPNResult.NewStack(stack.rotateUp())
|
||||
}
|
||||
|
||||
RPNCalculation.RotateDown -> {
|
||||
if (stack.size < 2) return RPNResult.BadInput
|
||||
return RPNResult.NewStack(stack.rotateDown())
|
||||
}
|
||||
|
||||
RPNCalculation.Swap -> {
|
||||
if (stack.isEmpty()) return RPNResult.BadInput
|
||||
if (input.isEmpty()) {
|
||||
// Swap last 2 in stack
|
||||
if (stack.size < 2) return RPNResult.BadInput
|
||||
return RPNResult.NewStack(stack.swapLastTwo())
|
||||
}
|
||||
|
||||
// Swap last and input
|
||||
val (lastFromStack, inputBD) = operands(stack, input) ?: return RPNResult.BadInput
|
||||
return RPNResult.Result(lastFromStack, stack.dropLast(1) + inputBD)
|
||||
}
|
||||
|
||||
RPNCalculation.Pop -> {
|
||||
val lastStacked = stack.lastOrNull() ?: return RPNResult.BadInput
|
||||
return RPNResult.Result(lastStacked, stack.dropLast(1))
|
||||
}
|
||||
|
||||
RPNCalculation.Plus -> {
|
||||
val (lastFromStack, inputBD) = operands(stack, input) ?: return RPNResult.BadInput
|
||||
val result = lastFromStack.plus(inputBD)
|
||||
return RPNResult.Result(result, stack.dropLast(1))
|
||||
}
|
||||
|
||||
RPNCalculation.Minus -> {
|
||||
val (lastFromStack, inputBD) = operands(stack, input) ?: return RPNResult.BadInput
|
||||
val result = lastFromStack.minus(inputBD)
|
||||
return RPNResult.Result(result, stack.dropLast(1))
|
||||
}
|
||||
|
||||
RPNCalculation.Multiply -> {
|
||||
val (lastFromStack, inputBD) = operands(stack, input) ?: return RPNResult.BadInput
|
||||
val result = lastFromStack.multiply(inputBD)
|
||||
return RPNResult.Result(result, stack.dropLast(1))
|
||||
}
|
||||
|
||||
RPNCalculation.Divide -> {
|
||||
val (lastFromStack, inputBD) = operands(stack, input) ?: return RPNResult.BadInput
|
||||
if (inputBD.compareTo(BigDecimal.ZERO) == 0) return RPNResult.DivideByZero
|
||||
|
||||
val result = lastFromStack.divide(inputBD, MAX_PRECISION, RoundingMode.HALF_EVEN)
|
||||
return RPNResult.Result(result, stack.dropLast(1))
|
||||
}
|
||||
|
||||
RPNCalculation.Percent -> {
|
||||
val (lastFromStack, inputBD) = operands(stack, input) ?: return RPNResult.BadInput
|
||||
// 100 * 24 / 100 =
|
||||
val result = lastFromStack
|
||||
.multiply(inputBD)
|
||||
.divide(bigDecimalHundred, MAX_PRECISION, RoundingMode.HALF_EVEN)
|
||||
return RPNResult.Result(result, stack.dropLast(1))
|
||||
}
|
||||
|
||||
RPNCalculation.Power -> {
|
||||
val (lastFromStack, inputBD) = operands(stack, input) ?: return RPNResult.BadInput
|
||||
val result = lastFromStack.pow(inputBD)
|
||||
return RPNResult.Result(result, stack.dropLast(1))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private val bigDecimalHundred by lazy { BigDecimal("100") }
|
||||
|
||||
private fun operands(
|
||||
stack: List<BigDecimal>,
|
||||
input: String,
|
||||
): Pair<BigDecimal, BigDecimal>? {
|
||||
val first = stack.lastOrNull() ?: return null
|
||||
val second = input.toBigDecimalOrNull() ?: return null
|
||||
|
||||
return first to second
|
||||
}
|
||||
|
||||
private fun <T> List<T>.swapLastTwo(): List<T> {
|
||||
if (size < 2) return this
|
||||
return this
|
||||
.dropLast(2)
|
||||
.plus(get(lastIndex))
|
||||
.plus(get(lastIndex - 1))
|
||||
}
|
||||
|
||||
private fun <T> List<T>.rotateUp(): List<T> {
|
||||
if (size < 2) return this
|
||||
return this
|
||||
.drop(1)
|
||||
.plus(first())
|
||||
}
|
||||
|
||||
private fun <T> List<T>.rotateDown(): List<T> {
|
||||
if (size < 2) return this
|
||||
return listOf(last())
|
||||
.plus(this.dropLast(1))
|
||||
}
|
@ -1,256 +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 io.github.sadellie.evaluatto
|
||||
|
||||
import com.sadellie.unitto.core.base.MAX_PRECISION
|
||||
import org.junit.Assert.assertEquals
|
||||
import org.junit.Test
|
||||
import java.math.BigDecimal
|
||||
|
||||
class RPNEngineKtTest {
|
||||
|
||||
@Test
|
||||
fun testBadOperands() {
|
||||
// no funny business if input and/or stack is empty
|
||||
val actual = RPNCalculation.Divide.perform(
|
||||
input = "",
|
||||
stack = emptyList()
|
||||
)
|
||||
|
||||
assertEquals(RPNResult.BadInput, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDivide() {
|
||||
val actual = RPNCalculation.Divide.perform(
|
||||
input = "2",
|
||||
stack = listOf(BigDecimal("5"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.Result) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(BigDecimal("2.5").setScale(MAX_PRECISION), actual.input)
|
||||
assertEquals(emptyList<BigDecimal>(), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testDivideByZero() {
|
||||
val actual = RPNCalculation.Divide.perform(
|
||||
input = "0",
|
||||
stack = listOf(BigDecimal("5"))
|
||||
)
|
||||
|
||||
assertEquals(RPNResult.DivideByZero, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMinus() {
|
||||
val actual = RPNCalculation.Minus.perform(
|
||||
input = "2",
|
||||
stack = listOf(BigDecimal("5"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.Result) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(BigDecimal("3"), actual.input)
|
||||
assertEquals(emptyList<BigDecimal>(), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testMultiply() {
|
||||
val actual = RPNCalculation.Multiply.perform(
|
||||
input = "2",
|
||||
stack = listOf(BigDecimal("5"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.Result) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(BigDecimal("10"), actual.input)
|
||||
assertEquals(emptyList<BigDecimal>(), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testNegate() {
|
||||
val actual = RPNCalculation.Negate.perform(
|
||||
input = "2",
|
||||
stack = listOf(BigDecimal("5"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.NewInput) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(BigDecimal("-2"), actual.input)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPercent() {
|
||||
val actual = RPNCalculation.Percent.perform(
|
||||
input = "150",
|
||||
stack = listOf(BigDecimal("69"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.Result) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(BigDecimal("103.5").setScale(MAX_PRECISION), actual.input)
|
||||
assertEquals(emptyList<BigDecimal>(), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPlus() {
|
||||
val actual = RPNCalculation.Plus.perform(
|
||||
input = "150",
|
||||
stack = listOf(BigDecimal("69"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.Result) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(BigDecimal("219"), actual.input)
|
||||
assertEquals(emptyList<BigDecimal>(), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPower() {
|
||||
val actual = RPNCalculation.Power.perform(
|
||||
input = "3",
|
||||
stack = listOf(BigDecimal("2"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.Result) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(BigDecimal("8"), actual.input)
|
||||
assertEquals(emptyList<BigDecimal>(), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRotateUp() {
|
||||
val actual = RPNCalculation.RotateUp.perform(
|
||||
input = "",
|
||||
stack = listOf(BigDecimal("1"), BigDecimal("2"), BigDecimal("3"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.NewStack) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(listOf(BigDecimal("2"), BigDecimal("3"), BigDecimal("1")), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testRotateDown() {
|
||||
val actual = RPNCalculation.RotateDown.perform(
|
||||
input = "",
|
||||
stack = listOf(BigDecimal("1"), BigDecimal("2"), BigDecimal("3"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.NewStack) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(listOf(BigDecimal("3"), BigDecimal("1"), BigDecimal("2")), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testPop() {
|
||||
val actual = RPNCalculation.Pop.perform(
|
||||
input = "",
|
||||
stack = listOf(BigDecimal("1"), BigDecimal("2"), BigDecimal("3"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.Result) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(BigDecimal("3"), actual.input)
|
||||
assertEquals(listOf(BigDecimal("1"), BigDecimal("2")), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testClear() {
|
||||
val actual = RPNCalculation.Clear.perform(
|
||||
input = "3",
|
||||
stack = listOf(BigDecimal("2"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.Result) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(null, actual.input)
|
||||
assertEquals(emptyList<BigDecimal>(), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testEnter() {
|
||||
val actual = RPNCalculation.Enter.perform(
|
||||
input = "3",
|
||||
stack = listOf(BigDecimal("2"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.Result) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(null, actual.input)
|
||||
assertEquals(listOf(BigDecimal("2"), BigDecimal("3")), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSwap() {
|
||||
val actual = RPNCalculation.Swap.perform(
|
||||
input = "3",
|
||||
stack = listOf(BigDecimal("2"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.Result) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(BigDecimal("2"), actual.input)
|
||||
assertEquals(listOf(BigDecimal("3")), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSwapEmptyInput() {
|
||||
val actual = RPNCalculation.Swap.perform(
|
||||
input = "",
|
||||
stack = listOf(BigDecimal("1"), BigDecimal("2"))
|
||||
)
|
||||
|
||||
if (actual !is RPNResult.NewStack) throw Exception("Wrong return")
|
||||
|
||||
assertEquals(listOf(BigDecimal("2"), BigDecimal("1")), actual.stack)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSwapEmptyInputNotEnoughInStack() {
|
||||
val actual = RPNCalculation.Swap.perform(
|
||||
input = "",
|
||||
stack = listOf(BigDecimal("1"))
|
||||
)
|
||||
|
||||
assertEquals(RPNResult.BadInput, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSwapEmptyStack() {
|
||||
val actual = RPNCalculation.Swap.perform(
|
||||
input = "123",
|
||||
stack = emptyList()
|
||||
)
|
||||
|
||||
assertEquals(RPNResult.BadInput, actual)
|
||||
}
|
||||
|
||||
@Test
|
||||
fun testSwapEmptyBoth() {
|
||||
val actual = RPNCalculation.Swap.perform(
|
||||
input = "",
|
||||
stack = emptyList()
|
||||
)
|
||||
|
||||
assertEquals(RPNResult.BadInput, actual)
|
||||
}
|
||||
}
|
@ -92,6 +92,4 @@ interface UserPreferencesRepository {
|
||||
suspend fun updatePartialHistoryView(enabled: Boolean)
|
||||
|
||||
suspend fun updateAcButton(enabled: Boolean)
|
||||
|
||||
suspend fun updateRpnMode(enabled: Boolean)
|
||||
}
|
||||
|
@ -30,6 +30,5 @@ interface AppPreferences {
|
||||
val startingScreen: String
|
||||
val enableToolsExperiment: Boolean
|
||||
val systemFont: Boolean
|
||||
val rpnMode: Boolean
|
||||
val enableVibrations: Boolean
|
||||
}
|
||||
|
@ -134,10 +134,6 @@ fun Preferences.getAcButton(): Boolean {
|
||||
return this[PrefsKeys.AC_BUTTON] ?: true
|
||||
}
|
||||
|
||||
fun Preferences.getRpnMode(): Boolean {
|
||||
return this[PrefsKeys.RPN_MODE] ?: false
|
||||
}
|
||||
|
||||
private inline fun <T, R> T.letTryOrNull(block: (T) -> R): R? = try {
|
||||
this?.let(block)
|
||||
} catch (e: Exception) {
|
||||
|
@ -44,7 +44,6 @@ data class AppPreferencesImpl(
|
||||
override val startingScreen: String,
|
||||
override val enableToolsExperiment: Boolean,
|
||||
override val systemFont: Boolean,
|
||||
override val rpnMode: Boolean,
|
||||
override val enableVibrations: Boolean,
|
||||
) : AppPreferences
|
||||
|
||||
|
@ -37,7 +37,7 @@ object PrefsKeys {
|
||||
val ENABLE_VIBRATIONS = booleanPreferencesKey("ENABLE_VIBRATIONS_PREF_KEY")
|
||||
val MIDDLE_ZERO = booleanPreferencesKey("MIDDLE_ZERO_PREF_KEY")
|
||||
val AC_BUTTON = booleanPreferencesKey("AC_BUTTON_PREF_KEY")
|
||||
val RPN_MODE = booleanPreferencesKey("RPN_MODE_PREF_KEY")
|
||||
// val RPN_MODE = booleanPreferencesKey("RPN_MODE_PREF_KEY")
|
||||
|
||||
// FORMATTER
|
||||
val DIGITS_PRECISION = intPreferencesKey("DIGITS_PRECISION_PREF_KEY")
|
||||
|
@ -62,7 +62,6 @@ class UserPreferencesRepositoryImpl @Inject constructor(
|
||||
startingScreen = preferences.getStartingScreen(),
|
||||
enableToolsExperiment = preferences.getEnableToolsExperiment(),
|
||||
systemFont = preferences.getSystemFont(),
|
||||
rpnMode = preferences.getRpnMode(),
|
||||
enableVibrations = preferences.getEnableVibrations(),
|
||||
)
|
||||
}
|
||||
@ -291,10 +290,4 @@ class UserPreferencesRepositoryImpl @Inject constructor(
|
||||
preferences[PrefsKeys.AC_BUTTON] = enabled
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateRpnMode(enabled: Boolean) {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[PrefsKeys.RPN_MODE] = enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -1,120 +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.calculator
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sadellie.unitto.core.base.OutputFormat
|
||||
import com.sadellie.unitto.core.ui.common.textfield.ExpressionTextField
|
||||
import com.sadellie.unitto.core.ui.common.textfield.FixedInputTextField
|
||||
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
|
||||
import com.sadellie.unitto.data.common.format
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Composable
|
||||
internal fun InputBox(
|
||||
modifier: Modifier,
|
||||
input: TextFieldValue,
|
||||
onCursorChange: (TextRange) -> Unit,
|
||||
stack: List<BigDecimal>,
|
||||
formatterSymbols: FormatterSymbols,
|
||||
precision: Int,
|
||||
outputFormat: Int,
|
||||
) {
|
||||
val listState = rememberLazyListState()
|
||||
|
||||
LaunchedEffect(stack) {
|
||||
listState.animateScrollToItem(stack.lastIndex.coerceAtLeast(0))
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.clip(RoundedCornerShape(24.dp))
|
||||
.fillMaxWidth()
|
||||
.background(MaterialTheme.colorScheme.surfaceVariant)
|
||||
.padding(start = 12.dp, end = 12.dp, bottom = 12.dp),
|
||||
verticalArrangement = Arrangement.Bottom
|
||||
) {
|
||||
LazyColumn(
|
||||
modifier = Modifier.weight(1f),
|
||||
state = listState,
|
||||
verticalArrangement = Arrangement.Bottom,
|
||||
) {
|
||||
items(stack) {
|
||||
FixedInputTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = it.format(precision, outputFormat),
|
||||
formatterSymbols = formatterSymbols,
|
||||
textColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
onClick = {}
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
ExpressionTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.fillMaxHeight(0.25f),
|
||||
value = input,
|
||||
minRatio = 0.6f,
|
||||
onCursorChange = onCursorChange,
|
||||
formatterSymbols = formatterSymbols,
|
||||
textColor = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(device = "spec:width=1080px,height=2160px,dpi=440")
|
||||
@Composable
|
||||
fun PreviewInputBox() {
|
||||
InputBox(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
input = TextFieldValue("123456.789"),
|
||||
onCursorChange = {},
|
||||
stack = listOf(
|
||||
BigDecimal("123456.7890"),
|
||||
BigDecimal("123456.7890"),
|
||||
BigDecimal("123456.7890"),
|
||||
BigDecimal("123456.7890"),
|
||||
BigDecimal("123456.7890"),
|
||||
BigDecimal("123456.7890"),
|
||||
),
|
||||
formatterSymbols = FormatterSymbols.Spaces,
|
||||
precision = 3,
|
||||
outputFormat = OutputFormat.PLAIN
|
||||
)
|
||||
}
|
@ -1,229 +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.calculator
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import com.sadellie.unitto.core.base.Token
|
||||
import com.sadellie.unitto.core.ui.LocalWindowSize
|
||||
import com.sadellie.unitto.core.ui.WindowHeightSizeClass
|
||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonAdditional
|
||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonContentHeightShort
|
||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonContentHeightTall
|
||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonContentHeightTallAdditional
|
||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled
|
||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonLight
|
||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonTertiary
|
||||
import com.sadellie.unitto.core.ui.common.KeypadFlow
|
||||
import com.sadellie.unitto.core.ui.common.icons.IconPack
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Backspace
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Clear
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Comma
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Divide
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Dot
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Down
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Enter
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key0
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key1
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key2
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key3
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key4
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key5
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key6
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key7
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key8
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key9
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Minus
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Multiply
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Percent
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Plus
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Pop
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Swap
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Unary
|
||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Up
|
||||
import io.github.sadellie.evaluatto.RPNCalculation
|
||||
|
||||
@Composable
|
||||
internal fun RPNCalculatorKeyboard(
|
||||
modifier: Modifier,
|
||||
fractional: String,
|
||||
middleZero: Boolean,
|
||||
onCalculationClick: (RPNCalculation) -> Unit,
|
||||
onInputEditClick: (RPNInputEdit) -> Unit,
|
||||
) {
|
||||
val fractionalIcon = remember(fractional) { if (fractional == Token.Digit.dot) IconPack.Dot else IconPack.Comma }
|
||||
|
||||
if (LocalWindowSize.current.heightSizeClass < WindowHeightSizeClass.Medium) {
|
||||
RPNCalculatorKeyboardLandscape(
|
||||
modifier = modifier,
|
||||
fractionalIcon = fractionalIcon,
|
||||
middleZero = middleZero,
|
||||
onCalculationClick = onCalculationClick,
|
||||
onInputEditClick = onInputEditClick
|
||||
)
|
||||
} else {
|
||||
RPNCalculatorKeyboardPortrait(
|
||||
modifier = modifier,
|
||||
fractionalIcon = fractionalIcon,
|
||||
middleZero = middleZero,
|
||||
onCalculationClick = onCalculationClick,
|
||||
onInputEditClick = onInputEditClick
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RPNCalculatorKeyboardPortrait(
|
||||
modifier: Modifier,
|
||||
fractionalIcon: ImageVector,
|
||||
middleZero: Boolean,
|
||||
onCalculationClick: (RPNCalculation) -> Unit,
|
||||
onInputEditClick: (RPNInputEdit) -> Unit,
|
||||
) {
|
||||
Column(
|
||||
modifier = modifier
|
||||
) {
|
||||
KeypadFlow(
|
||||
modifier = Modifier.fillMaxHeight(0.1f).fillMaxWidth(),
|
||||
rows = 1,
|
||||
columns = 4
|
||||
) { width, height ->
|
||||
val aModifier = Modifier
|
||||
.fillMaxWidth(width)
|
||||
.fillMaxHeight(height)
|
||||
|
||||
KeyboardButtonAdditional(aModifier, IconPack.Swap, null, KeyboardButtonContentHeightTallAdditional) { onCalculationClick(RPNCalculation.Swap) }
|
||||
KeyboardButtonAdditional(aModifier, IconPack.Up, null, KeyboardButtonContentHeightTallAdditional) { onCalculationClick(RPNCalculation.RotateUp) }
|
||||
KeyboardButtonAdditional(aModifier, IconPack.Down, null, KeyboardButtonContentHeightTallAdditional) { onCalculationClick(RPNCalculation.RotateDown) }
|
||||
KeyboardButtonAdditional(aModifier, IconPack.Pop, null, KeyboardButtonContentHeightTallAdditional) { onCalculationClick(RPNCalculation.Pop) }
|
||||
}
|
||||
|
||||
KeypadFlow(
|
||||
modifier = Modifier.weight(1f).fillMaxSize(),
|
||||
rows = 5,
|
||||
columns = 4
|
||||
) { width, height ->
|
||||
val bModifier = Modifier
|
||||
.fillMaxWidth(width)
|
||||
.fillMaxHeight(height)
|
||||
|
||||
KeyboardButtonTertiary(bModifier, IconPack.Clear, null, KeyboardButtonContentHeightTall) { onCalculationClick(RPNCalculation.Clear) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Unary, null, KeyboardButtonContentHeightTall) { onCalculationClick(RPNCalculation.Negate) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Percent, null, KeyboardButtonContentHeightTall) { onCalculationClick(RPNCalculation.Percent) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Divide, null, KeyboardButtonContentHeightTall) { onCalculationClick(RPNCalculation.Divide) }
|
||||
|
||||
KeyboardButtonLight(bModifier, IconPack.Key7, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._7)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key8, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._8)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key9, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._9)) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Multiply, null, KeyboardButtonContentHeightTall) { onCalculationClick(RPNCalculation.Multiply) }
|
||||
|
||||
KeyboardButtonLight(bModifier, IconPack.Key4, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._4)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key5, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._5)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key6, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._6)) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Minus, null, KeyboardButtonContentHeightTall) { onCalculationClick(RPNCalculation.Minus) }
|
||||
|
||||
KeyboardButtonLight(bModifier, IconPack.Key1, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._1)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key2, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._2)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key3, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._3)) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Plus, null, KeyboardButtonContentHeightTall) { onCalculationClick(RPNCalculation.Plus) }
|
||||
|
||||
if (middleZero) {
|
||||
KeyboardButtonLight(bModifier, fractionalIcon, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Dot) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key0, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._0)) }
|
||||
} else {
|
||||
KeyboardButtonLight(bModifier, IconPack.Key0, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._0)) }
|
||||
KeyboardButtonLight(bModifier, fractionalIcon, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Dot) }
|
||||
}
|
||||
KeyboardButtonLight(bModifier, IconPack.Backspace, null, KeyboardButtonContentHeightTall) { onInputEditClick(RPNInputEdit.Delete) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Enter, null, KeyboardButtonContentHeightTall) { onCalculationClick(RPNCalculation.Enter) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun RPNCalculatorKeyboardLandscape(
|
||||
modifier: Modifier,
|
||||
fractionalIcon: ImageVector,
|
||||
middleZero: Boolean,
|
||||
onCalculationClick: (RPNCalculation) -> Unit,
|
||||
onInputEditClick: (RPNInputEdit) -> Unit,
|
||||
) {
|
||||
KeypadFlow(
|
||||
modifier = modifier,
|
||||
rows = 4,
|
||||
columns = 6
|
||||
) { width, height ->
|
||||
val bModifier = Modifier
|
||||
.fillMaxHeight(height)
|
||||
.fillMaxWidth(width)
|
||||
|
||||
KeyboardButtonAdditional(bModifier, IconPack.Swap, null, KeyboardButtonContentHeightTallAdditional) { onCalculationClick(RPNCalculation.Swap) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key7, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._7)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key8, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._8)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key9, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._9)) }
|
||||
KeyboardButtonTertiary(bModifier, IconPack.Clear, null, KeyboardButtonContentHeightTall) { onCalculationClick(RPNCalculation.Clear) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Unary, null, KeyboardButtonContentHeightShort) { onCalculationClick(RPNCalculation.Negate) }
|
||||
|
||||
KeyboardButtonAdditional(bModifier, IconPack.Up, null, KeyboardButtonContentHeightTallAdditional) { onCalculationClick(RPNCalculation.RotateUp) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key4, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._4)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key5, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._5)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key6, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._6)) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Multiply, null, KeyboardButtonContentHeightShort) { onCalculationClick(RPNCalculation.Multiply) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Divide, null, KeyboardButtonContentHeightShort) { onCalculationClick(RPNCalculation.Divide) }
|
||||
|
||||
KeyboardButtonAdditional(bModifier, IconPack.Down, null, KeyboardButtonContentHeightTallAdditional) { onCalculationClick(RPNCalculation.RotateDown) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key1, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._1)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key2, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._2)) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key3, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._3)) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Plus, null, KeyboardButtonContentHeightShort) { onCalculationClick(RPNCalculation.Plus) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Minus, null, KeyboardButtonContentHeightShort) { onCalculationClick(RPNCalculation.Minus) }
|
||||
|
||||
KeyboardButtonAdditional(bModifier, IconPack.Pop, null, KeyboardButtonContentHeightTallAdditional) { onCalculationClick(RPNCalculation.Pop) }
|
||||
if (middleZero) {
|
||||
KeyboardButtonLight(bModifier, fractionalIcon, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Dot) }
|
||||
KeyboardButtonLight(bModifier, IconPack.Key0, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._0)) }
|
||||
} else {
|
||||
KeyboardButtonLight(bModifier, IconPack.Key0, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._0)) }
|
||||
KeyboardButtonLight(bModifier, fractionalIcon, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Dot) }
|
||||
}
|
||||
KeyboardButtonLight(bModifier, IconPack.Backspace, null, KeyboardButtonContentHeightShort) { onInputEditClick(RPNInputEdit.Delete) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Percent, null, KeyboardButtonContentHeightShort) { onCalculationClick(RPNCalculation.Percent) }
|
||||
KeyboardButtonFilled(bModifier, IconPack.Enter, null, KeyboardButtonContentHeightShort) { onCalculationClick(RPNCalculation.Enter) }
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(device = "spec:parent=pixel_5,orientation=portrait")
|
||||
@Preview(device = "spec:parent=pixel_5,orientation=landscape")
|
||||
@Composable
|
||||
private fun PreviewKeyboard() {
|
||||
RPNCalculatorKeyboard(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
fractional = Token.Digit.dot,
|
||||
middleZero = false,
|
||||
onCalculationClick = {},
|
||||
onInputEditClick = {}
|
||||
)
|
||||
}
|
@ -1,134 +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.calculator
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material3.Text
|
||||
import com.sadellie.unitto.core.ui.WindowHeightSizeClass
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.sadellie.unitto.core.base.OutputFormat
|
||||
import com.sadellie.unitto.core.base.R
|
||||
import com.sadellie.unitto.core.ui.LocalWindowSize
|
||||
import com.sadellie.unitto.core.ui.common.MenuButton
|
||||
import com.sadellie.unitto.core.ui.common.SettingsButton
|
||||
import com.sadellie.unitto.core.ui.common.EmptyScreen
|
||||
import com.sadellie.unitto.core.ui.common.ScaffoldWithTopBar
|
||||
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
|
||||
import io.github.sadellie.evaluatto.RPNCalculation
|
||||
import java.math.BigDecimal
|
||||
|
||||
@Composable
|
||||
internal fun RPNCalculatorRoute(
|
||||
openDrawer: () -> Unit,
|
||||
navigateToSettings: () -> Unit,
|
||||
viewModel: RPNCalculatorViewModel = hiltViewModel(),
|
||||
) {
|
||||
when (val uiState = viewModel.uiState.collectAsStateWithLifecycle().value) {
|
||||
RPNCalculatorUIState.Loading -> EmptyScreen()
|
||||
is RPNCalculatorUIState.Ready -> RPNCalculatorScreen(
|
||||
uiState = uiState,
|
||||
openDrawer = openDrawer,
|
||||
navigateToSettings = navigateToSettings,
|
||||
onCursorChange = viewModel::onCursorChange,
|
||||
onCalculationClick = viewModel::onCalculationClick,
|
||||
onInputEditClick = viewModel::onInputEdit
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
internal fun RPNCalculatorScreen(
|
||||
uiState: RPNCalculatorUIState.Ready,
|
||||
openDrawer: () -> Unit,
|
||||
navigateToSettings: () -> Unit,
|
||||
onCursorChange: (TextRange) -> Unit,
|
||||
onCalculationClick: (RPNCalculation) -> Unit,
|
||||
onInputEditClick: (RPNInputEdit) -> Unit,
|
||||
) {
|
||||
ScaffoldWithTopBar(
|
||||
title = { Text(stringResource(id = R.string.calculator_title)) },
|
||||
navigationIcon = { MenuButton(openDrawer) },
|
||||
actions = { SettingsButton(navigateToSettings) }
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
Modifier.padding(paddingValues)
|
||||
) {
|
||||
InputBox(
|
||||
modifier = Modifier
|
||||
.padding(8.dp)
|
||||
.fillMaxHeight(if (LocalWindowSize.current.heightSizeClass > WindowHeightSizeClass.Compact) 0.3f else 0.5f),
|
||||
input = uiState.input,
|
||||
stack = uiState.stack,
|
||||
formatterSymbols = uiState.formatterSymbols,
|
||||
precision = uiState.precision,
|
||||
outputFormat = uiState.outputFormat,
|
||||
onCursorChange = onCursorChange
|
||||
)
|
||||
RPNCalculatorKeyboard(
|
||||
modifier = Modifier
|
||||
.padding(horizontal = 4.dp)
|
||||
.fillMaxSize(),
|
||||
fractional = uiState.formatterSymbols.fractional,
|
||||
middleZero = uiState.middleZero,
|
||||
onCalculationClick = onCalculationClick,
|
||||
onInputEditClick = onInputEditClick
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(widthDp = 432, heightDp = 1008, device = "spec:parent=pixel_5,orientation=portrait")
|
||||
@Preview(widthDp = 432, heightDp = 864, device = "spec:parent=pixel_5,orientation=portrait")
|
||||
@Preview(widthDp = 597, heightDp = 1393, device = "spec:parent=pixel_5,orientation=portrait")
|
||||
@Composable
|
||||
private fun RPNCalculatorScreenPreview() {
|
||||
RPNCalculatorScreen(
|
||||
uiState = RPNCalculatorUIState.Ready(
|
||||
input = TextFieldValue("test"),
|
||||
stack = listOf(
|
||||
BigDecimal("123456.7890"),
|
||||
BigDecimal("123456.7890"),
|
||||
BigDecimal("123456.7890"),
|
||||
BigDecimal("123456.7890"),
|
||||
BigDecimal("123456.7890"),
|
||||
BigDecimal("123456.7890"),
|
||||
),
|
||||
precision = 3,
|
||||
outputFormat = OutputFormat.PLAIN,
|
||||
formatterSymbols = FormatterSymbols.Spaces,
|
||||
middleZero = true,
|
||||
),
|
||||
openDrawer = {},
|
||||
navigateToSettings = {},
|
||||
onCalculationClick = {},
|
||||
onInputEditClick = {},
|
||||
onCursorChange = {}
|
||||
)
|
||||
}
|
@ -1,36 +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.calculator
|
||||
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
|
||||
import java.math.BigDecimal
|
||||
|
||||
internal sealed class RPNCalculatorUIState {
|
||||
data object Loading : RPNCalculatorUIState()
|
||||
|
||||
data class Ready(
|
||||
val input: TextFieldValue,
|
||||
val stack: List<BigDecimal>,
|
||||
val precision: Int,
|
||||
val outputFormat: Int,
|
||||
val formatterSymbols: FormatterSymbols,
|
||||
val middleZero: Boolean,
|
||||
) : RPNCalculatorUIState()
|
||||
}
|
@ -1,118 +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.calculator
|
||||
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.lifecycle.SavedStateHandle
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.sadellie.unitto.core.base.Token
|
||||
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
|
||||
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.format
|
||||
import com.sadellie.unitto.data.common.stateIn
|
||||
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import io.github.sadellie.evaluatto.RPNCalculation
|
||||
import io.github.sadellie.evaluatto.RPNResult
|
||||
import io.github.sadellie.evaluatto.perform
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
|
||||
@HiltViewModel
|
||||
internal class RPNCalculatorViewModel @Inject constructor(
|
||||
userPrefsRepository: UserPreferencesRepository,
|
||||
private val savedStateHandle: SavedStateHandle,
|
||||
) : ViewModel() {
|
||||
|
||||
private val _inputKey = "RPN_CALCULATOR_INPUT"
|
||||
private val _input = MutableStateFlow(savedStateHandle.getTextField(_inputKey))
|
||||
private val _stack = MutableStateFlow(emptyList<BigDecimal>())
|
||||
private val _prefs = userPrefsRepository.calculatorPrefs.stateIn(viewModelScope, null)
|
||||
|
||||
val uiState = combine(
|
||||
_prefs,
|
||||
_input,
|
||||
_stack
|
||||
) { prefs, input, stack ->
|
||||
prefs ?: return@combine RPNCalculatorUIState.Loading
|
||||
|
||||
return@combine RPNCalculatorUIState.Ready(
|
||||
input = input,
|
||||
stack = stack,
|
||||
precision = prefs.precision,
|
||||
outputFormat = prefs.outputFormat,
|
||||
formatterSymbols = AllFormatterSymbols.getById(prefs.separator),
|
||||
middleZero = prefs.middleZero
|
||||
)
|
||||
}
|
||||
.stateIn(viewModelScope, RPNCalculatorUIState.Loading)
|
||||
|
||||
fun onInputEdit(action: RPNInputEdit) {
|
||||
val input = _input.value
|
||||
val newInput = when (action) {
|
||||
is RPNInputEdit.Digit -> input.addTokens(action.value)
|
||||
RPNInputEdit.Dot -> {
|
||||
if (_input.value.text.contains(Token.Digit.dot)) return
|
||||
input.addTokens(Token.Digit.dot)
|
||||
}
|
||||
RPNInputEdit.Delete -> input.deleteTokens()
|
||||
}
|
||||
|
||||
_input.update { newInput }
|
||||
savedStateHandle[_inputKey] = newInput.text
|
||||
}
|
||||
|
||||
fun onCalculationClick(action: RPNCalculation) = viewModelScope.launch {
|
||||
val prefs = _prefs.value ?: return@launch
|
||||
|
||||
val newResult = withContext(Dispatchers.Default) {
|
||||
action.perform(_input.value.text, _stack.value)
|
||||
}
|
||||
|
||||
when (newResult) {
|
||||
is RPNResult.Result -> {
|
||||
val newInput = newResult.input?.format(prefs.precision, prefs.outputFormat) ?: ""
|
||||
_input.update { TextFieldValue(newInput, TextRange(newInput.length)) }
|
||||
_stack.update { newResult.stack }
|
||||
}
|
||||
|
||||
is RPNResult.NewStack -> {
|
||||
_stack.update { newResult.stack }
|
||||
}
|
||||
|
||||
is RPNResult.NewInput -> {
|
||||
val newInput = newResult.input.format(prefs.precision, prefs.outputFormat)
|
||||
_input.update { TextFieldValue(newInput, TextRange(newInput.length)) }
|
||||
}
|
||||
RPNResult.BadInput, RPNResult.DivideByZero -> Unit
|
||||
}
|
||||
}
|
||||
|
||||
fun onCursorChange(selection: TextRange) = _input.update { it.copy(selection = selection) }
|
||||
}
|
@ -1,25 +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.calculator
|
||||
|
||||
sealed class RPNInputEdit {
|
||||
data class Digit(val value: String) : RPNInputEdit()
|
||||
data object Delete : RPNInputEdit()
|
||||
data object Dot : RPNInputEdit()
|
||||
}
|
@ -24,13 +24,11 @@ import com.sadellie.unitto.core.ui.model.DrawerItem
|
||||
import com.sadellie.unitto.core.ui.unittoComposable
|
||||
import com.sadellie.unitto.core.ui.unittoNavigation
|
||||
import com.sadellie.unitto.feature.calculator.CalculatorRoute
|
||||
import com.sadellie.unitto.feature.calculator.RPNCalculatorRoute
|
||||
|
||||
private val graph = DrawerItem.Calculator.graph
|
||||
private val start = DrawerItem.Calculator.start
|
||||
|
||||
fun NavGraphBuilder.calculatorGraph(
|
||||
rpnMode: Boolean,
|
||||
openDrawer: () -> Unit,
|
||||
navigateToSettings: () -> Unit
|
||||
) {
|
||||
@ -42,17 +40,10 @@ fun NavGraphBuilder.calculatorGraph(
|
||||
)
|
||||
) {
|
||||
unittoComposable(start) {
|
||||
if (rpnMode) {
|
||||
RPNCalculatorRoute(
|
||||
openDrawer = openDrawer,
|
||||
navigateToSettings = navigateToSettings
|
||||
)
|
||||
} else {
|
||||
CalculatorRoute(
|
||||
navigateToMenu = openDrawer,
|
||||
navigateToSettings = navigateToSettings
|
||||
)
|
||||
}
|
||||
CalculatorRoute(
|
||||
navigateToMenu = openDrawer,
|
||||
navigateToSettings = navigateToSettings
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,7 +18,6 @@
|
||||
|
||||
package com.sadellie.unitto.feature.settings.calculator
|
||||
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
@ -29,84 +28,51 @@ import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.sadellie.unitto.core.base.OutputFormat
|
||||
import com.sadellie.unitto.core.base.R
|
||||
import com.sadellie.unitto.core.ui.common.NavigateUpButton
|
||||
import com.sadellie.unitto.core.base.Separator
|
||||
import com.sadellie.unitto.core.ui.common.EmptyScreen
|
||||
import com.sadellie.unitto.core.ui.common.ListItem
|
||||
import com.sadellie.unitto.core.ui.common.NavigateUpButton
|
||||
import com.sadellie.unitto.core.ui.common.ScaffoldWithLargeTopBar
|
||||
import com.sadellie.unitto.data.model.userprefs.CalculatorPreferences
|
||||
import com.sadellie.unitto.data.userprefs.CalculatorPreferencesImpl
|
||||
|
||||
@Composable
|
||||
internal fun CalculatorSettingsRoute(
|
||||
viewModel: CalculatorSettingsViewModel = hiltViewModel(),
|
||||
navigateUpAction: () -> Unit,
|
||||
) {
|
||||
when (val prefs = viewModel.uiState.collectAsStateWithLifecycle().value) {
|
||||
CalculatorSettingsUIState.Loading -> EmptyScreen()
|
||||
when (val prefs = viewModel.prefs.collectAsStateWithLifecycle().value) {
|
||||
null -> EmptyScreen()
|
||||
else -> {
|
||||
CalculatorSettingsScreen(
|
||||
uiState = prefs,
|
||||
prefs = prefs,
|
||||
navigateUpAction = navigateUpAction,
|
||||
updatePartialHistoryView = viewModel::updatePartialHistoryView,
|
||||
updateRpnMode = viewModel::updateRpnMode,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Translate
|
||||
@Composable
|
||||
private fun CalculatorSettingsScreen(
|
||||
uiState: CalculatorSettingsUIState,
|
||||
prefs: CalculatorPreferences,
|
||||
navigateUpAction: () -> Unit,
|
||||
updatePartialHistoryView: (Boolean) -> Unit,
|
||||
updateRpnMode: (Boolean) -> Unit,
|
||||
) {
|
||||
ScaffoldWithLargeTopBar(
|
||||
title = stringResource(R.string.calculator_title),
|
||||
navigationIcon = { NavigateUpButton(navigateUpAction) }
|
||||
) { padding ->
|
||||
Column(Modifier.padding(padding)) {
|
||||
// SingleChoiceSegmentedButtonRow(
|
||||
// modifier = Modifier
|
||||
// .fillMaxWidth()
|
||||
// .padding(16.dp)
|
||||
// ) {
|
||||
// SegmentedButton(
|
||||
// selected = uiState is CalculatorSettingsUIState.Standard,
|
||||
// onClick = { updateRpnMode(false) },
|
||||
// shape = SegmentedButtonDefaults.itemShape(index = 0, count = 2),
|
||||
// ) {
|
||||
// Text("Standard")
|
||||
// }
|
||||
// SegmentedButton(
|
||||
// selected = uiState == CalculatorSettingsUIState.RPN,
|
||||
// onClick = { updateRpnMode(true) },
|
||||
// shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2),
|
||||
// ) {
|
||||
// Text("RPN")
|
||||
// }
|
||||
// }
|
||||
|
||||
Crossfade(
|
||||
targetState = uiState,
|
||||
label = "Mode switch"
|
||||
) { state ->
|
||||
when (state) {
|
||||
is CalculatorSettingsUIState.Standard -> {
|
||||
Column {
|
||||
ListItem(
|
||||
headlineText = stringResource(R.string.settings_partial_history_view),
|
||||
icon = Icons.Default.Timer,
|
||||
supportingText = stringResource(R.string.settings_partial_history_view_support),
|
||||
switchState = state.partialHistoryView,
|
||||
onSwitchChange = updatePartialHistoryView
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
ListItem(
|
||||
headlineText = stringResource(R.string.settings_partial_history_view),
|
||||
icon = Icons.Default.Timer,
|
||||
supportingText = stringResource(R.string.settings_partial_history_view_support),
|
||||
switchState = prefs.partialHistoryView,
|
||||
onSwitchChange = updatePartialHistoryView
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -115,22 +81,16 @@ private fun CalculatorSettingsScreen(
|
||||
@Composable
|
||||
private fun PreviewCalculatorSettingsScreenStandard() {
|
||||
CalculatorSettingsScreen(
|
||||
uiState = CalculatorSettingsUIState.Standard(
|
||||
partialHistoryView = true,
|
||||
prefs = CalculatorPreferencesImpl(
|
||||
radianMode = true,
|
||||
separator = Separator.SPACE,
|
||||
middleZero = false,
|
||||
acButton = false,
|
||||
partialHistoryView = false,
|
||||
precision = 3,
|
||||
outputFormat = OutputFormat.PLAIN
|
||||
),
|
||||
navigateUpAction = {},
|
||||
updatePartialHistoryView = {},
|
||||
updateRpnMode = {}
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewCalculatorSettingsScreenRPN() {
|
||||
CalculatorSettingsScreen(
|
||||
uiState = CalculatorSettingsUIState.RPN,
|
||||
navigateUpAction = {},
|
||||
updatePartialHistoryView = {},
|
||||
updateRpnMode = {}
|
||||
)
|
||||
}
|
||||
|
@ -1,29 +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.settings.calculator
|
||||
|
||||
internal sealed class CalculatorSettingsUIState {
|
||||
data object Loading : CalculatorSettingsUIState()
|
||||
|
||||
data object RPN : CalculatorSettingsUIState()
|
||||
|
||||
data class Standard(
|
||||
val partialHistoryView: Boolean,
|
||||
) : CalculatorSettingsUIState()
|
||||
}
|
@ -23,7 +23,6 @@ import androidx.lifecycle.viewModelScope
|
||||
import com.sadellie.unitto.data.common.stateIn
|
||||
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.launch
|
||||
import javax.inject.Inject
|
||||
|
||||
@ -31,25 +30,10 @@ import javax.inject.Inject
|
||||
internal class CalculatorSettingsViewModel @Inject constructor(
|
||||
private val userPrefsRepository: UserPreferencesRepository,
|
||||
) : ViewModel() {
|
||||
val uiState = combine(
|
||||
userPrefsRepository.appPrefs,
|
||||
userPrefsRepository.calculatorPrefs,
|
||||
) { app, calc ->
|
||||
if (app.rpnMode) {
|
||||
CalculatorSettingsUIState.RPN
|
||||
} else {
|
||||
CalculatorSettingsUIState.Standard(
|
||||
partialHistoryView = calc.partialHistoryView,
|
||||
)
|
||||
}
|
||||
}
|
||||
.stateIn(viewModelScope, CalculatorSettingsUIState.Loading)
|
||||
val prefs = userPrefsRepository.calculatorPrefs
|
||||
.stateIn(viewModelScope, null)
|
||||
|
||||
fun updatePartialHistoryView(enabled: Boolean) = viewModelScope.launch {
|
||||
userPrefsRepository.updatePartialHistoryView(enabled)
|
||||
}
|
||||
|
||||
fun updateRpnMode(enabled: Boolean) = viewModelScope.launch {
|
||||
userPrefsRepository.updateRpnMode(enabled)
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user