mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-18 16:25:27 +02:00
parent
cf59362708
commit
60e5f1f998
@ -136,6 +136,7 @@ internal fun UnittoApp(prefs: AppPreferences?) {
|
|||||||
navController = navController,
|
navController = navController,
|
||||||
themmoController = it,
|
themmoController = it,
|
||||||
startDestination = prefs.startingScreen,
|
startDestination = prefs.startingScreen,
|
||||||
|
rpnMode = prefs.rpnMode,
|
||||||
openDrawer = { drawerScope.launch { drawerState.open() } }
|
openDrawer = { drawerScope.launch { drawerState.open() } }
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -40,7 +40,8 @@ internal fun UnittoNavigation(
|
|||||||
navController: NavHostController,
|
navController: NavHostController,
|
||||||
themmoController: ThemmoController,
|
themmoController: ThemmoController,
|
||||||
startDestination: String,
|
startDestination: String,
|
||||||
openDrawer: () -> Unit
|
openDrawer: () -> Unit,
|
||||||
|
rpnMode: Boolean,
|
||||||
) {
|
) {
|
||||||
NavHost(
|
NavHost(
|
||||||
navController = navController,
|
navController = navController,
|
||||||
@ -62,7 +63,8 @@ internal fun UnittoNavigation(
|
|||||||
)
|
)
|
||||||
|
|
||||||
calculatorGraph(
|
calculatorGraph(
|
||||||
navigateToMenu = openDrawer,
|
openDrawer = openDrawer,
|
||||||
|
rpnMode = rpnMode,
|
||||||
navigateToSettings = navController::navigateToSettings
|
navigateToSettings = navController::navigateToSettings
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -22,7 +22,6 @@ import android.view.HapticFeedbackConstants
|
|||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
@ -30,6 +29,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.runtime.rememberCoroutineScope
|
import androidx.compose.runtime.rememberCoroutineScope
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.draw.scale
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
@ -72,7 +72,7 @@ fun BasicKeyboardButton(
|
|||||||
Icon(
|
Icon(
|
||||||
imageVector = icon,
|
imageVector = icon,
|
||||||
contentDescription = null,
|
contentDescription = null,
|
||||||
modifier = Modifier.fillMaxHeight(contentHeight),
|
modifier = Modifier.matchParentSize().scale(contentHeight),
|
||||||
tint = iconColor
|
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.floor
|
||||||
import kotlin.math.log10
|
import kotlin.math.log10
|
||||||
|
|
||||||
|
// TODO Use everywhere
|
||||||
fun BigDecimal.format(
|
fun BigDecimal.format(
|
||||||
scale: Int,
|
scale: Int,
|
||||||
outputFormat: Int
|
outputFormat: Int
|
||||||
@ -34,6 +35,7 @@ fun BigDecimal.format(
|
|||||||
.toStringWith(outputFormat)
|
.toStringWith(outputFormat)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Move tests and mark as internal
|
||||||
/**
|
/**
|
||||||
* Shorthand function to use correct `toString` method according to [outputFormat].
|
* Shorthand function to use correct `toString` method according to [outputFormat].
|
||||||
*/
|
*/
|
||||||
@ -76,5 +78,7 @@ fun BigDecimal.setMinimumRequiredScale(prefScale: Int): BigDecimal {
|
|||||||
* Removes all trailing zeroes.
|
* Removes all trailing zeroes.
|
||||||
*/
|
*/
|
||||||
fun BigDecimal.trimZeros(): BigDecimal {
|
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.MAX_PRECISION
|
||||||
import com.sadellie.unitto.core.base.Token
|
import com.sadellie.unitto.core.base.Token
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.MathContext
|
|
||||||
import java.math.RoundingMode
|
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) {
|
sealed class ExpressionException(override val message: String): Exception(message) {
|
||||||
class DivideByZero : ExpressionException("Can't divide by zero")
|
class DivideByZero : ExpressionException("Can't divide by zero")
|
||||||
@ -240,77 +227,3 @@ class Expression(
|
|||||||
return expr
|
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 updateAcButton(enabled: Boolean)
|
||||||
|
|
||||||
suspend fun updateClearInputAfterEquals(enabled: Boolean)
|
suspend fun updateClearInputAfterEquals(enabled: Boolean)
|
||||||
|
|
||||||
|
suspend fun updateRpnMode(enabled: Boolean)
|
||||||
}
|
}
|
||||||
|
@ -27,4 +27,5 @@ interface AppPreferences {
|
|||||||
val startingScreen: String
|
val startingScreen: String
|
||||||
val enableToolsExperiment: Boolean
|
val enableToolsExperiment: Boolean
|
||||||
val systemFont: Boolean
|
val systemFont: Boolean
|
||||||
|
val rpnMode: Boolean
|
||||||
}
|
}
|
||||||
|
@ -41,6 +41,7 @@ data class AppPreferencesImpl(
|
|||||||
override val startingScreen: String,
|
override val startingScreen: String,
|
||||||
override val enableToolsExperiment: Boolean,
|
override val enableToolsExperiment: Boolean,
|
||||||
override val systemFont: Boolean,
|
override val systemFont: Boolean,
|
||||||
|
override val rpnMode: Boolean,
|
||||||
) : AppPreferences
|
) : AppPreferences
|
||||||
|
|
||||||
data class GeneralPreferencesImpl(
|
data class GeneralPreferencesImpl(
|
||||||
|
@ -24,28 +24,35 @@ import androidx.datastore.preferences.core.longPreferencesKey
|
|||||||
import androidx.datastore.preferences.core.stringPreferencesKey
|
import androidx.datastore.preferences.core.stringPreferencesKey
|
||||||
|
|
||||||
internal object PrefsKeys {
|
internal object PrefsKeys {
|
||||||
|
// COMMON
|
||||||
val THEMING_MODE = stringPreferencesKey("THEMING_MODE_PREF_KEY")
|
val THEMING_MODE = stringPreferencesKey("THEMING_MODE_PREF_KEY")
|
||||||
val ENABLE_DYNAMIC_THEME = booleanPreferencesKey("ENABLE_DYNAMIC_THEME_PREF_KEY")
|
val ENABLE_DYNAMIC_THEME = booleanPreferencesKey("ENABLE_DYNAMIC_THEME_PREF_KEY")
|
||||||
val ENABLE_AMOLED_THEME = booleanPreferencesKey("ENABLE_AMOLED_THEME_PREF_KEY")
|
val ENABLE_AMOLED_THEME = booleanPreferencesKey("ENABLE_AMOLED_THEME_PREF_KEY")
|
||||||
val CUSTOM_COLOR = longPreferencesKey("CUSTOM_COLOR_PREF_KEY")
|
val CUSTOM_COLOR = longPreferencesKey("CUSTOM_COLOR_PREF_KEY")
|
||||||
val MONET_MODE = stringPreferencesKey("MONET_MODE_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 DIGITS_PRECISION = intPreferencesKey("DIGITS_PRECISION_PREF_KEY")
|
||||||
val SEPARATOR = intPreferencesKey("SEPARATOR_PREF_KEY")
|
val SEPARATOR = intPreferencesKey("SEPARATOR_PREF_KEY")
|
||||||
val OUTPUT_FORMAT = intPreferencesKey("OUTPUT_FORMAT_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_LEFT_SIDE = stringPreferencesKey("LATEST_LEFT_SIDE_PREF_KEY")
|
||||||
val LATEST_RIGHT_SIDE = stringPreferencesKey("LATEST_RIGHT_SIDE_PREF_KEY")
|
val LATEST_RIGHT_SIDE = stringPreferencesKey("LATEST_RIGHT_SIDE_PREF_KEY")
|
||||||
val SHOWN_UNIT_GROUPS = stringPreferencesKey("SHOWN_UNIT_GROUPS_PREF_KEY")
|
val SHOWN_UNIT_GROUPS = stringPreferencesKey("SHOWN_UNIT_GROUPS_PREF_KEY")
|
||||||
val ENABLE_VIBRATIONS = booleanPreferencesKey("ENABLE_VIBRATIONS_PREF_KEY")
|
val UNIT_CONVERTER_FAVORITES_ONLY = booleanPreferencesKey("UNIT_CONVERTER_FAVORITES_ONLY_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_FORMAT_TIME = booleanPreferencesKey("UNIT_CONVERTER_FORMAT_TIME_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 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(),
|
monetMode = preferences.getMonetMode(),
|
||||||
startingScreen = preferences.getStartingScreen(),
|
startingScreen = preferences.getStartingScreen(),
|
||||||
enableToolsExperiment = preferences.getEnableToolsExperiment(),
|
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
|
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 {
|
private fun Preferences.getEnableDynamicTheme(): Boolean {
|
||||||
@ -387,6 +394,10 @@ private fun Preferences.getClearInputAfterEquals(): Boolean {
|
|||||||
return this[PrefsKeys.CLEAR_INPUT_AFTER_EQUALS] ?: true
|
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 {
|
private inline fun <T, R> T.letTryOrNull(block: (T) -> R): R? = try {
|
||||||
this?.let(block)
|
this?.let(block)
|
||||||
} catch (e: Exception) {
|
} catch (e: Exception) {
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
package com.sadellie.unitto.feature.calculator
|
package com.sadellie.unitto.feature.calculator
|
||||||
|
|
||||||
import com.sadellie.unitto.core.base.Token
|
import com.sadellie.unitto.core.base.Token
|
||||||
|
import com.sadellie.unitto.data.common.isEqualTo
|
||||||
import java.math.BigDecimal
|
import java.math.BigDecimal
|
||||||
import java.math.BigInteger
|
import java.math.BigInteger
|
||||||
import java.math.RoundingMode
|
import java.math.RoundingMode
|
||||||
@ -101,5 +102,4 @@ private fun BigDecimal.repeatingDecimals(): String? {
|
|||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun BigDecimal.isEqualTo(bd: BigDecimal): Boolean = compareTo(bd) == 0
|
|
||||||
private val maxDenominator by lazy { BigInteger("1000000000") }
|
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
|
package com.sadellie.unitto.feature.calculator.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
import androidx.compose.foundation.layout.height
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.foundation.layout.wrapContentHeight
|
import androidx.compose.foundation.layout.wrapContentHeight
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.lazy.rememberLazyListState
|
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.Icons
|
||||||
import androidx.compose.material.icons.filled.History
|
import androidx.compose.material.icons.filled.History
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
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.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
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.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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.sadellie.unitto.core.base.R
|
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.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 com.sadellie.unitto.data.model.HistoryItem
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.Locale
|
import java.util.Locale
|
||||||
@ -151,83 +128,23 @@ private fun HistoryListItem(
|
|||||||
formatterSymbols: FormatterSymbols,
|
formatterSymbols: FormatterSymbols,
|
||||||
addTokens: (String) -> Unit,
|
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(
|
Column(
|
||||||
modifier = modifier.height(HistoryItemHeight),
|
modifier = modifier.height(HistoryItemHeight),
|
||||||
verticalArrangement = Arrangement.Center
|
verticalArrangement = Arrangement.Center
|
||||||
) {
|
) {
|
||||||
CompositionLocalProvider(
|
FixedInputTextField(
|
||||||
LocalTextInputService provides null,
|
value = historyItem.expression,
|
||||||
LocalTextToolbar provides UnittoTextToolbar(
|
formatterSymbols = formatterSymbols,
|
||||||
view = LocalView.current,
|
textColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
copyCallback = {
|
onClick = addTokens,
|
||||||
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 = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.End),
|
|
||||||
readOnly = true,
|
|
||||||
visualTransformation = ExpressionTransformer(formatterSymbols),
|
|
||||||
interactionSource = expressionInteractionSource
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
CompositionLocalProvider(
|
FixedInputTextField(
|
||||||
LocalTextInputService provides null,
|
value = historyItem.result,
|
||||||
LocalTextToolbar provides UnittoTextToolbar(
|
formatterSymbols = formatterSymbols,
|
||||||
view = LocalView.current,
|
textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
|
||||||
copyCallback = {
|
onClick = addTokens,
|
||||||
clipboardManager.copyWithoutGrouping(resultValue, formatterSymbols)
|
|
||||||
resultValue = resultValue.copy(selection = TextRange(resultValue.selection.end))
|
|
||||||
}
|
|
||||||
)
|
)
|
||||||
) {
|
|
||||||
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.unittoComposable
|
||||||
import com.sadellie.unitto.core.ui.unittoNavigation
|
import com.sadellie.unitto.core.ui.unittoNavigation
|
||||||
import com.sadellie.unitto.feature.calculator.CalculatorRoute
|
import com.sadellie.unitto.feature.calculator.CalculatorRoute
|
||||||
|
import com.sadellie.unitto.feature.calculator.RPNCalculatorRoute
|
||||||
|
|
||||||
private val graph = TopLevelDestinations.Calculator.graph
|
private val graph = TopLevelDestinations.Calculator.graph
|
||||||
private val start = TopLevelDestinations.Calculator.start
|
private val start = TopLevelDestinations.Calculator.start
|
||||||
|
|
||||||
fun NavGraphBuilder.calculatorGraph(
|
fun NavGraphBuilder.calculatorGraph(
|
||||||
navigateToMenu: () -> Unit,
|
rpnMode: Boolean,
|
||||||
|
openDrawer: () -> Unit,
|
||||||
navigateToSettings: () -> Unit
|
navigateToSettings: () -> Unit
|
||||||
) {
|
) {
|
||||||
unittoNavigation(
|
unittoNavigation(
|
||||||
@ -40,10 +42,17 @@ fun NavGraphBuilder.calculatorGraph(
|
|||||||
)
|
)
|
||||||
) {
|
) {
|
||||||
unittoComposable(start) {
|
unittoComposable(start) {
|
||||||
|
if (rpnMode) {
|
||||||
|
RPNCalculatorRoute(
|
||||||
|
openDrawer = openDrawer,
|
||||||
|
navigateToSettings = navigateToSettings
|
||||||
|
)
|
||||||
|
} else {
|
||||||
CalculatorRoute(
|
CalculatorRoute(
|
||||||
navigateToMenu = navigateToMenu,
|
navigateToMenu = openDrawer,
|
||||||
navigateToSettings = navigateToSettings
|
navigateToSettings = navigateToSettings
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
@ -18,95 +18,139 @@
|
|||||||
|
|
||||||
package com.sadellie.unitto.feature.settings.calculator
|
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.Icons
|
||||||
import androidx.compose.material.icons.automirrored.filled.Backspace
|
import androidx.compose.material.icons.automirrored.filled.Backspace
|
||||||
import androidx.compose.material.icons.filled.Timer
|
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.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.sadellie.unitto.core.base.OutputFormat
|
|
||||||
import com.sadellie.unitto.core.base.R
|
import com.sadellie.unitto.core.base.R
|
||||||
import com.sadellie.unitto.core.base.Separator
|
|
||||||
import com.sadellie.unitto.core.ui.common.NavigateUpButton
|
import com.sadellie.unitto.core.ui.common.NavigateUpButton
|
||||||
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
|
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
|
||||||
import com.sadellie.unitto.core.ui.common.UnittoListItem
|
import com.sadellie.unitto.core.ui.common.UnittoListItem
|
||||||
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
|
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
|
||||||
import com.sadellie.unitto.data.model.userprefs.CalculatorPreferences
|
|
||||||
import com.sadellie.unitto.data.userprefs.CalculatorPreferencesImpl
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun CalculatorSettingsRoute(
|
internal fun CalculatorSettingsRoute(
|
||||||
viewModel: CalculatorSettingsViewModel = hiltViewModel(),
|
viewModel: CalculatorSettingsViewModel = hiltViewModel(),
|
||||||
navigateUpAction: () -> Unit,
|
navigateUpAction: () -> Unit,
|
||||||
) {
|
) {
|
||||||
when (val prefs = viewModel.prefs.collectAsStateWithLifecycle().value) {
|
when (val prefs = viewModel.uiState.collectAsStateWithLifecycle().value) {
|
||||||
null -> UnittoEmptyScreen()
|
CalculatorSettingsUIState.Loading -> UnittoEmptyScreen()
|
||||||
else -> {
|
else -> {
|
||||||
CalculatorSettingsScreen(
|
CalculatorSettingsScreen(
|
||||||
prefs = prefs,
|
uiState = prefs,
|
||||||
navigateUpAction = navigateUpAction,
|
navigateUpAction = navigateUpAction,
|
||||||
updatePartialHistoryView = viewModel::updatePartialHistoryView,
|
updatePartialHistoryView = viewModel::updatePartialHistoryView,
|
||||||
updateClearInputAfterEquals = viewModel::updateClearInputAfterEquals,
|
updateClearInputAfterEquals = viewModel::updateClearInputAfterEquals,
|
||||||
|
updateRpnMode = viewModel::updateRpnMode,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TODO Translate
|
||||||
@Composable
|
@Composable
|
||||||
private fun CalculatorSettingsScreen(
|
private fun CalculatorSettingsScreen(
|
||||||
prefs: CalculatorPreferences,
|
uiState: CalculatorSettingsUIState,
|
||||||
navigateUpAction: () -> Unit,
|
navigateUpAction: () -> Unit,
|
||||||
updatePartialHistoryView: (Boolean) -> Unit,
|
updatePartialHistoryView: (Boolean) -> Unit,
|
||||||
updateClearInputAfterEquals: (Boolean) -> Unit,
|
updateClearInputAfterEquals: (Boolean) -> Unit,
|
||||||
|
updateRpnMode: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
UnittoScreenWithLargeTopBar(
|
UnittoScreenWithLargeTopBar(
|
||||||
title = stringResource(R.string.calculator_title),
|
title = stringResource(R.string.calculator_title),
|
||||||
navigationIcon = { NavigateUpButton(navigateUpAction) }
|
navigationIcon = { NavigateUpButton(navigateUpAction) }
|
||||||
) { padding ->
|
) { padding ->
|
||||||
LazyColumn(contentPadding = padding) {
|
Column(Modifier.padding(padding)) {
|
||||||
item("partial history") {
|
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(
|
UnittoListItem(
|
||||||
headlineText = stringResource(R.string.settings_partial_history_view),
|
headlineText = stringResource(R.string.settings_partial_history_view),
|
||||||
icon = Icons.Default.Timer,
|
icon = Icons.Default.Timer,
|
||||||
supportingText = stringResource(R.string.settings_partial_history_view_support),
|
supportingText = stringResource(R.string.settings_partial_history_view_support),
|
||||||
switchState = prefs.partialHistoryView,
|
switchState = state.partialHistoryView,
|
||||||
onSwitchChange = updatePartialHistoryView
|
onSwitchChange = updatePartialHistoryView
|
||||||
)
|
)
|
||||||
}
|
|
||||||
|
|
||||||
item("clear input") {
|
|
||||||
UnittoListItem(
|
UnittoListItem(
|
||||||
headlineText = stringResource(R.string.settings_clear_input),
|
headlineText = stringResource(R.string.settings_clear_input),
|
||||||
icon = Icons.AutoMirrored.Filled.Backspace,
|
icon = Icons.AutoMirrored.Filled.Backspace,
|
||||||
supportingText = stringResource(R.string.settings_clear_input_support),
|
supportingText = stringResource(R.string.settings_clear_input_support),
|
||||||
switchState = prefs.clearInputAfterEquals,
|
switchState = state.clearInputAfterEquals,
|
||||||
onSwitchChange = updateClearInputAfterEquals
|
onSwitchChange = updateClearInputAfterEquals
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
else -> Unit
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview
|
||||||
@Composable
|
@Composable
|
||||||
private fun PreviewCalculatorSettingsScreen() {
|
private fun PreviewCalculatorSettingsScreenStandard() {
|
||||||
CalculatorSettingsScreen(
|
CalculatorSettingsScreen(
|
||||||
prefs = CalculatorPreferencesImpl(
|
uiState = CalculatorSettingsUIState.Standard(
|
||||||
radianMode = false,
|
|
||||||
enableVibrations = false,
|
|
||||||
separator = Separator.SPACE,
|
|
||||||
middleZero = false,
|
|
||||||
partialHistoryView = true,
|
partialHistoryView = true,
|
||||||
precision = 3,
|
clearInputAfterEquals = false
|
||||||
outputFormat = OutputFormat.PLAIN,
|
|
||||||
acButton = true,
|
|
||||||
clearInputAfterEquals = true,
|
|
||||||
),
|
),
|
||||||
navigateUpAction = {},
|
navigateUpAction = {},
|
||||||
updatePartialHistoryView = {},
|
updatePartialHistoryView = {},
|
||||||
updateClearInputAfterEquals = {},
|
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.common.stateIn
|
||||||
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
|
||||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
|
||||||
@ -30,8 +31,20 @@ import javax.inject.Inject
|
|||||||
internal class CalculatorSettingsViewModel @Inject constructor(
|
internal class CalculatorSettingsViewModel @Inject constructor(
|
||||||
private val userPrefsRepository: UserPreferencesRepository,
|
private val userPrefsRepository: UserPreferencesRepository,
|
||||||
) : ViewModel() {
|
) : ViewModel() {
|
||||||
val prefs = userPrefsRepository.calculatorPrefs
|
val uiState = combine(
|
||||||
.stateIn(viewModelScope, null)
|
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 {
|
fun updatePartialHistoryView(enabled: Boolean) = viewModelScope.launch {
|
||||||
userPrefsRepository.updatePartialHistoryView(enabled)
|
userPrefsRepository.updatePartialHistoryView(enabled)
|
||||||
@ -40,4 +53,8 @@ internal class CalculatorSettingsViewModel @Inject constructor(
|
|||||||
fun updateClearInputAfterEquals(enabled: Boolean) = viewModelScope.launch {
|
fun updateClearInputAfterEquals(enabled: Boolean) = viewModelScope.launch {
|
||||||
userPrefsRepository.updateClearInputAfterEquals(enabled)
|
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