Keyboard in AddSubtract

This commit is contained in:
Sad Ellie 2023-09-16 18:46:48 +03:00
parent 532931914e
commit 7e6036f4ce
14 changed files with 584 additions and 207 deletions

View File

@ -21,8 +21,11 @@ package com.sadellie.unitto.core.ui
import android.content.ActivityNotFoundException import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.content.res.Configuration
import android.net.Uri import android.net.Uri
import android.widget.Toast import android.widget.Toast
import androidx.compose.runtime.Composable
import androidx.compose.ui.platform.LocalConfiguration
import com.sadellie.unitto.core.base.R import com.sadellie.unitto.core.base.R
/** /**
@ -35,3 +38,6 @@ fun openLink(mContext: Context, url: String) {
Toast.makeText(mContext, R.string.error_label, Toast.LENGTH_SHORT).show() Toast.makeText(mContext, R.string.error_label, Toast.LENGTH_SHORT).show()
} }
} }
@Composable
fun isPortrait() = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT

View File

@ -18,7 +18,6 @@
package com.sadellie.unitto.core.ui.common package com.sadellie.unitto.core.ui.common
import android.content.res.Configuration
import android.view.HapticFeedbackConstants import android.view.HapticFeedbackConstants
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight import androidx.compose.foundation.layout.fillMaxHeight
@ -29,20 +28,20 @@ import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalView import androidx.compose.ui.platform.LocalView
import com.sadellie.unitto.core.ui.isPortrait
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@Composable @Composable
fun BasicKeyboardButton( fun BasicKeyboardButton(
modifier: Modifier, modifier: Modifier,
contentHeight: Float,
onClick: () -> Unit, onClick: () -> Unit,
onLongClick: (() -> Unit)?, onLongClick: (() -> Unit)?,
containerColor: Color, containerColor: Color,
icon: ImageVector, icon: ImageVector,
iconColor: Color, iconColor: Color,
allowVibration: Boolean, allowVibration: Boolean,
contentHeight: Float,
) { ) {
val view = LocalView.current val view = LocalView.current
val coroutineScope = rememberCoroutineScope() val coroutineScope = rememberCoroutineScope()
@ -53,7 +52,6 @@ fun BasicKeyboardButton(
} }
} }
} }
UnittoButton( UnittoButton(
modifier = modifier, modifier = modifier,
onClick = { onClick(); vibrate() }, onClick = { onClick(); vibrate() },
@ -75,18 +73,19 @@ fun KeyboardButtonLight(
modifier: Modifier, modifier: Modifier,
icon: ImageVector, icon: ImageVector,
allowVibration: Boolean, allowVibration: Boolean,
contentHeight: Float = if (isPortrait()) 0.51f else 0.7f,
onLongClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
BasicKeyboardButton( BasicKeyboardButton(
modifier = modifier, modifier = modifier,
contentHeight = contentHeight,
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
containerColor = MaterialTheme.colorScheme.inverseOnSurface, containerColor = MaterialTheme.colorScheme.inverseOnSurface,
icon = icon, icon = icon,
iconColor = MaterialTheme.colorScheme.onSurfaceVariant, iconColor = MaterialTheme.colorScheme.onSurfaceVariant,
allowVibration = allowVibration, allowVibration = allowVibration,
contentHeight = if (isPortrait()) 0.51f else 0.7f
) )
} }
@ -95,18 +94,19 @@ fun KeyboardButtonFilled(
modifier: Modifier, modifier: Modifier,
icon: ImageVector, icon: ImageVector,
allowVibration: Boolean, allowVibration: Boolean,
contentHeight: Float = if (isPortrait()) 0.51f else 0.7f,
onLongClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
BasicKeyboardButton( BasicKeyboardButton(
modifier = modifier, modifier = modifier,
contentHeight = contentHeight,
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
containerColor = MaterialTheme.colorScheme.primaryContainer, containerColor = MaterialTheme.colorScheme.primaryContainer,
icon = icon, icon = icon,
iconColor = MaterialTheme.colorScheme.onSecondaryContainer, iconColor = MaterialTheme.colorScheme.onSecondaryContainer,
allowVibration = allowVibration, allowVibration = allowVibration,
contentHeight = if (isPortrait()) 0.51f else 0.7f
) )
} }
@ -115,21 +115,18 @@ fun KeyboardButtonAdditional(
modifier: Modifier, modifier: Modifier,
icon: ImageVector, icon: ImageVector,
allowVibration: Boolean, allowVibration: Boolean,
contentHeight: Float = 0.8f,
onLongClick: (() -> Unit)? = null, onLongClick: (() -> Unit)? = null,
onClick: () -> Unit, onClick: () -> Unit,
) { ) {
BasicKeyboardButton( BasicKeyboardButton(
modifier = modifier, modifier = modifier,
contentHeight = contentHeight,
onClick = onClick, onClick = onClick,
onLongClick = onLongClick, onLongClick = onLongClick,
containerColor = Color.Transparent, containerColor = Color.Transparent,
icon = icon, icon = icon,
iconColor = MaterialTheme.colorScheme.onSurfaceVariant, iconColor = MaterialTheme.colorScheme.onSurfaceVariant,
allowVibration = allowVibration, allowVibration = allowVibration,
contentHeight = if (isPortrait()) 0.8f else 0.8f
) )
} }
@Composable
private fun isPortrait() =
LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT

View File

@ -18,6 +18,11 @@
package com.sadellie.unitto.core.ui.common package com.sadellie.unitto.core.ui.common
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.RowScope import androidx.compose.foundation.layout.RowScope
import androidx.compose.material3.CenterAlignedTopAppBar import androidx.compose.material3.CenterAlignedTopAppBar
@ -51,11 +56,17 @@ fun UnittoScreenWithTopBar(
floatingActionButton: @Composable () -> Unit = {}, floatingActionButton: @Composable () -> Unit = {},
floatingActionButtonPosition: FabPosition = FabPosition.End, floatingActionButtonPosition: FabPosition = FabPosition.End,
scrollBehavior: TopAppBarScrollBehavior? = null, scrollBehavior: TopAppBarScrollBehavior? = null,
showTopBar: Boolean = true,
content: @Composable (PaddingValues) -> Unit content: @Composable (PaddingValues) -> Unit
) { ) {
Scaffold( Scaffold(
modifier = modifier, modifier = modifier,
topBar = { topBar = {
AnimatedVisibility(
visible = showTopBar,
enter = slideInVertically() + fadeIn(),
exit = slideOutVertically() + fadeOut()
) {
CenterAlignedTopAppBar( CenterAlignedTopAppBar(
title = title, title = title,
navigationIcon = navigationIcon, navigationIcon = navigationIcon,
@ -63,6 +74,7 @@ fun UnittoScreenWithTopBar(
colors = colors, colors = colors,
scrollBehavior = scrollBehavior, scrollBehavior = scrollBehavior,
) )
}
}, },
floatingActionButton = floatingActionButton, floatingActionButton = floatingActionButton,
floatingActionButtonPosition = floatingActionButtonPosition, floatingActionButtonPosition = floatingActionButtonPosition,

View File

@ -0,0 +1,56 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.core.ui.common.key.unittoicons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
val @Suppress("UnusedReceiverParameter") UnittoIcons.Check: ImageVector
get() {
if (_check != null) {
return _check!!
}
_check = Builder(name = "Check", defaultWidth = 150.0.dp, defaultHeight = 150.0.dp,
viewportWidth = 150.0f, viewportHeight = 150.0f).apply {
path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
pathFillType = NonZero) {
moveTo(59.625f, 112.156f)
lineTo(24.0f, 76.531f)
lineTo(32.906f, 67.625f)
lineTo(59.625f, 94.344f)
lineTo(116.969f, 37.0f)
lineTo(125.875f, 45.906f)
lineTo(59.625f, 112.156f)
close()
}
}
.build()
return _check!!
}
private var _check: ImageVector? = null

View File

@ -0,0 +1,65 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.core.ui.common.key.unittoicons
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.PathFillType.Companion.NonZero
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.graphics.StrokeCap.Companion.Butt
import androidx.compose.ui.graphics.StrokeJoin.Companion.Miter
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.graphics.vector.ImageVector.Builder
import androidx.compose.ui.graphics.vector.path
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
val @Suppress("UnusedReceiverParameter") UnittoIcons.Tab: ImageVector
get() {
if (_tab != null) {
return _tab!!
}
_tab = Builder(name = "Tab", defaultWidth = 150.0.dp, defaultHeight = 150.0.dp,
viewportWidth = 150.0f, viewportHeight = 150.0f).apply {
path(fill = SolidColor(Color(0xFF000000)), stroke = null, strokeLineWidth = 0.0f,
strokeLineCap = Butt, strokeLineJoin = Miter, strokeLineMiter = 4.0f,
pathFillType = NonZero) {
moveTo(125.0f, 112.5f)
verticalLineTo(37.5f)
horizontalLineTo(137.5f)
verticalLineTo(112.5f)
horizontalLineTo(125.0f)
close()
moveTo(75.0f, 112.5f)
lineTo(66.094f, 103.75f)
lineTo(88.594f, 81.25f)
horizontalLineTo(12.5f)
verticalLineTo(68.75f)
horizontalLineTo(88.594f)
lineTo(66.25f, 46.25f)
lineTo(75.0f, 37.5f)
lineTo(112.5f, 75.0f)
lineTo(75.0f, 112.5f)
close()
}
}
.build()
return _tab!!
}
private var _tab: ImageVector? = null

View File

@ -126,6 +126,7 @@ data class UnitGroupsPreferences(
data class AddSubtractPreferences( data class AddSubtractPreferences(
val separator: Int = Separator.SPACE, val separator: Int = Separator.SPACE,
val enableVibrations: Boolean = true,
) )
data class AboutPreferences( data class AboutPreferences(
@ -232,7 +233,8 @@ class UserPreferencesRepository @Inject constructor(private val dataStore: DataS
val addSubtractPrefs: Flow<AddSubtractPreferences> = data val addSubtractPrefs: Flow<AddSubtractPreferences> = data
.map { preferences -> .map { preferences ->
AddSubtractPreferences( AddSubtractPreferences(
separator = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE separator = preferences[PrefsKeys.SEPARATOR] ?: Separator.SPACE,
enableVibrations = preferences[PrefsKeys.ENABLE_VIBRATIONS] ?: true,
) )
} }

View File

@ -386,7 +386,7 @@ private fun PortraitKeyboard(
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key0, allowVibration) { addSymbol(Token.Digit._0) } KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key0, allowVibration) { addSymbol(Token.Digit._0) }
KeyboardButtonLight(mainButtonModifier, fractionalIcon, allowVibration) { addSymbol(Token.Digit.dot) } KeyboardButtonLight(mainButtonModifier, fractionalIcon, allowVibration) { addSymbol(Token.Digit.dot) }
} }
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Backspace, allowVibration, clearSymbols) { deleteSymbol() } KeyboardButtonLight(mainButtonModifier, UnittoIcons.Backspace, allowVibration, onLongClick = clearSymbols) { deleteSymbol() }
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Equal, allowVibration) { evaluate() } KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Equal, allowVibration) { evaluate() }
} }
@ -538,7 +538,7 @@ private fun LandscapeKeyboard(
KeyboardButtonLight(buttonModifier, UnittoIcons.Key9, allowVibration) { addSymbol(Token.Digit._9) } KeyboardButtonLight(buttonModifier, UnittoIcons.Key9, allowVibration) { addSymbol(Token.Digit._9) }
KeyboardButtonLight(buttonModifier, UnittoIcons.Key6, allowVibration) { addSymbol(Token.Digit._6) } KeyboardButtonLight(buttonModifier, UnittoIcons.Key6, allowVibration) { addSymbol(Token.Digit._6) }
KeyboardButtonLight(buttonModifier, UnittoIcons.Key3, allowVibration) { addSymbol(Token.Digit._3) } KeyboardButtonLight(buttonModifier, UnittoIcons.Key3, allowVibration) { addSymbol(Token.Digit._3) }
KeyboardButtonLight(buttonModifier, UnittoIcons.Backspace, allowVibration, clearSymbols) { deleteSymbol() } KeyboardButtonLight(buttonModifier, UnittoIcons.Backspace, allowVibration, onLongClick = clearSymbols) { deleteSymbol() }
} }
Column(Modifier.weight(1f)) { Column(Modifier.weight(1f)) {

View File

@ -109,7 +109,7 @@ internal fun DefaultKeyboard(
KeyboardButtonLight(bModifier, UnittoIcons.Key0, allowVibration) { addDigit(Token.Digit._0) } KeyboardButtonLight(bModifier, UnittoIcons.Key0, allowVibration) { addDigit(Token.Digit._0) }
KeyboardButtonLight(bModifier, fractionalIcon, allowVibration) { addDigit(Token.Digit.dot) } KeyboardButtonLight(bModifier, fractionalIcon, allowVibration) { addDigit(Token.Digit.dot) }
} }
KeyboardButtonLight(bModifier, UnittoIcons.Backspace, allowVibration, clearInput) { deleteDigit() } KeyboardButtonLight(bModifier, UnittoIcons.Backspace, allowVibration, onLongClick = clearInput) { deleteDigit() }
KeyboardButtonFilled(bModifier, UnittoIcons.Plus, allowVibration) { addDigit(Token.Operator.plus) } KeyboardButtonFilled(bModifier, UnittoIcons.Plus, allowVibration) { addDigit(Token.Operator.plus) }
} }
} }
@ -159,7 +159,7 @@ internal fun NumberBaseKeyboard(
Row(cModifier, horizontalArrangement) { Row(cModifier, horizontalArrangement) {
KeyboardButtonLight(bModifier, UnittoIcons.Key0, allowVibration) { addDigit(Token.Digit._0) } KeyboardButtonLight(bModifier, UnittoIcons.Key0, allowVibration) { addDigit(Token.Digit._0) }
KeyboardButtonLight( KeyboardButtonLight(
Modifier.fillMaxSize().weight(2f).padding(it.maxWidth * 0.015f, it.maxHeight * 0.008f), UnittoIcons.Backspace, allowVibration, clearInput) { deleteDigit() } Modifier.fillMaxSize().weight(2f).padding(it.maxWidth * 0.015f, it.maxHeight * 0.008f), UnittoIcons.Backspace, allowVibration, onLongClick = clearInput) { deleteDigit() }
} }
} }
} }

View File

@ -26,8 +26,11 @@ import androidx.compose.material3.Tab
import androidx.compose.material3.TabRow import androidx.compose.material3.TabRow
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.platform.LocalFocusManager
@ -60,6 +63,7 @@ internal fun DateCalculatorScreen(
val addSubtractLabel = "${stringResource(R.string.add)}/${stringResource(R.string.subtract)}" val addSubtractLabel = "${stringResource(R.string.add)}/${stringResource(R.string.subtract)}"
val differenceLabel = stringResource(R.string.difference) val differenceLabel = stringResource(R.string.difference)
val focusManager = LocalFocusManager.current val focusManager = LocalFocusManager.current
var topBarShown by remember { mutableStateOf(true) }
val allTabs = remember { mutableListOf(addSubtractLabel, differenceLabel) } val allTabs = remember { mutableListOf(addSubtractLabel, differenceLabel) }
val pagerState = rememberPagerState { allTabs.size } val pagerState = rememberPagerState { allTabs.size }
@ -69,9 +73,8 @@ internal fun DateCalculatorScreen(
modifier = Modifier, modifier = Modifier,
title = { Text(stringResource(R.string.date_calculator)) }, title = { Text(stringResource(R.string.date_calculator)) },
navigationIcon = { MenuButton(navigateToMenu) }, navigationIcon = { MenuButton(navigateToMenu) },
actions = { actions = { SettingsButton(navigateToSettings) },
SettingsButton(navigateToSettings) showTopBar = topBarShown,
},
) { paddingValues -> ) { paddingValues ->
Column( Column(
modifier = Modifier.padding(paddingValues), modifier = Modifier.padding(paddingValues),
@ -97,9 +100,14 @@ internal fun DateCalculatorScreen(
verticalAlignment = Alignment.Top verticalAlignment = Alignment.Top
) { page -> ) { page ->
when (page) { when (page) {
0 -> AddSubtractPage() 0 -> AddSubtractPage(
1 -> DateDifferencePage().also { toggleTopBar = { topBarShown = it }
)
1 -> {
focusManager.clearFocus(true) focusManager.clearFocus(true)
topBarShown = true
DateDifferencePage()
} }
} }
} }

View File

@ -24,11 +24,19 @@ import android.content.Context
import android.content.Intent import android.content.Intent
import android.provider.CalendarContract import android.provider.CalendarContract
import android.widget.Toast import android.widget.Toast
import androidx.activity.compose.BackHandler
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ExperimentalLayoutApi import androidx.compose.foundation.layout.ExperimentalLayoutApi
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth 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
@ -37,25 +45,34 @@ import androidx.compose.material.icons.filled.Event
import androidx.compose.material.icons.outlined.Add import androidx.compose.material.icons.outlined.Add
import androidx.compose.material.icons.outlined.Remove import androidx.compose.material.icons.outlined.Remove
import androidx.compose.material3.FloatingActionButton import androidx.compose.material3.FloatingActionButton
import androidx.compose.material3.HorizontalDivider
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
import androidx.compose.material3.Scaffold import androidx.compose.material3.Scaffold
import androidx.compose.material3.SegmentedButton import androidx.compose.material3.SegmentedButton
import androidx.compose.material3.SegmentedButtonDefaults import androidx.compose.material3.SegmentedButtonDefaults
import androidx.compose.material3.SingleChoiceSegmentedButtonRow import androidx.compose.material3.SingleChoiceSegmentedButtonRow
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.onFocusEvent
import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.res.stringResource import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.R import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.ui.common.textfield.addTokens
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
import com.sadellie.unitto.core.ui.isPortrait
import com.sadellie.unitto.feature.datecalculator.components.AddSubtractKeyboard
import com.sadellie.unitto.feature.datecalculator.components.DateTimeDialogs import com.sadellie.unitto.feature.datecalculator.components.DateTimeDialogs
import com.sadellie.unitto.feature.datecalculator.components.DateTimeSelectorBlock import com.sadellie.unitto.feature.datecalculator.components.DateTimeSelectorBlock
import com.sadellie.unitto.feature.datecalculator.components.DialogState import com.sadellie.unitto.feature.datecalculator.components.DialogState
@ -65,18 +82,20 @@ import java.time.ZonedDateTime
@Composable @Composable
internal fun AddSubtractPage( internal fun AddSubtractPage(
viewModel: AddSubtractViewModel = hiltViewModel(), viewModel: AddSubtractViewModel = hiltViewModel(),
toggleTopBar: (Boolean) -> Unit,
) { ) {
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
AddSubtractView( AddSubtractView(
uiState = uiState, uiState = uiState,
toggleTopBar = toggleTopBar,
updateStart = viewModel::updateStart, updateStart = viewModel::updateStart,
updateYears = viewModel::updateYears, updateYears = viewModel::updateYears,
updateMonths = viewModel::updateMonths, updateMonths = viewModel::updateMonths,
updateDays = viewModel::updateDays, updateDays = viewModel::updateDays,
updateHours = viewModel::updateHours, updateHours = viewModel::updateHours,
updateMinutes = viewModel::updateMinutes, updateMinutes = viewModel::updateMinutes,
updateAddition = viewModel::updateAddition updateAddition = viewModel::updateAddition,
) )
} }
@ -85,20 +104,44 @@ internal fun AddSubtractPage(
@Composable @Composable
private fun AddSubtractView( private fun AddSubtractView(
uiState: AddSubtractState, uiState: AddSubtractState,
toggleTopBar: (Boolean) -> Unit,
updateStart: (ZonedDateTime) -> Unit, updateStart: (ZonedDateTime) -> Unit,
updateYears: (String) -> Unit, updateYears: (TextFieldValue) -> Unit,
updateMonths: (String) -> Unit, updateMonths: (TextFieldValue) -> Unit,
updateDays: (String) -> Unit, updateDays: (TextFieldValue) -> Unit,
updateHours: (String) -> Unit, updateHours: (TextFieldValue) -> Unit,
updateMinutes: (String) -> Unit, updateMinutes: (TextFieldValue) -> Unit,
updateAddition: (Boolean) -> Unit, updateAddition: (Boolean) -> Unit,
) { ) {
var dialogState by remember { mutableStateOf(DialogState.NONE) } var dialogState by remember { mutableStateOf(DialogState.NONE) }
val mContext = LocalContext.current val mContext = LocalContext.current
var addSymbol: ((TextFieldValue) -> Unit)? by remember { mutableStateOf(null) }
var focusedTextFieldValue: TextFieldValue? by remember { mutableStateOf(null) }
val showKeyboard = (addSymbol != null) and (focusedTextFieldValue != null)
val landscape = !isPortrait()
val focusManager = LocalFocusManager.current
LaunchedEffect(showKeyboard, landscape) {
toggleTopBar(showKeyboard and landscape)
}
BackHandler(showKeyboard) {
focusManager.clearFocus()
addSymbol = null
focusedTextFieldValue = null
}
Column(Modifier.fillMaxSize()) {
Scaffold( Scaffold(
modifier = Modifier
.fillMaxHeight()
.weight(1f),
floatingActionButton = { floatingActionButton = {
FloatingActionButton(onClick = { mContext.addEvent(uiState.start, uiState.result) }) { FloatingActionButton(
onClick = {
mContext.addEvent(uiState.start, uiState.result)
}
) {
Icon( Icon(
imageVector = Icons.Default.Event, imageVector = Icons.Default.Event,
contentDescription = null, contentDescription = null,
@ -107,9 +150,9 @@ private fun AddSubtractView(
} }
) { ) {
LazyColumn( LazyColumn(
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp), modifier = Modifier.padding(horizontal = 16.dp),
verticalArrangement = Arrangement.spacedBy(16.dp), verticalArrangement = Arrangement.spacedBy(16.dp),
contentPadding = PaddingValues(bottom = 88.dp) contentPadding = PaddingValues(top = 16.dp)
) { ) {
item("dates") { item("dates") {
FlowRow( FlowRow(
@ -167,40 +210,103 @@ private fun AddSubtractView(
item("textFields") { item("textFields") {
Column { Column {
TimeUnitTextField( TimeUnitTextField(
modifier = Modifier.onFocusEvent {
if (it.hasFocus) {
addSymbol = updateYears
focusedTextFieldValue = uiState.years
}
},
value = uiState.years, value = uiState.years,
onValueChange = updateYears, onValueChange = updateYears,
label = stringResource(R.string.date_difference_years), label = stringResource(R.string.date_difference_years),
formatterSymbols = uiState.formatterSymbols formatterSymbols = uiState.formatterSymbols
) )
TimeUnitTextField( TimeUnitTextField(
modifier = Modifier.onFocusEvent {
if (it.hasFocus) {
addSymbol = updateMonths
focusedTextFieldValue = uiState.months
}
},
value = uiState.months, value = uiState.months,
onValueChange = updateMonths, onValueChange = updateMonths,
label = stringResource(R.string.date_difference_months), label = stringResource(R.string.date_difference_months),
formatterSymbols = uiState.formatterSymbols formatterSymbols = uiState.formatterSymbols
) )
TimeUnitTextField( TimeUnitTextField(
modifier = Modifier.onFocusEvent {
if (it.hasFocus) {
addSymbol = updateDays
focusedTextFieldValue = uiState.days
}
},
value = uiState.days, value = uiState.days,
onValueChange = updateDays, onValueChange = updateDays,
label = stringResource(R.string.date_difference_days), label = stringResource(R.string.date_difference_days),
formatterSymbols = uiState.formatterSymbols formatterSymbols = uiState.formatterSymbols
) )
TimeUnitTextField( TimeUnitTextField(
modifier = Modifier.onFocusEvent {
if (it.hasFocus) {
addSymbol = updateHours
focusedTextFieldValue = uiState.hours
}
},
value = uiState.hours, value = uiState.hours,
onValueChange = updateHours, onValueChange = updateHours,
label = stringResource(R.string.date_difference_hours), label = stringResource(R.string.date_difference_hours),
formatterSymbols = uiState.formatterSymbols formatterSymbols = uiState.formatterSymbols
) )
TimeUnitTextField( TimeUnitTextField(
modifier = Modifier.onFocusEvent {
if (it.hasFocus) {
addSymbol = updateMinutes
focusedTextFieldValue = uiState.minutes
}
},
value = uiState.minutes, value = uiState.minutes,
onValueChange = updateMinutes, onValueChange = updateMinutes,
label = stringResource(R.string.date_difference_minutes), label = stringResource(R.string.date_difference_minutes),
imeAction = ImeAction.Done,
formatterSymbols = uiState.formatterSymbols formatterSymbols = uiState.formatterSymbols
) )
} }
} }
} }
} }
AnimatedVisibility(
visible = showKeyboard,
enter = slideInVertically { it / 2 } + fadeIn(),
exit = slideOutVertically { it / 2 } + fadeOut()
) {
HorizontalDivider()
AddSubtractKeyboard(
modifier = Modifier
.weight(1f)
.fillMaxWidth()
.fillMaxHeight(if (isPortrait()) 0.4f else 0.6f)
.padding(2.dp, 4.dp),
addSymbol = {
val newValue = focusedTextFieldValue?.addTokens(it)
if (newValue != null) {
addSymbol?.invoke(newValue)
}
},
deleteSymbol = {
val newValue = focusedTextFieldValue?.deleteTokens()
if (newValue != null) {
addSymbol?.invoke(newValue)
}
},
onConfirm = {
focusManager.clearFocus()
addSymbol = null
focusedTextFieldValue = null
},
allowVibration = uiState.allowVibration,
imeAction = if (addSymbol == updateMinutes) ImeAction.Done else ImeAction.Next
)
}
}
DateTimeDialogs( DateTimeDialogs(
dialogState = dialogState, dialogState = dialogState,
@ -234,14 +340,15 @@ private fun Context.addEvent(start: ZonedDateTime, end: ZonedDateTime) {
fun AddSubtractViewPreview() { fun AddSubtractViewPreview() {
AddSubtractView( AddSubtractView(
uiState = AddSubtractState( uiState = AddSubtractState(
years = "12" years = TextFieldValue("12")
), ),
toggleTopBar = {},
updateStart = {}, updateStart = {},
updateYears = {}, updateYears = {},
updateMonths = {}, updateMonths = {},
updateDays = {}, updateDays = {},
updateHours = {}, updateHours = {},
updateMinutes = {}, updateMinutes = {},
updateAddition = {} updateAddition = {},
) )
} }

View File

@ -18,17 +18,19 @@
package com.sadellie.unitto.feature.datecalculator.addsubtract package com.sadellie.unitto.feature.datecalculator.addsubtract
import androidx.compose.ui.text.input.TextFieldValue
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import java.time.ZonedDateTime import java.time.ZonedDateTime
internal data class AddSubtractState( internal data class AddSubtractState(
val start: ZonedDateTime = ZonedDateTime.now(), val start: ZonedDateTime = ZonedDateTime.now(),
val result: ZonedDateTime = ZonedDateTime.now(), val result: ZonedDateTime = ZonedDateTime.now(),
val years: String = "", val years: TextFieldValue = TextFieldValue(),
val months: String = "", val months: TextFieldValue = TextFieldValue(),
val days: String = "", val days: TextFieldValue = TextFieldValue(),
val hours: String = "", val hours: TextFieldValue = TextFieldValue(),
val minutes: String = "", val minutes: TextFieldValue = TextFieldValue(),
val addition: Boolean = true, val addition: Boolean = true,
val formatterSymbols: FormatterSymbols = FormatterSymbols.Spaces, val formatterSymbols: FormatterSymbols = FormatterSymbols.Spaces,
val allowVibration: Boolean = false,
) )

View File

@ -18,6 +18,8 @@
package com.sadellie.unitto.feature.datecalculator.addsubtract package com.sadellie.unitto.feature.datecalculator.addsubtract
import androidx.compose.ui.text.TextRange
import androidx.compose.ui.text.input.TextFieldValue
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
@ -44,7 +46,8 @@ internal class AddSubtractViewModel @Inject constructor(
val uiState: StateFlow<AddSubtractState> = _uiState val uiState: StateFlow<AddSubtractState> = _uiState
.combine(userPreferencesRepository.addSubtractPrefs) { uiState, userPrefs -> .combine(userPreferencesRepository.addSubtractPrefs) { uiState, userPrefs ->
return@combine uiState.copy( return@combine uiState.copy(
formatterSymbols = AllFormatterSymbols.getById(userPrefs.separator) formatterSymbols = AllFormatterSymbols.getById(userPrefs.separator),
allowVibration = userPrefs.enableVibrations,
) )
} }
.onEach { updateResult() } .onEach { updateResult() }
@ -52,78 +55,57 @@ internal class AddSubtractViewModel @Inject constructor(
fun updateStart(newValue: ZonedDateTime) = _uiState.update { it.copy(start = newValue) } fun updateStart(newValue: ZonedDateTime) = _uiState.update { it.copy(start = newValue) }
fun updateYears(newValue: String) = _uiState.update { fun updateYears(value: TextFieldValue) = _uiState.update {
val years = when { it.copy(years = checkWithMax(value, 9_999L))
newValue.isEmpty() -> newValue
newValue.toLong() > 9_999L -> "9999"
else -> newValue
} }
it.copy(years = years) fun updateMonths(value: TextFieldValue) = _uiState.update {
it.copy(months = checkWithMax(value, 9_999L))
} }
fun updateMonths(newValue: String) = _uiState.update { fun updateDays(value: TextFieldValue) = _uiState.update {
val months = when { it.copy(days = checkWithMax(value, 99_999L))
newValue.isEmpty() -> newValue
newValue.toLong() > 9_999L -> "9999"
else -> newValue
} }
it.copy(months = months) fun updateHours(value: TextFieldValue) = _uiState.update {
it.copy(hours = checkWithMax(value, 9_999_999L))
} }
fun updateDays(newValue: String) = _uiState.update { fun updateMinutes(value: TextFieldValue) = _uiState.update {
val days = when { it.copy(minutes = checkWithMax(value, 99_999_999L))
newValue.isEmpty() -> newValue
newValue.toLong() > 99_999L -> "99999"
else -> newValue
}
it.copy(days = days)
}
fun updateHours(newValue: String) = _uiState.update {
val hours = when {
newValue.isEmpty() -> newValue
newValue.toLong() > 9_999_999L -> "9999999"
else -> newValue
}
it.copy(hours = hours)
}
fun updateMinutes(newValue: String) = _uiState.update {
val minutes = when {
newValue.isEmpty() -> newValue
newValue.toLong() > 99_999_999L -> "99999999"
else -> newValue
}
it.copy(minutes = minutes)
} }
// BCE is not handled properly because who gives a shit... // BCE is not handled properly because who gives a shit...
fun updateAddition(newValue: Boolean) = _uiState.update { it.copy(addition = newValue) } fun updateAddition(newValue: Boolean) = _uiState.update {
it.copy(addition = newValue)
}
private fun updateResult() = viewModelScope.launch(Dispatchers.Default) { private fun updateResult() = viewModelScope.launch(Dispatchers.Default) {
// Gets canceled, works with latest _uiState only // Gets canceled, works with latest _uiState only
_uiState.update { ui -> _uiState.update { ui ->
val newResult = if (ui.addition) { val newResult = if (ui.addition) {
ui.start ui.start
.plusYears(ui.years.ifEmpty { "0" }.toLong()) .plusYears(ui.years.text.ifEmpty { "0" }.toLong())
.plusMonths(ui.months.ifEmpty { "0" }.toLong()) .plusMonths(ui.months.text.ifEmpty { "0" }.toLong())
.plusDays(ui.days.ifEmpty { "0" }.toLong()) .plusDays(ui.days.text.ifEmpty { "0" }.toLong())
.plusHours(ui.hours.ifEmpty { "0" }.toLong()) .plusHours(ui.hours.text.ifEmpty { "0" }.toLong())
.plusMinutes(ui.minutes.ifEmpty { "0" }.toLong()) .plusMinutes(ui.minutes.text.ifEmpty { "0" }.toLong())
} else { } else {
ui.start ui.start
.minusYears(ui.years.ifEmpty { "0" }.toLong()) .minusYears(ui.years.text.ifEmpty { "0" }.toLong())
.minusMonths(ui.months.ifEmpty { "0" }.toLong()) .minusMonths(ui.months.text.ifEmpty { "0" }.toLong())
.minusDays(ui.days.ifEmpty { "0" }.toLong()) .minusDays(ui.days.text.ifEmpty { "0" }.toLong())
.minusHours(ui.hours.ifEmpty { "0" }.toLong()) .minusHours(ui.hours.text.ifEmpty { "0" }.toLong())
.minusMinutes(ui.minutes.ifEmpty { "0" }.toLong()) .minusMinutes(ui.minutes.text.ifEmpty { "0" }.toLong())
} }
ui.copy(result = newResult) ui.copy(result = newResult)
} }
} }
private fun checkWithMax(value: TextFieldValue, maxValue: Long): TextFieldValue {
if (value.text.isEmpty()) return value
if (value.text.toLong() <= maxValue) return value
val maxValueText = maxValue.toString()
return TextFieldValue(maxValueText, TextRange(maxValueText.length))
}
} }

View File

@ -0,0 +1,145 @@
/*
* 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.datecalculator.components
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.focus.FocusDirection
import androidx.compose.ui.focus.FocusManager
import androidx.compose.ui.platform.LocalFocusManager
import androidx.compose.ui.text.input.ImeAction
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled
import com.sadellie.unitto.core.ui.common.KeyboardButtonLight
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.Check
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key0
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key1
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key2
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key3
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key4
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key5
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key6
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key7
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key8
import com.sadellie.unitto.core.ui.common.key.unittoicons.Key9
import com.sadellie.unitto.core.ui.common.key.unittoicons.Tab
import com.sadellie.unitto.core.ui.isPortrait
@Composable
internal fun AddSubtractKeyboard(
modifier: Modifier,
addSymbol: (String) -> Unit,
deleteSymbol: () -> Unit,
onConfirm: () -> Unit,
allowVibration: Boolean,
imeAction: ImeAction,
focusManager: FocusManager = LocalFocusManager.current
) {
Row(modifier) {
val weightModifier = Modifier.weight(1f)
val mainButtonModifier = Modifier
.fillMaxSize()
.weight(1f)
.padding(4.dp)
val actionIconHeight = if (isPortrait()) 0.35f else 0.6f
fun keyboardAction() {
when (imeAction) {
ImeAction.Next -> focusManager.moveFocus(FocusDirection.Next)
else -> onConfirm()
}
}
Column(weightModifier) {
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key7, allowVibration) {
addSymbol(Token.Digit._7)
}
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key4, allowVibration) {
addSymbol(Token.Digit._4
)
}
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key1, allowVibration) {
addSymbol(Token.Digit._1)
}
Spacer(mainButtonModifier)
}
Column(weightModifier) {
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key8, allowVibration) {
addSymbol(Token.Digit._8)
}
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key5, allowVibration) {
addSymbol(Token.Digit._5)
}
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key2, allowVibration) {
addSymbol(Token.Digit._2)
}
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key0, allowVibration) {
addSymbol(Token.Digit._0)
}
}
Column(weightModifier) {
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key9, allowVibration) {
addSymbol(Token.Digit._9)
}
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key6, allowVibration) {
addSymbol(Token.Digit._6)
}
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key3, allowVibration) {
addSymbol(Token.Digit._3)
}
Spacer(mainButtonModifier)
}
Column(weightModifier) {
Crossfade(targetState = imeAction, modifier = mainButtonModifier) {
when (it) {
ImeAction.Next -> KeyboardButtonFilled(
Modifier.fillMaxSize(),
UnittoIcons.Tab,
allowVibration,
actionIconHeight
) { keyboardAction() }
else -> KeyboardButtonFilled(
Modifier.fillMaxSize(),
UnittoIcons.Check,
allowVibration,
actionIconHeight
) { keyboardAction() }
}
}
KeyboardButtonLight(
mainButtonModifier,
UnittoIcons.Backspace,
allowVibration,
actionIconHeight
) { deleteSymbol() }
}
}
}

View File

@ -22,7 +22,6 @@ import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.scaleIn import androidx.compose.animation.scaleIn
import androidx.compose.animation.scaleOut import androidx.compose.animation.scaleOut
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.text.KeyboardOptions
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.Clear import androidx.compose.material.icons.outlined.Clear
import androidx.compose.material3.Icon import androidx.compose.material3.Icon
@ -31,43 +30,39 @@ import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedTextField import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.text.input.ImeAction import androidx.compose.ui.platform.LocalTextInputService
import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue
import com.sadellie.unitto.core.ui.common.textfield.ExpressionTransformer import com.sadellie.unitto.core.ui.common.textfield.ExpressionTransformer
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
@Composable @Composable
internal fun TimeUnitTextField( internal fun TimeUnitTextField(
value: String, modifier: Modifier,
onValueChange: (String) -> Unit, value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit,
label: String, label: String,
imeAction: ImeAction = ImeAction.Next,
formatterSymbols: FormatterSymbols formatterSymbols: FormatterSymbols
) { ) = CompositionLocalProvider(LocalTextInputService provides null) {
OutlinedTextField( OutlinedTextField(
modifier = Modifier.fillMaxWidth(), modifier = modifier.fillMaxWidth(),
value = value, value = value,
onValueChange = { newValue -> onValueChange = { newValue ->
onValueChange(newValue.filter { it.isDigit() }) onValueChange(newValue.copy(newValue.text.filter { it.isDigit() }))
}, },
label = { Text(label, color = MaterialTheme.colorScheme.onSurfaceVariant) }, label = { Text(label, color = MaterialTheme.colorScheme.onSurfaceVariant) },
trailingIcon = { trailingIcon = {
AnimatedVisibility( AnimatedVisibility(
visible = value.isNotBlank(), visible = value.text.isNotBlank(),
enter = scaleIn(), enter = scaleIn(),
exit = scaleOut() exit = scaleOut()
) { ) {
IconButton(onClick = { onValueChange("") }) { IconButton(onClick = { onValueChange(TextFieldValue()) }) {
Icon(Icons.Outlined.Clear, null) Icon(Icons.Outlined.Clear, null)
} }
} }
}, },
keyboardOptions = KeyboardOptions(
autoCorrect = false,
keyboardType = KeyboardType.Decimal,
imeAction = imeAction
),
visualTransformation = ExpressionTransformer(formatterSymbols) visualTransformation = ExpressionTransformer(formatterSymbols)
) )
} }