From 4c20c6c0b882c23e902c08ff5dba876fd5872ff5 Mon Sep 17 00:00:00 2001 From: Sad Ellie Date: Thu, 30 Mar 2023 12:17:33 +0300 Subject: [PATCH] Fixes for responsive ui squashed commit closes: #24 and #34 --- .../unitto/core/ui/common/KeyboardButton.kt | 9 +- .../core/ui/common/PortraitLandscape.kt | 15 +- .../ui/common/textfield/InputTextField.kt | 143 ++++++++++++------ .../ui/common/textfield/UnittoTextToolbar.kt | 6 +- .../feature/calculator/CalculatorScreen.kt | 70 +++------ .../components/CalculatorKeyboard.kt | 17 ++- .../calculator/components/HistoryList.kt | 110 ++++++++------ .../converter/components/MyTextField.kt | 5 +- .../feature/converter/components/TopScreen.kt | 69 ++++----- 9 files changed, 243 insertions(+), 201 deletions(-) diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/KeyboardButton.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/KeyboardButton.kt index 58ab5577..ae5e1551 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/KeyboardButton.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/KeyboardButton.kt @@ -27,11 +27,9 @@ import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.interaction.collectIsPressedAsState import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.fillMaxHeight -import androidx.compose.foundation.layout.heightIn import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.getValue @@ -41,7 +39,6 @@ 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 @Composable fun BasicKeyboardButton( @@ -133,16 +130,14 @@ fun KeyboardButtonAdditional( onClick: () -> Unit ) { BasicKeyboardButton( - modifier = modifier - .minimumInteractiveComponentSize() - .heightIn(max = 48.dp), + modifier = modifier, onClick = onClick, onLongClick = onLongClick, containerColor = Color.Transparent, icon = icon, iconColor = MaterialTheme.colorScheme.onSurfaceVariant, allowVibration = allowVibration, - contentHeight = if (isPortrait()) 0.9f else 0.85f + contentHeight = if (isPortrait()) 0.8f else 0.85f ) } diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/PortraitLandscape.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/PortraitLandscape.kt index 0e5847f3..74d20f99 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/PortraitLandscape.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/PortraitLandscape.kt @@ -27,6 +27,7 @@ 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]. @@ -41,13 +42,19 @@ fun PortraitLandscape( ) { if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) { 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)) + content1(Modifier.fillMaxHeight(0.38f).padding(horizontal = it.maxWidth * 0.03f)) + content2(Modifier.fillMaxSize().padding(it.maxWidth * 0.03f, it.maxHeight * 0.015f)) } } else { 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)) + val contentModifier = Modifier + .weight(1f) + .fillMaxSize() + .padding( + it.maxWidth * 0.015f, 0.dp, + it.maxHeight * 0.03f, it.maxHeight * 0.03f) + content1(contentModifier) + content2(contentModifier) } } } diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/InputTextField.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/InputTextField.kt index 0d735ad6..7ef702a6 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/InputTextField.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/InputTextField.kt @@ -18,6 +18,8 @@ package com.sadellie.unitto.core.ui.common.textfield +import androidx.compose.foundation.clickable +import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.layout.BoxWithConstraints import androidx.compose.foundation.layout.widthIn import androidx.compose.foundation.text.BasicTextField @@ -30,10 +32,16 @@ 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.focus.FocusRequester +import androidx.compose.ui.focus.focusRequester +import androidx.compose.ui.geometry.Offset +import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.layout.layout +import androidx.compose.ui.layout.onGloballyPositioned +import androidx.compose.ui.layout.positionInWindow +import androidx.compose.ui.platform.ClipboardManager import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.platform.LocalDensity @@ -42,6 +50,7 @@ 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.TextRange import androidx.compose.ui.text.TextStyle import androidx.compose.ui.text.font.createFontFamilyResolver import androidx.compose.ui.text.input.TextFieldValue @@ -66,40 +75,39 @@ fun InputTextField( textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, ) { val clipboardManager = LocalClipboardManager.current - fun copyCallback() { - clipboardManager.setText( - AnnotatedString( - Formatter.removeGrouping( - value.annotatedString.subSequence(value.selection).text + fun copyCallback() = clipboardManager.copyWithoutGrouping(value) + + val textToolbar = UnittoTextToolbar( + view = LocalView.current, + copyCallback = ::copyCallback, + pasteCallback = { + pasteCallback( + Formatter.toSeparator( + clipboardManager.getText()?.text ?: "", Separator.COMMA ) ) - ) - } + }, + cutCallback = { + copyCallback() + cutCallback() + onCursorChange(value.selection.end..value.selection.end) + } + ) CompositionLocalProvider( LocalTextInputService provides null, - LocalTextToolbar provides UnittoTextToolbar( - view = LocalView.current, - copyCallback = ::copyCallback, - pasteCallback = { - pasteCallback( - Formatter.toSeparator( - clipboardManager.getText()?.text ?: "", Separator.COMMA - ) - ) - }, - cutCallback = { copyCallback(); cutCallback() } - ) + LocalTextToolbar provides textToolbar ) { AutoSizableTextField( modifier = modifier, value = value, + textStyle = textStyle.copy(color = textColor), + minRatio = minRatio, onValueChange = { onCursorChange(it.selection.start..it.selection.end) }, - textStyle = textStyle, - minRatio = minRatio, - readOnly = false + showToolbar = textToolbar::showMenu, + hideToolbar = textToolbar::hide ) } } @@ -107,34 +115,54 @@ fun InputTextField( @Composable fun InputTextField( modifier: Modifier = Modifier, - value: TextFieldValue, + value: String, textStyle: TextStyle = NumbersTextStyleDisplayLarge, minRatio: Float = 1f, textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } ) { - AutoSizableTextField( - modifier = modifier, - value = value, - textStyle = textStyle, - minRatio = minRatio, - readOnly = true - ) + var textFieldValue by remember(value) { + mutableStateOf(TextFieldValue(value, selection = TextRange(value.length))) + } + val clipboardManager = LocalClipboardManager.current + fun copyCallback() { + clipboardManager.copyWithoutGrouping(textFieldValue) + textFieldValue = textFieldValue.copy(selection = TextRange(textFieldValue.selection.end)) + } + + CompositionLocalProvider( + LocalTextInputService provides null, + LocalTextToolbar provides UnittoTextToolbar( + view = LocalView.current, + copyCallback = ::copyCallback, + ) + ) { + AutoSizableTextField( + modifier = modifier, + value = textFieldValue, + onValueChange = { textFieldValue = it }, + textStyle = textStyle.copy(color = textColor), + minRatio = minRatio, + readOnly = true, + interactionSource = interactionSource + ) + } } @Composable private fun AutoSizableTextField( modifier: Modifier = Modifier, value: TextFieldValue, - onValueChange: (TextFieldValue) -> Unit = {}, textStyle: TextStyle = TextStyle(), scaleFactor: Float = 0.95f, minRatio: Float = 1f, + onValueChange: (TextFieldValue) -> Unit, readOnly: Boolean = false, - textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant, - cursorBrush: Brush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant) + showToolbar: (rect: Rect) -> Unit = {}, + hideToolbar: () -> Unit = {}, + interactionSource: MutableInteractionSource = remember { MutableInteractionSource() } ) { - // FIXME Acts strange when minRatio is set to 0 (still scales down). - + val focusRequester = remember { FocusRequester() } val density = LocalDensity.current var nFontSize: TextUnit by remember { mutableStateOf(0.sp) } @@ -178,18 +206,30 @@ private fun AutoSizableTextField( val nTextStyle = textStyle.copy( // https://issuetracker.google.com/issues/266470454 // textAlign = TextAlign.End, - color = textColor, fontSize = nFontSize ) + var offset = Offset.Zero BasicTextField( value = value, - singleLine = true, - onValueChange = onValueChange, + onValueChange = { + showToolbar(Rect(offset, 0f)) + hideToolbar() + onValueChange(it) + }, modifier = Modifier - .widthIn( - max = with(density) { intrinsics.width.toDp() } + .focusRequester(focusRequester) + .clickable( + interactionSource = remember { MutableInteractionSource() }, + indication = null, + onClick = { + hideToolbar() + focusRequester.requestFocus() + onValueChange(value.copy(selection = TextRange.Zero)) + showToolbar(Rect(offset, 0f)) + } ) + .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. @@ -201,10 +241,27 @@ private fun AutoSizableTextField( y = (placeable.height - intrinsics.height).roundToInt() ) } - }, + } + .onGloballyPositioned { layoutCoords -> offset = layoutCoords.positionInWindow() }, textStyle = nTextStyle, + cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant), + singleLine = true, readOnly = readOnly, - cursorBrush = cursorBrush + interactionSource = interactionSource ) } } + +/** + * Copy value to clipboard without grouping symbols. + * + * Example: + * "123.456,789" will be copied as "123456,789" + * + * @param value Formatted value that has grouping symbols. + */ +fun ClipboardManager.copyWithoutGrouping(value: TextFieldValue) = this.setText( + AnnotatedString( + Formatter.removeGrouping(value.annotatedString.subSequence(value.selection).text) + ) +) diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoTextToolbar.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoTextToolbar.kt index d5edd057..93bd577b 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoTextToolbar.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoTextToolbar.kt @@ -45,10 +45,8 @@ class UnittoTextToolbar( onSelectAllRequested: (() -> Unit)? ) { textActionModeCallback.rect = rect - textActionModeCallback.onCopyRequested = { onCopyRequested?.invoke(); copyCallback.invoke() } - textActionModeCallback.onCutRequested = cutCallback?.let { - { it.invoke(); onCutRequested?.invoke() } - } + textActionModeCallback.onCopyRequested = copyCallback + textActionModeCallback.onCutRequested = cutCallback textActionModeCallback.onPasteRequested = pasteCallback textActionModeCallback.onSelectAllRequested = onSelectAllRequested if (actionMode == null) { diff --git a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/CalculatorScreen.kt b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/CalculatorScreen.kt index 65782103..bc8990eb 100644 --- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/CalculatorScreen.kt +++ b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/CalculatorScreen.kt @@ -35,7 +35,6 @@ import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.sizeIn import androidx.compose.foundation.shape.RoundedCornerShape -import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Delete import androidx.compose.material.icons.outlined.MoreVert @@ -47,7 +46,6 @@ import androidx.compose.material3.Text import androidx.compose.material3.TextButton import androidx.compose.material3.TopAppBarDefaults 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 @@ -57,12 +55,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.layout.onPlaced -import androidx.compose.ui.platform.LocalClipboardManager import androidx.compose.ui.platform.LocalConfiguration -import androidx.compose.ui.platform.LocalTextToolbar -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.tooling.preview.Preview import androidx.compose.ui.unit.dp @@ -73,7 +67,6 @@ 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.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 @@ -90,39 +83,20 @@ internal fun CalculatorRoute( navigateToSettings: () -> Unit, viewModel: CalculatorViewModel = hiltViewModel() ) { - val clipboardManager = LocalClipboardManager.current val uiState = viewModel.uiState.collectAsStateWithLifecycle() - fun copyToClipboard() { - val clipboardText = clipboardManager.getText() ?: return - // This method is called immediately after copying formatted text, we replace it with the - // the unformatted version. - clipboardManager.setText( - AnnotatedString( - clipboardText.text.replace(Formatter.grouping, "") - ) - ) - } - - CompositionLocalProvider( - LocalTextToolbar provides UnittoTextToolbar( - view = LocalView.current, - copyCallback = ::copyToClipboard - ) - ) { - CalculatorScreen( - uiState = uiState.value, - navigateToMenu = navigateToMenu, - navigateToSettings = navigateToSettings, - addSymbol = viewModel::addSymbol, - clearSymbols = viewModel::clearSymbols, - deleteSymbol = viewModel::deleteSymbol, - onCursorChange = viewModel::onCursorChange, - toggleAngleMode = viewModel::toggleCalculatorMode, - evaluate = viewModel::evaluate, - clearHistory = viewModel::clearHistory - ) - } + CalculatorScreen( + uiState = uiState.value, + navigateToMenu = navigateToMenu, + navigateToSettings = navigateToSettings, + addSymbol = viewModel::addSymbol, + clearSymbols = viewModel::clearSymbols, + deleteSymbol = viewModel::deleteSymbol, + onCursorChange = viewModel::onCursorChange, + toggleAngleMode = viewModel::toggleCalculatorMode, + evaluate = viewModel::evaluate, + clearHistory = viewModel::clearHistory + ) } @Composable @@ -186,7 +160,6 @@ private fun CalculatorScreen( .fillMaxSize(), historyItems = uiState.history, historyItemHeightCallback = { historyItemHeight = it }, - onTextClick = addSymbol ) }, textFields = { maxDragAmount -> @@ -243,17 +216,14 @@ private fun CalculatorScreen( onCursorChange = onCursorChange ) if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) { - SelectionContainer(Modifier.weight(1f)) { - InputTextField( - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp), - value = TextFieldValue( - Formatter.fromSeparator(uiState.output, Separator.COMMA) - ), - textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.6f) - ) - } + InputTextField( + modifier = Modifier + .weight(1f) + .fillMaxWidth() + .padding(horizontal = 8.dp), + value = Formatter.format(uiState.output), + textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.6f) + ) } // Handle Box( diff --git a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/CalculatorKeyboard.kt b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/CalculatorKeyboard.kt index c49b3eb3..16d1f1a6 100644 --- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/CalculatorKeyboard.kt +++ b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/CalculatorKeyboard.kt @@ -33,13 +33,13 @@ import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.ExpandLess import androidx.compose.material3.Icon import androidx.compose.material3.IconButton import androidx.compose.material3.IconButtonDefaults import androidx.compose.material3.MaterialTheme -import androidx.compose.material3.minimumInteractiveComponentSize import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf @@ -164,13 +164,12 @@ private fun PortraitKeyboard( val mainButtonModifier = Modifier .fillMaxSize() .weight(1f) - .padding(horizontalFraction(0.015f), verticalFraction(0.008f)) + .padding(horizontalFraction(0.015f), verticalFraction(0.009f)) val additionalButtonModifier = Modifier - .minimumInteractiveComponentSize() .weight(1f) - .height(verticalFraction(0.075f)) + .height(verticalFraction(0.09f)) - Spacer(modifier = Modifier.height(verticalFraction(0.015f))) + Spacer(modifier = Modifier.height(verticalFraction(0.025f))) Row( modifier = Modifier, @@ -201,7 +200,10 @@ private fun PortraitKeyboard( } } - Box(modifier = Modifier.height(verticalFraction(0.075f)), contentAlignment = Alignment.Center) { + Box( + modifier = Modifier.size(verticalFraction(0.09f)), + contentAlignment = Alignment.Center + ) { // Expand/Collapse IconButton( onClick = { showAdditional = !showAdditional }, @@ -212,7 +214,7 @@ private fun PortraitKeyboard( } } - Spacer(modifier = Modifier.height(verticalFraction(0.015f))) + Spacer(modifier = Modifier.height(verticalFraction(0.025f))) Row(weightModifier) { KeyboardButtonFilled(mainButtonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.leftBracket) } @@ -220,7 +222,6 @@ private fun PortraitKeyboard( KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Percent, allowVibration) { addSymbol(Token.percent) } KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Divide, allowVibration) { addSymbol(Token.divideDisplay) } } - Row(weightModifier) { KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key7, allowVibration) { addSymbol(Token._7) } KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key8, allowVibration) { addSymbol(Token._8) } diff --git a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/HistoryList.kt b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/HistoryList.kt index b6ab6402..7f68594c 100644 --- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/HistoryList.kt +++ b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/HistoryList.kt @@ -19,10 +19,8 @@ package com.sadellie.unitto.feature.calculator.components import androidx.compose.foundation.background -import androidx.compose.foundation.clickable 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.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth @@ -30,24 +28,35 @@ import androidx.compose.foundation.layout.padding import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.items import androidx.compose.foundation.rememberScrollState -import androidx.compose.foundation.text.selection.SelectionContainer +import androidx.compose.foundation.text.BasicTextField import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.History import androidx.compose.material3.Icon import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.derivedStateOf 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.layout.onPlaced +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.res.stringResource +import androidx.compose.ui.text.TextRange +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 com.sadellie.unitto.core.ui.Formatter +import com.sadellie.unitto.core.ui.common.textfield.UnittoTextToolbar +import com.sadellie.unitto.core.ui.common.textfield.copyWithoutGrouping import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium import com.sadellie.unitto.data.model.HistoryItem import com.sadellie.unitto.feature.calculator.R @@ -59,7 +68,6 @@ internal fun HistoryList( modifier: Modifier, historyItems: List, historyItemHeightCallback: (Int) -> Unit, - onTextClick: (String) -> Unit ) { val verticalArrangement by remember(historyItems) { derivedStateOf { @@ -95,15 +103,13 @@ internal fun HistoryList( item { HistoryListItem( modifier = Modifier.onPlaced { historyItemHeightCallback(it.size.height) }, - historyItem = historyItems.first(), - onTextClick = onTextClick + historyItem = historyItems.first() ) } items(historyItems.drop(1)) { historyItem -> HistoryListItem( modifier = Modifier, - historyItem = historyItem, - onTextClick = onTextClick + historyItem = historyItem ) } } @@ -114,40 +120,56 @@ internal fun HistoryList( private fun HistoryListItem( modifier: Modifier = Modifier, historyItem: HistoryItem, - onTextClick: (String) -> Unit ) { - SelectionContainer { - Column(modifier = modifier) { - Box( - Modifier.clickable { onTextClick(historyItem.expression) } - ) { - Text( - text = Formatter.format(historyItem.expression), - maxLines = 1, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp) - .horizontalScroll(rememberScrollState(), reverseScrolling = true), - style = NumbersTextStyleDisplayMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant, - textAlign = TextAlign.End - ) - } - Box( - Modifier.clickable { onTextClick(historyItem.result) } - ) { - Text( - text = Formatter.format(historyItem.result), - maxLines = 1, - modifier = Modifier - .fillMaxWidth() - .padding(horizontal = 8.dp) - .horizontalScroll(rememberScrollState(), reverseScrolling = true), - style = NumbersTextStyleDisplayMedium, - color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f), - textAlign = TextAlign.End - ) - } + val clipboardManager = LocalClipboardManager.current + val expr = Formatter.format(historyItem.expression) + var textFieldexpr by remember(expr) { + mutableStateOf(TextFieldValue(expr, selection = TextRange(expr.length))) + } + val res = Formatter.format(historyItem.expression) + var textFieldRes by remember(res) { + mutableStateOf(TextFieldValue(res, selection = TextRange(res.length))) + } + + Column(modifier = modifier) { + CompositionLocalProvider( + LocalTextInputService provides null, + LocalTextToolbar provides UnittoTextToolbar( + view = LocalView.current, + copyCallback = { clipboardManager.copyWithoutGrouping(textFieldexpr) } + ) + ) { + BasicTextField( + value = textFieldexpr, + onValueChange = { textFieldexpr = it }, + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .horizontalScroll(rememberScrollState(), reverseScrolling = true), + textStyle = NumbersTextStyleDisplayMedium.copy(color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.End), + readOnly = true + ) + } + + CompositionLocalProvider( + LocalTextInputService provides null, + LocalTextToolbar provides UnittoTextToolbar( + view = LocalView.current, + copyCallback = { clipboardManager.copyWithoutGrouping(textFieldRes) } + ) + ) { + BasicTextField( + value = textFieldRes, + onValueChange = { textFieldRes = it }, + maxLines = 1, + modifier = Modifier + .fillMaxWidth() + .padding(horizontal = 8.dp) + .horizontalScroll(rememberScrollState(), reverseScrolling = true), + textStyle = NumbersTextStyleDisplayMedium.copy(color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f), textAlign = TextAlign.End), + readOnly = true + ) } } } @@ -178,8 +200,6 @@ private fun PreviewHistoryList() { modifier = Modifier .background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f)) .fillMaxSize(), - historyItems = historyItems, - historyItemHeightCallback = {}, - onTextClick = {} - ) + historyItems = historyItems + ) {} } diff --git a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/MyTextField.kt b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/MyTextField.kt index 94ca5b8d..e344d063 100644 --- a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/MyTextField.kt +++ b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/MyTextField.kt @@ -48,7 +48,6 @@ 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.R @@ -114,7 +113,7 @@ internal fun MyTextField( ) { InputTextField( modifier = Modifier.fillMaxWidth(), - value = TextFieldValue(it.take(1000)), + value = it.take(1000), textStyle = NumbersTextStyleDisplayLarge.copy(textAlign = TextAlign.End) ) } @@ -147,7 +146,7 @@ internal fun MyTextField( ) { InputTextField( modifier = Modifier.fillMaxWidth(), - value = TextFieldValue(it?.take(1000) ?: ""), + value = it?.take(1000) ?: "", textStyle = NumbersTextStyleDisplayLarge.copy( textAlign = TextAlign.End, color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) diff --git a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/TopScreen.kt b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/TopScreen.kt index 95cbfcab..aa40fe21 100644 --- a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/TopScreen.kt +++ b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/TopScreen.kt @@ -30,11 +30,10 @@ 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.Spacer import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.material.icons.Icons import androidx.compose.material.icons.outlined.SwapHoriz import androidx.compose.material3.Icon @@ -51,11 +50,10 @@ 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.ColumnWithConstraints import com.sadellie.unitto.core.ui.common.textfield.InputTextField import com.sadellie.unitto.data.model.AbstractUnit import com.sadellie.unitto.data.model.UnitGroup @@ -105,18 +103,15 @@ internal fun TopScreenPart( ) val mContext = LocalContext.current - Column( + ColumnWithConstraints( modifier = modifier, - verticalArrangement = Arrangement.spacedBy(8.dp) ) { InputTextField( modifier = Modifier.weight(2f), - value = TextFieldValue( - when (converterMode) { - ConverterMode.BASE -> inputValue.uppercase() - else -> Formatter.format(inputValue) - } - ), + value = when (converterMode) { + ConverterMode.BASE -> inputValue.uppercase() + else -> Formatter.format(inputValue) + }, minRatio = 0.7f ) AnimatedVisibility( @@ -126,8 +121,9 @@ internal fun TopScreenPart( exit = shrinkVertically(clip = false) ) { InputTextField( - value = TextFieldValue(calculatedValue?.let { Formatter.format(it) } ?: ""), - minRatio = 0.7f + value = calculatedValue?.let { value -> Formatter.format(value) } ?: "", + minRatio = 0.7f, + textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f) ) } AnimatedContent( @@ -140,33 +136,30 @@ internal fun TopScreenPart( with fadeOut()) .using(SizeTransform(clip = false)) } - ) { + ) { value -> Text( - text = it, + text = value, 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) - converterMode == ConverterMode.BASE -> outputValue.uppercase() - formatTime and (unitTo?.group == UnitGroup.TIME) -> { - Formatter.formatTime( - context = mContext, - input = calculatedValue ?: inputValue, - basicUnit = unitFrom?.basicUnit - ) - } - else -> Formatter.format(outputValue) + .weight(2f), + value = when { + networkLoading -> stringResource(R.string.loading_label) + networkError -> stringResource(R.string.error_label) + converterMode == ConverterMode.BASE -> outputValue.uppercase() + formatTime and (unitTo?.group == UnitGroup.TIME) -> { + Formatter.formatTime( + context = mContext, + input = calculatedValue ?: inputValue, + basicUnit = unitFrom?.basicUnit + ) } - ), - minRatio = 0.7f + else -> Formatter.format(outputValue) + }, + minRatio = 0.7f, ) AnimatedContent( modifier = Modifier.fillMaxWidth(), @@ -178,19 +171,21 @@ internal fun TopScreenPart( with fadeOut()) .using(SizeTransform(clip = false)) } - ) { + ) { value -> Text( - text = it, + text = value, style = MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.End) ) } + Spacer(modifier = Modifier.height(it.maxHeight * 0.03f)) + Row(verticalAlignment = Alignment.CenterVertically) { UnitSelectionButton( modifier = Modifier .fillMaxWidth() .weight(1f), - onClick = { unitFrom?.let { navigateToLeftScreen(it.unitId) } }, + onClick = { unitFrom?.let { unit -> navigateToLeftScreen(unit.unitId) } }, label = unitFrom?.displayName ?: R.string.loading_label, ) IconButton(