mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-19 08:45:27 +02:00
Added Calculator (#2)
Note: First iteration of this feature, will change the navigation, cursor and copy/paste/cut.
This commit is contained in:
parent
b963cdc644
commit
5ab779d136
@ -110,6 +110,7 @@ dependencies {
|
|||||||
implementation(libs.com.google.accompanist.systemuicontroller)
|
implementation(libs.com.google.accompanist.systemuicontroller)
|
||||||
|
|
||||||
implementation(project(mapOf("path" to ":feature:converter")))
|
implementation(project(mapOf("path" to ":feature:converter")))
|
||||||
|
implementation(project(mapOf("path" to ":feature:calculator")))
|
||||||
implementation(project(mapOf("path" to ":feature:settings")))
|
implementation(project(mapOf("path" to ":feature:settings")))
|
||||||
implementation(project(mapOf("path" to ":feature:unitslist")))
|
implementation(project(mapOf("path" to ":feature:unitslist")))
|
||||||
implementation(project(mapOf("path" to ":feature:tools")))
|
implementation(project(mapOf("path" to ":feature:tools")))
|
||||||
|
@ -21,6 +21,8 @@ package com.sadellie.unitto
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.navigation.NavHostController
|
import androidx.navigation.NavHostController
|
||||||
import androidx.navigation.compose.NavHost
|
import androidx.navigation.compose.NavHost
|
||||||
|
import com.sadellie.unitto.feature.calculator.navigation.calculatorScreen
|
||||||
|
import com.sadellie.unitto.feature.calculator.navigation.navigateToCalculator
|
||||||
import com.sadellie.unitto.feature.converter.MainViewModel
|
import com.sadellie.unitto.feature.converter.MainViewModel
|
||||||
import com.sadellie.unitto.feature.converter.navigation.converterRoute
|
import com.sadellie.unitto.feature.converter.navigation.converterRoute
|
||||||
import com.sadellie.unitto.feature.converter.navigation.converterScreen
|
import com.sadellie.unitto.feature.converter.navigation.converterScreen
|
||||||
@ -83,9 +85,12 @@ fun UnittoNavigation(
|
|||||||
|
|
||||||
toolsScreen(
|
toolsScreen(
|
||||||
navigateUpAction = navController::navigateUp,
|
navigateUpAction = navController::navigateUp,
|
||||||
|
navigateToCalculator = navController::navigateToCalculator,
|
||||||
navigateToEpoch = navController::navigateToEpoch
|
navigateToEpoch = navController::navigateToEpoch
|
||||||
)
|
)
|
||||||
|
|
||||||
|
calculatorScreen(navigateUpAction = navController::navigateUp)
|
||||||
|
|
||||||
epochScreen(
|
epochScreen(
|
||||||
navigateUpAction = navController::navigateUp
|
navigateUpAction = navController::navigateUp
|
||||||
)
|
)
|
||||||
|
@ -57,6 +57,17 @@ const val KEY_RIGHT_BRACKET = ")"
|
|||||||
const val KEY_EXPONENT = "^"
|
const val KEY_EXPONENT = "^"
|
||||||
|
|
||||||
const val KEY_SQRT = "√"
|
const val KEY_SQRT = "√"
|
||||||
|
const val KEY_PI = "π"
|
||||||
|
const val KEY_FACTORIAL = "!"
|
||||||
|
const val KEY_SIN = "sin("
|
||||||
|
const val KEY_COS = "cos("
|
||||||
|
const val KEY_TAN = "tan("
|
||||||
|
const val KEY_E_SMALL = "e"
|
||||||
|
const val KEY_MODULO = "#"
|
||||||
|
const val KEY_LN = "ln("
|
||||||
|
const val KEY_LOG = "log("
|
||||||
|
const val KEY_PERCENT = "%"
|
||||||
|
const val KEY_EVALUATE = "="
|
||||||
|
|
||||||
val OPERATORS by lazy {
|
val OPERATORS by lazy {
|
||||||
listOf(
|
listOf(
|
||||||
|
@ -1022,6 +1022,10 @@
|
|||||||
<string name="tools_notice_description">This screen is part of an experiment. It may change in the future.</string>
|
<string name="tools_notice_description">This screen is part of an experiment. It may change in the future.</string>
|
||||||
<string name="click_to_read_more">Click to read more!</string>
|
<string name="click_to_read_more">Click to read more!</string>
|
||||||
|
|
||||||
|
<!--Calculator-->
|
||||||
|
<string name="calculator">Calculator</string>
|
||||||
|
<string name="calculator_support">Calculate, but don\'t convert</string>
|
||||||
|
|
||||||
<!--Precision-->
|
<!--Precision-->
|
||||||
<string name="precision_setting_support">Number of decimal places</string>
|
<string name="precision_setting_support">Number of decimal places</string>
|
||||||
<string name="precision_setting_info">Converted values may have a precision higher than the preferred one.</string>
|
<string name="precision_setting_info">Converted values may have a precision higher than the preferred one.</string>
|
||||||
|
@ -29,14 +29,18 @@ import androidx.compose.foundation.shape.RoundedCornerShape
|
|||||||
import androidx.compose.material3.Button
|
import androidx.compose.material3.Button
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleTitleLarge
|
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleTitleLarge
|
||||||
|
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleTitleSmall
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Button for keyboard
|
* Button for keyboard
|
||||||
@ -87,3 +91,105 @@ fun KeyboardButton(
|
|||||||
if (isPressed and allowVibration) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
if (isPressed and allowVibration) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun BasicKeyboardButton(
|
||||||
|
modifier: Modifier,
|
||||||
|
onClick: () -> Unit,
|
||||||
|
onLongClick: (() -> Unit)?,
|
||||||
|
containerColor: Color,
|
||||||
|
contentColor: Color,
|
||||||
|
text: String,
|
||||||
|
textColor: Color,
|
||||||
|
fontSize: TextUnit,
|
||||||
|
allowVibration: Boolean
|
||||||
|
) {
|
||||||
|
val view = LocalView.current
|
||||||
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
|
val isPressed by interactionSource.collectIsPressedAsState()
|
||||||
|
val cornerRadius: Int by animateIntAsState(
|
||||||
|
targetValue = if (isPressed) 30 else 50,
|
||||||
|
animationSpec = tween(easing = FastOutSlowInEasing),
|
||||||
|
)
|
||||||
|
|
||||||
|
UnittoButton(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
shape = RoundedCornerShape(cornerRadius),
|
||||||
|
containerColor = containerColor,
|
||||||
|
contentColor = contentColor,
|
||||||
|
interactionSource = interactionSource
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = text,
|
||||||
|
style = NumbersTextStyleTitleLarge,
|
||||||
|
color = textColor,
|
||||||
|
fontSize = fontSize
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
LaunchedEffect(key1 = isPressed) {
|
||||||
|
if (isPressed and allowVibration) view.performHapticFeedback(HapticFeedbackConstants.KEYBOARD_TAP)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun KeyboardButtonLight(
|
||||||
|
modifier: Modifier,
|
||||||
|
symbol: String,
|
||||||
|
onClick: (String) -> Unit,
|
||||||
|
onLongClick: (() -> Unit)? = null,
|
||||||
|
allowVibration: Boolean = false
|
||||||
|
) {
|
||||||
|
BasicKeyboardButton(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = { onClick(symbol) },
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
containerColor = MaterialTheme.colorScheme.inverseOnSurface,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
text = symbol,
|
||||||
|
textColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
fontSize = TextUnit.Unspecified,
|
||||||
|
allowVibration = allowVibration,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun KeyboardButtonFilled(
|
||||||
|
modifier: Modifier,
|
||||||
|
symbol: String,
|
||||||
|
onClick: (String) -> Unit,
|
||||||
|
onLongClick: (() -> Unit)? = null,
|
||||||
|
allowVibration: Boolean = false
|
||||||
|
) {
|
||||||
|
BasicKeyboardButton(
|
||||||
|
modifier = modifier,
|
||||||
|
onClick = { onClick(symbol) },
|
||||||
|
onLongClick = onLongClick,
|
||||||
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
|
contentColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
text = symbol,
|
||||||
|
textColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
fontSize = TextUnit.Unspecified,
|
||||||
|
allowVibration = allowVibration
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun KeyboardButtonAdditional(
|
||||||
|
modifier: Modifier,
|
||||||
|
symbol: String,
|
||||||
|
onClick: (String) -> Unit
|
||||||
|
) {
|
||||||
|
TextButton(
|
||||||
|
onClick = { onClick(symbol) },
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
Text(
|
||||||
|
text = symbol,
|
||||||
|
color = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||||
|
style = NumbersTextStyleTitleSmall
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -64,6 +64,15 @@ val NumbersTextStyleTitleLarge = TextStyle(
|
|||||||
letterSpacing = 0.sp,
|
letterSpacing = 0.sp,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
// This text style is used for secondary keyboard button
|
||||||
|
val NumbersTextStyleTitleSmall = TextStyle(
|
||||||
|
fontFamily = Lato,
|
||||||
|
fontWeight = FontWeight.W500,
|
||||||
|
fontSize = 14.sp,
|
||||||
|
lineHeight = 20.sp,
|
||||||
|
letterSpacing = 0.sp,
|
||||||
|
)
|
||||||
|
|
||||||
val AppTypography = Typography(
|
val AppTypography = Typography(
|
||||||
displayLarge = TextStyle(
|
displayLarge = TextStyle(
|
||||||
fontFamily = Montserrat,
|
fontFamily = Montserrat,
|
||||||
|
1
feature/calculator/.gitignore
vendored
Normal file
1
feature/calculator/.gitignore
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
/build
|
38
feature/calculator/build.gradle.kts
Normal file
38
feature/calculator/build.gradle.kts
Normal file
@ -0,0 +1,38 @@
|
|||||||
|
/*
|
||||||
|
* 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/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
plugins {
|
||||||
|
id("unitto.library")
|
||||||
|
id("unitto.library.compose")
|
||||||
|
id("unitto.library.feature")
|
||||||
|
id("unitto.android.hilt")
|
||||||
|
}
|
||||||
|
|
||||||
|
android {
|
||||||
|
namespace = "com.sadellie.unitto.feature.calculator"
|
||||||
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
testImplementation(libs.junit)
|
||||||
|
implementation(libs.org.mariuszgromada.math.mxparser)
|
||||||
|
implementation(libs.com.github.sadellie.themmo)
|
||||||
|
|
||||||
|
implementation(project(mapOf("path" to ":data:userprefs")))
|
||||||
|
implementation(project(mapOf("path" to ":data:unitgroups")))
|
||||||
|
implementation(project(mapOf("path" to ":data:units")))
|
||||||
|
}
|
0
feature/calculator/consumer-rules.pro
Normal file
0
feature/calculator/consumer-rules.pro
Normal file
22
feature/calculator/src/main/AndroidManifest.xml
Normal file
22
feature/calculator/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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/>.
|
||||||
|
-->
|
||||||
|
|
||||||
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
|
||||||
|
</manifest>
|
@ -0,0 +1,21 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
internal enum class AngleMode { DEG, RAD }
|
@ -0,0 +1,121 @@
|
|||||||
|
/*
|
||||||
|
* 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.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
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.text.style.TextAlign
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
|
import com.sadellie.unitto.core.ui.common.UnittoTopAppBar
|
||||||
|
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium
|
||||||
|
import com.sadellie.unitto.feature.calculator.components.CalculatorKeyboard
|
||||||
|
import com.sadellie.unitto.feature.calculator.components.InputTextField
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun CalculatorRoute(
|
||||||
|
navigateUpAction: () -> Unit,
|
||||||
|
viewModel: CalculatorViewModel = hiltViewModel()
|
||||||
|
) {
|
||||||
|
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
|
CalculatorScreen(
|
||||||
|
uiState = uiState.value,
|
||||||
|
navigateUpAction = navigateUpAction,
|
||||||
|
addSymbol = viewModel::addSymbol,
|
||||||
|
clearSymbols = viewModel::clearSymbols,
|
||||||
|
deleteSymbol = viewModel::deleteSymbol,
|
||||||
|
onCursorChange = viewModel::onCursorChange,
|
||||||
|
toggleAngleMode = viewModel::toggleCalculatorMode,
|
||||||
|
evaluate = viewModel::evaluate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun CalculatorScreen(
|
||||||
|
uiState: CalculatorUIState,
|
||||||
|
navigateUpAction: () -> Unit,
|
||||||
|
addSymbol: (String) -> Unit,
|
||||||
|
clearSymbols: () -> Unit,
|
||||||
|
deleteSymbol: () -> Unit,
|
||||||
|
onCursorChange: (IntRange) -> Unit,
|
||||||
|
toggleAngleMode: () -> Unit,
|
||||||
|
evaluate: () -> Unit
|
||||||
|
) {
|
||||||
|
UnittoTopAppBar(
|
||||||
|
title = stringResource(R.string.calculator),
|
||||||
|
navigateUpAction = navigateUpAction,
|
||||||
|
) {
|
||||||
|
Column(Modifier.padding(it)) {
|
||||||
|
InputTextField(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
value = TextFieldValue(
|
||||||
|
text = uiState.input,
|
||||||
|
selection = TextRange(uiState.selection.first, uiState.selection.last)
|
||||||
|
),
|
||||||
|
onCursorChange = onCursorChange
|
||||||
|
)
|
||||||
|
AnimatedVisibility(visible = uiState.output.isNotEmpty()) {
|
||||||
|
Text(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
// Quick fix to prevent the UI from crashing
|
||||||
|
text = uiState.output,
|
||||||
|
textAlign = TextAlign.End,
|
||||||
|
softWrap = false,
|
||||||
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
|
||||||
|
style = NumbersTextStyleDisplayMedium,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
CalculatorKeyboard(
|
||||||
|
modifier = Modifier,
|
||||||
|
addSymbol = addSymbol,
|
||||||
|
clearSymbols = clearSymbols,
|
||||||
|
deleteSymbol = deleteSymbol,
|
||||||
|
toggleAngleMode = toggleAngleMode,
|
||||||
|
angleMode = uiState.angleMode,
|
||||||
|
evaluate = evaluate
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun PreviewCalculatorScreen() {
|
||||||
|
CalculatorScreen(
|
||||||
|
uiState = CalculatorUIState(),
|
||||||
|
navigateUpAction = {},
|
||||||
|
addSymbol = {},
|
||||||
|
clearSymbols = {},
|
||||||
|
deleteSymbol = {},
|
||||||
|
onCursorChange = {},
|
||||||
|
toggleAngleMode = {},
|
||||||
|
evaluate = {}
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* 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
|
||||||
|
|
||||||
|
internal data class CalculatorUIState(
|
||||||
|
val input: String = "",
|
||||||
|
val output: String = "",
|
||||||
|
val selection: IntRange = 0..0,
|
||||||
|
val angleMode: AngleMode = AngleMode.RAD
|
||||||
|
)
|
@ -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 com.sadellie.unitto.feature.calculator
|
||||||
|
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.viewModelScope
|
||||||
|
import com.sadellie.unitto.core.base.KEY_LEFT_BRACKET
|
||||||
|
import com.sadellie.unitto.core.base.KEY_MINUS
|
||||||
|
import com.sadellie.unitto.core.base.KEY_MINUS_DISPLAY
|
||||||
|
import com.sadellie.unitto.core.base.KEY_RIGHT_BRACKET
|
||||||
|
import com.sadellie.unitto.data.setMinimumRequiredScale
|
||||||
|
import com.sadellie.unitto.data.toStringWith
|
||||||
|
import com.sadellie.unitto.data.trimZeros
|
||||||
|
import com.sadellie.unitto.data.userprefs.UserPreferences
|
||||||
|
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
|
||||||
|
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||||
|
import kotlinx.coroutines.Dispatchers
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.SharingStarted
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.flow.collectLatest
|
||||||
|
import kotlinx.coroutines.flow.combine
|
||||||
|
import kotlinx.coroutines.flow.merge
|
||||||
|
import kotlinx.coroutines.flow.stateIn
|
||||||
|
import kotlinx.coroutines.flow.update
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import org.mariuszgromada.math.mxparser.Expression
|
||||||
|
import java.math.BigDecimal
|
||||||
|
import javax.inject.Inject
|
||||||
|
import org.mariuszgromada.math.mxparser.mXparser as MathParser
|
||||||
|
|
||||||
|
@HiltViewModel
|
||||||
|
internal class CalculatorViewModel @Inject constructor(
|
||||||
|
userPrefsRepository: UserPreferencesRepository
|
||||||
|
) : ViewModel() {
|
||||||
|
private val _userPrefs: StateFlow<UserPreferences> =
|
||||||
|
userPrefsRepository.userPreferencesFlow.stateIn(
|
||||||
|
viewModelScope,
|
||||||
|
SharingStarted.WhileSubscribed(5000L),
|
||||||
|
UserPreferences()
|
||||||
|
)
|
||||||
|
|
||||||
|
private val _input: MutableStateFlow<String> = MutableStateFlow("")
|
||||||
|
private val _output: MutableStateFlow<String> = MutableStateFlow("")
|
||||||
|
private val _selection: MutableStateFlow<IntRange> = MutableStateFlow(IntRange(0, 0))
|
||||||
|
private val _angleMode: MutableStateFlow<AngleMode> = MutableStateFlow(AngleMode.RAD)
|
||||||
|
|
||||||
|
val uiState = combine(
|
||||||
|
_input, _output, _selection, _angleMode
|
||||||
|
) { input, output, selection, angleMode ->
|
||||||
|
return@combine CalculatorUIState(
|
||||||
|
input = input,
|
||||||
|
output = output,
|
||||||
|
selection = selection,
|
||||||
|
angleMode = angleMode
|
||||||
|
)
|
||||||
|
}.stateIn(
|
||||||
|
viewModelScope, SharingStarted.WhileSubscribed(5000L), CalculatorUIState()
|
||||||
|
)
|
||||||
|
|
||||||
|
fun addSymbol(symbol: String) {
|
||||||
|
val selection = _selection.value
|
||||||
|
_input.update {
|
||||||
|
if (it.isEmpty()) symbol else it.replaceRange(selection.first, selection.last, symbol)
|
||||||
|
}
|
||||||
|
_selection.update { it.first + symbol.length..it.first + symbol.length }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun deleteSymbol() {
|
||||||
|
val selection = _selection.value
|
||||||
|
val newSelectionStart = when (selection.last) {
|
||||||
|
0 -> return
|
||||||
|
selection.first -> _selection.value.first - 1
|
||||||
|
else -> _selection.value.first
|
||||||
|
}
|
||||||
|
|
||||||
|
_selection.update { newSelectionStart..newSelectionStart }
|
||||||
|
_input.update { it.removeRange(newSelectionStart, selection.last) }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun clearSymbols() {
|
||||||
|
_selection.update { 0..0 }
|
||||||
|
_input.update { "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun toggleCalculatorMode() {
|
||||||
|
_angleMode.update {
|
||||||
|
if (it == AngleMode.DEG) {
|
||||||
|
MathParser.setRadiansMode()
|
||||||
|
AngleMode.RAD
|
||||||
|
} else {
|
||||||
|
MathParser.setDegreesMode()
|
||||||
|
AngleMode.DEG
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Called when user clicks "=" on a keyboard
|
||||||
|
fun evaluate() {
|
||||||
|
if (!Expression(_input.value.clean).checkSyntax()) return
|
||||||
|
|
||||||
|
_input.update { _output.value }
|
||||||
|
_selection.update { _input.value.length.._input.value.length }
|
||||||
|
_output.update { "" }
|
||||||
|
}
|
||||||
|
|
||||||
|
fun onCursorChange(selection: IntRange) {
|
||||||
|
_selection.update { selection }
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun calculateInput() {
|
||||||
|
// Input is empty, don't calculate
|
||||||
|
if (_input.value.isEmpty()) {
|
||||||
|
_output.update { "" }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val calculated = Expression(_input.value.clean).calculate()
|
||||||
|
|
||||||
|
// Calculation error, return NaN
|
||||||
|
if (calculated.isNaN() or calculated.isInfinite()) {
|
||||||
|
_output.update { calculated.toString() }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val calculatedBigDecimal = calculated
|
||||||
|
.toBigDecimal()
|
||||||
|
.setMinimumRequiredScale(_userPrefs.value.digitsPrecision)
|
||||||
|
.trimZeros()
|
||||||
|
|
||||||
|
try {
|
||||||
|
val inputBigDecimal = BigDecimal(_input.value)
|
||||||
|
|
||||||
|
// Input and output are identical values
|
||||||
|
if (inputBigDecimal.compareTo(calculatedBigDecimal) == 0) {
|
||||||
|
_output.update { "" }
|
||||||
|
return
|
||||||
|
}
|
||||||
|
} catch (e: NumberFormatException) {
|
||||||
|
// Cannot compare input and output
|
||||||
|
}
|
||||||
|
_output.update {
|
||||||
|
calculatedBigDecimal.toStringWith(_userPrefs.value.outputFormat)
|
||||||
|
}
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clean input so that there are no syntax errors
|
||||||
|
*/
|
||||||
|
private val String.clean: String
|
||||||
|
get() {
|
||||||
|
val leftBrackets = count { it.toString() == KEY_LEFT_BRACKET }
|
||||||
|
val rightBrackets = count { it.toString() == KEY_RIGHT_BRACKET }
|
||||||
|
val neededBrackets = leftBrackets - rightBrackets
|
||||||
|
return this
|
||||||
|
.replace(KEY_MINUS_DISPLAY, KEY_MINUS)
|
||||||
|
.plus(KEY_RIGHT_BRACKET.repeat(neededBrackets.coerceAtLeast(0)))
|
||||||
|
}
|
||||||
|
|
||||||
|
init {
|
||||||
|
/**
|
||||||
|
* mxParser uses some unnecessary rounding for doubles. It causes expressions like 9999^9999
|
||||||
|
* to load CPU very much. We use BigDecimal to achieve same result without CPU overload.
|
||||||
|
*/
|
||||||
|
MathParser.setCanonicalRounding(false)
|
||||||
|
|
||||||
|
// Observe and invoke calculation without UI lag.
|
||||||
|
viewModelScope.launch(Dispatchers.Default) {
|
||||||
|
merge(_userPrefs, _input, _angleMode).collectLatest {
|
||||||
|
calculateInput()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,182 @@
|
|||||||
|
/*
|
||||||
|
* 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.components
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
|
import androidx.compose.animation.core.tween
|
||||||
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
|
import androidx.compose.foundation.layout.padding
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.ExpandLess
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
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.draw.rotate
|
||||||
|
import androidx.compose.ui.tooling.preview.Preview
|
||||||
|
import androidx.compose.ui.unit.dp
|
||||||
|
import com.sadellie.unitto.core.base.KEY_0
|
||||||
|
import com.sadellie.unitto.core.base.KEY_1
|
||||||
|
import com.sadellie.unitto.core.base.KEY_2
|
||||||
|
import com.sadellie.unitto.core.base.KEY_3
|
||||||
|
import com.sadellie.unitto.core.base.KEY_4
|
||||||
|
import com.sadellie.unitto.core.base.KEY_5
|
||||||
|
import com.sadellie.unitto.core.base.KEY_6
|
||||||
|
import com.sadellie.unitto.core.base.KEY_7
|
||||||
|
import com.sadellie.unitto.core.base.KEY_8
|
||||||
|
import com.sadellie.unitto.core.base.KEY_9
|
||||||
|
import com.sadellie.unitto.core.base.KEY_CLEAR
|
||||||
|
import com.sadellie.unitto.core.base.KEY_COS
|
||||||
|
import com.sadellie.unitto.core.base.KEY_DIVIDE_DISPLAY
|
||||||
|
import com.sadellie.unitto.core.base.KEY_DOT
|
||||||
|
import com.sadellie.unitto.core.base.KEY_EVALUATE
|
||||||
|
import com.sadellie.unitto.core.base.KEY_EXPONENT
|
||||||
|
import com.sadellie.unitto.core.base.KEY_E_SMALL
|
||||||
|
import com.sadellie.unitto.core.base.KEY_FACTORIAL
|
||||||
|
import com.sadellie.unitto.core.base.KEY_LEFT_BRACKET
|
||||||
|
import com.sadellie.unitto.core.base.KEY_LN
|
||||||
|
import com.sadellie.unitto.core.base.KEY_LOG
|
||||||
|
import com.sadellie.unitto.core.base.KEY_MINUS_DISPLAY
|
||||||
|
import com.sadellie.unitto.core.base.KEY_MODULO
|
||||||
|
import com.sadellie.unitto.core.base.KEY_MULTIPLY_DISPLAY
|
||||||
|
import com.sadellie.unitto.core.base.KEY_PERCENT
|
||||||
|
import com.sadellie.unitto.core.base.KEY_PI
|
||||||
|
import com.sadellie.unitto.core.base.KEY_PLUS
|
||||||
|
import com.sadellie.unitto.core.base.KEY_RIGHT_BRACKET
|
||||||
|
import com.sadellie.unitto.core.base.KEY_SIN
|
||||||
|
import com.sadellie.unitto.core.base.KEY_SQRT
|
||||||
|
import com.sadellie.unitto.core.base.KEY_TAN
|
||||||
|
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.feature.calculator.AngleMode
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun CalculatorKeyboard(
|
||||||
|
modifier: Modifier,
|
||||||
|
addSymbol: (String) -> Unit,
|
||||||
|
clearSymbols: () -> Unit,
|
||||||
|
deleteSymbol: () -> Unit,
|
||||||
|
toggleAngleMode: () -> Unit,
|
||||||
|
angleMode: AngleMode,
|
||||||
|
evaluate: () -> Unit
|
||||||
|
) {
|
||||||
|
var showAdditional: Boolean by remember { mutableStateOf(false) }
|
||||||
|
val expandRotation: Float by animateFloatAsState(
|
||||||
|
targetValue = if (showAdditional) 0f else 180f,
|
||||||
|
animationSpec = tween(easing = FastOutSlowInEasing)
|
||||||
|
)
|
||||||
|
|
||||||
|
Column(
|
||||||
|
modifier = modifier
|
||||||
|
) {
|
||||||
|
val weightModifier = Modifier.weight(1f)
|
||||||
|
val mainButtonModifier = Modifier
|
||||||
|
.fillMaxSize()
|
||||||
|
.weight(1f)
|
||||||
|
.padding(4.dp)
|
||||||
|
|
||||||
|
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
// Additional buttons
|
||||||
|
Column(modifier = weightModifier) {
|
||||||
|
Row(Modifier, horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
KeyboardButtonAdditional(weightModifier, KEY_SQRT, onClick = addSymbol)
|
||||||
|
KeyboardButtonAdditional(weightModifier, KEY_PI, onClick = addSymbol)
|
||||||
|
KeyboardButtonAdditional(weightModifier, KEY_EXPONENT, onClick = addSymbol)
|
||||||
|
KeyboardButtonAdditional(weightModifier, KEY_FACTORIAL, onClick = addSymbol)
|
||||||
|
}
|
||||||
|
AnimatedVisibility(visible = showAdditional) {
|
||||||
|
Column {
|
||||||
|
Row(Modifier, horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
KeyboardButtonAdditional(weightModifier, angleMode.name, onClick = { toggleAngleMode() })
|
||||||
|
KeyboardButtonAdditional(weightModifier, KEY_SIN, onClick = addSymbol)
|
||||||
|
KeyboardButtonAdditional(weightModifier, KEY_COS, onClick = addSymbol)
|
||||||
|
KeyboardButtonAdditional(weightModifier, KEY_TAN, onClick = addSymbol)
|
||||||
|
}
|
||||||
|
Row(Modifier, horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||||
|
KeyboardButtonAdditional(weightModifier, KEY_MODULO, onClick = addSymbol)
|
||||||
|
KeyboardButtonAdditional(weightModifier, KEY_E_SMALL, onClick = addSymbol)
|
||||||
|
KeyboardButtonAdditional(weightModifier, KEY_LN, onClick = addSymbol)
|
||||||
|
KeyboardButtonAdditional(weightModifier, KEY_LOG, onClick = addSymbol)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// Expand/Collapse
|
||||||
|
IconButton({ showAdditional = !showAdditional }) {
|
||||||
|
Icon(Icons.Default.ExpandLess, null, Modifier.rotate(expandRotation))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(weightModifier) {
|
||||||
|
KeyboardButtonFilled(mainButtonModifier, KEY_LEFT_BRACKET, addSymbol)
|
||||||
|
KeyboardButtonFilled(mainButtonModifier, KEY_RIGHT_BRACKET, addSymbol)
|
||||||
|
KeyboardButtonFilled(mainButtonModifier, KEY_PERCENT, addSymbol)
|
||||||
|
KeyboardButtonFilled(mainButtonModifier, KEY_DIVIDE_DISPLAY, addSymbol)
|
||||||
|
}
|
||||||
|
|
||||||
|
Row(weightModifier) {
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_7, addSymbol)
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_8, addSymbol)
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_9, addSymbol)
|
||||||
|
KeyboardButtonFilled(mainButtonModifier, KEY_MULTIPLY_DISPLAY, addSymbol)
|
||||||
|
}
|
||||||
|
Row(weightModifier) {
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_4, addSymbol)
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_5, addSymbol)
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_6, addSymbol)
|
||||||
|
KeyboardButtonFilled(mainButtonModifier, KEY_MINUS_DISPLAY, addSymbol)
|
||||||
|
}
|
||||||
|
Row(weightModifier) {
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_1, addSymbol)
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_2, addSymbol)
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_3, addSymbol)
|
||||||
|
KeyboardButtonFilled(mainButtonModifier, KEY_PLUS, addSymbol)
|
||||||
|
}
|
||||||
|
Row(weightModifier) {
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_0, addSymbol)
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_DOT, addSymbol)
|
||||||
|
KeyboardButtonLight(mainButtonModifier, KEY_CLEAR, { deleteSymbol() }, onLongClick = clearSymbols)
|
||||||
|
KeyboardButtonFilled(mainButtonModifier, KEY_EVALUATE, { evaluate() })
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Preview
|
||||||
|
@Composable
|
||||||
|
private fun PreviewCalculatorKeyboard() {
|
||||||
|
CalculatorKeyboard(
|
||||||
|
modifier = Modifier,
|
||||||
|
addSymbol = {},
|
||||||
|
clearSymbols = {},
|
||||||
|
deleteSymbol = {},
|
||||||
|
toggleAngleMode = {},
|
||||||
|
angleMode = AngleMode.DEG,
|
||||||
|
evaluate = {}
|
||||||
|
)
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* 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.components
|
||||||
|
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.platform.LocalTextInputService
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
|
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
internal fun InputTextField(
|
||||||
|
modifier: Modifier,
|
||||||
|
value: TextFieldValue,
|
||||||
|
onCursorChange: (IntRange) -> Unit
|
||||||
|
) {
|
||||||
|
CompositionLocalProvider(
|
||||||
|
// FIXME Can't paste if this is null
|
||||||
|
LocalTextInputService provides null
|
||||||
|
) {
|
||||||
|
BasicTextField(
|
||||||
|
modifier = modifier,
|
||||||
|
value = value,
|
||||||
|
onValueChange = { onCursorChange(it.selection.start..it.selection.end) },
|
||||||
|
textStyle = NumbersTextStyleDisplayLarge.copy(
|
||||||
|
textAlign = TextAlign.End,
|
||||||
|
color = MaterialTheme.colorScheme.onBackground
|
||||||
|
),
|
||||||
|
minLines = 1,
|
||||||
|
maxLines = 1,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,43 @@
|
|||||||
|
/*
|
||||||
|
* 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.navigation
|
||||||
|
|
||||||
|
import androidx.navigation.NavController
|
||||||
|
import androidx.navigation.NavGraphBuilder
|
||||||
|
import androidx.navigation.compose.composable
|
||||||
|
import androidx.navigation.navDeepLink
|
||||||
|
import com.sadellie.unitto.feature.calculator.CalculatorRoute
|
||||||
|
|
||||||
|
private const val calculatorRoute = "calculator_route"
|
||||||
|
|
||||||
|
fun NavController.navigateToCalculator() {
|
||||||
|
navigate(calculatorRoute)
|
||||||
|
}
|
||||||
|
|
||||||
|
fun NavGraphBuilder.calculatorScreen(
|
||||||
|
navigateUpAction: () -> Unit
|
||||||
|
) {
|
||||||
|
composable(
|
||||||
|
route = calculatorRoute,
|
||||||
|
deepLinks = listOf(
|
||||||
|
navDeepLink { uriPattern = "app://com.sadellie.unitto/$calculatorRoute" }
|
||||||
|
)) {
|
||||||
|
CalculatorRoute(navigateUpAction = navigateUpAction)
|
||||||
|
}
|
||||||
|
}
|
@ -6,6 +6,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
|
|||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.filled.Calculate
|
||||||
import androidx.compose.material.icons.filled.Schedule
|
import androidx.compose.material.icons.filled.Schedule
|
||||||
import androidx.compose.material3.Card
|
import androidx.compose.material3.Card
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
@ -23,6 +24,7 @@ import com.sadellie.unitto.core.ui.common.UnittoLargeTopAppBar
|
|||||||
@Composable
|
@Composable
|
||||||
internal fun ToolsScreen(
|
internal fun ToolsScreen(
|
||||||
navigateUpAction: () -> Unit,
|
navigateUpAction: () -> Unit,
|
||||||
|
navigateToCalculator: () -> Unit,
|
||||||
navigateToEpoch: () -> Unit
|
navigateToEpoch: () -> Unit
|
||||||
) {
|
) {
|
||||||
UnittoLargeTopAppBar(
|
UnittoLargeTopAppBar(
|
||||||
@ -57,6 +59,19 @@ internal fun ToolsScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
item {
|
||||||
|
ListItem(
|
||||||
|
leadingContent = {
|
||||||
|
Icon(
|
||||||
|
Icons.Default.Calculate,
|
||||||
|
stringResource(R.string.calculator)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
headlineText = { Text(stringResource(R.string.calculator)) },
|
||||||
|
supportingText = { Text(stringResource(R.string.calculator_support)) },
|
||||||
|
modifier = Modifier.clickable { navigateToCalculator() }
|
||||||
|
)
|
||||||
|
}
|
||||||
item {
|
item {
|
||||||
ListItem(
|
ListItem(
|
||||||
leadingContent = {
|
leadingContent = {
|
||||||
@ -79,6 +94,7 @@ internal fun ToolsScreen(
|
|||||||
internal fun PreviewToolsScreen() {
|
internal fun PreviewToolsScreen() {
|
||||||
ToolsScreen(
|
ToolsScreen(
|
||||||
navigateUpAction = {},
|
navigateUpAction = {},
|
||||||
navigateToEpoch = {}
|
navigateToEpoch = {},
|
||||||
|
navigateToCalculator = {}
|
||||||
)
|
)
|
||||||
}
|
}
|
@ -31,11 +31,13 @@ fun NavController.navigateToTools() {
|
|||||||
|
|
||||||
fun NavGraphBuilder.toolsScreen(
|
fun NavGraphBuilder.toolsScreen(
|
||||||
navigateUpAction: () -> Unit,
|
navigateUpAction: () -> Unit,
|
||||||
|
navigateToCalculator: () -> Unit,
|
||||||
navigateToEpoch: () -> Unit
|
navigateToEpoch: () -> Unit
|
||||||
) {
|
) {
|
||||||
composable(toolsRoute) {
|
composable(toolsRoute) {
|
||||||
ToolsScreen(
|
ToolsScreen(
|
||||||
navigateUpAction = navigateUpAction,
|
navigateUpAction = navigateUpAction,
|
||||||
|
navigateToCalculator = navigateToCalculator,
|
||||||
navigateToEpoch = navigateToEpoch
|
navigateToEpoch = navigateToEpoch
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ orgBurnoutcrewComposereorderable = "0.9.6"
|
|||||||
comGithubSadellieExprk = "e55cba8f41"
|
comGithubSadellieExprk = "e55cba8f41"
|
||||||
androidGradlePlugin = "7.4.1"
|
androidGradlePlugin = "7.4.1"
|
||||||
kotlin = "1.8.0"
|
kotlin = "1.8.0"
|
||||||
|
mxParser = "5.2.1"
|
||||||
|
|
||||||
[libraries]
|
[libraries]
|
||||||
androidx-core = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" }
|
androidx-core = { group = "androidx.core", name = "core-ktx", version.ref = "androidxCore" }
|
||||||
@ -63,6 +64,7 @@ com-github-sadellie-themmo = { group = "com.github.sadellie", name = "themmo", v
|
|||||||
org-burnoutcrew-composereorderable = { group = "org.burnoutcrew.composereorderable", name = "reorderable", version.ref = "orgBurnoutcrewComposereorderable" }
|
org-burnoutcrew-composereorderable = { group = "org.burnoutcrew.composereorderable", name = "reorderable", version.ref = "orgBurnoutcrewComposereorderable" }
|
||||||
com-github-sadellie-exprk = { group = "com.github.sadellie", name = "ExprK", version.ref = "comGithubSadellieExprk" }
|
com-github-sadellie-exprk = { group = "com.github.sadellie", name = "ExprK", version.ref = "comGithubSadellieExprk" }
|
||||||
android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }
|
android-desugarJdkLibs = { group = "com.android.tools", name = "desugar_jdk_libs", version.ref = "androidDesugarJdkLibs" }
|
||||||
|
org-mariuszgromada-math-mxparser = { group = "org.mariuszgromada.math", name = "MathParser.org-mXparser", version.ref = "mxParser" }
|
||||||
|
|
||||||
# classpath
|
# classpath
|
||||||
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
|
android-gradlePlugin = { group = "com.android.tools.build", name = "gradle", version.ref = "androidGradlePlugin" }
|
||||||
|
@ -22,6 +22,7 @@ include(":core:base")
|
|||||||
include(":core:ui")
|
include(":core:ui")
|
||||||
include(":feature:converter")
|
include(":feature:converter")
|
||||||
include(":feature:unitslist")
|
include(":feature:unitslist")
|
||||||
|
include(":feature:calculator")
|
||||||
include(":feature:settings")
|
include(":feature:settings")
|
||||||
include(":feature:tools")
|
include(":feature:tools")
|
||||||
include(":feature:epoch")
|
include(":feature:epoch")
|
||||||
|
Loading…
x
Reference in New Issue
Block a user