mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-18 08:15:26 +02:00
parent
cf59362708
commit
60e5f1f998
@ -136,6 +136,7 @@ internal fun UnittoApp(prefs: AppPreferences?) {
|
||||
navController = navController,
|
||||
themmoController = it,
|
||||
startDestination = prefs.startingScreen,
|
||||
rpnMode = prefs.rpnMode,
|
||||
openDrawer = { drawerScope.launch { drawerState.open() } }
|
||||
)
|
||||
}
|
||||
|
@ -40,7 +40,8 @@ internal fun UnittoNavigation(
|
||||
navController: NavHostController,
|
||||
themmoController: ThemmoController,
|
||||
startDestination: String,
|
||||
openDrawer: () -> Unit
|
||||
openDrawer: () -> Unit,
|
||||
rpnMode: Boolean,
|
||||
) {
|
||||
NavHost(
|
||||
navController = navController,
|
||||
@ -62,7 +63,8 @@ internal fun UnittoNavigation(
|
||||
)
|
||||
|
||||
calculatorGraph(
|
||||
navigateToMenu = openDrawer,
|
||||
openDrawer = openDrawer,
|
||||
rpnMode = rpnMode,
|
||||
navigateToSettings = navController::navigateToSettings
|
||||
)
|
||||
|
||||
|
@ -22,7 +22,6 @@ import android.view.HapticFeedbackConstants
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
@ -30,6 +29,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
@ -72,7 +72,7 @@ fun BasicKeyboardButton(
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxHeight(contentHeight),
|
||||
modifier = Modifier.matchParentSize().scale(contentHeight),
|
||||
tint = iconColor
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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.core.ui.common.key.unittoicons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
|
||||
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.ImageVector.Builder
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
val UnittoIcons.Down: ImageVector
|
||||
get() {
|
||||
if (_down != null) {
|
||||
return _down!!
|
||||
}
|
||||
_down = Builder(name = "Down", defaultWidth = 170.0.dp, defaultHeight = 170.0.dp,
|
||||
viewportWidth = 170.0f, viewportHeight = 170.0f).apply {
|
||||
path(fill = SolidColor(Color(0xFF1C1B1F)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero) {
|
||||
moveTo(79.0f, 37.0f)
|
||||
verticalLineTo(110.05f)
|
||||
lineTo(45.4f, 76.45f)
|
||||
lineTo(37.0f, 85.0f)
|
||||
lineTo(85.0f, 133.0f)
|
||||
lineTo(133.0f, 85.0f)
|
||||
lineTo(124.6f, 76.45f)
|
||||
lineTo(91.0f, 110.05f)
|
||||
verticalLineTo(37.0f)
|
||||
horizontalLineTo(79.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
return _down!!
|
||||
}
|
||||
|
||||
private var _down: ImageVector? = null
|
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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.core.ui.common.key.unittoicons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
|
||||
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.ImageVector.Builder
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
val UnittoIcons.Enter: ImageVector
|
||||
get() {
|
||||
if (_enter != null) {
|
||||
return _enter!!
|
||||
}
|
||||
_enter = Builder(name = "Enter", defaultWidth = 170.0.dp, defaultHeight = 170.0.dp,
|
||||
viewportWidth = 170.0f, viewportHeight = 170.0f).apply {
|
||||
path(fill = SolidColor(Color(0xFF1C1B1F)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero) {
|
||||
moveTo(128.0f, 37.0f)
|
||||
lineTo(128.0f, 104.765f)
|
||||
lineTo(64.894f, 104.765f)
|
||||
lineTo(85.224f, 124.953f)
|
||||
lineTo(77.176f, 133.0f)
|
||||
lineTo(43.294f, 99.118f)
|
||||
lineTo(77.318f, 65.094f)
|
||||
lineTo(85.224f, 73.141f)
|
||||
lineTo(64.894f, 93.471f)
|
||||
lineTo(116.706f, 93.471f)
|
||||
lineTo(116.706f, 37.0f)
|
||||
lineTo(128.0f, 37.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
return _enter!!
|
||||
}
|
||||
|
||||
private var _enter: ImageVector? = null
|
@ -0,0 +1,137 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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.core.ui.common.key.unittoicons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
|
||||
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.ImageVector.Builder
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
val UnittoIcons.Pop: ImageVector
|
||||
get() {
|
||||
if (_pop != null) {
|
||||
return _pop!!
|
||||
}
|
||||
_pop = Builder(name = "Pop", defaultWidth = 274.0.dp, defaultHeight = 170.0.dp,
|
||||
viewportWidth = 274.0f, viewportHeight = 170.0f).apply {
|
||||
path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero) {
|
||||
moveTo(47.507f, 87.46f)
|
||||
curveTo(51.159f, 87.46f, 54.371f, 86.976f, 57.143f, 86.008f)
|
||||
curveTo(59.959f, 85.04f, 62.313f, 83.698f, 64.205f, 81.982f)
|
||||
curveTo(66.141f, 80.222f, 67.593f, 78.132f, 68.561f, 75.712f)
|
||||
curveTo(69.529f, 73.292f, 70.013f, 70.63f, 70.013f, 67.726f)
|
||||
curveTo(70.013f, 61.698f, 68.143f, 56.99f, 64.403f, 53.602f)
|
||||
curveTo(60.707f, 50.214f, 55.075f, 48.52f, 47.507f, 48.52f)
|
||||
horizontalLineTo(32.327f)
|
||||
verticalLineTo(87.46f)
|
||||
horizontalLineTo(47.507f)
|
||||
close()
|
||||
moveTo(47.507f, 38.422f)
|
||||
curveTo(53.491f, 38.422f, 58.683f, 39.126f, 63.083f, 40.534f)
|
||||
curveTo(67.527f, 41.898f, 71.201f, 43.856f, 74.105f, 46.408f)
|
||||
curveTo(77.009f, 48.96f, 79.165f, 52.04f, 80.573f, 55.648f)
|
||||
curveTo(82.025f, 59.256f, 82.751f, 63.282f, 82.751f, 67.726f)
|
||||
curveTo(82.751f, 72.126f, 81.981f, 76.152f, 80.441f, 79.804f)
|
||||
curveTo(78.901f, 83.456f, 76.635f, 86.602f, 73.643f, 89.242f)
|
||||
curveTo(70.695f, 91.882f, 67.021f, 93.95f, 62.621f, 95.446f)
|
||||
curveTo(58.265f, 96.898f, 53.227f, 97.624f, 47.507f, 97.624f)
|
||||
horizontalLineTo(32.327f)
|
||||
verticalLineTo(133.0f)
|
||||
horizontalLineTo(19.589f)
|
||||
verticalLineTo(38.422f)
|
||||
horizontalLineTo(47.507f)
|
||||
close()
|
||||
moveTo(183.61f, 85.744f)
|
||||
curveTo(183.61f, 92.828f, 182.488f, 99.34f, 180.244f, 105.28f)
|
||||
curveTo(178.0f, 111.176f, 174.832f, 116.258f, 170.74f, 120.526f)
|
||||
curveTo(166.648f, 124.794f, 161.72f, 128.116f, 155.956f, 130.492f)
|
||||
curveTo(150.236f, 132.824f, 143.9f, 133.99f, 136.948f, 133.99f)
|
||||
curveTo(129.996f, 133.99f, 123.66f, 132.824f, 117.94f, 130.492f)
|
||||
curveTo(112.22f, 128.116f, 107.314f, 124.794f, 103.222f, 120.526f)
|
||||
curveTo(99.13f, 116.258f, 95.962f, 111.176f, 93.718f, 105.28f)
|
||||
curveTo(91.474f, 99.34f, 90.352f, 92.828f, 90.352f, 85.744f)
|
||||
curveTo(90.352f, 78.66f, 91.474f, 72.17f, 93.718f, 66.274f)
|
||||
curveTo(95.962f, 60.334f, 99.13f, 55.23f, 103.222f, 50.962f)
|
||||
curveTo(107.314f, 46.65f, 112.22f, 43.306f, 117.94f, 40.93f)
|
||||
curveTo(123.66f, 38.554f, 129.996f, 37.366f, 136.948f, 37.366f)
|
||||
curveTo(143.9f, 37.366f, 150.236f, 38.554f, 155.956f, 40.93f)
|
||||
curveTo(161.72f, 43.306f, 166.648f, 46.65f, 170.74f, 50.962f)
|
||||
curveTo(174.832f, 55.23f, 178.0f, 60.334f, 180.244f, 66.274f)
|
||||
curveTo(182.488f, 72.17f, 183.61f, 78.66f, 183.61f, 85.744f)
|
||||
close()
|
||||
moveTo(170.476f, 85.744f)
|
||||
curveTo(170.476f, 79.936f, 169.684f, 74.722f, 168.1f, 70.102f)
|
||||
curveTo(166.516f, 65.482f, 164.272f, 61.588f, 161.368f, 58.42f)
|
||||
curveTo(158.464f, 55.208f, 154.944f, 52.744f, 150.808f, 51.028f)
|
||||
curveTo(146.672f, 49.312f, 142.052f, 48.454f, 136.948f, 48.454f)
|
||||
curveTo(131.888f, 48.454f, 127.29f, 49.312f, 123.154f, 51.028f)
|
||||
curveTo(119.018f, 52.744f, 115.476f, 55.208f, 112.528f, 58.42f)
|
||||
curveTo(109.624f, 61.588f, 107.38f, 65.482f, 105.796f, 70.102f)
|
||||
curveTo(104.212f, 74.722f, 103.42f, 79.936f, 103.42f, 85.744f)
|
||||
curveTo(103.42f, 91.552f, 104.212f, 96.766f, 105.796f, 101.386f)
|
||||
curveTo(107.38f, 105.962f, 109.624f, 109.856f, 112.528f, 113.068f)
|
||||
curveTo(115.476f, 116.236f, 119.018f, 118.678f, 123.154f, 120.394f)
|
||||
curveTo(127.29f, 122.066f, 131.888f, 122.902f, 136.948f, 122.902f)
|
||||
curveTo(142.052f, 122.902f, 146.672f, 122.066f, 150.808f, 120.394f)
|
||||
curveTo(154.944f, 118.678f, 158.464f, 116.236f, 161.368f, 113.068f)
|
||||
curveTo(164.272f, 109.856f, 166.516f, 105.962f, 168.1f, 101.386f)
|
||||
curveTo(169.684f, 96.766f, 170.476f, 91.552f, 170.476f, 85.744f)
|
||||
close()
|
||||
moveTo(227.208f, 87.46f)
|
||||
curveTo(230.86f, 87.46f, 234.072f, 86.976f, 236.844f, 86.008f)
|
||||
curveTo(239.66f, 85.04f, 242.014f, 83.698f, 243.906f, 81.982f)
|
||||
curveTo(245.842f, 80.222f, 247.294f, 78.132f, 248.262f, 75.712f)
|
||||
curveTo(249.23f, 73.292f, 249.714f, 70.63f, 249.714f, 67.726f)
|
||||
curveTo(249.714f, 61.698f, 247.844f, 56.99f, 244.104f, 53.602f)
|
||||
curveTo(240.408f, 50.214f, 234.776f, 48.52f, 227.208f, 48.52f)
|
||||
horizontalLineTo(212.028f)
|
||||
verticalLineTo(87.46f)
|
||||
horizontalLineTo(227.208f)
|
||||
close()
|
||||
moveTo(227.208f, 38.422f)
|
||||
curveTo(233.192f, 38.422f, 238.384f, 39.126f, 242.784f, 40.534f)
|
||||
curveTo(247.228f, 41.898f, 250.902f, 43.856f, 253.806f, 46.408f)
|
||||
curveTo(256.71f, 48.96f, 258.866f, 52.04f, 260.274f, 55.648f)
|
||||
curveTo(261.726f, 59.256f, 262.452f, 63.282f, 262.452f, 67.726f)
|
||||
curveTo(262.452f, 72.126f, 261.682f, 76.152f, 260.142f, 79.804f)
|
||||
curveTo(258.602f, 83.456f, 256.336f, 86.602f, 253.344f, 89.242f)
|
||||
curveTo(250.396f, 91.882f, 246.722f, 93.95f, 242.322f, 95.446f)
|
||||
curveTo(237.966f, 96.898f, 232.928f, 97.624f, 227.208f, 97.624f)
|
||||
horizontalLineTo(212.028f)
|
||||
verticalLineTo(133.0f)
|
||||
horizontalLineTo(199.29f)
|
||||
verticalLineTo(38.422f)
|
||||
horizontalLineTo(227.208f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
return _pop!!
|
||||
}
|
||||
|
||||
private var _pop: ImageVector? = null
|
@ -0,0 +1,71 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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.core.ui.common.key.unittoicons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
|
||||
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.ImageVector.Builder
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
val UnittoIcons.Swap: ImageVector
|
||||
get() {
|
||||
if (_swap != null) {
|
||||
return _swap!!
|
||||
}
|
||||
_swap = Builder(name = "Swap", defaultWidth = 170.0.dp, defaultHeight = 170.0.dp,
|
||||
viewportWidth = 170.0f, viewportHeight = 170.0f).apply {
|
||||
path(fill = SolidColor(Color(0xFF1C1B1F)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero) {
|
||||
moveTo(66.2f, 89.8f)
|
||||
verticalLineTo(55.36f)
|
||||
lineTo(53.84f, 67.72f)
|
||||
lineTo(47.0f, 61.0f)
|
||||
lineTo(71.0f, 37.0f)
|
||||
lineTo(95.0f, 61.0f)
|
||||
lineTo(88.16f, 67.72f)
|
||||
lineTo(75.8f, 55.36f)
|
||||
verticalLineTo(89.8f)
|
||||
horizontalLineTo(66.2f)
|
||||
close()
|
||||
moveTo(99.8f, 133.0f)
|
||||
lineTo(75.8f, 109.0f)
|
||||
lineTo(82.64f, 102.28f)
|
||||
lineTo(95.0f, 114.64f)
|
||||
verticalLineTo(80.2f)
|
||||
horizontalLineTo(104.6f)
|
||||
verticalLineTo(114.64f)
|
||||
lineTo(116.96f, 102.28f)
|
||||
lineTo(123.8f, 109.0f)
|
||||
lineTo(99.8f, 133.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
return _swap!!
|
||||
}
|
||||
|
||||
private var _swap: ImageVector? = null
|
@ -0,0 +1,75 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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.core.ui.common.key.unittoicons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
|
||||
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.ImageVector.Builder
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
val UnittoIcons.Unary: ImageVector
|
||||
get() {
|
||||
if (_unary != null) {
|
||||
return _unary!!
|
||||
}
|
||||
_unary = Builder(name = "Unary", defaultWidth = 170.0.dp, defaultHeight = 170.0.dp,
|
||||
viewportWidth = 170.0f, viewportHeight = 170.0f).apply {
|
||||
path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero) {
|
||||
moveTo(46.0f, 71.125f)
|
||||
horizontalLineTo(124.0f)
|
||||
verticalLineTo(81.363f)
|
||||
horizontalLineTo(46.0f)
|
||||
verticalLineTo(71.125f)
|
||||
close()
|
||||
}
|
||||
path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero) {
|
||||
moveTo(90.85f, 37.0f)
|
||||
lineTo(90.85f, 115.0f)
|
||||
horizontalLineTo(80.613f)
|
||||
lineTo(80.613f, 37.0f)
|
||||
lineTo(90.85f, 37.0f)
|
||||
close()
|
||||
}
|
||||
path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero) {
|
||||
moveTo(46.0f, 124.0f)
|
||||
horizontalLineTo(124.0f)
|
||||
verticalLineTo(133.0f)
|
||||
horizontalLineTo(46.0f)
|
||||
verticalLineTo(124.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
return _unary!!
|
||||
}
|
||||
|
||||
private var _unary: ImageVector? = null
|
@ -0,0 +1,60 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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.core.ui.common.key.unittoicons
|
||||
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
|
||||
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.graphics.vector.ImageVector.Builder
|
||||
import androidx.compose.ui.graphics.vector.path
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
|
||||
|
||||
@Suppress("UnusedReceiverParameter")
|
||||
val UnittoIcons.Up: ImageVector
|
||||
get() {
|
||||
if (_up != null) {
|
||||
return _up!!
|
||||
}
|
||||
_up = Builder(name = "Up", defaultWidth = 170.0.dp, defaultHeight = 170.0.dp, viewportWidth
|
||||
= 170.0f, viewportHeight = 170.0f).apply {
|
||||
path(fill = SolidColor(Color(0xFF1C1B1F)), stroke = null, strokeLineWidth = 0.0f,
|
||||
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
|
||||
pathFillType = NonZero) {
|
||||
moveTo(79.0f, 133.0f)
|
||||
verticalLineTo(59.95f)
|
||||
lineTo(45.4f, 93.55f)
|
||||
lineTo(37.0f, 85.0f)
|
||||
lineTo(85.0f, 37.0f)
|
||||
lineTo(133.0f, 85.0f)
|
||||
lineTo(124.6f, 93.55f)
|
||||
lineTo(91.0f, 59.95f)
|
||||
verticalLineTo(133.0f)
|
||||
horizontalLineTo(79.0f)
|
||||
close()
|
||||
}
|
||||
}
|
||||
.build()
|
||||
return _up!!
|
||||
}
|
||||
|
||||
private var _up: ImageVector? = null
|
@ -0,0 +1,92 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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.core.ui.common.textfield
|
||||
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalTextInputService
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sadellie.unitto.core.ui.theme.LocalNumberTypography
|
||||
|
||||
@Composable
|
||||
fun FixedInputTextField(
|
||||
modifier: Modifier = Modifier,
|
||||
value: String,
|
||||
formatterSymbols: FormatterSymbols,
|
||||
textColor: Color,
|
||||
onClick: (cleanValue: String) -> Unit
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val expression = value.take(1000)
|
||||
var expressionValue by remember(expression) {
|
||||
mutableStateOf(TextFieldValue(expression, TextRange(expression.length)))
|
||||
}
|
||||
|
||||
val expressionInteractionSource = remember(expression) { MutableInteractionSource() }
|
||||
LaunchedEffect(expressionInteractionSource) {
|
||||
expressionInteractionSource.interactions.collect {
|
||||
if (it is PressInteraction.Release) onClick(expression.clearAndFilterExpression(formatterSymbols))
|
||||
}
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalTextInputService provides null,
|
||||
LocalTextToolbar provides UnittoTextToolbar(
|
||||
view = LocalView.current,
|
||||
copyCallback = {
|
||||
clipboardManager.copyWithoutGrouping(expressionValue, formatterSymbols)
|
||||
expressionValue = expressionValue.copy(selection = TextRange(expressionValue.selection.end))
|
||||
}
|
||||
)
|
||||
) {
|
||||
BasicTextField(
|
||||
value = expressionValue,
|
||||
onValueChange = { expressionValue = it },
|
||||
maxLines = 1,
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp)
|
||||
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
|
||||
textStyle = LocalNumberTypography.current.displaySmall.copy(color = textColor, textAlign = TextAlign.End),
|
||||
readOnly = true,
|
||||
visualTransformation = ExpressionTransformer(formatterSymbols),
|
||||
interactionSource = expressionInteractionSource
|
||||
)
|
||||
}
|
||||
}
|
@ -24,6 +24,7 @@ import java.math.RoundingMode
|
||||
import kotlin.math.floor
|
||||
import kotlin.math.log10
|
||||
|
||||
// TODO Use everywhere
|
||||
fun BigDecimal.format(
|
||||
scale: Int,
|
||||
outputFormat: Int
|
||||
@ -34,6 +35,7 @@ fun BigDecimal.format(
|
||||
.toStringWith(outputFormat)
|
||||
}
|
||||
|
||||
// TODO Move tests and mark as internal
|
||||
/**
|
||||
* Shorthand function to use correct `toString` method according to [outputFormat].
|
||||
*/
|
||||
@ -76,5 +78,7 @@ fun BigDecimal.setMinimumRequiredScale(prefScale: Int): BigDecimal {
|
||||
* Removes all trailing zeroes.
|
||||
*/
|
||||
fun BigDecimal.trimZeros(): BigDecimal {
|
||||
return if (this.compareTo(BigDecimal.ZERO) == 0) BigDecimal.ZERO else this.stripTrailingZeros()
|
||||
return if (this.isEqualTo(BigDecimal.ZERO)) BigDecimal.ZERO else this.stripTrailingZeros()
|
||||
}
|
||||
|
||||
fun BigDecimal.isEqualTo(bd: BigDecimal): Boolean = compareTo(bd) == 0
|
||||
|
@ -0,0 +1,103 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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 java.math.BigDecimal
|
||||
import java.math.MathContext
|
||||
import java.math.RoundingMode
|
||||
import kotlin.math.acos
|
||||
import kotlin.math.asin
|
||||
import kotlin.math.atan
|
||||
import kotlin.math.pow
|
||||
|
||||
internal fun BigDecimal.sin(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = if (radianMode) this.toDouble() else Math.toRadians(this.toDouble())
|
||||
return kotlin.math.sin(angle).toBigDecimal()
|
||||
}
|
||||
|
||||
internal fun BigDecimal.arsin(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = asin(this.toDouble())
|
||||
return (if (radianMode) angle else Math.toDegrees(angle)).toBigDecimal()
|
||||
}
|
||||
|
||||
internal fun BigDecimal.cos(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = if (radianMode) this.toDouble() else Math.toRadians(this.toDouble())
|
||||
return kotlin.math.cos(angle).toBigDecimal()
|
||||
}
|
||||
|
||||
internal fun BigDecimal.arcos(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = acos(this.toDouble())
|
||||
return (if (radianMode) angle else Math.toDegrees(angle)).toBigDecimal()
|
||||
}
|
||||
|
||||
internal fun BigDecimal.tan(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = if (radianMode) this.toDouble() else Math.toRadians(this.toDouble())
|
||||
return kotlin.math.tan(angle).toBigDecimal()
|
||||
}
|
||||
|
||||
internal fun BigDecimal.artan(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = atan(this.toDouble())
|
||||
return (if (radianMode) angle else Math.toDegrees(angle)).toBigDecimal()
|
||||
}
|
||||
|
||||
internal fun BigDecimal.ln(): BigDecimal {
|
||||
return kotlin.math.ln(this.toDouble()).toBigDecimal()
|
||||
}
|
||||
|
||||
internal fun BigDecimal.log(): BigDecimal {
|
||||
return kotlin.math.log(this.toDouble(), 10.0).toBigDecimal()
|
||||
}
|
||||
|
||||
internal fun BigDecimal.exp(): BigDecimal {
|
||||
return kotlin.math.exp(this.toDouble()).toBigDecimal()
|
||||
}
|
||||
|
||||
internal fun BigDecimal.pow(n: BigDecimal): BigDecimal {
|
||||
val mathContext: MathContext = MathContext.DECIMAL64
|
||||
|
||||
var right = n
|
||||
val signOfRight = right.signum()
|
||||
right = right.multiply(signOfRight.toBigDecimal())
|
||||
val remainderOfRight = right.remainder(BigDecimal.ONE)
|
||||
val n2IntPart = right.subtract(remainderOfRight)
|
||||
val intPow = pow(n2IntPart.intValueExact(), mathContext)
|
||||
val doublePow = BigDecimal(
|
||||
toDouble().pow(remainderOfRight.toDouble())
|
||||
)
|
||||
|
||||
var result = intPow.multiply(doublePow, mathContext)
|
||||
if (signOfRight == -1) result =
|
||||
BigDecimal.ONE.divide(result, mathContext.precision, RoundingMode.HALF_UP)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
internal fun BigDecimal.factorial(): BigDecimal {
|
||||
if (this.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) != 0) throw ExpressionException.FactorialCalculation()
|
||||
if (this < BigDecimal.ZERO) throw ExpressionException.FactorialCalculation()
|
||||
if (this > maxFactorial) throw ExpressionException.TooBig()
|
||||
|
||||
var expr = this
|
||||
for (i in 1 until this.toInt()) {
|
||||
expr *= BigDecimal(i)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
||||
private val maxFactorial by lazy { BigDecimal("9999") }
|
@ -21,20 +21,7 @@ package io.github.sadellie.evaluatto
|
||||
import com.sadellie.unitto.core.base.MAX_PRECISION
|
||||
import com.sadellie.unitto.core.base.Token
|
||||
import java.math.BigDecimal
|
||||
import java.math.MathContext
|
||||
import java.math.RoundingMode
|
||||
import kotlin.math.acos
|
||||
import kotlin.math.asin
|
||||
import kotlin.math.atan
|
||||
import kotlin.math.cos
|
||||
import kotlin.math.exp
|
||||
import kotlin.math.ln
|
||||
import kotlin.math.log
|
||||
import kotlin.math.pow
|
||||
import kotlin.math.sin
|
||||
import kotlin.math.tan
|
||||
|
||||
private val maxFactorial by lazy { BigDecimal("9999") }
|
||||
|
||||
sealed class ExpressionException(override val message: String): Exception(message) {
|
||||
class DivideByZero : ExpressionException("Can't divide by zero")
|
||||
@ -240,77 +227,3 @@ class Expression(
|
||||
return expr
|
||||
}
|
||||
}
|
||||
|
||||
private fun BigDecimal.sin(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = if (radianMode) this.toDouble() else Math.toRadians(this.toDouble())
|
||||
return sin(angle).toBigDecimal()
|
||||
}
|
||||
|
||||
private fun BigDecimal.arsin(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = asin(this.toDouble())
|
||||
return (if (radianMode) angle else Math.toDegrees(angle)).toBigDecimal()
|
||||
}
|
||||
|
||||
private fun BigDecimal.cos(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = if (radianMode) this.toDouble() else Math.toRadians(this.toDouble())
|
||||
return cos(angle).toBigDecimal()
|
||||
}
|
||||
|
||||
private fun BigDecimal.arcos(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = acos(this.toDouble())
|
||||
return (if (radianMode) angle else Math.toDegrees(angle)).toBigDecimal()
|
||||
}
|
||||
|
||||
private fun BigDecimal.tan(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = if (radianMode) this.toDouble() else Math.toRadians(this.toDouble())
|
||||
return tan(angle).toBigDecimal()
|
||||
}
|
||||
|
||||
private fun BigDecimal.artan(radianMode: Boolean): BigDecimal {
|
||||
val angle: Double = atan(this.toDouble())
|
||||
return (if (radianMode) angle else Math.toDegrees(angle)).toBigDecimal()
|
||||
}
|
||||
|
||||
private fun BigDecimal.ln(): BigDecimal {
|
||||
return ln(this.toDouble()).toBigDecimal()
|
||||
}
|
||||
|
||||
private fun BigDecimal.log(): BigDecimal {
|
||||
return log(this.toDouble(), 10.0).toBigDecimal()
|
||||
}
|
||||
|
||||
private fun BigDecimal.exp(): BigDecimal {
|
||||
return exp(this.toDouble()).toBigDecimal()
|
||||
}
|
||||
|
||||
private fun BigDecimal.pow(n: BigDecimal): BigDecimal {
|
||||
val mathContext: MathContext = MathContext.DECIMAL64
|
||||
|
||||
var right = n
|
||||
val signOfRight = right.signum()
|
||||
right = right.multiply(signOfRight.toBigDecimal())
|
||||
val remainderOfRight = right.remainder(BigDecimal.ONE)
|
||||
val n2IntPart = right.subtract(remainderOfRight)
|
||||
val intPow = pow(n2IntPart.intValueExact(), mathContext)
|
||||
val doublePow = BigDecimal(
|
||||
toDouble().pow(remainderOfRight.toDouble())
|
||||
)
|
||||
|
||||
var result = intPow.multiply(doublePow, mathContext)
|
||||
if (signOfRight == -1) result =
|
||||
BigDecimal.ONE.divide(result, mathContext.precision, RoundingMode.HALF_UP)
|
||||
|
||||
return result
|
||||
}
|
||||
|
||||
private fun BigDecimal.factorial(): BigDecimal {
|
||||
if (this.remainder(BigDecimal.ONE).compareTo(BigDecimal.ZERO) != 0) throw ExpressionException.FactorialCalculation()
|
||||
if (this < BigDecimal.ZERO) throw ExpressionException.FactorialCalculation()
|
||||
if (this > maxFactorial) throw ExpressionException.TooBig()
|
||||
|
||||
var expr = this
|
||||
for (i in 1 until this.toInt()) {
|
||||
expr *= BigDecimal(i)
|
||||
}
|
||||
return expr
|
||||
}
|
||||
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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
|
||||
}
|
@ -0,0 +1,191 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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))
|
||||
}
|
@ -0,0 +1,256 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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)
|
||||
}
|
||||
}
|
@ -88,4 +88,6 @@ interface UserPreferencesRepository {
|
||||
suspend fun updateAcButton(enabled: Boolean)
|
||||
|
||||
suspend fun updateClearInputAfterEquals(enabled: Boolean)
|
||||
|
||||
suspend fun updateRpnMode(enabled: Boolean)
|
||||
}
|
||||
|
@ -27,4 +27,5 @@ interface AppPreferences {
|
||||
val startingScreen: String
|
||||
val enableToolsExperiment: Boolean
|
||||
val systemFont: Boolean
|
||||
val rpnMode: Boolean
|
||||
}
|
||||
|
@ -41,6 +41,7 @@ data class AppPreferencesImpl(
|
||||
override val startingScreen: String,
|
||||
override val enableToolsExperiment: Boolean,
|
||||
override val systemFont: Boolean,
|
||||
override val rpnMode: Boolean,
|
||||
) : AppPreferences
|
||||
|
||||
data class GeneralPreferencesImpl(
|
||||
|
@ -24,28 +24,35 @@ import androidx.datastore.preferences.core.longPreferencesKey
|
||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||
|
||||
internal object PrefsKeys {
|
||||
// COMMON
|
||||
val THEMING_MODE = stringPreferencesKey("THEMING_MODE_PREF_KEY")
|
||||
val ENABLE_DYNAMIC_THEME = booleanPreferencesKey("ENABLE_DYNAMIC_THEME_PREF_KEY")
|
||||
val ENABLE_AMOLED_THEME = booleanPreferencesKey("ENABLE_AMOLED_THEME_PREF_KEY")
|
||||
val CUSTOM_COLOR = longPreferencesKey("CUSTOM_COLOR_PREF_KEY")
|
||||
val MONET_MODE = stringPreferencesKey("MONET_MODE_PREF_KEY")
|
||||
val STARTING_SCREEN = stringPreferencesKey("STARTING_SCREEN_PREF_KEY")
|
||||
val ENABLE_TOOLS_EXPERIMENT = booleanPreferencesKey("ENABLE_TOOLS_EXPERIMENT_PREF_KEY")
|
||||
val SYSTEM_FONT = booleanPreferencesKey("SYSTEM_FONT_PREF_KEY")
|
||||
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")
|
||||
|
||||
// FORMATTER
|
||||
val DIGITS_PRECISION = intPreferencesKey("DIGITS_PRECISION_PREF_KEY")
|
||||
val SEPARATOR = intPreferencesKey("SEPARATOR_PREF_KEY")
|
||||
val OUTPUT_FORMAT = intPreferencesKey("OUTPUT_FORMAT_PREF_KEY")
|
||||
|
||||
// CALCULATOR
|
||||
val RADIAN_MODE = booleanPreferencesKey("RADIAN_MODE_PREF_KEY")
|
||||
val PARTIAL_HISTORY_VIEW = booleanPreferencesKey("PARTIAL_HISTORY_VIEW_PREF_KEY")
|
||||
val CLEAR_INPUT_AFTER_EQUALS = booleanPreferencesKey("CLEAR_INPUT_AFTER_EQUALS_PREF_KEY")
|
||||
|
||||
// UNIT CONVERTER
|
||||
val LATEST_LEFT_SIDE = stringPreferencesKey("LATEST_LEFT_SIDE_PREF_KEY")
|
||||
val LATEST_RIGHT_SIDE = stringPreferencesKey("LATEST_RIGHT_SIDE_PREF_KEY")
|
||||
val SHOWN_UNIT_GROUPS = stringPreferencesKey("SHOWN_UNIT_GROUPS_PREF_KEY")
|
||||
val ENABLE_VIBRATIONS = booleanPreferencesKey("ENABLE_VIBRATIONS_PREF_KEY")
|
||||
val ENABLE_TOOLS_EXPERIMENT = booleanPreferencesKey("ENABLE_TOOLS_EXPERIMENT_PREF_KEY")
|
||||
val STARTING_SCREEN = stringPreferencesKey("STARTING_SCREEN_PREF_KEY")
|
||||
val RADIAN_MODE = booleanPreferencesKey("RADIAN_MODE_PREF_KEY")
|
||||
val UNIT_CONVERTER_FAVORITES_ONLY =
|
||||
booleanPreferencesKey("UNIT_CONVERTER_FAVORITES_ONLY_PREF_KEY")
|
||||
val UNIT_CONVERTER_FAVORITES_ONLY = booleanPreferencesKey("UNIT_CONVERTER_FAVORITES_ONLY_PREF_KEY")
|
||||
val UNIT_CONVERTER_FORMAT_TIME = booleanPreferencesKey("UNIT_CONVERTER_FORMAT_TIME_PREF_KEY")
|
||||
val UNIT_CONVERTER_SORTING = stringPreferencesKey("UNIT_CONVERTER_SORTING_PREF_KEY")
|
||||
val MIDDLE_ZERO = booleanPreferencesKey("MIDDLE_ZERO_PREF_KEY")
|
||||
val SYSTEM_FONT = booleanPreferencesKey("SYSTEM_FONT_PREF_KEY")
|
||||
val PARTIAL_HISTORY_VIEW = booleanPreferencesKey("PARTIAL_HISTORY_VIEW_PREF_KEY")
|
||||
val AC_BUTTON = booleanPreferencesKey("AC_BUTTON_PREF_KEY")
|
||||
val CLEAR_INPUT_AFTER_EQUALS = booleanPreferencesKey("CLEAR_INPUT_AFTER_EQUALS_PREF_KEY")
|
||||
}
|
||||
|
@ -63,7 +63,8 @@ class UserPreferencesRepositoryImpl @Inject constructor(
|
||||
monetMode = preferences.getMonetMode(),
|
||||
startingScreen = preferences.getStartingScreen(),
|
||||
enableToolsExperiment = preferences.getEnableToolsExperiment(),
|
||||
systemFont = preferences.getSystemFont()
|
||||
systemFont = preferences.getSystemFont(),
|
||||
rpnMode = preferences.getRpnMode(),
|
||||
)
|
||||
}
|
||||
|
||||
@ -287,6 +288,12 @@ class UserPreferencesRepositoryImpl @Inject constructor(
|
||||
preferences[PrefsKeys.CLEAR_INPUT_AFTER_EQUALS] = enabled
|
||||
}
|
||||
}
|
||||
|
||||
override suspend fun updateRpnMode(enabled: Boolean) {
|
||||
dataStore.edit { preferences ->
|
||||
preferences[PrefsKeys.RPN_MODE] = enabled
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun Preferences.getEnableDynamicTheme(): Boolean {
|
||||
@ -387,6 +394,10 @@ private fun Preferences.getClearInputAfterEquals(): Boolean {
|
||||
return this[PrefsKeys.CLEAR_INPUT_AFTER_EQUALS] ?: true
|
||||
}
|
||||
|
||||
private 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) {
|
||||
|
@ -19,6 +19,7 @@
|
||||
package com.sadellie.unitto.feature.calculator
|
||||
|
||||
import com.sadellie.unitto.core.base.Token
|
||||
import com.sadellie.unitto.data.common.isEqualTo
|
||||
import java.math.BigDecimal
|
||||
import java.math.BigInteger
|
||||
import java.math.RoundingMode
|
||||
@ -101,5 +102,4 @@ private fun BigDecimal.repeatingDecimals(): String? {
|
||||
return null
|
||||
}
|
||||
|
||||
private fun BigDecimal.isEqualTo(bd: BigDecimal): Boolean = compareTo(bd) == 0
|
||||
private val maxDenominator by lazy { BigInteger("1000000000") }
|
||||
|
@ -0,0 +1,120 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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
|
||||
)
|
||||
}
|
@ -0,0 +1,149 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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.Arrangement
|
||||
import androidx.compose.foundation.layout.ExperimentalLayoutApi
|
||||
import androidx.compose.foundation.layout.FlowRow
|
||||
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.tooling.preview.Preview
|
||||
import com.sadellie.unitto.core.base.Token
|
||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonAdditional
|
||||
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.key.UnittoIcons
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Backspace
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Clear
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Comma
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Divide
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Dot
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Down
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Enter
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key0
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key1
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key2
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key3
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key4
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key5
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key6
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key7
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key8
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key9
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Minus
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Multiply
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Percent
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Plus
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Pop
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Swap
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Unary
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.Up
|
||||
import io.github.sadellie.evaluatto.RPNCalculation
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@Composable
|
||||
internal fun RPNCalculatorKeyboard(
|
||||
modifier: Modifier,
|
||||
fractional: String,
|
||||
middleZero: Boolean,
|
||||
allowVibration: Boolean,
|
||||
onCalculationClick: (RPNCalculation) -> Unit,
|
||||
onInputEditClick: (RPNInputEdit) -> Unit,
|
||||
) {
|
||||
val fractionalIcon = remember(fractional) { if (fractional == Token.Digit.dot) UnittoIcons.Dot else UnittoIcons.Comma }
|
||||
|
||||
val columns = 4
|
||||
val mainButtonRows = 5f
|
||||
val additionalButtonRows = 1f
|
||||
val additionalButtonRowFactor = 0.7f // How much smaller are the additional buttons than the main buttons
|
||||
val additionalButtonIconHeight = 0.65f
|
||||
val fillFactor = 0.92f
|
||||
|
||||
val rows = remember { mainButtonRows + additionalButtonRows * additionalButtonRowFactor }
|
||||
val height = remember { fillFactor / rows }
|
||||
val width = remember { fillFactor / columns }
|
||||
|
||||
FlowRow(
|
||||
maxItemsInEachRow = columns,
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.SpaceAround,
|
||||
verticalArrangement = Arrangement.SpaceAround
|
||||
) {
|
||||
val aModifier = Modifier
|
||||
.fillMaxHeight(height * additionalButtonRowFactor)
|
||||
.fillMaxWidth(width)
|
||||
|
||||
val bModifier = Modifier
|
||||
.fillMaxHeight(height)
|
||||
.fillMaxWidth(width)
|
||||
|
||||
KeyboardButtonAdditional(modifier = aModifier, icon = UnittoIcons.Swap, allowVibration = allowVibration, contentHeight = additionalButtonIconHeight) { onCalculationClick(RPNCalculation.Swap) }
|
||||
KeyboardButtonAdditional(modifier = aModifier, icon = UnittoIcons.Up, allowVibration = allowVibration, contentHeight = additionalButtonIconHeight) { onCalculationClick(RPNCalculation.RotateUp) }
|
||||
KeyboardButtonAdditional(modifier = aModifier, icon = UnittoIcons.Down, allowVibration = allowVibration, contentHeight = additionalButtonIconHeight) { onCalculationClick(RPNCalculation.RotateDown) }
|
||||
KeyboardButtonAdditional(modifier = aModifier, icon = UnittoIcons.Pop, allowVibration = allowVibration, contentHeight = additionalButtonIconHeight) { onCalculationClick(RPNCalculation.Pop) }
|
||||
|
||||
KeyboardButtonTertiary(modifier = bModifier, icon = UnittoIcons.Clear, allowVibration = allowVibration) { onCalculationClick(RPNCalculation.Clear) }
|
||||
KeyboardButtonFilled(modifier = bModifier, icon = UnittoIcons.Unary, allowVibration = allowVibration) { onCalculationClick(RPNCalculation.Negate) }
|
||||
KeyboardButtonFilled(modifier = bModifier, icon = UnittoIcons.Percent, allowVibration = allowVibration) { onCalculationClick(RPNCalculation.Percent) }
|
||||
KeyboardButtonFilled(modifier = bModifier, icon = UnittoIcons.Divide, allowVibration = allowVibration) { onCalculationClick(RPNCalculation.Divide) }
|
||||
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Key7, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._7)) }
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Key8, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._8)) }
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Key9, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._9)) }
|
||||
KeyboardButtonFilled(modifier = bModifier, icon = UnittoIcons.Multiply, allowVibration = allowVibration) { onCalculationClick(RPNCalculation.Multiply) }
|
||||
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Key4, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._4)) }
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Key5, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._5)) }
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Key6, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._6)) }
|
||||
KeyboardButtonFilled(modifier = bModifier, icon = UnittoIcons.Minus, allowVibration = allowVibration) { onCalculationClick(RPNCalculation.Minus) }
|
||||
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Key1, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._1)) }
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Key2, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._2)) }
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Key3, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._3)) }
|
||||
KeyboardButtonFilled(modifier = bModifier, icon = UnittoIcons.Plus, allowVibration = allowVibration) { onCalculationClick(RPNCalculation.Plus) }
|
||||
|
||||
if (middleZero) {
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Key0, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._0)) }
|
||||
KeyboardButtonLight(modifier = bModifier, icon = fractionalIcon, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Dot) }
|
||||
} else {
|
||||
KeyboardButtonLight(modifier = bModifier, icon = fractionalIcon, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Dot) }
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Key0, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Digit(Token.Digit._0)) }
|
||||
}
|
||||
KeyboardButtonLight(modifier = bModifier, icon = UnittoIcons.Backspace, allowVibration = allowVibration) { onInputEditClick(RPNInputEdit.Delete) }
|
||||
KeyboardButtonFilled(modifier = bModifier, icon = UnittoIcons.Enter, allowVibration = allowVibration) { onCalculationClick(RPNCalculation.Enter) }
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewKeyboard() {
|
||||
RPNCalculatorKeyboard(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
fractional = Token.Digit.dot,
|
||||
middleZero = false,
|
||||
allowVibration = false,
|
||||
onCalculationClick = {},
|
||||
onInputEditClick = {}
|
||||
)
|
||||
}
|
@ -0,0 +1,134 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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 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.common.MenuButton
|
||||
import com.sadellie.unitto.core.ui.common.SettingsButton
|
||||
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
|
||||
import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar
|
||||
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 -> UnittoEmptyScreen()
|
||||
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,
|
||||
) {
|
||||
UnittoScreenWithTopBar(
|
||||
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(0.3f),
|
||||
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,
|
||||
allowVibration = uiState.allowVibration,
|
||||
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,
|
||||
allowVibration = true,
|
||||
middleZero = true,
|
||||
),
|
||||
openDrawer = {},
|
||||
navigateToSettings = {},
|
||||
onCalculationClick = {},
|
||||
onInputEditClick = {},
|
||||
onCursorChange = {}
|
||||
)
|
||||
}
|
@ -0,0 +1,37 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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 allowVibration: Boolean,
|
||||
val middleZero: Boolean,
|
||||
) : RPNCalculatorUIState()
|
||||
}
|
@ -0,0 +1,118 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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),
|
||||
allowVibration = prefs.enableVibrations,
|
||||
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()
|
||||
}
|
||||
|
||||
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) }
|
||||
}
|
@ -0,0 +1,25 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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()
|
||||
}
|
@ -19,53 +19,30 @@
|
||||
package com.sadellie.unitto.feature.calculator.components
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.interaction.PressInteraction
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentHeight
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.lazy.items
|
||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.History
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalFocusManager
|
||||
import androidx.compose.ui.platform.LocalTextInputService
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.TextRange
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sadellie.unitto.core.base.R
|
||||
import com.sadellie.unitto.core.ui.common.textfield.ExpressionTransformer
|
||||
import com.sadellie.unitto.core.ui.common.textfield.FixedInputTextField
|
||||
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
|
||||
import com.sadellie.unitto.core.ui.common.textfield.UnittoTextToolbar
|
||||
import com.sadellie.unitto.core.ui.common.textfield.clearAndFilterExpression
|
||||
import com.sadellie.unitto.core.ui.common.textfield.copyWithoutGrouping
|
||||
import com.sadellie.unitto.core.ui.theme.LocalNumberTypography
|
||||
import com.sadellie.unitto.data.model.HistoryItem
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.Locale
|
||||
@ -151,83 +128,23 @@ private fun HistoryListItem(
|
||||
formatterSymbols: FormatterSymbols,
|
||||
addTokens: (String) -> Unit,
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
val expression = historyItem.expression.take(1000)
|
||||
var expressionValue by remember(expression) {
|
||||
mutableStateOf(TextFieldValue(expression, TextRange(expression.length)))
|
||||
}
|
||||
val result = historyItem.result.take(1000)
|
||||
var resultValue by remember(result) {
|
||||
mutableStateOf(TextFieldValue(result, TextRange(result.length)))
|
||||
}
|
||||
|
||||
val expressionInteractionSource = remember(expression) { MutableInteractionSource() }
|
||||
LaunchedEffect(expressionInteractionSource) {
|
||||
expressionInteractionSource.interactions.collect {
|
||||
if (it is PressInteraction.Release) addTokens(expression.clearAndFilterExpression(formatterSymbols))
|
||||
}
|
||||
}
|
||||
|
||||
val resultInteractionSource = remember(result) { MutableInteractionSource() }
|
||||
LaunchedEffect(resultInteractionSource) {
|
||||
resultInteractionSource.interactions.collect {
|
||||
if (it is PressInteraction.Release) addTokens(result.clearAndFilterExpression(formatterSymbols))
|
||||
}
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier.height(HistoryItemHeight),
|
||||
verticalArrangement = Arrangement.Center
|
||||
) {
|
||||
CompositionLocalProvider(
|
||||
LocalTextInputService provides null,
|
||||
LocalTextToolbar provides UnittoTextToolbar(
|
||||
view = LocalView.current,
|
||||
copyCallback = {
|
||||
clipboardManager.copyWithoutGrouping(expressionValue, formatterSymbols)
|
||||
expressionValue = expressionValue.copy(selection = TextRange(expressionValue.selection.end))
|
||||
}
|
||||
FixedInputTextField(
|
||||
value = historyItem.expression,
|
||||
formatterSymbols = formatterSymbols,
|
||||
textColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
onClick = addTokens,
|
||||
)
|
||||
) {
|
||||
BasicTextField(
|
||||
value = expressionValue,
|
||||
onValueChange = { expressionValue = it },
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp)
|
||||
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
|
||||
textStyle = LocalNumberTypography.current.displaySmall.copy(color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.End),
|
||||
readOnly = true,
|
||||
visualTransformation = ExpressionTransformer(formatterSymbols),
|
||||
interactionSource = expressionInteractionSource
|
||||
)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalTextInputService provides null,
|
||||
LocalTextToolbar provides UnittoTextToolbar(
|
||||
view = LocalView.current,
|
||||
copyCallback = {
|
||||
clipboardManager.copyWithoutGrouping(resultValue, formatterSymbols)
|
||||
resultValue = resultValue.copy(selection = TextRange(resultValue.selection.end))
|
||||
}
|
||||
FixedInputTextField(
|
||||
value = historyItem.result,
|
||||
formatterSymbols = formatterSymbols,
|
||||
textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
|
||||
onClick = addTokens,
|
||||
)
|
||||
) {
|
||||
BasicTextField(
|
||||
value = resultValue,
|
||||
onValueChange = { resultValue = it },
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp)
|
||||
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
|
||||
textStyle = LocalNumberTypography.current.displaySmall.copy(color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f), textAlign = TextAlign.End),
|
||||
readOnly = true,
|
||||
visualTransformation = ExpressionTransformer(formatterSymbols),
|
||||
interactionSource = resultInteractionSource
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -24,12 +24,14 @@ import com.sadellie.unitto.core.base.TopLevelDestinations
|
||||
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 = TopLevelDestinations.Calculator.graph
|
||||
private val start = TopLevelDestinations.Calculator.start
|
||||
|
||||
fun NavGraphBuilder.calculatorGraph(
|
||||
navigateToMenu: () -> Unit,
|
||||
rpnMode: Boolean,
|
||||
openDrawer: () -> Unit,
|
||||
navigateToSettings: () -> Unit
|
||||
) {
|
||||
unittoNavigation(
|
||||
@ -40,10 +42,17 @@ fun NavGraphBuilder.calculatorGraph(
|
||||
)
|
||||
) {
|
||||
unittoComposable(start) {
|
||||
if (rpnMode) {
|
||||
RPNCalculatorRoute(
|
||||
openDrawer = openDrawer,
|
||||
navigateToSettings = navigateToSettings
|
||||
)
|
||||
} else {
|
||||
CalculatorRoute(
|
||||
navigateToMenu = navigateToMenu,
|
||||
navigateToMenu = openDrawer,
|
||||
navigateToSettings = navigateToSettings
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -18,95 +18,139 @@
|
||||
|
||||
package com.sadellie.unitto.feature.settings.calculator
|
||||
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.animation.Crossfade
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.automirrored.filled.Backspace
|
||||
import androidx.compose.material.icons.filled.Timer
|
||||
import androidx.compose.material3.SegmentedButton
|
||||
import androidx.compose.material3.SegmentedButtonDefaults
|
||||
import androidx.compose.material3.SingleChoiceSegmentedButtonRow
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
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.base.Separator
|
||||
import com.sadellie.unitto.core.ui.common.NavigateUpButton
|
||||
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
|
||||
import com.sadellie.unitto.core.ui.common.UnittoListItem
|
||||
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
|
||||
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.prefs.collectAsStateWithLifecycle().value) {
|
||||
null -> UnittoEmptyScreen()
|
||||
when (val prefs = viewModel.uiState.collectAsStateWithLifecycle().value) {
|
||||
CalculatorSettingsUIState.Loading -> UnittoEmptyScreen()
|
||||
else -> {
|
||||
CalculatorSettingsScreen(
|
||||
prefs = prefs,
|
||||
uiState = prefs,
|
||||
navigateUpAction = navigateUpAction,
|
||||
updatePartialHistoryView = viewModel::updatePartialHistoryView,
|
||||
updateClearInputAfterEquals = viewModel::updateClearInputAfterEquals,
|
||||
updateRpnMode = viewModel::updateRpnMode,
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TODO Translate
|
||||
@Composable
|
||||
private fun CalculatorSettingsScreen(
|
||||
prefs: CalculatorPreferences,
|
||||
uiState: CalculatorSettingsUIState,
|
||||
navigateUpAction: () -> Unit,
|
||||
updatePartialHistoryView: (Boolean) -> Unit,
|
||||
updateClearInputAfterEquals: (Boolean) -> Unit,
|
||||
updateRpnMode: (Boolean) -> Unit,
|
||||
) {
|
||||
UnittoScreenWithLargeTopBar(
|
||||
title = stringResource(R.string.calculator_title),
|
||||
navigationIcon = { NavigateUpButton(navigateUpAction) }
|
||||
) { padding ->
|
||||
LazyColumn(contentPadding = padding) {
|
||||
item("partial history") {
|
||||
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 {
|
||||
UnittoListItem(
|
||||
headlineText = stringResource(R.string.settings_partial_history_view),
|
||||
icon = Icons.Default.Timer,
|
||||
supportingText = stringResource(R.string.settings_partial_history_view_support),
|
||||
switchState = prefs.partialHistoryView,
|
||||
switchState = state.partialHistoryView,
|
||||
onSwitchChange = updatePartialHistoryView
|
||||
)
|
||||
}
|
||||
|
||||
item("clear input") {
|
||||
UnittoListItem(
|
||||
headlineText = stringResource(R.string.settings_clear_input),
|
||||
icon = Icons.AutoMirrored.Filled.Backspace,
|
||||
supportingText = stringResource(R.string.settings_clear_input_support),
|
||||
switchState = prefs.clearInputAfterEquals,
|
||||
switchState = state.clearInputAfterEquals,
|
||||
onSwitchChange = updateClearInputAfterEquals
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
else -> Unit
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewCalculatorSettingsScreen() {
|
||||
private fun PreviewCalculatorSettingsScreenStandard() {
|
||||
CalculatorSettingsScreen(
|
||||
prefs = CalculatorPreferencesImpl(
|
||||
radianMode = false,
|
||||
enableVibrations = false,
|
||||
separator = Separator.SPACE,
|
||||
middleZero = false,
|
||||
uiState = CalculatorSettingsUIState.Standard(
|
||||
partialHistoryView = true,
|
||||
precision = 3,
|
||||
outputFormat = OutputFormat.PLAIN,
|
||||
acButton = true,
|
||||
clearInputAfterEquals = true,
|
||||
clearInputAfterEquals = false
|
||||
),
|
||||
navigateUpAction = {},
|
||||
updatePartialHistoryView = {},
|
||||
updateClearInputAfterEquals = {},
|
||||
updateRpnMode = {}
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewCalculatorSettingsScreenRPN() {
|
||||
CalculatorSettingsScreen(
|
||||
uiState = CalculatorSettingsUIState.RPN,
|
||||
navigateUpAction = {},
|
||||
updatePartialHistoryView = {},
|
||||
updateClearInputAfterEquals = {},
|
||||
updateRpnMode = {}
|
||||
)
|
||||
}
|
||||
|
@ -0,0 +1,30 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 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,
|
||||
val clearInputAfterEquals: Boolean,
|
||||
) : CalculatorSettingsUIState()
|
||||
}
|
@ -23,6 +23,7 @@ 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
|
||||
|
||||
@ -30,8 +31,20 @@ import javax.inject.Inject
|
||||
internal class CalculatorSettingsViewModel @Inject constructor(
|
||||
private val userPrefsRepository: UserPreferencesRepository,
|
||||
) : ViewModel() {
|
||||
val prefs = userPrefsRepository.calculatorPrefs
|
||||
.stateIn(viewModelScope, null)
|
||||
val uiState = combine(
|
||||
userPrefsRepository.appPrefs,
|
||||
userPrefsRepository.calculatorPrefs,
|
||||
) { app, calc ->
|
||||
if (app.rpnMode) {
|
||||
CalculatorSettingsUIState.RPN
|
||||
} else {
|
||||
CalculatorSettingsUIState.Standard(
|
||||
partialHistoryView = calc.partialHistoryView,
|
||||
clearInputAfterEquals = calc.clearInputAfterEquals
|
||||
)
|
||||
}
|
||||
}
|
||||
.stateIn(viewModelScope, CalculatorSettingsUIState.Loading)
|
||||
|
||||
fun updatePartialHistoryView(enabled: Boolean) = viewModelScope.launch {
|
||||
userPrefsRepository.updatePartialHistoryView(enabled)
|
||||
@ -40,4 +53,8 @@ internal class CalculatorSettingsViewModel @Inject constructor(
|
||||
fun updateClearInputAfterEquals(enabled: Boolean) = viewModelScope.launch {
|
||||
userPrefsRepository.updateClearInputAfterEquals(enabled)
|
||||
}
|
||||
|
||||
fun updateRpnMode(enabled: Boolean) = viewModelScope.launch {
|
||||
userPrefsRepository.updateRpnMode(enabled)
|
||||
}
|
||||
}
|
||||
|
3
password.txt
Normal file
3
password.txt
Normal file
@ -0,0 +1,3 @@
|
||||
009D4E
|
||||
|
||||
super secret classified sauce, don't share
|
Loading…
x
Reference in New Issue
Block a user