mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-19 08:45:27 +02:00
Keyboard in AddSubtract
This commit is contained in:
parent
532931914e
commit
7e6036f4ce
@ -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
|
||||||
|
@ -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
|
|
||||||
|
@ -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,18 +56,25 @@ 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 = {
|
||||||
CenterAlignedTopAppBar(
|
AnimatedVisibility(
|
||||||
title = title,
|
visible = showTopBar,
|
||||||
navigationIcon = navigationIcon,
|
enter = slideInVertically() + fadeIn(),
|
||||||
actions = actions,
|
exit = slideOutVertically() + fadeOut()
|
||||||
colors = colors,
|
) {
|
||||||
scrollBehavior = scrollBehavior,
|
CenterAlignedTopAppBar(
|
||||||
)
|
title = title,
|
||||||
|
navigationIcon = navigationIcon,
|
||||||
|
actions = actions,
|
||||||
|
colors = colors,
|
||||||
|
scrollBehavior = scrollBehavior,
|
||||||
|
)
|
||||||
|
}
|
||||||
},
|
},
|
||||||
floatingActionButton = floatingActionButton,
|
floatingActionButton = floatingActionButton,
|
||||||
floatingActionButtonPosition = floatingActionButtonPosition,
|
floatingActionButtonPosition = floatingActionButtonPosition,
|
||||||
|
@ -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
|
@ -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
|
@ -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,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -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)) {
|
||||||
|
@ -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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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,120 +104,207 @@ 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
|
||||||
|
|
||||||
Scaffold(
|
LaunchedEffect(showKeyboard, landscape) {
|
||||||
floatingActionButton = {
|
toggleTopBar(showKeyboard and landscape)
|
||||||
FloatingActionButton(onClick = { mContext.addEvent(uiState.start, uiState.result) }) {
|
}
|
||||||
Icon(
|
|
||||||
imageVector = Icons.Default.Event,
|
BackHandler(showKeyboard) {
|
||||||
contentDescription = null,
|
focusManager.clearFocus()
|
||||||
)
|
addSymbol = null
|
||||||
|
focusedTextFieldValue = null
|
||||||
|
}
|
||||||
|
|
||||||
|
Column(Modifier.fillMaxSize()) {
|
||||||
|
Scaffold(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxHeight()
|
||||||
|
.weight(1f),
|
||||||
|
floatingActionButton = {
|
||||||
|
FloatingActionButton(
|
||||||
|
onClick = {
|
||||||
|
mContext.addEvent(uiState.start, uiState.result)
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
Icon(
|
||||||
|
imageVector = Icons.Default.Event,
|
||||||
|
contentDescription = null,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
) {
|
||||||
|
LazyColumn(
|
||||||
|
modifier = Modifier.padding(horizontal = 16.dp),
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
contentPadding = PaddingValues(top = 16.dp)
|
||||||
|
) {
|
||||||
|
item("dates") {
|
||||||
|
FlowRow(
|
||||||
|
modifier = Modifier,
|
||||||
|
maxItemsInEachRow = 2,
|
||||||
|
verticalArrangement = Arrangement.spacedBy(16.dp),
|
||||||
|
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
||||||
|
) {
|
||||||
|
DateTimeSelectorBlock(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
title = stringResource(R.string.date_difference_start),
|
||||||
|
dateTime = uiState.start,
|
||||||
|
onLongClick = { updateStart(ZonedDateTime.now()) },
|
||||||
|
onClick = { dialogState = DialogState.FROM },
|
||||||
|
onTimeClick = { dialogState = DialogState.FROM_TIME },
|
||||||
|
onDateClick = { dialogState = DialogState.FROM_DATE },
|
||||||
|
)
|
||||||
|
|
||||||
|
DateTimeSelectorBlock(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(1f)
|
||||||
|
.fillMaxWidth(),
|
||||||
|
title = stringResource(R.string.date_difference_end),
|
||||||
|
dateTime = uiState.result,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item("modes") {
|
||||||
|
SingleChoiceSegmentedButtonRow(
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth(),
|
||||||
|
) {
|
||||||
|
SegmentedButton(
|
||||||
|
selected = uiState.addition,
|
||||||
|
onClick = { updateAddition(true) },
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(index = 0, count = 2),
|
||||||
|
icon = {}
|
||||||
|
) {
|
||||||
|
Icon(Icons.Outlined.Add, null)
|
||||||
|
}
|
||||||
|
SegmentedButton(
|
||||||
|
selected = !uiState.addition,
|
||||||
|
onClick = { updateAddition(false) },
|
||||||
|
shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2),
|
||||||
|
icon = {}
|
||||||
|
) {
|
||||||
|
Icon(Icons.Outlined.Remove, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
item("textFields") {
|
||||||
|
Column {
|
||||||
|
TimeUnitTextField(
|
||||||
|
modifier = Modifier.onFocusEvent {
|
||||||
|
if (it.hasFocus) {
|
||||||
|
addSymbol = updateYears
|
||||||
|
focusedTextFieldValue = uiState.years
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value = uiState.years,
|
||||||
|
onValueChange = updateYears,
|
||||||
|
label = stringResource(R.string.date_difference_years),
|
||||||
|
formatterSymbols = uiState.formatterSymbols
|
||||||
|
)
|
||||||
|
TimeUnitTextField(
|
||||||
|
modifier = Modifier.onFocusEvent {
|
||||||
|
if (it.hasFocus) {
|
||||||
|
addSymbol = updateMonths
|
||||||
|
focusedTextFieldValue = uiState.months
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value = uiState.months,
|
||||||
|
onValueChange = updateMonths,
|
||||||
|
label = stringResource(R.string.date_difference_months),
|
||||||
|
formatterSymbols = uiState.formatterSymbols
|
||||||
|
)
|
||||||
|
TimeUnitTextField(
|
||||||
|
modifier = Modifier.onFocusEvent {
|
||||||
|
if (it.hasFocus) {
|
||||||
|
addSymbol = updateDays
|
||||||
|
focusedTextFieldValue = uiState.days
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value = uiState.days,
|
||||||
|
onValueChange = updateDays,
|
||||||
|
label = stringResource(R.string.date_difference_days),
|
||||||
|
formatterSymbols = uiState.formatterSymbols
|
||||||
|
)
|
||||||
|
TimeUnitTextField(
|
||||||
|
modifier = Modifier.onFocusEvent {
|
||||||
|
if (it.hasFocus) {
|
||||||
|
addSymbol = updateHours
|
||||||
|
focusedTextFieldValue = uiState.hours
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value = uiState.hours,
|
||||||
|
onValueChange = updateHours,
|
||||||
|
label = stringResource(R.string.date_difference_hours),
|
||||||
|
formatterSymbols = uiState.formatterSymbols
|
||||||
|
)
|
||||||
|
TimeUnitTextField(
|
||||||
|
modifier = Modifier.onFocusEvent {
|
||||||
|
if (it.hasFocus) {
|
||||||
|
addSymbol = updateMinutes
|
||||||
|
focusedTextFieldValue = uiState.minutes
|
||||||
|
}
|
||||||
|
},
|
||||||
|
value = uiState.minutes,
|
||||||
|
onValueChange = updateMinutes,
|
||||||
|
label = stringResource(R.string.date_difference_minutes),
|
||||||
|
formatterSymbols = uiState.formatterSymbols
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
) {
|
AnimatedVisibility(
|
||||||
LazyColumn(
|
visible = showKeyboard,
|
||||||
modifier = Modifier.padding(start = 16.dp, end = 16.dp, top = 16.dp),
|
enter = slideInVertically { it / 2 } + fadeIn(),
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
exit = slideOutVertically { it / 2 } + fadeOut()
|
||||||
contentPadding = PaddingValues(bottom = 88.dp)
|
|
||||||
) {
|
) {
|
||||||
item("dates") {
|
HorizontalDivider()
|
||||||
FlowRow(
|
AddSubtractKeyboard(
|
||||||
modifier = Modifier,
|
modifier = Modifier
|
||||||
maxItemsInEachRow = 2,
|
.weight(1f)
|
||||||
verticalArrangement = Arrangement.spacedBy(16.dp),
|
.fillMaxWidth()
|
||||||
horizontalArrangement = Arrangement.spacedBy(16.dp)
|
.fillMaxHeight(if (isPortrait()) 0.4f else 0.6f)
|
||||||
) {
|
.padding(2.dp, 4.dp),
|
||||||
DateTimeSelectorBlock(
|
addSymbol = {
|
||||||
modifier = Modifier
|
val newValue = focusedTextFieldValue?.addTokens(it)
|
||||||
.weight(1f)
|
if (newValue != null) {
|
||||||
.fillMaxWidth(),
|
addSymbol?.invoke(newValue)
|
||||||
title = stringResource(R.string.date_difference_start),
|
|
||||||
dateTime = uiState.start,
|
|
||||||
onLongClick = { updateStart(ZonedDateTime.now()) },
|
|
||||||
onClick = { dialogState = DialogState.FROM },
|
|
||||||
onTimeClick = { dialogState = DialogState.FROM_TIME },
|
|
||||||
onDateClick = { dialogState = DialogState.FROM_DATE },
|
|
||||||
)
|
|
||||||
|
|
||||||
DateTimeSelectorBlock(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.fillMaxWidth(),
|
|
||||||
title = stringResource(R.string.date_difference_end),
|
|
||||||
dateTime = uiState.result,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
item("modes") {
|
|
||||||
SingleChoiceSegmentedButtonRow(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxWidth(),
|
|
||||||
) {
|
|
||||||
SegmentedButton(
|
|
||||||
selected = uiState.addition,
|
|
||||||
onClick = { updateAddition(true) },
|
|
||||||
shape = SegmentedButtonDefaults.itemShape(index = 0, count = 2),
|
|
||||||
icon = {}
|
|
||||||
) {
|
|
||||||
Icon(Icons.Outlined.Add, null)
|
|
||||||
}
|
}
|
||||||
SegmentedButton(
|
},
|
||||||
selected = !uiState.addition,
|
deleteSymbol = {
|
||||||
onClick = { updateAddition(false) },
|
val newValue = focusedTextFieldValue?.deleteTokens()
|
||||||
shape = SegmentedButtonDefaults.itemShape(index = 1, count = 2),
|
if (newValue != null) {
|
||||||
icon = {}
|
addSymbol?.invoke(newValue)
|
||||||
) {
|
|
||||||
Icon(Icons.Outlined.Remove, null)
|
|
||||||
}
|
}
|
||||||
}
|
},
|
||||||
}
|
onConfirm = {
|
||||||
|
focusManager.clearFocus()
|
||||||
item("textFields") {
|
addSymbol = null
|
||||||
Column {
|
focusedTextFieldValue = null
|
||||||
TimeUnitTextField(
|
},
|
||||||
value = uiState.years,
|
allowVibration = uiState.allowVibration,
|
||||||
onValueChange = updateYears,
|
imeAction = if (addSymbol == updateMinutes) ImeAction.Done else ImeAction.Next
|
||||||
label = stringResource(R.string.date_difference_years),
|
)
|
||||||
formatterSymbols = uiState.formatterSymbols
|
|
||||||
)
|
|
||||||
TimeUnitTextField(
|
|
||||||
value = uiState.months,
|
|
||||||
onValueChange = updateMonths,
|
|
||||||
label = stringResource(R.string.date_difference_months),
|
|
||||||
formatterSymbols = uiState.formatterSymbols
|
|
||||||
)
|
|
||||||
TimeUnitTextField(
|
|
||||||
value = uiState.days,
|
|
||||||
onValueChange = updateDays,
|
|
||||||
label = stringResource(R.string.date_difference_days),
|
|
||||||
formatterSymbols = uiState.formatterSymbols
|
|
||||||
)
|
|
||||||
TimeUnitTextField(
|
|
||||||
value = uiState.hours,
|
|
||||||
onValueChange = updateHours,
|
|
||||||
label = stringResource(R.string.date_difference_hours),
|
|
||||||
formatterSymbols = uiState.formatterSymbols
|
|
||||||
)
|
|
||||||
TimeUnitTextField(
|
|
||||||
value = uiState.minutes,
|
|
||||||
onValueChange = updateMinutes,
|
|
||||||
label = stringResource(R.string.date_difference_minutes),
|
|
||||||
imeAction = ImeAction.Done,
|
|
||||||
formatterSymbols = uiState.formatterSymbols
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -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 = {},
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -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,
|
||||||
)
|
)
|
||||||
|
@ -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(newValue: String) = _uiState.update {
|
fun updateMonths(value: TextFieldValue) = _uiState.update {
|
||||||
val months = when {
|
it.copy(months = checkWithMax(value, 9_999L))
|
||||||
newValue.isEmpty() -> newValue
|
|
||||||
newValue.toLong() > 9_999L -> "9999"
|
|
||||||
else -> newValue
|
|
||||||
}
|
|
||||||
|
|
||||||
it.copy(months = months)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateDays(newValue: String) = _uiState.update {
|
fun updateDays(value: TextFieldValue) = _uiState.update {
|
||||||
val days = when {
|
it.copy(days = checkWithMax(value, 99_999L))
|
||||||
newValue.isEmpty() -> newValue
|
|
||||||
newValue.toLong() > 99_999L -> "99999"
|
|
||||||
else -> newValue
|
|
||||||
}
|
|
||||||
|
|
||||||
it.copy(days = days)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateHours(newValue: String) = _uiState.update {
|
fun updateHours(value: TextFieldValue) = _uiState.update {
|
||||||
val hours = when {
|
it.copy(hours = checkWithMax(value, 9_999_999L))
|
||||||
newValue.isEmpty() -> newValue
|
|
||||||
newValue.toLong() > 9_999_999L -> "9999999"
|
|
||||||
else -> newValue
|
|
||||||
}
|
|
||||||
|
|
||||||
it.copy(hours = hours)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fun updateMinutes(newValue: String) = _uiState.update {
|
fun updateMinutes(value: TextFieldValue) = _uiState.update {
|
||||||
val minutes = when {
|
it.copy(minutes = checkWithMax(value, 99_999_999L))
|
||||||
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))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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() }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -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)
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user