The most responsive UI

- Auto-size text field (animated)
- Auto-size keyboard
- Auto-size buttons
- Fix cursor
This commit is contained in:
Sad Ellie 2023-03-03 23:17:28 +04:00 committed by Sad Ellie
parent bfafed4501
commit 07fdb03ab3
16 changed files with 500 additions and 251 deletions

View File

@ -18,6 +18,7 @@
package com.sadellie.unitto.core.ui.common
import android.content.res.Configuration
import android.view.HapticFeedbackConstants
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateIntAsState
@ -38,6 +39,7 @@ import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.unit.dp
@ -50,7 +52,7 @@ fun BasicKeyboardButton(
icon: ImageVector,
iconColor: Color,
allowVibration: Boolean,
contentPadding: PaddingValues = PaddingValues(24.dp, 8.dp)
contentHeight: Float
) {
val view = LocalView.current
val interactionSource = remember { MutableInteractionSource() }
@ -66,10 +68,15 @@ fun BasicKeyboardButton(
onLongClick = onLongClick,
shape = RoundedCornerShape(cornerRadius),
containerColor = containerColor,
contentPadding = contentPadding,
contentPadding = PaddingValues(),
interactionSource = interactionSource
) {
Icon(icon, null, modifier = Modifier.fillMaxHeight(), tint = iconColor)
Icon(
imageVector = icon,
contentDescription = null,
modifier = Modifier.fillMaxHeight(contentHeight),
tint = iconColor
)
}
LaunchedEffect(key1 = isPressed) {
@ -93,6 +100,7 @@ fun KeyboardButtonLight(
icon = icon,
iconColor = MaterialTheme.colorScheme.onSurfaceVariant,
allowVibration = allowVibration,
contentHeight = if (isPortrait()) 0.5f else 0.85f
)
}
@ -111,7 +119,8 @@ fun KeyboardButtonFilled(
containerColor = MaterialTheme.colorScheme.primaryContainer,
icon = icon,
iconColor = MaterialTheme.colorScheme.onSecondaryContainer,
allowVibration = allowVibration
allowVibration = allowVibration,
contentHeight = if (isPortrait()) 0.5f else 0.85f
)
}
@ -123,17 +132,19 @@ fun KeyboardButtonAdditional(
onLongClick: (() -> Unit)? = null,
onClick: () -> Unit
) {
BasicKeyboardButton(
modifier = modifier
.minimumInteractiveComponentSize()
.heightIn(max = 48.dp),
onClick = onClick,
onLongClick = onLongClick,
containerColor = Color.Transparent,
icon = icon,
iconColor = MaterialTheme.colorScheme.onSurfaceVariant,
allowVibration = allowVibration,
contentPadding = PaddingValues(12.dp, 2.dp),
onLongClick = onLongClick
contentHeight = if (isPortrait()) 0.9f else 0.85f
)
}
@Composable
private fun isPortrait() = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT

View File

@ -19,7 +19,6 @@
package com.sadellie.unitto.core.ui.common
import android.content.res.Configuration
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxHeight
@ -28,7 +27,6 @@ import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.unit.dp
/**
* When Portrait mode will place [content1] and [content2] in a [Column].
@ -42,33 +40,14 @@ fun PortraitLandscape(
content2: @Composable (Modifier) -> Unit,
) {
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
Column(
modifier = modifier.fillMaxSize(),
verticalArrangement = Arrangement.spacedBy(8.dp),
) {
content1(Modifier)
content2(
Modifier
.fillMaxSize()
.padding(horizontal = 8.dp)
)
ColumnWithConstraints(modifier) {
content1(Modifier.fillMaxHeight(0.34f).padding(horizontal = it.maxWidth * 0.03f))
content2(Modifier.fillMaxSize().padding(horizontal = it.maxWidth * 0.03f, vertical = it.maxHeight * 0.015f))
}
} else {
Row(
modifier = modifier.fillMaxSize(),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
content1(
Modifier
.weight(1f)
.fillMaxHeight()
)
content2(
Modifier
.weight(1f)
.fillMaxSize()
.padding(horizontal = 8.dp)
)
RowWithConstraints(modifier) {
content1(Modifier.weight(1f).fillMaxHeight().padding(bottom = it.maxWidth * 0.015f, start = it.maxWidth * 0.015f, end = it.maxWidth * 0.015f))
content2(Modifier.weight(1f).fillMaxSize().padding(bottom = it.maxWidth * 0.015f, start = it.maxWidth * 0.015f, end = it.maxWidth * 0.015f))
}
}
}

View File

@ -0,0 +1,56 @@
/*
* Unitto is a unit converter for Android
* Copyright (c) 2023 Elshan Agaev
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.core.ui.common
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.BoxWithConstraintsScope
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.RowScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
@Composable
fun ColumnWithConstraints(
modifier: Modifier = Modifier,
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
content: @Composable (ColumnScope.(BoxWithConstraintsScope)-> Unit)
) = BoxWithConstraints(modifier) {
Column(
verticalArrangement = verticalArrangement,
horizontalAlignment = horizontalAlignment
) { content(this@BoxWithConstraints) }
}
@Composable
fun RowWithConstraints(
modifier: Modifier = Modifier,
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
verticalAlignment: Alignment.Vertical = Alignment.Top,
content: @Composable (RowScope.(BoxWithConstraintsScope)-> Unit)
) = BoxWithConstraints(modifier) {
Row(
horizontalArrangement = horizontalArrangement,
verticalAlignment = verticalAlignment
) { content(this@BoxWithConstraints) }
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.calculator.components
package com.sadellie.unitto.core.ui.common.textfield
import android.view.ActionMode
import android.view.Menu

View File

@ -0,0 +1,210 @@
/*
* 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.textfield
import androidx.compose.foundation.layout.BoxWithConstraints
import androidx.compose.foundation.layout.widthIn
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Brush
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.layout.layout
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.platform.LocalTextInputService
import androidx.compose.ui.platform.LocalTextToolbar
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.Paragraph
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.createFontFamilyResolver
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.unit.Constraints
import androidx.compose.ui.unit.TextUnit
import androidx.compose.ui.unit.sp
import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.ui.Formatter
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
import kotlin.math.ceil
import kotlin.math.roundToInt
@Composable
fun InputTextField(
modifier: Modifier,
value: TextFieldValue,
textStyle: TextStyle = NumbersTextStyleDisplayLarge,
minRatio: Float = 1f,
cutCallback: () -> Unit,
pasteCallback: (String) -> Unit,
onCursorChange: (IntRange) -> Unit,
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
) {
val clipboardManager = LocalClipboardManager.current
fun copyCallback() {
clipboardManager.setText(
AnnotatedString(
Formatter.removeGrouping(
value.annotatedString.subSequence(value.selection).text
)
)
)
}
CompositionLocalProvider(
LocalTextInputService provides null,
LocalTextToolbar provides UnittoTextToolbar(
view = LocalView.current,
copyCallback = ::copyCallback,
pasteCallback = {
pasteCallback(
Formatter.toSeparator(
clipboardManager.getText()?.text ?: "", Separator.COMMA
)
)
},
cutCallback = { copyCallback(); cutCallback() }
)
) {
AutoSizableTextField(
modifier = modifier,
value = value,
onValueChange = {
onCursorChange(it.selection.start..it.selection.end)
},
textStyle = textStyle,
minRatio = minRatio,
readOnly = false
)
}
}
@Composable
fun InputTextField(
modifier: Modifier = Modifier,
value: TextFieldValue,
textStyle: TextStyle = NumbersTextStyleDisplayLarge,
minRatio: Float = 1f,
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
) {
AutoSizableTextField(
modifier = modifier,
value = value,
textStyle = textStyle,
minRatio = minRatio,
readOnly = true
)
}
@Composable
private fun AutoSizableTextField(
modifier: Modifier = Modifier,
value: TextFieldValue,
onValueChange: (TextFieldValue) -> Unit = {},
textStyle: TextStyle = TextStyle(),
scaleFactor: Float = 0.95f,
minRatio: Float = 1f,
readOnly: Boolean = false,
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
cursorBrush: Brush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant)
) {
// FIXME Acts strange when minRatio is set to 0 (still scales down).
val density = LocalDensity.current
var nFontSize: TextUnit by remember { mutableStateOf(0.sp) }
var minFontSize: TextUnit
BoxWithConstraints(
modifier = modifier,
contentAlignment = Alignment.BottomStart
) {
with(density) {
// Cursor handle is not visible without this, 0.836f is the minimum required factor here
nFontSize = maxHeight.toSp() * 0.836f
minFontSize = nFontSize * minRatio
}
// Modified: https://blog.canopas.com/autosizing-textfield-in-jetpack-compose-7a80f0270853
val calculateParagraph = @Composable {
Paragraph(
text = value.text,
style = textStyle.copy(fontSize = nFontSize),
constraints = Constraints(
maxWidth = ceil(with(density) { maxWidth.toPx() }).toInt()
),
density = density,
fontFamilyResolver = createFontFamilyResolver(LocalContext.current),
spanStyles = listOf(),
placeholders = listOf(),
maxLines = 1,
ellipsis = false
)
}
var intrinsics = calculateParagraph()
with(density) {
while ((intrinsics.maxIntrinsicWidth > maxWidth.toPx()) && nFontSize >= minFontSize) {
nFontSize *= scaleFactor
intrinsics = calculateParagraph()
}
}
val nTextStyle = textStyle.copy(
// https://issuetracker.google.com/issues/266470454
// textAlign = TextAlign.End,
color = textColor,
fontSize = nFontSize
)
BasicTextField(
value = value,
singleLine = true,
onValueChange = onValueChange,
modifier = Modifier
.widthIn(
max = with(density) { intrinsics.width.toDp() }
)
.layout { measurable, constraints ->
val placeable = measurable.measure(constraints)
// TextField size is changed with a delay (text jumps). Here we correct it.
layout(placeable.width, placeable.height) {
placeable.place(
x = (intrinsics.width - intrinsics.maxIntrinsicWidth)
.coerceAtLeast(0f)
.roundToInt(),
y = (placeable.height - intrinsics.height).roundToInt()
)
}
},
textStyle = nTextStyle,
readOnly = readOnly,
cursorBrush = cursorBrush
)
}
}

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.calculator.components
package com.sadellie.unitto.core.ui.common.textfield
import android.view.ActionMode
import android.view.Menu

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.calculator.components
package com.sadellie.unitto.core.ui.common.textfield
import android.view.ActionMode
import android.view.Menu

View File

@ -16,7 +16,7 @@
* along with this program. If not, see <https://www.gnu.org/licenses/>.
*/
package com.sadellie.unitto.feature.calculator.components
package com.sadellie.unitto.core.ui.common.textfield
import android.os.Build
import android.view.ActionMode
@ -25,7 +25,7 @@ import androidx.compose.ui.geometry.Rect
import androidx.compose.ui.platform.TextToolbar
import androidx.compose.ui.platform.TextToolbarStatus
internal class UnittoTextToolbar(
class UnittoTextToolbar(
private val view: View,
private val copyCallback: () -> Unit,
private val pasteCallback: (() -> Unit)? = null,

View File

@ -23,6 +23,7 @@ import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.Font
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.em
import androidx.compose.ui.unit.sp
import com.sadellie.unitto.core.ui.R
@ -42,7 +43,7 @@ val NumbersTextStyleDisplayLarge = TextStyle(
fontFamily = Lato,
fontWeight = FontWeight.W400,
fontSize = 57.sp,
lineHeight = 64.sp,
lineHeight = (1.4).em,
letterSpacing = (-0.25).sp,
)

View File

@ -26,15 +26,14 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.gestures.Orientation
import androidx.compose.foundation.gestures.draggable
import androidx.compose.foundation.gestures.rememberDraggableState
import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxHeight
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.sizeIn
import androidx.compose.foundation.rememberScrollState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.foundation.text.selection.SelectionContainer
import androidx.compose.material.icons.Icons
@ -65,21 +64,20 @@ import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.ui.Formatter
import com.sadellie.unitto.core.ui.common.MenuButton
import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium
import com.sadellie.unitto.core.ui.common.textfield.InputTextField
import com.sadellie.unitto.core.ui.common.textfield.UnittoTextToolbar
import com.sadellie.unitto.data.model.HistoryItem
import com.sadellie.unitto.feature.calculator.components.CalculatorKeyboard
import com.sadellie.unitto.feature.calculator.components.DragDownView
import com.sadellie.unitto.feature.calculator.components.HistoryList
import com.sadellie.unitto.feature.calculator.components.InputTextField
import com.sadellie.unitto.feature.calculator.components.UnittoTextToolbar
import kotlinx.coroutines.launch
import java.text.SimpleDateFormat
import java.util.*
@ -194,12 +192,13 @@ private fun CalculatorScreen(
textFields = { maxDragAmount ->
Column(
Modifier
.fillMaxHeight(0.25f)
.onPlaced { textThingyHeight = it.size.height }
.background(
MaterialTheme.colorScheme.surfaceVariant,
RoundedCornerShape(
topStart = 0.dp, topEnd = 0.dp,
bottomStart = 32.dp, bottomEnd = 32.dp
topStartPercent = 0, topEndPercent = 0,
bottomStartPercent = 20, bottomEndPercent = 20
)
)
.draggable(
@ -226,31 +225,33 @@ private fun CalculatorScreen(
}
}
)
.padding(top = 8.dp),
.padding(top = 12.dp),
horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
InputTextField(
modifier = Modifier
.weight(2f)
.fillMaxWidth()
.padding(horizontal = 8.dp),
value = uiState.input,
onCursorChange = onCursorChange,
value = uiState.input.copy(
Formatter.fromSeparator(uiState.input.text, Separator.COMMA)
),
minRatio = 0.5f,
cutCallback = deleteSymbol,
pasteCallback = addSymbol,
cutCallback = deleteSymbol
onCursorChange = onCursorChange
)
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
SelectionContainer {
Text(
SelectionContainer(Modifier.weight(1f)) {
InputTextField(
modifier = Modifier
.fillMaxWidth()
.padding(horizontal = 8.dp)
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
text = Formatter.format(uiState.output),
textAlign = TextAlign.End,
softWrap = false,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
style = NumbersTextStyleDisplayMedium,
.padding(horizontal = 8.dp),
value = TextFieldValue(
Formatter.fromSeparator(uiState.output, Separator.COMMA)
),
textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.6f)
)
}
}
@ -268,7 +269,7 @@ private fun CalculatorScreen(
},
numPad = {
CalculatorKeyboard(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp, vertical = 4.dp),
angleMode = uiState.angleMode,
allowVibration = uiState.allowVibration,
addSymbol = addSymbol,
@ -314,7 +315,12 @@ private fun CalculatorScreen(
}
}
@Preview
@Preview(widthDp = 432, heightDp = 1008, device = "spec:parent=pixel_5,orientation=portrait")
@Preview(widthDp = 432, heightDp = 864, device = "spec:parent=pixel_5,orientation=portrait")
@Preview(widthDp = 597, heightDp = 1393, device = "spec:parent=pixel_5,orientation=portrait")
@Preview(heightDp = 432, widthDp = 1008, device = "spec:parent=pixel_5,orientation=landscape")
@Preview(heightDp = 432, widthDp = 864, device = "spec:parent=pixel_5,orientation=landscape")
@Preview(heightDp = 597, widthDp = 1393, device = "spec:parent=pixel_5,orientation=landscape")
@Composable
private fun PreviewCalculatorScreen() {
val dtf = SimpleDateFormat("dd.MM.yyyy HH:mm:ss", Locale.getDefault())
@ -332,14 +338,14 @@ private fun PreviewCalculatorScreen() {
HistoryItem(
date = dtf.parse(it)!!,
expression = "12345".repeat(10),
result = "67890"
result = "1234"
)
}
CalculatorScreen(
uiState = CalculatorUIState(
input = TextFieldValue("12345"),
output = "12345",
input = TextFieldValue("1.2345"),
output = "1234",
history = historyItems
),
navigateToMenu = {},

View File

@ -25,11 +25,13 @@ import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
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.fillMaxWidth
import androidx.compose.foundation.layout.heightIn
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.ExpandLess
@ -43,16 +45,19 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalConfiguration
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.compose.ui.unit.Dp
import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.Formatter
import com.sadellie.unitto.core.ui.common.ColumnWithConstraints
import com.sadellie.unitto.core.ui.common.KeyboardButtonAdditional
import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled
import com.sadellie.unitto.core.ui.common.KeyboardButtonLight
import com.sadellie.unitto.core.ui.common.RowWithConstraints
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
import com.sadellie.unitto.core.ui.common.key.unittoicons.AcTan
import com.sadellie.unitto.core.ui.common.key.unittoicons.ArCos
@ -150,22 +155,27 @@ private fun PortraitKeyboard(
animationSpec = tween(easing = FastOutSlowInEasing)
)
Column(
ColumnWithConstraints(
modifier = modifier
) {
) { constraints ->
fun verticalFraction(fraction: Float): Dp = constraints.maxHeight * fraction
fun horizontalFraction(fraction: Float): Dp = constraints.maxWidth * fraction
val weightModifier = Modifier.weight(1f)
val mainButtonModifier = Modifier
.fillMaxSize()
.weight(1f)
.padding(4.dp)
.padding(horizontalFraction(0.015f), verticalFraction(0.008f))
val additionalButtonModifier = Modifier
.minimumInteractiveComponentSize()
.weight(1f)
.heightIn(max = 48.dp)
.height(verticalFraction(0.075f))
Spacer(modifier = Modifier.height(verticalFraction(0.015f)))
Row(
modifier = Modifier.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.spacedBy(2.dp)
modifier = Modifier,
horizontalArrangement = Arrangement.spacedBy(horizontalFraction(0.03f))
) {
// Additional buttons
Crossfade(invMode, weightModifier) {
@ -192,14 +202,18 @@ private fun PortraitKeyboard(
}
}
// Expand/Collapse
IconButton(
onClick = { showAdditional = !showAdditional },
colors = IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.inverseOnSurface)
) {
Icon(Icons.Default.ExpandLess, null, Modifier.rotate(expandRotation))
Box(modifier = Modifier.height(verticalFraction(0.075f)), contentAlignment = Alignment.Center) {
// Expand/Collapse
IconButton(
onClick = { showAdditional = !showAdditional },
colors = IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.inverseOnSurface)
) {
Icon(Icons.Default.ExpandLess, null, Modifier.rotate(expandRotation))
}
}
}
Spacer(modifier = Modifier.height(verticalFraction(0.015f)))
Row(weightModifier) {
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.leftBracket) }
@ -232,6 +246,8 @@ private fun PortraitKeyboard(
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Backspace, allowVibration, clearSymbols) { deleteSymbol() }
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Equal, allowVibration) { evaluate() }
}
Spacer(modifier = Modifier.height(verticalFraction(0.015f)))
}
}
@ -246,7 +262,7 @@ private fun AdditionalButtonsPortrait(
toggleInvMode: () -> Unit
) {
Column {
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
Row {
KeyboardButtonAdditional(modifier, UnittoIcons.SquareRootWide, allowVibration) { addSymbol(Token.sqrt) }
KeyboardButtonAdditional(modifier, UnittoIcons.Pi, allowVibration) { addSymbol(Token.pi) }
KeyboardButtonAdditional(modifier, UnittoIcons.ExponentWide, allowVibration) { addSymbol(Token.exponent) }
@ -254,13 +270,13 @@ private fun AdditionalButtonsPortrait(
}
AnimatedVisibility(showAdditional) {
Column {
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
Row {
KeyboardButtonAdditional(modifier, if (angleMode == AngleMode.DEG) UnittoIcons.Deg else UnittoIcons.Rad, allowVibration) { toggleAngleMode() }
KeyboardButtonAdditional(modifier, UnittoIcons.Sin, allowVibration) { addSymbol(Token.sin) }
KeyboardButtonAdditional(modifier, UnittoIcons.Cos, allowVibration) { addSymbol(Token.cos) }
KeyboardButtonAdditional(modifier, UnittoIcons.Tan, allowVibration) { addSymbol(Token.tan) }
}
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
Row {
KeyboardButtonAdditional(modifier, UnittoIcons.Inv, allowVibration) { toggleInvMode() }
KeyboardButtonAdditional(modifier, UnittoIcons.E, allowVibration) { addSymbol(Token.e) }
KeyboardButtonAdditional(modifier, UnittoIcons.Ln, allowVibration) { addSymbol(Token.ln) }
@ -282,7 +298,7 @@ private fun AdditionalButtonsPortraitInverse(
toggleInvMode: () -> Unit
) {
Column {
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
Row {
KeyboardButtonAdditional(modifier, UnittoIcons.Modulo, allowVibration) { addSymbol(Token.modulo) }
KeyboardButtonAdditional(modifier, UnittoIcons.Pi, allowVibration) { addSymbol(Token.pi) }
KeyboardButtonAdditional(modifier, UnittoIcons.ExponentWide, allowVibration) { addSymbol(Token.exponent) }
@ -290,13 +306,13 @@ private fun AdditionalButtonsPortraitInverse(
}
AnimatedVisibility(showAdditional) {
Column {
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
Row {
KeyboardButtonAdditional(modifier, if (angleMode == AngleMode.DEG) UnittoIcons.Deg else UnittoIcons.Rad, allowVibration) { toggleAngleMode() }
KeyboardButtonAdditional(modifier, UnittoIcons.ArSin, allowVibration) { addSymbol(Token.arSin) }
KeyboardButtonAdditional(modifier, UnittoIcons.ArCos, allowVibration) { addSymbol(Token.arCos) }
KeyboardButtonAdditional(modifier, UnittoIcons.AcTan, allowVibration) { addSymbol(Token.acTan) }
}
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
Row {
KeyboardButtonAdditional(modifier, UnittoIcons.Inv, allowVibration) { toggleInvMode() }
KeyboardButtonAdditional(modifier, UnittoIcons.E, allowVibration) { addSymbol(Token.e) }
KeyboardButtonAdditional(modifier, UnittoIcons.Exp, allowVibration) { addSymbol(Token.exp) }
@ -321,11 +337,11 @@ private fun LandscapeKeyboard(
val fractionalIcon = remember { if (Formatter.fractional == Token.dot) UnittoIcons.Dot else UnittoIcons.Comma }
var invMode: Boolean by remember { mutableStateOf(false) }
Row(modifier) {
RowWithConstraints(modifier) { constraints ->
val buttonModifier = Modifier
.fillMaxWidth()
.weight(1f)
.padding(4.dp)
.padding(constraints.maxWidth * 0.005f, constraints.maxHeight * 0.02f)
Crossfade(invMode, Modifier.weight(3f)) {
Row {

View File

@ -1,100 +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.calculator.components
import androidx.compose.foundation.text.BasicTextField
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.derivedStateOf
import androidx.compose.runtime.getValue
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.SolidColor
import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalTextInputService
import androidx.compose.ui.platform.LocalTextToolbar
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.ui.Formatter
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
@Composable
internal fun InputTextField(
modifier: Modifier,
value: TextFieldValue,
onCursorChange: (IntRange) -> Unit,
pasteCallback: (String) -> Unit,
cutCallback: () -> Unit
) {
val clipboardManager = LocalClipboardManager.current
val formattedInput: TextFieldValue by remember(value) {
derivedStateOf {
value.copy(
// We replace this because internally input value is already formatted, but uses
// comma as separator.
Formatter.fromSeparator(value.text, Separator.COMMA)
)
}
}
fun copyToClipboard() = clipboardManager.setText(
AnnotatedString(
Formatter.removeGrouping(
formattedInput.annotatedString.subSequence(formattedInput.selection).text
)
)
)
CompositionLocalProvider(
LocalTextInputService provides null,
LocalTextToolbar provides UnittoTextToolbar(
view = LocalView.current,
copyCallback = ::copyToClipboard,
pasteCallback = {
pasteCallback(
Formatter.toSeparator(
clipboardManager.getText()?.text ?: "", Separator.COMMA
)
)
},
cutCallback = { copyToClipboard(); cutCallback() }
)
) {
BasicTextField(
modifier = modifier,
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant),
singleLine = true,
value = formattedInput,
onValueChange = {
onCursorChange(it.selection.start..it.selection.end)
},
textStyle = NumbersTextStyleDisplayLarge.copy(
textAlign = TextAlign.End,
color = MaterialTheme.colorScheme.onSurfaceVariant
),
minLines = 1,
maxLines = 1,
)
}
}

View File

@ -18,6 +18,7 @@
package com.sadellie.unitto.feature.converter
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.MoreVert
@ -30,6 +31,8 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.hilt.navigation.compose.hiltViewModel
import androidx.lifecycle.compose.collectAsStateWithLifecycle
import com.sadellie.unitto.core.ui.R
@ -91,7 +94,7 @@ private fun ConverterScreen(
.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
content = { padding ->
PortraitLandscape(
modifier = Modifier.padding(padding),
modifier = Modifier.padding(padding).fillMaxSize(),
content1 = {
TopScreenPart(
modifier = it,
@ -125,11 +128,26 @@ private fun ConverterScreen(
)
}
@Preview
class PreviewUIState: PreviewParameterProvider<ConverterUIState> {
override val values: Sequence<ConverterUIState>
get() = listOf(
ConverterUIState(inputValue = "1234", calculatedValue = null, resultValue = "5678", showLoading = false),
ConverterUIState(inputValue = "1234", calculatedValue = "234", resultValue = "5678", showLoading = false),
).asSequence()
}
@Preview(widthDp = 432, heightDp = 1008, device = "spec:parent=pixel_5,orientation=portrait")
@Preview(widthDp = 432, heightDp = 864, device = "spec:parent=pixel_5,orientation=portrait")
@Preview(widthDp = 597, heightDp = 1393, device = "spec:parent=pixel_5,orientation=portrait")
@Preview(heightDp = 432, widthDp = 1008, device = "spec:parent=pixel_5,orientation=landscape")
@Preview(heightDp = 432, widthDp = 864, device = "spec:parent=pixel_5,orientation=landscape")
@Preview(heightDp = 597, widthDp = 1393, device = "spec:parent=pixel_5,orientation=landscape")
@Composable
private fun PreviewConverterScreen() {
private fun PreviewConverterScreen(
@PreviewParameter(PreviewUIState::class) uiState: ConverterUIState
) {
ConverterScreen(
uiState = ConverterUIState(),
uiState = ConverterUIState(inputValue = "1234", calculatedValue = null, resultValue = "5678", showLoading = false),
navigateToLeftScreen = {},
navigateToRightScreen = {_, _, _ -> },
navigateToSettings = {},

View File

@ -19,16 +19,16 @@
package com.sadellie.unitto.feature.converter.components
import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.padding
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.base.Token
import com.sadellie.unitto.core.ui.Formatter
import com.sadellie.unitto.core.ui.common.ColumnWithConstraints
import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled
import com.sadellie.unitto.core.ui.common.KeyboardButtonLight
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
@ -85,7 +85,6 @@ internal fun Keyboard(
ConverterMode.BASE -> BaseKeyboard(addDigit, clearInput, deleteDigit, allowVibration)
}
}
}
@Composable
@ -96,39 +95,39 @@ private fun DefaultKeyboard(
allowVibration: Boolean
) {
val fractionalIcon = remember { if (Formatter.fractional == Token.dot) UnittoIcons.Dot else UnittoIcons.Comma }
Column {
ColumnWithConstraints {
// Button modifier
val bModifier = Modifier
.fillMaxSize()
.weight(1f)
.padding(4.dp)
// Column modifier
val cModifier = Modifier.weight(1f)
Row(cModifier) {
val cModifier = Modifier.weight(1f).padding(vertical = it.maxHeight * 0.01f)
val horizontalArrangement = Arrangement.spacedBy(it.maxWidth * 0.03f)
Row(cModifier, horizontalArrangement) {
KeyboardButtonFilled(bModifier, UnittoIcons.LeftBracket, allowVibration) { addDigit(Token.leftBracket) }
KeyboardButtonFilled(bModifier, UnittoIcons.RightBracket, allowVibration) { addDigit(Token.rightBracket) }
KeyboardButtonFilled(bModifier, UnittoIcons.Exponent, allowVibration) { addDigit(Token.exponent) }
KeyboardButtonFilled(bModifier, UnittoIcons.SquareRoot, allowVibration) { addDigit(Token.sqrt) }
}
Row(cModifier) {
Row(cModifier, horizontalArrangement) {
KeyboardButtonLight(bModifier, UnittoIcons.Key7, allowVibration) { addDigit(Token._7) }
KeyboardButtonLight(bModifier, UnittoIcons.Key8, allowVibration) { addDigit(Token._8) }
KeyboardButtonLight(bModifier, UnittoIcons.Key9, allowVibration) { addDigit(Token._9) }
KeyboardButtonFilled(bModifier, UnittoIcons.Divide, allowVibration) { addDigit(Token.divide) }
}
Row(cModifier) {
Row(cModifier, horizontalArrangement) {
KeyboardButtonLight(bModifier, UnittoIcons.Key4, allowVibration) { addDigit(Token._4) }
KeyboardButtonLight(bModifier, UnittoIcons.Key5, allowVibration) { addDigit(Token._5) }
KeyboardButtonLight(bModifier, UnittoIcons.Key6, allowVibration) { addDigit(Token._6) }
KeyboardButtonFilled(bModifier, UnittoIcons.Multiply, allowVibration) { addDigit(Token.multiply) }
}
Row(cModifier) {
Row(cModifier, horizontalArrangement) {
KeyboardButtonLight(bModifier, UnittoIcons.Key1, allowVibration) { addDigit(Token._1) }
KeyboardButtonLight(bModifier, UnittoIcons.Key2, allowVibration) { addDigit(Token._2) }
KeyboardButtonLight(bModifier, UnittoIcons.Key3, allowVibration) { addDigit(Token._3) }
KeyboardButtonFilled(bModifier, UnittoIcons.Minus, allowVibration) { addDigit(Token.minus) }
}
Row(cModifier) {
Row(cModifier, horizontalArrangement) {
KeyboardButtonLight(bModifier, UnittoIcons.Key0, allowVibration) { addDigit(Token._0) }
KeyboardButtonLight(bModifier, fractionalIcon, allowVibration) { addDigit(Token.dot) }
KeyboardButtonLight(bModifier, UnittoIcons.Backspace, allowVibration, clearInput) { deleteDigit() }
@ -144,43 +143,43 @@ private fun BaseKeyboard(
deleteDigit: () -> Unit,
allowVibration: Boolean
) {
Column {
ColumnWithConstraints {
// Button modifier
val bModifier = Modifier
.fillMaxSize()
.weight(1f)
.padding(4.dp)
// Column modifier
val cModifier = Modifier.weight(1f)
Row(cModifier) {
val cModifier = Modifier.weight(1f).padding(vertical = it.maxHeight * 0.01f)
val horizontalArrangement = Arrangement.spacedBy(it.maxWidth * 0.03f)
Row(cModifier, horizontalArrangement) {
KeyboardButtonFilled(bModifier, UnittoIcons.KeyA, allowVibration) { addDigit(Token.baseA) }
KeyboardButtonFilled(bModifier, UnittoIcons.KeyB, allowVibration) { addDigit(Token.baseB) }
KeyboardButtonFilled(bModifier, UnittoIcons.KeyC, allowVibration) { addDigit(Token.baseC) }
}
Row(cModifier) {
Row(cModifier, horizontalArrangement) {
KeyboardButtonFilled(bModifier, UnittoIcons.KeyD, allowVibration) { addDigit(Token.baseD) }
KeyboardButtonFilled(bModifier, UnittoIcons.KeyE, allowVibration) { addDigit(Token.baseE) }
KeyboardButtonFilled(bModifier, UnittoIcons.KeyF, allowVibration) { addDigit(Token.baseF) }
}
Row(cModifier) {
Row(cModifier, horizontalArrangement) {
KeyboardButtonLight(bModifier, UnittoIcons.Key7, allowVibration) { addDigit(Token._7) }
KeyboardButtonLight(bModifier, UnittoIcons.Key8, allowVibration) { addDigit(Token._8) }
KeyboardButtonLight(bModifier, UnittoIcons.Key9, allowVibration) { addDigit(Token._9) }
}
Row(cModifier) {
Row(cModifier, horizontalArrangement) {
KeyboardButtonLight(bModifier, UnittoIcons.Key4, allowVibration) { addDigit(Token._4) }
KeyboardButtonLight(bModifier, UnittoIcons.Key5, allowVibration) { addDigit(Token._5) }
KeyboardButtonLight(bModifier, UnittoIcons.Key6, allowVibration) { addDigit(Token._6) }
}
Row(cModifier) {
Row(cModifier, horizontalArrangement) {
KeyboardButtonLight(bModifier, UnittoIcons.Key1, allowVibration) { addDigit(Token._1) }
KeyboardButtonLight(bModifier, UnittoIcons.Key2, allowVibration) { addDigit(Token._2) }
KeyboardButtonLight(bModifier, UnittoIcons.Key3, allowVibration) { addDigit(Token._3) }
}
Row(cModifier) {
Row(cModifier, horizontalArrangement) {
KeyboardButtonLight(bModifier, UnittoIcons.Key0, allowVibration) { addDigit(Token._0) }
KeyboardButtonLight(Modifier.fillMaxSize().weight(2f).padding(4.dp), UnittoIcons.Backspace, allowVibration, clearInput) { deleteDigit() }
KeyboardButtonLight(
Modifier.fillMaxSize().weight(2f).padding(it.maxWidth * 0.015f, it.maxHeight * 0.008f), UnittoIcons.Backspace, allowVibration, clearInput) { deleteDigit() }
}
}
}

View File

@ -48,11 +48,12 @@ import androidx.compose.ui.platform.LocalClipboardManager
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium
import com.sadellie.unitto.core.ui.R
import com.sadellie.unitto.core.ui.common.textfield.InputTextField
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
/**
* Component for input and output
@ -94,7 +95,8 @@ internal fun MyTextField(
) {
LazyRow(
modifier = modifier
.wrapContentHeight(),
.wrapContentHeight()
.weight(2f),
reverseLayout = true,
horizontalArrangement = Arrangement.End,
contentPadding = PaddingValues(horizontal = 8.dp)
@ -110,19 +112,17 @@ internal fun MyTextField(
.using(SizeTransform(clip = false))
}
) {
Text(
InputTextField(
modifier = Modifier.fillMaxWidth(),
// Quick fix to prevent the UI from crashing
text = it.take(1000),
textAlign = TextAlign.End,
softWrap = false,
style = NumbersTextStyleDisplayLarge
value = TextFieldValue(it.take(1000)),
textStyle = NumbersTextStyleDisplayLarge.copy(textAlign = TextAlign.End)
)
}
}
}
AnimatedVisibility(
modifier = Modifier.weight(1f),
visible = !secondaryText.isNullOrEmpty(),
enter = expandVertically(),
exit = shrinkVertically()
@ -145,14 +145,14 @@ internal fun MyTextField(
.using(SizeTransform(clip = false))
}
) {
Text(
modifier = Modifier,
// Quick fix to prevent the UI from crashing
text = it?.take(1000) ?: "",
textAlign = TextAlign.End,
softWrap = false,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
style = NumbersTextStyleDisplayMedium
InputTextField(
modifier = Modifier.fillMaxWidth(),
value = TextFieldValue(it?.take(1000) ?: ""),
textStyle = NumbersTextStyleDisplayLarge.copy(
textAlign = TextAlign.End,
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)
),
minRatio = 0.7f
)
}
}
@ -162,7 +162,8 @@ internal fun MyTextField(
AnimatedContent(
modifier = Modifier
.align(Alignment.End)
.padding(horizontal = 8.dp),
.padding(horizontal = 8.dp)
.weight(1f),
targetState = helperText
) {
Text(

View File

@ -18,18 +18,29 @@
package com.sadellie.unitto.feature.converter.components
import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.SizeTransform
import androidx.compose.animation.core.FastOutSlowInEasing
import androidx.compose.animation.core.animateFloatAsState
import androidx.compose.animation.core.tween
import androidx.compose.animation.expandHorizontally
import androidx.compose.animation.expandVertically
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.shrinkVertically
import androidx.compose.animation.with
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.outlined.SwapHoriz
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@ -40,9 +51,12 @@ import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.rotate
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.input.TextFieldValue
import androidx.compose.ui.text.style.TextAlign
import androidx.compose.ui.unit.dp
import com.sadellie.unitto.core.ui.Formatter
import com.sadellie.unitto.core.ui.R
import com.sadellie.unitto.core.ui.common.textfield.InputTextField
import com.sadellie.unitto.data.model.AbstractUnit
import com.sadellie.unitto.data.model.UnitGroup
import com.sadellie.unitto.feature.converter.ConverterMode
@ -95,22 +109,49 @@ internal fun TopScreenPart(
modifier = modifier,
verticalArrangement = Arrangement.spacedBy(8.dp)
) {
MyTextField(
modifier = Modifier.fillMaxWidth(),
primaryText = {
InputTextField(
modifier = Modifier.weight(2f),
value = TextFieldValue(
when (converterMode) {
ConverterMode.BASE -> inputValue.uppercase()
else -> Formatter.format(inputValue)
}
},
secondaryText = calculatedValue?.let { Formatter.format(it) },
helperText = stringResource(unitFrom?.shortName ?: R.string.loading_label),
textToCopy = calculatedValue ?: inputValue,
),
minRatio = 0.7f
)
MyTextField(
AnimatedVisibility(
visible = !calculatedValue.isNullOrEmpty(),
modifier = Modifier.weight(1f),
enter = expandVertically(clip = false),
exit = shrinkVertically(clip = false)
) {
InputTextField(
value = TextFieldValue(calculatedValue?.let { Formatter.format(it) } ?: ""),
minRatio = 0.7f
)
}
AnimatedContent(
modifier = Modifier.fillMaxWidth(),
onClick = onOutputTextFieldClick,
primaryText = {
targetState = stringResource(unitFrom?.shortName ?: R.string.loading_label),
transitionSpec = {
// Enter animation
(expandHorizontally(clip = false, expandFrom = Alignment.Start) + fadeIn()
// Exit animation
with fadeOut())
.using(SizeTransform(clip = false))
}
) {
Text(
text = it,
style = MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.End)
)
}
InputTextField(
modifier = Modifier
.weight(2f)
.clickable { onOutputTextFieldClick() },
value = TextFieldValue(
when {
networkLoading -> stringResource(R.string.loading_label)
networkError -> stringResource(R.string.error_label)
@ -124,16 +165,27 @@ internal fun TopScreenPart(
}
else -> Formatter.format(outputValue)
}
},
secondaryText = null,
helperText = stringResource(unitTo?.shortName ?: R.string.loading_label),
textToCopy = outputValue,
),
minRatio = 0.7f
)
// Unit selection buttons
Row(
modifier = Modifier.padding(horizontal = 8.dp),
verticalAlignment = Alignment.Bottom,
AnimatedContent(
modifier = Modifier.fillMaxWidth(),
targetState = stringResource(unitTo?.shortName ?: R.string.loading_label),
transitionSpec = {
// Enter animation
(expandHorizontally(clip = false, expandFrom = Alignment.Start) + fadeIn()
// Exit animation
with fadeOut())
.using(SizeTransform(clip = false))
}
) {
Text(
text = it,
style = MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.End)
)
}
Row(verticalAlignment = Alignment.CenterVertically) {
UnitSelectionButton(
modifier = Modifier
.fillMaxWidth()