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 acbf9a08..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
@@ -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
@@ -26,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
@@ -38,8 +37,8 @@ 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
@Composable
fun BasicKeyboardButton(
@@ -50,7 +49,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 +65,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 +97,7 @@ fun KeyboardButtonLight(
icon = icon,
iconColor = MaterialTheme.colorScheme.onSurfaceVariant,
allowVibration = allowVibration,
+ contentHeight = if (isPortrait()) 0.5f else 0.85f
)
}
@@ -111,7 +116,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 +129,17 @@ fun KeyboardButtonAdditional(
onLongClick: (() -> Unit)? = null,
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,
- contentPadding = PaddingValues(12.dp, 2.dp),
- onLongClick = onLongClick
+ contentHeight = if (isPortrait()) 0.8f else 0.85f
)
}
+
+@Composable
+private fun isPortrait() = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
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 abf7d731..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
@@ -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
@@ -42,33 +41,20 @@ 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.38f).padding(horizontal = it.maxWidth * 0.03f))
+ content2(Modifier.fillMaxSize().padding(it.maxWidth * 0.03f, 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) {
+ 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/WithConstraints.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/WithConstraints.kt
new file mode 100644
index 00000000..09dc46d0
--- /dev/null
+++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/WithConstraints.kt
@@ -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 .
+ */
+
+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) }
+}
diff --git a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/FloatingTextActionModeCallback.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/FloatingTextActionModeCallback.kt
similarity index 97%
rename from feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/FloatingTextActionModeCallback.kt
rename to core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/FloatingTextActionModeCallback.kt
index ab6711d8..07bbac80 100644
--- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/FloatingTextActionModeCallback.kt
+++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/FloatingTextActionModeCallback.kt
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package com.sadellie.unitto.feature.calculator.components
+package com.sadellie.unitto.core.ui.common.textfield
import android.view.ActionMode
import android.view.Menu
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
new file mode 100644
index 00000000..7ef702a6
--- /dev/null
+++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/InputTextField.kt
@@ -0,0 +1,267 @@
+/*
+ * 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 .
+ */
+
+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
+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.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
+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.TextRange
+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.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 textToolbar
+ ) {
+ AutoSizableTextField(
+ modifier = modifier,
+ value = value,
+ textStyle = textStyle.copy(color = textColor),
+ minRatio = minRatio,
+ onValueChange = {
+ onCursorChange(it.selection.start..it.selection.end)
+ },
+ showToolbar = textToolbar::showMenu,
+ hideToolbar = textToolbar::hide
+ )
+ }
+}
+
+@Composable
+fun InputTextField(
+ modifier: Modifier = Modifier,
+ value: String,
+ textStyle: TextStyle = NumbersTextStyleDisplayLarge,
+ minRatio: Float = 1f,
+ textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+) {
+ 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,
+ textStyle: TextStyle = TextStyle(),
+ scaleFactor: Float = 0.95f,
+ minRatio: Float = 1f,
+ onValueChange: (TextFieldValue) -> Unit,
+ readOnly: Boolean = false,
+ showToolbar: (rect: Rect) -> Unit = {},
+ hideToolbar: () -> Unit = {},
+ interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
+) {
+ val focusRequester = remember { FocusRequester() }
+ 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,
+ fontSize = nFontSize
+ )
+ var offset = Offset.Zero
+
+ BasicTextField(
+ value = value,
+ onValueChange = {
+ showToolbar(Rect(offset, 0f))
+ hideToolbar()
+ onValueChange(it)
+ },
+ modifier = Modifier
+ .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.
+ layout(placeable.width, placeable.height) {
+ placeable.place(
+ x = (intrinsics.width - intrinsics.maxIntrinsicWidth)
+ .coerceAtLeast(0f)
+ .roundToInt(),
+ y = (placeable.height - intrinsics.height).roundToInt()
+ )
+ }
+ }
+ .onGloballyPositioned { layoutCoords -> offset = layoutCoords.positionInWindow() },
+ textStyle = nTextStyle,
+ cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant),
+ singleLine = true,
+ readOnly = readOnly,
+ 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/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoActionModeCallback.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoActionModeCallback.kt
similarity index 97%
rename from feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoActionModeCallback.kt
rename to core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoActionModeCallback.kt
index bb18c16f..f6b8b480 100644
--- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoActionModeCallback.kt
+++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoActionModeCallback.kt
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package com.sadellie.unitto.feature.calculator.components
+package com.sadellie.unitto.core.ui.common.textfield
import android.view.ActionMode
import android.view.Menu
diff --git a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoPrimaryTextActionModeCallback.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoPrimaryTextActionModeCallback.kt
similarity index 96%
rename from feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoPrimaryTextActionModeCallback.kt
rename to core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoPrimaryTextActionModeCallback.kt
index 21750f3a..391c6687 100644
--- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoPrimaryTextActionModeCallback.kt
+++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoPrimaryTextActionModeCallback.kt
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-package com.sadellie.unitto.feature.calculator.components
+package com.sadellie.unitto.core.ui.common.textfield
import android.view.ActionMode
import android.view.Menu
diff --git a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoTextToolbar.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoTextToolbar.kt
similarity index 89%
rename from feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoTextToolbar.kt
rename to core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoTextToolbar.kt
index 46d32b8c..93bd577b 100644
--- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/UnittoTextToolbar.kt
+++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/textfield/UnittoTextToolbar.kt
@@ -16,7 +16,7 @@
* along with this program. If not, see .
*/
-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,
@@ -45,10 +45,8 @@ internal 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/core/ui/src/main/java/com/sadellie/unitto/core/ui/theme/Type.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/theme/Type.kt
index 19dc898a..5b90a70e 100644
--- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/theme/Type.kt
+++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/theme/Type.kt
@@ -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,
)
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 d45f567a..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
@@ -26,17 +26,15 @@ 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
import androidx.compose.material.icons.filled.Delete
import androidx.compose.material.icons.outlined.MoreVert
@@ -48,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
@@ -58,28 +55,22 @@ 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.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.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.*
@@ -92,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
@@ -188,18 +160,18 @@ private fun CalculatorScreen(
.fillMaxSize(),
historyItems = uiState.history,
historyItemHeightCallback = { historyItemHeight = it },
- onTextClick = addSymbol
)
},
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,33 +198,32 @@ 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(
- 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,
- )
- }
+ InputTextField(
+ modifier = Modifier
+ .weight(1f)
+ .fillMaxWidth()
+ .padding(horizontal = 8.dp),
+ value = Formatter.format(uiState.output),
+ textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.6f)
+ )
}
// Handle
Box(
@@ -268,7 +239,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),
radianMode = uiState.radianMode,
allowVibration = uiState.allowVibration,
addSymbol = addSymbol,
@@ -314,7 +285,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 +308,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 = {},
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 54357868..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
@@ -25,34 +25,39 @@ 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.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
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
@@ -149,22 +154,26 @@ 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.009f))
val additionalButtonModifier = Modifier
- .minimumInteractiveComponentSize()
.weight(1f)
- .heightIn(max = 48.dp)
+ .height(verticalFraction(0.09f))
+
+ Spacer(modifier = Modifier.height(verticalFraction(0.025f)))
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) {
@@ -191,22 +200,28 @@ private fun PortraitKeyboard(
}
}
- // Expand/Collapse
- IconButton(
- onClick = { showAdditional = !showAdditional },
- colors = IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.inverseOnSurface)
+ Box(
+ modifier = Modifier.size(verticalFraction(0.09f)),
+ contentAlignment = Alignment.Center
) {
- Icon(Icons.Default.ExpandLess, null, Modifier.rotate(expandRotation))
+ // 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.025f)))
+
Row(weightModifier) {
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.leftBracket) }
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.RightBracket, allowVibration) { addSymbol(Token.rightBracket) }
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) }
@@ -231,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)))
}
}
@@ -245,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) }
@@ -253,13 +270,13 @@ private fun AdditionalButtonsPortrait(
}
AnimatedVisibility(showAdditional) {
Column {
- Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
+ Row {
KeyboardButtonAdditional(modifier, if (radianMode) UnittoIcons.Rad else UnittoIcons.Deg, 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) }
@@ -281,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) }
@@ -289,13 +306,13 @@ private fun AdditionalButtonsPortraitInverse(
}
AnimatedVisibility(showAdditional) {
Column {
- Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
+ Row {
KeyboardButtonAdditional(modifier, if (radianMode) UnittoIcons.Rad else UnittoIcons.Deg, 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) }
@@ -320,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 {
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/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/InputTextField.kt b/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/InputTextField.kt
deleted file mode 100644
index e225f790..00000000
--- a/feature/calculator/src/main/java/com/sadellie/unitto/feature/calculator/components/InputTextField.kt
+++ /dev/null
@@ -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 .
- */
-
-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,
- )
- }
-}
diff --git a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterScreen.kt b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterScreen.kt
index f2fe804c..697ba53e 100644
--- a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterScreen.kt
+++ b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/ConverterScreen.kt
@@ -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 {
+ override val values: Sequence
+ 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 = {},
diff --git a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/Keyboard.kt b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/Keyboard.kt
index 7c7bbd63..e999fe20 100644
--- a/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/Keyboard.kt
+++ b/feature/converter/src/main/java/com/sadellie/unitto/feature/converter/components/Keyboard.kt
@@ -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() }
}
}
}
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 675dbba3..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
@@ -50,9 +50,9 @@ import androidx.compose.ui.res.stringResource
import androidx.compose.ui.text.AnnotatedString
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 +94,8 @@ internal fun MyTextField(
) {
LazyRow(
modifier = modifier
- .wrapContentHeight(),
+ .wrapContentHeight()
+ .weight(2f),
reverseLayout = true,
horizontalArrangement = Arrangement.End,
contentPadding = PaddingValues(horizontal = 8.dp)
@@ -110,19 +111,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 = it.take(1000),
+ textStyle = NumbersTextStyleDisplayLarge.copy(textAlign = TextAlign.End)
)
}
}
}
AnimatedVisibility(
+ modifier = Modifier.weight(1f),
visible = !secondaryText.isNullOrEmpty(),
enter = expandVertically(),
exit = shrinkVertically()
@@ -145,14 +144,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 = it?.take(1000) ?: "",
+ textStyle = NumbersTextStyleDisplayLarge.copy(
+ textAlign = TextAlign.End,
+ color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)
+ ),
+ minRatio = 0.7f
)
}
}
@@ -162,7 +161,8 @@ internal fun MyTextField(
AnimatedContent(
modifier = Modifier
.align(Alignment.End)
- .padding(horizontal = 8.dp),
+ .padding(horizontal = 8.dp)
+ .weight(1f),
targetState = helperText
) {
Text(
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 26a5d7d9..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
@@ -18,18 +18,28 @@
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.foundation.layout.Arrangement
-import androidx.compose.foundation.layout.Column
+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.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.fillMaxWidth
-import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.layout.height
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 +50,11 @@ 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.unit.dp
+import androidx.compose.ui.text.style.TextAlign
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
import com.sadellie.unitto.feature.converter.ConverterMode
@@ -91,54 +103,89 @@ internal fun TopScreenPart(
)
val mContext = LocalContext.current
- Column(
+ ColumnWithConstraints(
modifier = modifier,
- verticalArrangement = Arrangement.spacedBy(8.dp)
) {
- MyTextField(
- modifier = Modifier.fillMaxWidth(),
- primaryText = {
- when (converterMode) {
- ConverterMode.BASE -> inputValue.uppercase()
- else -> Formatter.format(inputValue)
- }
+ InputTextField(
+ modifier = Modifier.weight(2f),
+ value = 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(
- modifier = Modifier.fillMaxWidth(),
- onClick = onOutputTextFieldClick,
- primaryText = {
- 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)
- }
- },
- secondaryText = null,
- helperText = stringResource(unitTo?.shortName ?: R.string.loading_label),
- textToCopy = outputValue,
- )
- // Unit selection buttons
- Row(
- modifier = Modifier.padding(horizontal = 8.dp),
- verticalAlignment = Alignment.Bottom,
+ AnimatedVisibility(
+ visible = !calculatedValue.isNullOrEmpty(),
+ modifier = Modifier.weight(1f),
+ enter = expandVertically(clip = false),
+ exit = shrinkVertically(clip = false)
) {
+ InputTextField(
+ value = calculatedValue?.let { value -> Formatter.format(value) } ?: "",
+ minRatio = 0.7f,
+ textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)
+ )
+ }
+ AnimatedContent(
+ modifier = Modifier.fillMaxWidth(),
+ 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))
+ }
+ ) { value ->
+ Text(
+ text = value,
+ style = MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.End)
+ )
+ }
+
+ InputTextField(
+ modifier = Modifier
+ .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
+ )
+ }
+ else -> Formatter.format(outputValue)
+ },
+ minRatio = 0.7f,
+ )
+ 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))
+ }
+ ) { value ->
+ Text(
+ 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(