Auto brackets

closes: #41
This commit is contained in:
Sad Ellie 2023-10-11 23:16:22 +03:00
parent 9ff7e39ece
commit fb1b7fab2b
20 changed files with 285 additions and 60 deletions

View File

@ -81,6 +81,12 @@ Used in this dialog window. Should be short -->
<string name="select_time_label">Select time</string>
<string name="settings_about_unitto">About Unitto</string>
<string name="settings_about_unitto_support">Learn about the app</string>
<!-- https://s3.eu-west-1.amazonaws.com/po-pub/i/p3aY3IWUI5m9Vjs6vZP3EXAo.png -->
<string name="settings_ac_button">AC button</string>
<!-- https://s3.eu-west-1.amazonaws.com/po-pub/i/p3aY3IWUI5m9Vjs6vZP3EXAo.png -->
<string name="settings_ac_button_support">Show separate clear button</string>
<string name="settings_additional">Additional</string>
<string name="settings_amoled_dark">AMOLED Dark</string>
<string name="settings_amoled_dark_support">Use black background for dark themes</string>

View File

@ -140,3 +140,24 @@ fun KeyboardButtonAdditional(
allowVibration = allowVibration,
)
}
@Composable
fun KeyboardButtonTertiary(
modifier: Modifier,
icon: ImageVector,
allowVibration: Boolean,
contentHeight: Float = if (isPortrait()) 0.578f else 0.793f,
onLongClick: (() -> Unit)? = null,
onClick: () -> Unit,
) {
BasicKeyboardButton(
modifier = modifier,
contentHeight = contentHeight,
onClick = onClick,
onLongClick = onLongClick,
containerColor = MaterialTheme.colorScheme.tertiaryContainer,
icon = icon,
iconColor = MaterialTheme.colorScheme.onTertiaryContainer,
allowVibration = allowVibration,
)
}

View File

@ -85,14 +85,14 @@ private fun Int.isAfterToken(str: String, token: String): Boolean {
}
// This can make [TextFieldValue.addTokens] better by checking tokens both ways. Needs more tests
//fun String.tokenAfter(pos: Int): String {
// Token.Func.allWithOpeningBracket.forEach {
// if (pos.isBeforeToken(this, it)) return it
// }
//
// return substring(pos, (pos + 1).coerceAtMost(this.length))
//}
// This can also make [TextFieldValue.addTokens] better by checking tokens both ways. Needs more tests
fun String.tokenAfter(pos: Int): String {
Token.Func.allWithOpeningBracket.forEach {
if (pos.isBeforeToken(this, it)) return it
}
return substring(pos, (pos + 1).coerceAtMost(this.length))
}
//private fun String.numberNearby(cursor: Int): String {
// val text = this
@ -111,8 +111,8 @@ private fun Int.isAfterToken(str: String, token: String): Boolean {
// return text.substring(aheadCursor, afterCursor)
//}
//private fun Int.isBeforeToken(str: String, token: String): Boolean {
// return str
// .substring(this, (this + token.length).coerceAtMost(str.length))
// .contains(token)
//}
private fun Int.isBeforeToken(str: String, token: String): Boolean {
return str
.substring(this, (this + token.length).coerceAtMost(str.length))
.contains(token)
}

View File

@ -53,15 +53,52 @@ fun TextFieldValue.addTokens(tokens: String): TextFieldValue {
)
}
/**
* <b>!!! Recursive !!!</b> (one wrong step and you are dead 💀)
*/
private fun TextFieldValue.deleteAheadAndAdd(tokens: String): TextFieldValue {
var newValue = this
if (!selection.collapsed) newValue = this.deleteTokens()
return newValue
.deleteTokens()
.addTokens(tokens)
fun TextFieldValue.addBracket(): TextFieldValue {
val subStringBeforeCursor = text.substring(0..<selection.start)
// Always open when empty in front
if (subStringBeforeCursor.isEmpty()) {
return addTokens(Token.Operator.leftBracket)
}
// Always close before operator
val operators = listOf(
Token.Operator.multiply,
Token.Operator.divide,
Token.Operator.plus,
Token.Operator.minus,
Token.Operator.power,
)
if (text.tokenAfter(selection.start) in operators) {
return addTokens(Token.Operator.rightBracket)
}
// Always open when balanced in front
val leftBracketChar: Char = Token.Operator.leftBracket.first()
val rightBracketChar: Char = Token.Operator.rightBracket.first()
var balance = 0
subStringBeforeCursor.forEach {
if (it == leftBracketChar) balance += 1
if (it == rightBracketChar) balance -= 1
}
if (balance == 0) {
return addTokens(Token.Operator.leftBracket)
}
// Always open after operator
val operators2 = listOf(
Token.Operator.multiply,
Token.Operator.divide,
Token.Operator.plus,
Token.Operator.minus,
Token.Operator.power,
Token.Operator.leftBracket
)
if (text.tokenAhead(selection.start) in operators2) {
return addTokens(Token.Operator.leftBracket)
}
return addTokens(Token.Operator.rightBracket)
}
fun TextFieldValue.deleteTokens(): TextFieldValue {
@ -93,3 +130,14 @@ fun TextFieldValue.deleteTokens(): TextFieldValue {
selection = TextRange((newText.length - distanceFromEnd).coerceAtLeast(0))
)
}
/**
* <b>!!! Recursive !!!</b> (one wrong step and you are dead 💀)
*/
private fun TextFieldValue.deleteAheadAndAdd(tokens: String): TextFieldValue {
var newValue = this
if (!selection.collapsed) newValue = this.deleteTokens()
return newValue
.deleteTokens()
.addTokens(tokens)
}

View File

@ -21,6 +21,7 @@ package com.sadellie.unitto.core.ui
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.common.textfield.addBracket
import com.sadellie.unitto.core.ui.common.textfield.addTokens
import org.junit.Assert.assertEquals
import org.junit.Test
@ -260,6 +261,60 @@ class TextFieldValueExtensionsTest {
assertEquals(tf("123+45.[]78"), tf("123+45[6.]78").addTokens(Token.Digit.dot))
}
@Test
fun auto() {
// Open on empty in front
assertEquals(tf("([]"), tf("[]").addBracket())
assertEquals(tf("([]123("), tf("[]123(").addBracket())
assertEquals(tf("([]("), tf("[123](").addBracket())
assertEquals(tf("([]"), tf("[123(]").addBracket())
// Close before multiply
assertEquals(tf("123)[]*456"), tf("123[]*456").addBracket())
assertEquals(tf("(123)[]*456"), tf("(123[]*456").addBracket())
assertEquals(tf(")123)[]*456"), tf(")123[]*456").addBracket())
// Close before divide
assertEquals(tf("123)[]/456"), tf("123[]/456").addBracket())
assertEquals(tf("(123)[]/456"), tf("(123[]/456").addBracket())
assertEquals(tf(")123)[]/456"), tf(")123[]/456").addBracket())
// Close before plus
assertEquals(tf("123)[]+456"), tf("123[]+456").addBracket())
assertEquals(tf("(123)[]+456"), tf("(123[]+456").addBracket())
assertEquals(tf(")123)[]+456"), tf(")123[]+456").addBracket())
// Close before minus
assertEquals(tf("123)[]-456"), tf("123[]-456").addBracket())
assertEquals(tf("(123)[]-456"), tf("(123[]-456").addBracket())
assertEquals(tf(")123)[]-456"), tf(")123[]-456").addBracket())
// Close before power
assertEquals(tf("123)[]^456"), tf("123[]^456").addBracket())
assertEquals(tf("(123)[]^456"), tf("(123[]^456").addBracket())
assertEquals(tf(")123)[]^456"), tf(")123[]^456").addBracket())
// Open on balanced in front
assertEquals(tf("123([]"), tf("123[]").addBracket())
assertEquals(tf("123([](((("), tf("123[]((((").addBracket())
// Open after multiply
assertEquals(tf("123*([]456"), tf("123*[]456").addBracket())
assertEquals(tf("123*([]"), tf("123*[456]").addBracket())
// Open after divide
assertEquals(tf("123/([]456"), tf("123/[]456").addBracket())
assertEquals(tf("123/([]"), tf("123/[456]").addBracket())
// Open after plus
assertEquals(tf("123+([]456"), tf("123+[]456").addBracket())
assertEquals(tf("123+([]"), tf("123+[456]").addBracket())
// Open after minus
assertEquals(tf("123-([]456"), tf("123-[]456").addBracket())
assertEquals(tf("123-([]"), tf("123-[456]").addBracket())
// Open after power
assertEquals(tf("123^([]456"), tf("123^[]456").addBracket())
assertEquals(tf("123^([]"), tf("123^[456]").addBracket())
// Default
assertEquals(tf("123([]"), tf("123[]").addBracket())
assertEquals(tf("123(456+789)[]"), tf("123(456+789[]").addBracket())
}
// Use [] for selection
private fun tf(
text: String = "",
@ -267,6 +322,9 @@ class TextFieldValueExtensionsTest {
val selectionStart = text.indexOf("[")
val selectionEnd = text.indexOf("]") - 1
if (selectionStart < 0) throw Exception("forgot selectionStart")
if (selectionEnd < 0) throw Exception("forgot selectionEnd")
return TextFieldValue(
text = text
.replace("[", "")

View File

@ -45,6 +45,7 @@ data class CalculatorPreferences(
val enableVibrations: Boolean,
val separator: Int,
val middleZero: Boolean,
val acButton: Boolean,
val partialHistoryView: Boolean,
val precision: Int,
val outputFormat: Int,
@ -54,6 +55,7 @@ data class ConverterPreferences(
val enableVibrations: Boolean,
val separator: Int,
val middleZero: Boolean,
val acButton: Boolean,
val precision: Int,
val outputFormat: Int,
val unitConverterFormatTime: Boolean,
@ -68,6 +70,7 @@ data class ConverterPreferences(
data class DisplayPreferences(
val systemFont: Boolean,
val middleZero: Boolean,
val acButton: Boolean,
)
data class FormattingPreferences(

View File

@ -46,4 +46,5 @@ internal object PrefsKeys {
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")
}

View File

@ -75,7 +75,8 @@ class UserPreferencesRepository @Inject constructor(
middleZero = preferences.getMiddleZero(),
partialHistoryView = preferences.getPartialHistoryView(),
precision = preferences.getDigitsPrecision(),
outputFormat = preferences.getOutputFormat()
outputFormat = preferences.getOutputFormat(),
acButton = preferences.getAcButton(),
)
}
@ -94,6 +95,7 @@ class UserPreferencesRepository @Inject constructor(
enableToolsExperiment = preferences.getEnableToolsExperiment(),
latestLeftSideUnit = preferences.getLatestLeftSide(),
latestRightSideUnit = preferences.getLatestRightSide(),
acButton = preferences.getAcButton(),
)
}
@ -102,6 +104,7 @@ class UserPreferencesRepository @Inject constructor(
DisplayPreferences(
systemFont = preferences.getSystemFont(),
middleZero = preferences.getMiddleZero(),
acButton = preferences.getAcButton(),
)
}
@ -263,6 +266,12 @@ class UserPreferencesRepository @Inject constructor(
preferences[PrefsKeys.PARTIAL_HISTORY_VIEW] = enabled
}
}
suspend fun updateAcButton(enabled: Boolean) {
dataStore.edit { preferences ->
preferences[PrefsKeys.AC_BUTTON] = enabled
}
}
}
private fun Preferences.getEnableDynamicTheme(): Boolean {
@ -359,6 +368,10 @@ private fun Preferences.getLatestRightSide(): String {
return this[PrefsKeys.LATEST_RIGHT_SIDE] ?: MyUnitIDS.mile
}
private fun Preferences.getAcButton(): Boolean {
return this[PrefsKeys.AC_BUTTON] ?: false
}
private inline fun <T, R> T.letTryOrNull(block: (T) -> R): R? = try {
this?.let(block)
} catch (e: Exception) {

View File

@ -89,7 +89,8 @@ internal fun CalculatorRoute(
onCursorChange = viewModel::onCursorChange,
toggleCalculatorMode = viewModel::toggleCalculatorMode,
evaluate = viewModel::evaluate,
clearHistory = viewModel::clearHistory
clearHistory = viewModel::clearHistory,
addBracket = viewModel::addBracket
)
}
@ -99,6 +100,7 @@ internal fun CalculatorScreen(
navigateToMenu: () -> Unit,
navigateToSettings: () -> Unit,
addTokens: (String) -> Unit,
addBracket: () -> Unit,
clearInput: () -> Unit,
deleteTokens: () -> Unit,
onCursorChange: (TextRange) -> Unit,
@ -118,7 +120,8 @@ internal fun CalculatorScreen(
onCursorChange = onCursorChange,
toggleAngleMode = toggleCalculatorMode,
evaluate = evaluate,
clearHistory = clearHistory
clearHistory = clearHistory,
addBracket = addBracket
)
}
}
@ -129,6 +132,7 @@ private fun Ready(
navigateToMenu: () -> Unit,
navigateToSettings: () -> Unit,
addSymbol: (String) -> Unit,
addBracket: () -> Unit,
clearSymbols: () -> Unit,
deleteSymbol: () -> Unit,
onCursorChange: (TextRange) -> Unit,
@ -264,6 +268,8 @@ private fun Ready(
toggleAngleMode = toggleAngleMode,
evaluate = evaluate,
middleZero = uiState.middleZero,
acButton = uiState.acButton,
addBracket = addBracket
)
}
}
@ -343,6 +349,7 @@ private fun PreviewCalculatorScreen() {
onCursorChange = {},
toggleCalculatorMode = {},
evaluate = {},
clearHistory = {}
clearHistory = {},
addBracket = {}
)
}

View File

@ -35,6 +35,7 @@ internal sealed class CalculatorUIState {
val allowVibration: Boolean = false,
val formatterSymbols: FormatterSymbols = FormatterSymbols.Spaces,
val middleZero: Boolean = false,
val acButton: Boolean = false,
val partialHistoryView: Boolean = true,
) : CalculatorUIState()
}

View File

@ -26,6 +26,7 @@ import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.addBracket
import com.sadellie.unitto.core.ui.common.textfield.addTokens
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
import com.sadellie.unitto.data.calculator.CalculatorHistoryRepository
@ -67,7 +68,8 @@ internal class CalculatorViewModel @Inject constructor(
middleZero = false,
partialHistoryView = true,
precision = 3,
outputFormat = OutputFormat.PLAIN
outputFormat = OutputFormat.PLAIN,
acButton = false,
)
)
@ -87,6 +89,7 @@ internal class CalculatorViewModel @Inject constructor(
allowVibration = userPrefs.enableVibrations,
formatterSymbols = AllFormatterSymbols.getById(userPrefs.separator),
middleZero = userPrefs.middleZero,
acButton = userPrefs.acButton,
partialHistoryView = userPrefs.partialHistoryView,
)
}
@ -95,6 +98,7 @@ internal class CalculatorViewModel @Inject constructor(
)
fun addTokens(tokens: String) = _input.update { it.addTokens(tokens) }
fun addBracket() = _input.update { it.addBracket() }
fun deleteTokens() = _input.update { it.deleteTokens() }
fun clearInput() = _input.update { TextFieldValue() }
fun onCursorChange(selection: TextRange) = _input.update { it.copy(selection = selection) }

View File

@ -56,12 +56,15 @@ import com.sadellie.unitto.core.ui.common.ColumnWithConstraints
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.RowWithConstraints
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
import com.sadellie.unitto.core.ui.common.key.unittoicons.AcTan
import com.sadellie.unitto.core.ui.common.key.unittoicons.ArCos
import com.sadellie.unitto.core.ui.common.key.unittoicons.ArSin
import com.sadellie.unitto.core.ui.common.key.unittoicons.Backspace
import com.sadellie.unitto.core.ui.common.key.unittoicons.Brackets
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.Cos
import com.sadellie.unitto.core.ui.common.key.unittoicons.Deg
@ -106,7 +109,9 @@ internal fun CalculatorKeyboard(
fractional: String,
allowVibration: Boolean,
middleZero: Boolean,
acButton: Boolean,
addSymbol: (String) -> Unit,
addBracket: () -> Unit,
clearSymbols: () -> Unit,
deleteSymbol: () -> Unit,
toggleAngleMode: () -> Unit,
@ -123,7 +128,9 @@ internal fun CalculatorKeyboard(
toggleAngleMode = toggleAngleMode,
deleteSymbol = deleteSymbol,
clearSymbols = clearSymbols,
evaluate = evaluate
evaluate = evaluate,
acButton = acButton,
addBracket = addBracket,
)
} else {
LandscapeKeyboard(
@ -136,7 +143,9 @@ internal fun CalculatorKeyboard(
toggleAngleMode = toggleAngleMode,
deleteSymbol = deleteSymbol,
clearSymbols = clearSymbols,
evaluate = evaluate
evaluate = evaluate,
acButton = acButton,
addBracket = addBracket,
)
}
}
@ -152,7 +161,9 @@ private fun PortraitKeyboard(
toggleAngleMode: () -> Unit,
deleteSymbol: () -> Unit,
clearSymbols: () -> Unit,
evaluate: () -> Unit
evaluate: () -> Unit,
acButton: Boolean,
addBracket: () -> Unit,
) {
val fractionalIcon = remember { if (fractional == Token.Digit.dot) UnittoIcons.Dot else UnittoIcons.Comma }
var showAdditional: Boolean by remember { mutableStateOf(false) }
@ -239,8 +250,13 @@ private fun PortraitKeyboard(
Spacer(modifier = Modifier.height(spacerHeight))
Row(weightModifier) {
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.Operator.leftBracket) }
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.RightBracket, allowVibration) { addSymbol(Token.Operator.rightBracket) }
if (acButton) {
KeyboardButtonTertiary(mainButtonModifier, UnittoIcons.Clear, allowVibration) { clearSymbols() }
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Brackets, allowVibration) { addBracket() }
} else {
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.Operator.leftBracket) }
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.RightBracket, allowVibration) { addSymbol(Token.Operator.rightBracket) }
}
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Percent, allowVibration) { addSymbol(Token.Operator.percent) }
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Divide, allowVibration) { addSymbol(Token.Operator.divide) }
}
@ -361,7 +377,9 @@ private fun LandscapeKeyboard(
toggleAngleMode: () -> Unit,
deleteSymbol: () -> Unit,
clearSymbols: () -> Unit,
evaluate: () -> Unit
evaluate: () -> Unit,
acButton: Boolean,
addBracket: () -> Unit,
) {
val fractionalIcon = remember { if (fractional == Token.Digit.dot) UnittoIcons.Dot else UnittoIcons.Comma }
var invMode: Boolean by remember { mutableStateOf(false) }
@ -426,13 +444,21 @@ private fun LandscapeKeyboard(
}
Column(Modifier.weight(1f)) {
KeyboardButtonFilled(buttonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.Operator.leftBracket) }
if (acButton) {
KeyboardButtonTertiary(buttonModifier, UnittoIcons.Clear, allowVibration) { clearSymbols() }
} else {
KeyboardButtonFilled(buttonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.Operator.leftBracket) }
}
KeyboardButtonFilled(buttonModifier, UnittoIcons.Multiply, allowVibration) { addSymbol(Token.Operator.multiply) }
KeyboardButtonFilled(buttonModifier, UnittoIcons.Minus, allowVibration) { addSymbol(Token.Operator.minus) }
KeyboardButtonFilled(buttonModifier, UnittoIcons.Plus, allowVibration) { addSymbol(Token.Operator.plus) }
}
Column(Modifier.weight(1f)) {
KeyboardButtonFilled(buttonModifier, UnittoIcons.RightBracket, allowVibration) { addSymbol(Token.Operator.rightBracket) }
if (acButton) {
KeyboardButtonTertiary(buttonModifier, UnittoIcons.Brackets, allowVibration) { addBracket() }
} else {
KeyboardButtonFilled(buttonModifier, UnittoIcons.RightBracket, allowVibration) { addSymbol(Token.Operator.rightBracket) }
}
KeyboardButtonFilled(buttonModifier, UnittoIcons.Divide, allowVibration) { addSymbol(Token.Operator.divide) }
KeyboardButtonFilled(buttonModifier, UnittoIcons.Percent, allowVibration) { addSymbol(Token.Operator.percent) }
KeyboardButtonFilled(buttonModifier, UnittoIcons.Equal, allowVibration) { evaluate() }
@ -518,5 +544,7 @@ private fun PreviewCalculatorKeyboard() {
evaluate = {},
allowVibration = false,
middleZero = false,
acButton = true,
addBracket = {}
)
}

View File

@ -110,6 +110,7 @@ internal fun ConverterRoute(
clearInput = viewModel::clearInput,
onCursorChange = viewModel::onCursorChange,
onErrorClick = viewModel::updateCurrencyRates,
addBracket = viewModel::addBracket
)
}
@ -126,6 +127,7 @@ private fun ConverterScreen(
clearInput: () -> Unit,
onCursorChange: (TextRange) -> Unit,
onErrorClick: (AbstractUnit) -> Unit,
addBracket: () -> Unit,
) {
when (uiState) {
UnitConverterUIState.Loading -> UnittoEmptyScreen()
@ -164,7 +166,8 @@ private fun ConverterScreen(
swapUnits = swapUnits,
navigateToRightScreen = navigateToRightScreen,
clearInput = clearInput,
refreshCurrencyRates = onErrorClick
refreshCurrencyRates = onErrorClick,
addBracket = addBracket,
)
}
}
@ -258,6 +261,7 @@ private fun Default(
navigateToRightScreen: () -> Unit,
clearInput: () -> Unit,
refreshCurrencyRates: (AbstractUnit) -> Unit,
addBracket: () -> Unit,
) {
val locale: Locale = LocalLocale.current
var calculation by remember(uiState.calculation) {
@ -365,7 +369,9 @@ private fun Default(
clearInput = clearInput,
allowVibration = uiState.enableHaptic,
fractional = uiState.formatterSymbols.fractional,
middleZero = uiState.middleZero
middleZero = uiState.middleZero,
acButton = uiState.acButton,
addBracket = addBracket
)
}
)
@ -522,5 +528,6 @@ private fun PreviewConverterScreen() {
clearInput = {},
onCursorChange = {},
onErrorClick = {},
addBracket = {}
)
}

View File

@ -46,6 +46,7 @@ internal sealed class UnitConverterUIState {
val outputFormat: Int,
val formatTime: Boolean,
val currencyRateUpdateState: CurrencyRateUpdateState,
val acButton: Boolean,
) : UnitConverterUIState()
data class NumberBase(

View File

@ -25,6 +25,7 @@ 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.addBracket
import com.sadellie.unitto.core.ui.common.textfield.addTokens
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
import com.sadellie.unitto.data.common.combine
@ -105,7 +106,8 @@ internal class ConverterViewModel @Inject constructor(
scale = prefs.precision,
outputFormat = prefs.outputFormat,
formatTime = prefs.unitConverterFormatTime,
currencyRateUpdateState = currenciesState
currencyRateUpdateState = currenciesState,
acButton = prefs.acButton,
)
}
(unitFrom is NumberBaseUnit) and (unitTo is NumberBaseUnit) -> {
@ -256,6 +258,12 @@ internal class ConverterViewModel @Inject constructor(
newValue
}
fun addBracket() = _input.update {
val newValue = it.addBracket()
savedStateHandle[converterInputKey] = newValue.text
newValue
}
fun deleteTokens() = _input.update {
val newValue = it.deleteTokens()
savedStateHandle[converterInputKey] = newValue.text

View File

@ -31,8 +31,11 @@ import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.common.ColumnWithConstraints
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.Brackets
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
@ -70,6 +73,8 @@ internal fun DefaultKeyboard(
allowVibration: Boolean,
fractional: String,
middleZero: Boolean,
acButton: Boolean,
addBracket: () -> Unit,
) {
ColumnWithConstraints(modifier) {
val fractionalIcon = remember { if (fractional == Token.Digit.dot) UnittoIcons.Dot else UnittoIcons.Comma }
@ -85,8 +90,13 @@ internal fun DefaultKeyboard(
// Column modifier
val cModifier = Modifier.weight(1f)
Row(cModifier) {
KeyboardButtonFilled(bModifier, UnittoIcons.LeftBracket, allowVibration) { addDigit(Token.Operator.leftBracket) }
KeyboardButtonFilled(bModifier, UnittoIcons.RightBracket, allowVibration) { addDigit(Token.Operator.rightBracket) }
if (acButton) {
KeyboardButtonTertiary(bModifier, UnittoIcons.Clear, allowVibration) { clearInput() }
KeyboardButtonFilled(bModifier, UnittoIcons.Brackets, allowVibration) { addBracket() }
} else {
KeyboardButtonFilled(bModifier, UnittoIcons.LeftBracket, allowVibration) { addDigit(Token.Operator.leftBracket) }
KeyboardButtonFilled(bModifier, UnittoIcons.RightBracket, allowVibration) { addDigit(Token.Operator.rightBracket) }
}
KeyboardButtonFilled(bModifier, UnittoIcons.Power, allowVibration) { addDigit(Token.Operator.power) }
KeyboardButtonFilled(bModifier, UnittoIcons.Root, allowVibration) { addDigit(Token.Operator.sqrt) }
}
@ -188,7 +198,9 @@ private fun PreviewConverterKeyboard() {
deleteDigit = {},
allowVibration = false,
fractional = FormatterSymbols.Spaces.fractional,
middleZero = false
middleZero = false,
acButton = true,
addBracket = {}
)
}

View File

@ -88,7 +88,8 @@ private fun PreviewCalculatorSettingsScreen() {
middleZero = false,
partialHistoryView = true,
precision = 3,
outputFormat = OutputFormat.PLAIN
outputFormat = OutputFormat.PLAIN,
acButton = true,
),
navigateUpAction = {},
updatePartialHistoryView = {}

View File

@ -147,6 +147,7 @@ private fun PreviewConverterSettingsScreen() {
enableToolsExperiment = false,
latestLeftSideUnit = "kilometer",
latestRightSideUnit = "mile",
acButton = true,
),
navigateUpAction = {},
navigateToUnitsGroup = {},

View File

@ -62,6 +62,8 @@ import com.sadellie.unitto.core.ui.common.SegmentedButtonsRow
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
import com.sadellie.unitto.core.ui.common.key.unittoicons.Clear
import com.sadellie.unitto.feature.settings.components.ColorSelector
import com.sadellie.unitto.feature.settings.components.MonetModeSelector
import io.github.sadellie.themmo.MonetMode
@ -113,6 +115,8 @@ internal fun DisplayRoute(
},
systemFont = prefs.systemFont,
updateSystemFont = viewModel::updateSystemFont,
acButton = prefs.acButton,
updateAcButton = viewModel::updateAcButton,
middleZero = prefs.middleZero,
updateMiddleZero = viewModel::updateMiddleZero,
navigateToLanguages = navigateToLanguages
@ -136,6 +140,8 @@ private fun DisplayScreen(
onMonetModeChange: (MonetMode) -> Unit,
systemFont: Boolean,
updateSystemFont: (Boolean) -> Unit,
acButton: Boolean,
updateAcButton: (Boolean) -> Unit,
middleZero: Boolean,
updateMiddleZero: (Boolean) -> Unit,
navigateToLanguages: () -> Unit,
@ -266,6 +272,15 @@ private fun DisplayScreen(
onSwitchChange = updateSystemFont
)
UnittoListItem(
icon = UnittoIcons.Clear,
iconDescription = stringResource(R.string.settings_middle_zero),
headlineText = stringResource(R.string.settings_ac_button),
supportingText = stringResource(R.string.settings_ac_button_support),
switchState = acButton,
onSwitchChange = updateAcButton
)
UnittoListItem(
icon = Icons.Default.ExposureZero,
iconDescription = stringResource(R.string.settings_middle_zero),
@ -304,6 +319,8 @@ private fun Preview() {
onMonetModeChange = themmoController::setMonetMode,
systemFont = false,
updateSystemFont = {},
acButton = false,
updateAcButton = {},
middleZero = false,
updateMiddleZero = {},
navigateToLanguages = {}

View File

@ -37,60 +37,48 @@ class DisplayViewModel @Inject constructor(
val prefs = userPrefsRepository.displayPrefs
.stateIn(viewModelScope, null)
/**
* @see UserPreferencesRepository.updateThemingMode
*/
fun updateThemingMode(themingMode: ThemingMode) {
viewModelScope.launch {
userPrefsRepository.updateThemingMode(themingMode)
}
}
/**
* @see UserPreferencesRepository.updateDynamicTheme
*/
fun updateDynamicTheme(enabled: Boolean) {
viewModelScope.launch {
userPrefsRepository.updateDynamicTheme(enabled)
}
}
/**
* @see UserPreferencesRepository.updateAmoledTheme
*/
fun updateAmoledTheme(enabled: Boolean) {
viewModelScope.launch {
userPrefsRepository.updateAmoledTheme(enabled)
}
}
/**
* @see UserPreferencesRepository.updateCustomColor
*/
fun updateCustomColor(color: Color) {
viewModelScope.launch {
userPrefsRepository.updateCustomColor(color)
}
}
/**
* @see UserPreferencesRepository.updateMonetMode
*/
fun updateMonetMode(monetMode: MonetMode) {
viewModelScope.launch {
userPrefsRepository.updateMonetMode(monetMode)
}
}
/**
* @see UserPreferencesRepository.updateSystemFont
*/
fun updateSystemFont(enabled: Boolean) {
viewModelScope.launch {
userPrefsRepository.updateSystemFont(enabled)
}
}
fun updateAcButton(enabled: Boolean) {
viewModelScope.launch {
userPrefsRepository.updateAcButton(enabled)
}
}
fun updateMiddleZero(enabled: Boolean) = viewModelScope.launch {
userPrefsRepository.updateMiddleZero(enabled)
}