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="select_time_label">Select time</string>
<string name="settings_about_unitto">About Unitto</string> <string name="settings_about_unitto">About Unitto</string>
<string name="settings_about_unitto_support">Learn about the app</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_additional">Additional</string>
<string name="settings_amoled_dark">AMOLED Dark</string> <string name="settings_amoled_dark">AMOLED Dark</string>
<string name="settings_amoled_dark_support">Use black background for dark themes</string> <string name="settings_amoled_dark_support">Use black background for dark themes</string>

View File

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

View File

@ -53,15 +53,52 @@ fun TextFieldValue.addTokens(tokens: String): TextFieldValue {
) )
} }
/** fun TextFieldValue.addBracket(): TextFieldValue {
* <b>!!! Recursive !!!</b> (one wrong step and you are dead 💀) val subStringBeforeCursor = text.substring(0..<selection.start)
*/
private fun TextFieldValue.deleteAheadAndAdd(tokens: String): TextFieldValue { // Always open when empty in front
var newValue = this if (subStringBeforeCursor.isEmpty()) {
if (!selection.collapsed) newValue = this.deleteTokens() return addTokens(Token.Operator.leftBracket)
return newValue }
.deleteTokens()
.addTokens(tokens) // 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 { fun TextFieldValue.deleteTokens(): TextFieldValue {
@ -93,3 +130,14 @@ fun TextFieldValue.deleteTokens(): TextFieldValue {
selection = TextRange((newText.length - distanceFromEnd).coerceAtLeast(0)) 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.TextRange
import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.text.input.TextFieldValue
import com.sadellie.unitto.core.base.Token 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 com.sadellie.unitto.core.ui.common.textfield.addTokens
import org.junit.Assert.assertEquals import org.junit.Assert.assertEquals
import org.junit.Test import org.junit.Test
@ -260,6 +261,60 @@ class TextFieldValueExtensionsTest {
assertEquals(tf("123+45.[]78"), tf("123+45[6.]78").addTokens(Token.Digit.dot)) 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 // Use [] for selection
private fun tf( private fun tf(
text: String = "", text: String = "",
@ -267,6 +322,9 @@ class TextFieldValueExtensionsTest {
val selectionStart = text.indexOf("[") val selectionStart = text.indexOf("[")
val selectionEnd = text.indexOf("]") - 1 val selectionEnd = text.indexOf("]") - 1
if (selectionStart < 0) throw Exception("forgot selectionStart")
if (selectionEnd < 0) throw Exception("forgot selectionEnd")
return TextFieldValue( return TextFieldValue(
text = text text = text
.replace("[", "") .replace("[", "")

View File

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

View File

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

View File

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

View File

@ -35,6 +35,7 @@ internal sealed class CalculatorUIState {
val allowVibration: Boolean = false, val allowVibration: Boolean = false,
val formatterSymbols: FormatterSymbols = FormatterSymbols.Spaces, val formatterSymbols: FormatterSymbols = FormatterSymbols.Spaces,
val middleZero: Boolean = false, val middleZero: Boolean = false,
val acButton: Boolean = false,
val partialHistoryView: Boolean = true, val partialHistoryView: Boolean = true,
) : CalculatorUIState() ) : 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.Separator
import com.sadellie.unitto.core.base.Token import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols 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.addTokens
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
import com.sadellie.unitto.data.calculator.CalculatorHistoryRepository import com.sadellie.unitto.data.calculator.CalculatorHistoryRepository
@ -67,7 +68,8 @@ internal class CalculatorViewModel @Inject constructor(
middleZero = false, middleZero = false,
partialHistoryView = true, partialHistoryView = true,
precision = 3, precision = 3,
outputFormat = OutputFormat.PLAIN outputFormat = OutputFormat.PLAIN,
acButton = false,
) )
) )
@ -87,6 +89,7 @@ internal class CalculatorViewModel @Inject constructor(
allowVibration = userPrefs.enableVibrations, allowVibration = userPrefs.enableVibrations,
formatterSymbols = AllFormatterSymbols.getById(userPrefs.separator), formatterSymbols = AllFormatterSymbols.getById(userPrefs.separator),
middleZero = userPrefs.middleZero, middleZero = userPrefs.middleZero,
acButton = userPrefs.acButton,
partialHistoryView = userPrefs.partialHistoryView, partialHistoryView = userPrefs.partialHistoryView,
) )
} }
@ -95,6 +98,7 @@ internal class CalculatorViewModel @Inject constructor(
) )
fun addTokens(tokens: String) = _input.update { it.addTokens(tokens) } fun addTokens(tokens: String) = _input.update { it.addTokens(tokens) }
fun addBracket() = _input.update { it.addBracket() }
fun deleteTokens() = _input.update { it.deleteTokens() } fun deleteTokens() = _input.update { it.deleteTokens() }
fun clearInput() = _input.update { TextFieldValue() } fun clearInput() = _input.update { TextFieldValue() }
fun onCursorChange(selection: TextRange) = _input.update { it.copy(selection = selection) } 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.KeyboardButtonAdditional
import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled
import com.sadellie.unitto.core.ui.common.KeyboardButtonLight 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.RowWithConstraints
import com.sadellie.unitto.core.ui.common.key.UnittoIcons 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.AcTan
import com.sadellie.unitto.core.ui.common.key.unittoicons.ArCos 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.ArSin
import com.sadellie.unitto.core.ui.common.key.unittoicons.Backspace 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.Comma
import com.sadellie.unitto.core.ui.common.key.unittoicons.Cos import com.sadellie.unitto.core.ui.common.key.unittoicons.Cos
import com.sadellie.unitto.core.ui.common.key.unittoicons.Deg import com.sadellie.unitto.core.ui.common.key.unittoicons.Deg
@ -106,7 +109,9 @@ internal fun CalculatorKeyboard(
fractional: String, fractional: String,
allowVibration: Boolean, allowVibration: Boolean,
middleZero: Boolean, middleZero: Boolean,
acButton: Boolean,
addSymbol: (String) -> Unit, addSymbol: (String) -> Unit,
addBracket: () -> Unit,
clearSymbols: () -> Unit, clearSymbols: () -> Unit,
deleteSymbol: () -> Unit, deleteSymbol: () -> Unit,
toggleAngleMode: () -> Unit, toggleAngleMode: () -> Unit,
@ -123,7 +128,9 @@ internal fun CalculatorKeyboard(
toggleAngleMode = toggleAngleMode, toggleAngleMode = toggleAngleMode,
deleteSymbol = deleteSymbol, deleteSymbol = deleteSymbol,
clearSymbols = clearSymbols, clearSymbols = clearSymbols,
evaluate = evaluate evaluate = evaluate,
acButton = acButton,
addBracket = addBracket,
) )
} else { } else {
LandscapeKeyboard( LandscapeKeyboard(
@ -136,7 +143,9 @@ internal fun CalculatorKeyboard(
toggleAngleMode = toggleAngleMode, toggleAngleMode = toggleAngleMode,
deleteSymbol = deleteSymbol, deleteSymbol = deleteSymbol,
clearSymbols = clearSymbols, clearSymbols = clearSymbols,
evaluate = evaluate evaluate = evaluate,
acButton = acButton,
addBracket = addBracket,
) )
} }
} }
@ -152,7 +161,9 @@ private fun PortraitKeyboard(
toggleAngleMode: () -> Unit, toggleAngleMode: () -> Unit,
deleteSymbol: () -> Unit, deleteSymbol: () -> Unit,
clearSymbols: () -> Unit, clearSymbols: () -> Unit,
evaluate: () -> Unit evaluate: () -> Unit,
acButton: Boolean,
addBracket: () -> Unit,
) { ) {
val fractionalIcon = remember { if (fractional == Token.Digit.dot) UnittoIcons.Dot else UnittoIcons.Comma } val fractionalIcon = remember { if (fractional == Token.Digit.dot) UnittoIcons.Dot else UnittoIcons.Comma }
var showAdditional: Boolean by remember { mutableStateOf(false) } var showAdditional: Boolean by remember { mutableStateOf(false) }
@ -239,8 +250,13 @@ private fun PortraitKeyboard(
Spacer(modifier = Modifier.height(spacerHeight)) Spacer(modifier = Modifier.height(spacerHeight))
Row(weightModifier) { Row(weightModifier) {
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.LeftBracket, allowVibration) { addSymbol(Token.Operator.leftBracket) }
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.RightBracket, allowVibration) { addSymbol(Token.Operator.rightBracket) } KeyboardButtonFilled(mainButtonModifier, UnittoIcons.RightBracket, allowVibration) { addSymbol(Token.Operator.rightBracket) }
}
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Percent, allowVibration) { addSymbol(Token.Operator.percent) } KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Percent, allowVibration) { addSymbol(Token.Operator.percent) }
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Divide, allowVibration) { addSymbol(Token.Operator.divide) } KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Divide, allowVibration) { addSymbol(Token.Operator.divide) }
} }
@ -361,7 +377,9 @@ private fun LandscapeKeyboard(
toggleAngleMode: () -> Unit, toggleAngleMode: () -> Unit,
deleteSymbol: () -> Unit, deleteSymbol: () -> Unit,
clearSymbols: () -> Unit, clearSymbols: () -> Unit,
evaluate: () -> Unit evaluate: () -> Unit,
acButton: Boolean,
addBracket: () -> Unit,
) { ) {
val fractionalIcon = remember { if (fractional == Token.Digit.dot) UnittoIcons.Dot else UnittoIcons.Comma } val fractionalIcon = remember { if (fractional == Token.Digit.dot) UnittoIcons.Dot else UnittoIcons.Comma }
var invMode: Boolean by remember { mutableStateOf(false) } var invMode: Boolean by remember { mutableStateOf(false) }
@ -426,13 +444,21 @@ private fun LandscapeKeyboard(
} }
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
if (acButton) {
KeyboardButtonTertiary(buttonModifier, UnittoIcons.Clear, allowVibration) { clearSymbols() }
} else {
KeyboardButtonFilled(buttonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.Operator.leftBracket) } KeyboardButtonFilled(buttonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.Operator.leftBracket) }
}
KeyboardButtonFilled(buttonModifier, UnittoIcons.Multiply, allowVibration) { addSymbol(Token.Operator.multiply) } KeyboardButtonFilled(buttonModifier, UnittoIcons.Multiply, allowVibration) { addSymbol(Token.Operator.multiply) }
KeyboardButtonFilled(buttonModifier, UnittoIcons.Minus, allowVibration) { addSymbol(Token.Operator.minus) } KeyboardButtonFilled(buttonModifier, UnittoIcons.Minus, allowVibration) { addSymbol(Token.Operator.minus) }
KeyboardButtonFilled(buttonModifier, UnittoIcons.Plus, allowVibration) { addSymbol(Token.Operator.plus) } KeyboardButtonFilled(buttonModifier, UnittoIcons.Plus, allowVibration) { addSymbol(Token.Operator.plus) }
} }
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {
if (acButton) {
KeyboardButtonTertiary(buttonModifier, UnittoIcons.Brackets, allowVibration) { addBracket() }
} else {
KeyboardButtonFilled(buttonModifier, UnittoIcons.RightBracket, allowVibration) { addSymbol(Token.Operator.rightBracket) } KeyboardButtonFilled(buttonModifier, UnittoIcons.RightBracket, allowVibration) { addSymbol(Token.Operator.rightBracket) }
}
KeyboardButtonFilled(buttonModifier, UnittoIcons.Divide, allowVibration) { addSymbol(Token.Operator.divide) } KeyboardButtonFilled(buttonModifier, UnittoIcons.Divide, allowVibration) { addSymbol(Token.Operator.divide) }
KeyboardButtonFilled(buttonModifier, UnittoIcons.Percent, allowVibration) { addSymbol(Token.Operator.percent) } KeyboardButtonFilled(buttonModifier, UnittoIcons.Percent, allowVibration) { addSymbol(Token.Operator.percent) }
KeyboardButtonFilled(buttonModifier, UnittoIcons.Equal, allowVibration) { evaluate() } KeyboardButtonFilled(buttonModifier, UnittoIcons.Equal, allowVibration) { evaluate() }
@ -518,5 +544,7 @@ private fun PreviewCalculatorKeyboard() {
evaluate = {}, evaluate = {},
allowVibration = false, allowVibration = false,
middleZero = false, middleZero = false,
acButton = true,
addBracket = {}
) )
} }

View File

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

View File

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

View File

@ -25,6 +25,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.core.base.Token import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols 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.addTokens
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
import com.sadellie.unitto.data.common.combine import com.sadellie.unitto.data.common.combine
@ -105,7 +106,8 @@ internal class ConverterViewModel @Inject constructor(
scale = prefs.precision, scale = prefs.precision,
outputFormat = prefs.outputFormat, outputFormat = prefs.outputFormat,
formatTime = prefs.unitConverterFormatTime, formatTime = prefs.unitConverterFormatTime,
currencyRateUpdateState = currenciesState currencyRateUpdateState = currenciesState,
acButton = prefs.acButton,
) )
} }
(unitFrom is NumberBaseUnit) and (unitTo is NumberBaseUnit) -> { (unitFrom is NumberBaseUnit) and (unitTo is NumberBaseUnit) -> {
@ -256,6 +258,12 @@ internal class ConverterViewModel @Inject constructor(
newValue newValue
} }
fun addBracket() = _input.update {
val newValue = it.addBracket()
savedStateHandle[converterInputKey] = newValue.text
newValue
}
fun deleteTokens() = _input.update { fun deleteTokens() = _input.update {
val newValue = it.deleteTokens() val newValue = it.deleteTokens()
savedStateHandle[converterInputKey] = newValue.text 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.ColumnWithConstraints
import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled
import com.sadellie.unitto.core.ui.common.KeyboardButtonLight 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
import com.sadellie.unitto.core.ui.common.key.unittoicons.Backspace 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.Comma
import com.sadellie.unitto.core.ui.common.key.unittoicons.Divide 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.Dot
@ -70,6 +73,8 @@ internal fun DefaultKeyboard(
allowVibration: Boolean, allowVibration: Boolean,
fractional: String, fractional: String,
middleZero: Boolean, middleZero: Boolean,
acButton: Boolean,
addBracket: () -> Unit,
) { ) {
ColumnWithConstraints(modifier) { ColumnWithConstraints(modifier) {
val fractionalIcon = remember { if (fractional == Token.Digit.dot) UnittoIcons.Dot else UnittoIcons.Comma } val fractionalIcon = remember { if (fractional == Token.Digit.dot) UnittoIcons.Dot else UnittoIcons.Comma }
@ -85,8 +90,13 @@ internal fun DefaultKeyboard(
// Column modifier // Column modifier
val cModifier = Modifier.weight(1f) val cModifier = Modifier.weight(1f)
Row(cModifier) { Row(cModifier) {
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.LeftBracket, allowVibration) { addDigit(Token.Operator.leftBracket) }
KeyboardButtonFilled(bModifier, UnittoIcons.RightBracket, allowVibration) { addDigit(Token.Operator.rightBracket) } KeyboardButtonFilled(bModifier, UnittoIcons.RightBracket, allowVibration) { addDigit(Token.Operator.rightBracket) }
}
KeyboardButtonFilled(bModifier, UnittoIcons.Power, allowVibration) { addDigit(Token.Operator.power) } KeyboardButtonFilled(bModifier, UnittoIcons.Power, allowVibration) { addDigit(Token.Operator.power) }
KeyboardButtonFilled(bModifier, UnittoIcons.Root, allowVibration) { addDigit(Token.Operator.sqrt) } KeyboardButtonFilled(bModifier, UnittoIcons.Root, allowVibration) { addDigit(Token.Operator.sqrt) }
} }
@ -188,7 +198,9 @@ private fun PreviewConverterKeyboard() {
deleteDigit = {}, deleteDigit = {},
allowVibration = false, allowVibration = false,
fractional = FormatterSymbols.Spaces.fractional, fractional = FormatterSymbols.Spaces.fractional,
middleZero = false middleZero = false,
acButton = true,
addBracket = {}
) )
} }

View File

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

View File

@ -147,6 +147,7 @@ private fun PreviewConverterSettingsScreen() {
enableToolsExperiment = false, enableToolsExperiment = false,
latestLeftSideUnit = "kilometer", latestLeftSideUnit = "kilometer",
latestRightSideUnit = "mile", latestRightSideUnit = "mile",
acButton = true,
), ),
navigateUpAction = {}, navigateUpAction = {},
navigateToUnitsGroup = {}, 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.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.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.ColorSelector
import com.sadellie.unitto.feature.settings.components.MonetModeSelector import com.sadellie.unitto.feature.settings.components.MonetModeSelector
import io.github.sadellie.themmo.MonetMode import io.github.sadellie.themmo.MonetMode
@ -113,6 +115,8 @@ internal fun DisplayRoute(
}, },
systemFont = prefs.systemFont, systemFont = prefs.systemFont,
updateSystemFont = viewModel::updateSystemFont, updateSystemFont = viewModel::updateSystemFont,
acButton = prefs.acButton,
updateAcButton = viewModel::updateAcButton,
middleZero = prefs.middleZero, middleZero = prefs.middleZero,
updateMiddleZero = viewModel::updateMiddleZero, updateMiddleZero = viewModel::updateMiddleZero,
navigateToLanguages = navigateToLanguages navigateToLanguages = navigateToLanguages
@ -136,6 +140,8 @@ private fun DisplayScreen(
onMonetModeChange: (MonetMode) -> Unit, onMonetModeChange: (MonetMode) -> Unit,
systemFont: Boolean, systemFont: Boolean,
updateSystemFont: (Boolean) -> Unit, updateSystemFont: (Boolean) -> Unit,
acButton: Boolean,
updateAcButton: (Boolean) -> Unit,
middleZero: Boolean, middleZero: Boolean,
updateMiddleZero: (Boolean) -> Unit, updateMiddleZero: (Boolean) -> Unit,
navigateToLanguages: () -> Unit, navigateToLanguages: () -> Unit,
@ -266,6 +272,15 @@ private fun DisplayScreen(
onSwitchChange = updateSystemFont 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( UnittoListItem(
icon = Icons.Default.ExposureZero, icon = Icons.Default.ExposureZero,
iconDescription = stringResource(R.string.settings_middle_zero), iconDescription = stringResource(R.string.settings_middle_zero),
@ -304,6 +319,8 @@ private fun Preview() {
onMonetModeChange = themmoController::setMonetMode, onMonetModeChange = themmoController::setMonetMode,
systemFont = false, systemFont = false,
updateSystemFont = {}, updateSystemFont = {},
acButton = false,
updateAcButton = {},
middleZero = false, middleZero = false,
updateMiddleZero = {}, updateMiddleZero = {},
navigateToLanguages = {} navigateToLanguages = {}

View File

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