Remove RPN experiment

This commit is contained in:
Sad Ellie 2024-02-04 19:16:10 +03:00
parent 4a499b8fd3
commit b2030ad9dc
21 changed files with 31 additions and 1289 deletions

View File

@ -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() } }
)
}

View File

@ -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
)

View File

@ -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
}

View File

@ -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))
}

View File

@ -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)
}
}

View File

@ -92,6 +92,4 @@ interface UserPreferencesRepository {
suspend fun updatePartialHistoryView(enabled: Boolean)
suspend fun updateAcButton(enabled: Boolean)
suspend fun updateRpnMode(enabled: Boolean)
}

View File

@ -30,6 +30,5 @@ interface AppPreferences {
val startingScreen: String
val enableToolsExperiment: Boolean
val systemFont: Boolean
val rpnMode: Boolean
val enableVibrations: Boolean
}

View File

@ -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) {

View File

@ -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

View File

@ -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")

View File

@ -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
}
}
}

View File

@ -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
)
}

View File

@ -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 = {}
)
}

View File

@ -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 = {}
)
}

View File

@ -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()
}

View File

@ -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) }
}

View File

@ -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()
}

View File

@ -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,12 +40,6 @@ fun NavGraphBuilder.calculatorGraph(
)
) {
unittoComposable(start) {
if (rpnMode) {
RPNCalculatorRoute(
openDrawer = openDrawer,
navigateToSettings = navigateToSettings
)
} else {
CalculatorRoute(
navigateToMenu = openDrawer,
navigateToSettings = navigateToSettings
@ -55,4 +47,3 @@ fun NavGraphBuilder.calculatorGraph(
}
}
}
}

View File

@ -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,108 +28,69 @@ 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,
switchState = prefs.partialHistoryView,
onSwitchChange = updatePartialHistoryView
)
}
}
else -> Unit
}
}
}
}
}
@Preview
@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 = {}
)
}

View File

@ -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()
}

View File

@ -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)
}
}