mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-19 08:45:27 +02:00
Refactor text fields for number input
- removed custom keyboard to improve accessibility
This commit is contained in:
parent
84c29682f8
commit
5141652394
@ -0,0 +1,65 @@
|
|||||||
|
/*
|
||||||
|
* Unitto is a unit converter for Android
|
||||||
|
* Copyright (c) 2024 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.textfield
|
||||||
|
|
||||||
|
import androidx.compose.animation.AnimatedVisibility
|
||||||
|
import androidx.compose.animation.scaleIn
|
||||||
|
import androidx.compose.animation.scaleOut
|
||||||
|
import androidx.compose.foundation.text.KeyboardOptions
|
||||||
|
import androidx.compose.material.icons.Icons
|
||||||
|
import androidx.compose.material.icons.outlined.Clear
|
||||||
|
import androidx.compose.material3.Icon
|
||||||
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.OutlinedTextField
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
|
import androidx.compose.ui.text.input.KeyboardType
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun OutlinedDecimalTextField(
|
||||||
|
modifier: Modifier,
|
||||||
|
value: TextFieldValue,
|
||||||
|
onValueChange: (TextFieldValue) -> Unit,
|
||||||
|
label: @Composable () -> Unit,
|
||||||
|
expressionFormatter: VisualTransformation,
|
||||||
|
imeAction: ImeAction = ImeAction.Next
|
||||||
|
) {
|
||||||
|
OutlinedTextField(
|
||||||
|
modifier = modifier,
|
||||||
|
value = value,
|
||||||
|
onValueChange = onValueChange,
|
||||||
|
trailingIcon = {
|
||||||
|
AnimatedVisibility(value.text.isNotBlank(), enter = scaleIn(), exit = scaleOut()) {
|
||||||
|
IconButton(onClick = { onValueChange(TextFieldValue()) }) {
|
||||||
|
Icon(Icons.Outlined.Clear, null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
},
|
||||||
|
label = label,
|
||||||
|
singleLine = true,
|
||||||
|
visualTransformation = expressionFormatter,
|
||||||
|
keyboardOptions = KeyboardOptions(
|
||||||
|
keyboardType = KeyboardType.Decimal,
|
||||||
|
imeAction = imeAction
|
||||||
|
),
|
||||||
|
)
|
||||||
|
}
|
@ -153,7 +153,6 @@ private fun BodyMassScreen(
|
|||||||
onValueChange = updateHeight1,
|
onValueChange = updateHeight1,
|
||||||
label = "${stringResource(R.string.body_mass_height)}, ${stringResource(R.string.unit_centimeter_short)}",
|
label = "${stringResource(R.string.body_mass_height)}, ${stringResource(R.string.unit_centimeter_short)}",
|
||||||
expressionFormatter = expressionTransformer,
|
expressionFormatter = expressionTransformer,
|
||||||
imeAction = ImeAction.Next
|
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
Row(
|
Row(
|
||||||
@ -165,7 +164,6 @@ private fun BodyMassScreen(
|
|||||||
onValueChange = updateHeight1,
|
onValueChange = updateHeight1,
|
||||||
label = "${stringResource(R.string.body_mass_height)}, ${stringResource(R.string.unit_foot_short)}",
|
label = "${stringResource(R.string.body_mass_height)}, ${stringResource(R.string.unit_foot_short)}",
|
||||||
expressionFormatter = expressionTransformer,
|
expressionFormatter = expressionTransformer,
|
||||||
imeAction = ImeAction.Next
|
|
||||||
)
|
)
|
||||||
BodyMassTextField(
|
BodyMassTextField(
|
||||||
modifier = Modifier.weight(1f),
|
modifier = Modifier.weight(1f),
|
||||||
@ -173,7 +171,6 @@ private fun BodyMassScreen(
|
|||||||
onValueChange = updateHeight2,
|
onValueChange = updateHeight2,
|
||||||
label = "${stringResource(R.string.body_mass_height)}, ${stringResource(R.string.unit_inch_short)}",
|
label = "${stringResource(R.string.body_mass_height)}, ${stringResource(R.string.unit_inch_short)}",
|
||||||
expressionFormatter = expressionTransformer,
|
expressionFormatter = expressionTransformer,
|
||||||
imeAction = ImeAction.Next
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,32 +19,25 @@
|
|||||||
package com.sadellie.unitto.feature.bodymass.components
|
package com.sadellie.unitto.feature.bodymass.components
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.foundation.text.KeyboardActions
|
|
||||||
import androidx.compose.foundation.text.KeyboardOptions
|
|
||||||
import androidx.compose.material3.LocalTextStyle
|
|
||||||
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.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalFocusManager
|
|
||||||
import androidx.compose.ui.text.input.ImeAction
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.KeyboardType
|
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.input.VisualTransformation
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
|
||||||
import com.sadellie.unitto.core.base.Token
|
import com.sadellie.unitto.core.base.Token
|
||||||
|
import com.sadellie.unitto.core.ui.common.textfield.OutlinedDecimalTextField
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun BodyMassTextField(
|
internal fun BodyMassTextField(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
value: TextFieldValue,
|
value: TextFieldValue,
|
||||||
onValueChange: (TextFieldValue) -> Unit,
|
|
||||||
label: String,
|
label: String,
|
||||||
|
onValueChange: (TextFieldValue) -> Unit,
|
||||||
expressionFormatter: VisualTransformation,
|
expressionFormatter: VisualTransformation,
|
||||||
imeAction: ImeAction
|
imeAction: ImeAction = ImeAction.Next
|
||||||
) {
|
) {
|
||||||
val focusManager = LocalFocusManager.current
|
OutlinedDecimalTextField(
|
||||||
OutlinedTextField(
|
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = {
|
onValueChange = {
|
||||||
@ -56,13 +49,7 @@ internal fun BodyMassTextField(
|
|||||||
onValueChange(it.copy(cleanText))
|
onValueChange(it.copy(cleanText))
|
||||||
},
|
},
|
||||||
label = { AnimatedContent(label) { Text(it) } },
|
label = { AnimatedContent(label) { Text(it) } },
|
||||||
singleLine = true,
|
expressionFormatter = expressionFormatter,
|
||||||
visualTransformation = expressionFormatter,
|
imeAction = imeAction
|
||||||
keyboardOptions = KeyboardOptions(
|
|
||||||
keyboardType = KeyboardType.Decimal,
|
|
||||||
imeAction = imeAction
|
|
||||||
),
|
|
||||||
textStyle = LocalTextStyle.current.copy(textAlign = TextAlign.End),
|
|
||||||
keyboardActions = KeyboardActions(onDone = { focusManager.clearFocus() })
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -26,11 +26,9 @@ import androidx.compose.material3.PrimaryTabRow
|
|||||||
import androidx.compose.material3.Tab
|
import androidx.compose.material3.Tab
|
||||||
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.SideEffect
|
||||||
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
|
||||||
@ -63,7 +61,6 @@ internal fun DateCalculatorScreen(
|
|||||||
val addSubtractLabel = "${stringResource(R.string.date_calculator_add)}/${stringResource(R.string.date_calculator_subtract)}"
|
val addSubtractLabel = "${stringResource(R.string.date_calculator_add)}/${stringResource(R.string.date_calculator_subtract)}"
|
||||||
val differenceLabel = stringResource(R.string.date_calculator_difference)
|
val differenceLabel = stringResource(R.string.date_calculator_difference)
|
||||||
val focusManager = LocalFocusManager.current
|
val focusManager = LocalFocusManager.current
|
||||||
var showKeyboard by remember { mutableStateOf(false) }
|
|
||||||
|
|
||||||
val allTabs = remember { listOf(addSubtractLabel, differenceLabel) }
|
val allTabs = remember { listOf(addSubtractLabel, differenceLabel) }
|
||||||
val pagerState = rememberPagerState { allTabs.size }
|
val pagerState = rememberPagerState { allTabs.size }
|
||||||
@ -99,14 +96,9 @@ internal fun DateCalculatorScreen(
|
|||||||
verticalAlignment = Alignment.Top
|
verticalAlignment = Alignment.Top
|
||||||
) { page ->
|
) { page ->
|
||||||
when (page) {
|
when (page) {
|
||||||
0 -> AddSubtractPage(
|
0 -> AddSubtractPage()
|
||||||
showKeyboard = showKeyboard,
|
|
||||||
toggleKeyboard = {showKeyboard = it }
|
|
||||||
)
|
|
||||||
1 -> {
|
1 -> {
|
||||||
focusManager.clearFocus(true)
|
SideEffect { focusManager.clearFocus(true) }
|
||||||
showKeyboard = false
|
|
||||||
|
|
||||||
DateDifferencePage()
|
DateDifferencePage()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -23,14 +23,10 @@ import android.content.ActivityNotFoundException
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.provider.CalendarContract
|
import android.provider.CalendarContract
|
||||||
import androidx.activity.compose.BackHandler
|
|
||||||
import androidx.compose.animation.AnimatedContent
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
|
||||||
import androidx.compose.animation.SizeTransform
|
import androidx.compose.animation.SizeTransform
|
||||||
import androidx.compose.animation.fadeIn
|
import androidx.compose.animation.fadeIn
|
||||||
import androidx.compose.animation.fadeOut
|
import androidx.compose.animation.fadeOut
|
||||||
import androidx.compose.animation.slideInVertically
|
|
||||||
import androidx.compose.animation.slideOutVertically
|
|
||||||
import androidx.compose.animation.togetherWith
|
import androidx.compose.animation.togetherWith
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
@ -50,25 +46,20 @@ 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.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
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 com.sadellie.unitto.core.ui.WindowHeightSizeClass
|
|
||||||
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.draw.clip
|
import androidx.compose.ui.draw.clip
|
||||||
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.text.input.TextFieldValue
|
||||||
@ -77,12 +68,9 @@ 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.LocalWindowSize
|
import com.sadellie.unitto.core.ui.common.textfield.ExpressionTransformer
|
||||||
import com.sadellie.unitto.core.ui.common.textfield.addTokens
|
|
||||||
import com.sadellie.unitto.core.ui.common.textfield.deleteTokens
|
|
||||||
import com.sadellie.unitto.core.ui.showToast
|
import com.sadellie.unitto.core.ui.showToast
|
||||||
import com.sadellie.unitto.feature.datecalculator.ZonedDateTimeUtils
|
import com.sadellie.unitto.feature.datecalculator.ZonedDateTimeUtils
|
||||||
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
|
||||||
@ -92,15 +80,11 @@ import java.time.ZonedDateTime
|
|||||||
@Composable
|
@Composable
|
||||||
internal fun AddSubtractPage(
|
internal fun AddSubtractPage(
|
||||||
viewModel: AddSubtractViewModel = hiltViewModel(),
|
viewModel: AddSubtractViewModel = hiltViewModel(),
|
||||||
showKeyboard: Boolean,
|
|
||||||
toggleKeyboard: (Boolean) -> Unit,
|
|
||||||
) {
|
) {
|
||||||
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
|
val uiState = viewModel.uiState.collectAsStateWithLifecycle().value
|
||||||
|
|
||||||
AddSubtractView(
|
AddSubtractView(
|
||||||
uiState = uiState,
|
uiState = uiState,
|
||||||
showKeyboard = showKeyboard,
|
|
||||||
toggleKeyboard = toggleKeyboard,
|
|
||||||
updateStart = viewModel::updateStart,
|
updateStart = viewModel::updateStart,
|
||||||
updateYears = viewModel::updateYears,
|
updateYears = viewModel::updateYears,
|
||||||
updateMonths = viewModel::updateMonths,
|
updateMonths = viewModel::updateMonths,
|
||||||
@ -115,8 +99,6 @@ internal fun AddSubtractPage(
|
|||||||
@Composable
|
@Composable
|
||||||
private fun AddSubtractView(
|
private fun AddSubtractView(
|
||||||
uiState: AddSubtractState,
|
uiState: AddSubtractState,
|
||||||
showKeyboard: Boolean,
|
|
||||||
toggleKeyboard: (Boolean) -> Unit,
|
|
||||||
updateStart: (ZonedDateTime) -> Unit,
|
updateStart: (ZonedDateTime) -> Unit,
|
||||||
updateYears: (TextFieldValue) -> Unit,
|
updateYears: (TextFieldValue) -> Unit,
|
||||||
updateMonths: (TextFieldValue) -> Unit,
|
updateMonths: (TextFieldValue) -> Unit,
|
||||||
@ -126,20 +108,9 @@ private fun AddSubtractView(
|
|||||||
updateAddition: (Boolean) -> Unit,
|
updateAddition: (Boolean) -> Unit,
|
||||||
) {
|
) {
|
||||||
val mContext = LocalContext.current
|
val mContext = LocalContext.current
|
||||||
val focusManager = LocalFocusManager.current
|
|
||||||
|
|
||||||
var dialogState by remember { mutableStateOf(DialogState.NONE) }
|
var dialogState by remember { mutableStateOf(DialogState.NONE) }
|
||||||
var addSymbol: ((TextFieldValue) -> Unit)? by remember { mutableStateOf(null) }
|
val expressionTransformer = remember(uiState.formatterSymbols) {
|
||||||
var focusedTextFieldValue: TextFieldValue? by remember { mutableStateOf(null) }
|
ExpressionTransformer(uiState.formatterSymbols)
|
||||||
|
|
||||||
LaunchedEffect(addSymbol, focusedTextFieldValue) {
|
|
||||||
toggleKeyboard((addSymbol != null) and (focusedTextFieldValue != null))
|
|
||||||
}
|
|
||||||
|
|
||||||
BackHandler(showKeyboard) {
|
|
||||||
focusManager.clearFocus()
|
|
||||||
addSymbol = null
|
|
||||||
focusedTextFieldValue = null
|
|
||||||
}
|
}
|
||||||
|
|
||||||
val showResult = remember(uiState.start, uiState.result) { uiState.start != uiState.result }
|
val showResult = remember(uiState.start, uiState.result) { uiState.start != uiState.result }
|
||||||
@ -241,127 +212,58 @@ private fun AddSubtractView(
|
|||||||
verticalArrangement = Arrangement.spacedBy(4.dp)
|
verticalArrangement = Arrangement.spacedBy(4.dp)
|
||||||
) {
|
) {
|
||||||
TimeUnitTextField(
|
TimeUnitTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.onFocusEvent {
|
|
||||||
if (it.hasFocus) {
|
|
||||||
addSymbol = updateYears
|
|
||||||
focusedTextFieldValue = uiState.years
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.fillMaxWidth(),
|
|
||||||
value = uiState.years,
|
value = uiState.years,
|
||||||
onValueChange = updateYears,
|
onValueChange = updateYears,
|
||||||
label = stringResource(R.string.date_calculator_years),
|
label = stringResource(R.string.date_calculator_years),
|
||||||
formatterSymbols = uiState.formatterSymbols
|
expressionFormatter = expressionTransformer,
|
||||||
)
|
)
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
TimeUnitTextField(
|
TimeUnitTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f),
|
||||||
.onFocusEvent {
|
|
||||||
if (it.hasFocus) {
|
|
||||||
addSymbol = updateMonths
|
|
||||||
focusedTextFieldValue = uiState.months
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.weight(1f),
|
|
||||||
value = uiState.months,
|
value = uiState.months,
|
||||||
onValueChange = updateMonths,
|
onValueChange = updateMonths,
|
||||||
label = stringResource(R.string.date_calculator_months),
|
label = stringResource(R.string.date_calculator_months),
|
||||||
formatterSymbols = uiState.formatterSymbols
|
expressionFormatter = expressionTransformer,
|
||||||
)
|
)
|
||||||
|
|
||||||
TimeUnitTextField(
|
TimeUnitTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f),
|
||||||
.onFocusEvent {
|
|
||||||
if (it.hasFocus) {
|
|
||||||
addSymbol = updateDays
|
|
||||||
focusedTextFieldValue = uiState.days
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.weight(1f),
|
|
||||||
value = uiState.days,
|
value = uiState.days,
|
||||||
onValueChange = updateDays,
|
onValueChange = updateDays,
|
||||||
label = stringResource(R.string.date_calculator_days),
|
label = stringResource(R.string.date_calculator_days),
|
||||||
formatterSymbols = uiState.formatterSymbols
|
expressionFormatter = expressionTransformer,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier
|
modifier = Modifier.fillMaxWidth(),
|
||||||
.fillMaxWidth(),
|
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
TimeUnitTextField(
|
TimeUnitTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f),
|
||||||
.onFocusEvent {
|
|
||||||
if (it.hasFocus) {
|
|
||||||
addSymbol = updateHours
|
|
||||||
focusedTextFieldValue = uiState.hours
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.weight(1f),
|
|
||||||
value = uiState.hours,
|
value = uiState.hours,
|
||||||
onValueChange = updateHours,
|
onValueChange = updateHours,
|
||||||
label = stringResource(R.string.date_calculator_hours),
|
label = stringResource(R.string.date_calculator_hours),
|
||||||
formatterSymbols = uiState.formatterSymbols
|
expressionFormatter = expressionTransformer,
|
||||||
)
|
)
|
||||||
|
|
||||||
TimeUnitTextField(
|
TimeUnitTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier.weight(1f),
|
||||||
.onFocusEvent {
|
|
||||||
if (it.hasFocus) {
|
|
||||||
addSymbol = updateMinutes
|
|
||||||
focusedTextFieldValue = uiState.minutes
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.weight(1f),
|
|
||||||
value = uiState.minutes,
|
value = uiState.minutes,
|
||||||
onValueChange = updateMinutes,
|
onValueChange = updateMinutes,
|
||||||
label = stringResource(R.string.date_calculator_minutes),
|
label = stringResource(R.string.date_calculator_minutes),
|
||||||
formatterSymbols = uiState.formatterSymbols
|
expressionFormatter = expressionTransformer,
|
||||||
|
imeAction = ImeAction.Done
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
AnimatedVisibility(
|
|
||||||
visible = showKeyboard,
|
|
||||||
enter = slideInVertically { it / 2 } + fadeIn(),
|
|
||||||
exit = slideOutVertically { it / 2 } + fadeOut()
|
|
||||||
) {
|
|
||||||
HorizontalDivider()
|
|
||||||
AddSubtractKeyboard(
|
|
||||||
modifier = Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.fillMaxWidth()
|
|
||||||
.fillMaxHeight(if (LocalWindowSize.current.heightSizeClass > WindowHeightSizeClass.Compact) 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(
|
||||||
@ -399,8 +301,6 @@ fun AddSubtractViewPreview() {
|
|||||||
start = ZonedDateTimeUtils.nowWithMinutes(),
|
start = ZonedDateTimeUtils.nowWithMinutes(),
|
||||||
result = ZonedDateTimeUtils.nowWithMinutes().plusSeconds(1)
|
result = ZonedDateTimeUtils.nowWithMinutes().plusSeconds(1)
|
||||||
),
|
),
|
||||||
showKeyboard = false,
|
|
||||||
toggleKeyboard = {},
|
|
||||||
updateStart = {},
|
updateStart = {},
|
||||||
updateYears = {},
|
updateYears = {},
|
||||||
updateMonths = {},
|
updateMonths = {},
|
||||||
|
@ -1,141 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.Row
|
|
||||||
import androidx.compose.foundation.layout.Spacer
|
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
|
||||||
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.tooling.preview.Preview
|
|
||||||
import com.sadellie.unitto.core.base.Token
|
|
||||||
import com.sadellie.unitto.core.ui.LocalWindowSize
|
|
||||||
import com.sadellie.unitto.core.ui.WindowHeightSizeClass
|
|
||||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonContentHeightTall
|
|
||||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled
|
|
||||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonLight
|
|
||||||
import com.sadellie.unitto.core.ui.common.KeypadFlow
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.IconPack
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Backspace
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Check
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key0
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key1
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key2
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key3
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key4
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key5
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key6
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key7
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key8
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Key9
|
|
||||||
import com.sadellie.unitto.core.ui.common.icons.iconpack.Tab
|
|
||||||
|
|
||||||
@Composable
|
|
||||||
internal fun AddSubtractKeyboard(
|
|
||||||
modifier: Modifier,
|
|
||||||
addSymbol: (String) -> Unit,
|
|
||||||
deleteSymbol: () -> Unit,
|
|
||||||
onConfirm: () -> Unit,
|
|
||||||
allowVibration: Boolean,
|
|
||||||
imeAction: ImeAction,
|
|
||||||
focusManager: FocusManager = LocalFocusManager.current
|
|
||||||
) {
|
|
||||||
Row(
|
|
||||||
modifier = modifier
|
|
||||||
) {
|
|
||||||
KeypadFlow(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxHeight()
|
|
||||||
.weight(3f),
|
|
||||||
rows = 4,
|
|
||||||
columns = 3
|
|
||||||
) { width, height ->
|
|
||||||
val buttonModifier = Modifier
|
|
||||||
.fillMaxWidth(width)
|
|
||||||
.fillMaxHeight(height)
|
|
||||||
|
|
||||||
KeyboardButtonLight(buttonModifier, IconPack.Key7, allowVibration, KeyboardButtonContentHeightTall) { addSymbol(Token.Digit._7) }
|
|
||||||
KeyboardButtonLight(buttonModifier, IconPack.Key8, allowVibration, KeyboardButtonContentHeightTall) { addSymbol(Token.Digit._8) }
|
|
||||||
KeyboardButtonLight(buttonModifier, IconPack.Key9, allowVibration, KeyboardButtonContentHeightTall) { addSymbol(Token.Digit._9) }
|
|
||||||
|
|
||||||
KeyboardButtonLight(buttonModifier, IconPack.Key4, allowVibration, KeyboardButtonContentHeightTall) { addSymbol(Token.Digit._4) }
|
|
||||||
KeyboardButtonLight(buttonModifier, IconPack.Key5, allowVibration, KeyboardButtonContentHeightTall) { addSymbol(Token.Digit._5) }
|
|
||||||
KeyboardButtonLight(buttonModifier, IconPack.Key6, allowVibration, KeyboardButtonContentHeightTall) { addSymbol(Token.Digit._6) }
|
|
||||||
|
|
||||||
KeyboardButtonLight(buttonModifier, IconPack.Key1, allowVibration, KeyboardButtonContentHeightTall) { addSymbol(Token.Digit._1) }
|
|
||||||
KeyboardButtonLight(buttonModifier, IconPack.Key2, allowVibration, KeyboardButtonContentHeightTall) { addSymbol(Token.Digit._2) }
|
|
||||||
KeyboardButtonLight(buttonModifier, IconPack.Key3, allowVibration, KeyboardButtonContentHeightTall) { addSymbol(Token.Digit._3) }
|
|
||||||
|
|
||||||
Spacer(buttonModifier)
|
|
||||||
KeyboardButtonLight(buttonModifier, IconPack.Key0, allowVibration, KeyboardButtonContentHeightTall) { addSymbol(Token.Digit._0) }
|
|
||||||
Spacer(buttonModifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
KeypadFlow(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxHeight()
|
|
||||||
.weight(1f),
|
|
||||||
rows = 2,
|
|
||||||
columns = 1,
|
|
||||||
// In digits keypad there are 4 rows with verticalPadding set to 10
|
|
||||||
// In this keypad we have 2 times less rows, we use 2 times smaller verticalPadding -> 5
|
|
||||||
verticalPadding = 5
|
|
||||||
) { width, height ->
|
|
||||||
val mainButtonModifier = Modifier
|
|
||||||
.fillMaxWidth(width)
|
|
||||||
.fillMaxHeight(height)
|
|
||||||
val actionIconHeight = if (LocalWindowSize.current.heightSizeClass > WindowHeightSizeClass.Compact) 0.8f else 1.3f
|
|
||||||
|
|
||||||
Crossfade(
|
|
||||||
targetState = imeAction == ImeAction.Next,
|
|
||||||
modifier = mainButtonModifier,
|
|
||||||
label = "Primary button animation"
|
|
||||||
) { showNext ->
|
|
||||||
if (showNext) {
|
|
||||||
KeyboardButtonFilled(Modifier.fillMaxSize(), IconPack.Tab, allowVibration, actionIconHeight) { focusManager.moveFocus(FocusDirection.Next) }
|
|
||||||
} else {
|
|
||||||
KeyboardButtonFilled(Modifier.fillMaxSize(), IconPack.Check, allowVibration, actionIconHeight) { onConfirm() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
KeyboardButtonLight(mainButtonModifier, IconPack.Backspace, allowVibration, actionIconHeight) { deleteSymbol() }
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Preview
|
|
||||||
@Composable
|
|
||||||
fun PreviewAddSubtractKeyboardNew() {
|
|
||||||
AddSubtractKeyboard(
|
|
||||||
modifier = Modifier
|
|
||||||
.fillMaxSize(),
|
|
||||||
addSymbol = {},
|
|
||||||
deleteSymbol = {},
|
|
||||||
onConfirm = {},
|
|
||||||
allowVibration = true,
|
|
||||||
imeAction = ImeAction.Next
|
|
||||||
)
|
|
||||||
}
|
|
@ -18,50 +18,32 @@
|
|||||||
|
|
||||||
package com.sadellie.unitto.feature.datecalculator.components
|
package com.sadellie.unitto.feature.datecalculator.components
|
||||||
|
|
||||||
import androidx.compose.animation.AnimatedVisibility
|
import androidx.compose.animation.AnimatedContent
|
||||||
import androidx.compose.animation.scaleIn
|
|
||||||
import androidx.compose.animation.scaleOut
|
|
||||||
import androidx.compose.material.icons.Icons
|
|
||||||
import androidx.compose.material.icons.outlined.Clear
|
|
||||||
import androidx.compose.material3.Icon
|
|
||||||
import androidx.compose.material3.IconButton
|
|
||||||
import androidx.compose.material3.MaterialTheme
|
|
||||||
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.platform.LocalTextInputService
|
import androidx.compose.ui.text.input.ImeAction
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import com.sadellie.unitto.core.ui.common.textfield.ExpressionTransformer
|
import androidx.compose.ui.text.input.VisualTransformation
|
||||||
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
|
import com.sadellie.unitto.core.ui.common.textfield.OutlinedDecimalTextField
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
internal fun TimeUnitTextField(
|
internal fun TimeUnitTextField(
|
||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
value: TextFieldValue,
|
value: TextFieldValue,
|
||||||
onValueChange: (TextFieldValue) -> Unit,
|
|
||||||
label: String,
|
label: String,
|
||||||
formatterSymbols: FormatterSymbols
|
onValueChange: (TextFieldValue) -> Unit,
|
||||||
) = CompositionLocalProvider(LocalTextInputService provides null) {
|
expressionFormatter: VisualTransformation,
|
||||||
OutlinedTextField(
|
imeAction: ImeAction = ImeAction.Next,
|
||||||
|
) {
|
||||||
|
OutlinedDecimalTextField(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
value = value,
|
value = value,
|
||||||
onValueChange = { newValue ->
|
onValueChange = { newValue ->
|
||||||
onValueChange(newValue.copy(newValue.text.filter { it.isDigit() }))
|
onValueChange(newValue.copy(newValue.text.filter { it.isDigit() }))
|
||||||
},
|
},
|
||||||
label = { Text(label, color = MaterialTheme.colorScheme.onSurfaceVariant) },
|
label = { AnimatedContent(label) { Text(it) } },
|
||||||
trailingIcon = {
|
expressionFormatter = expressionFormatter,
|
||||||
AnimatedVisibility(
|
imeAction = imeAction
|
||||||
visible = value.text.isNotBlank(),
|
|
||||||
enter = scaleIn(),
|
|
||||||
exit = scaleOut()
|
|
||||||
) {
|
|
||||||
IconButton(onClick = { onValueChange(TextFieldValue()) }) {
|
|
||||||
Icon(Icons.Outlined.Clear, null)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
},
|
|
||||||
visualTransformation = ExpressionTransformer(formatterSymbols)
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user