mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-19 16:55:26 +02:00
Merge branch 'feature/responsive-ui'
This commit is contained in:
commit
3a620452c2
@ -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
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,56 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 Elshan Agaev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sadellie.unitto.core.ui.common
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.BoxWithConstraintsScope
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.ColumnScope
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.RowScope
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
fun ColumnWithConstraints(
|
||||
modifier: Modifier = Modifier,
|
||||
verticalArrangement: Arrangement.Vertical = Arrangement.Top,
|
||||
horizontalAlignment: Alignment.Horizontal = Alignment.Start,
|
||||
content: @Composable (ColumnScope.(BoxWithConstraintsScope)-> Unit)
|
||||
) = BoxWithConstraints(modifier) {
|
||||
Column(
|
||||
verticalArrangement = verticalArrangement,
|
||||
horizontalAlignment = horizontalAlignment
|
||||
) { content(this@BoxWithConstraints) }
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun RowWithConstraints(
|
||||
modifier: Modifier = Modifier,
|
||||
horizontalArrangement: Arrangement.Horizontal = Arrangement.Start,
|
||||
verticalAlignment: Alignment.Vertical = Alignment.Top,
|
||||
content: @Composable (RowScope.(BoxWithConstraintsScope)-> Unit)
|
||||
) = BoxWithConstraints(modifier) {
|
||||
Row(
|
||||
horizontalArrangement = horizontalArrangement,
|
||||
verticalAlignment = verticalAlignment
|
||||
) { content(this@BoxWithConstraints) }
|
||||
}
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sadellie.unitto.feature.calculator.components
|
||||
package com.sadellie.unitto.core.ui.common.textfield
|
||||
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
@ -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 <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
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)
|
||||
)
|
||||
)
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sadellie.unitto.feature.calculator.components
|
||||
package com.sadellie.unitto.core.ui.common.textfield
|
||||
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sadellie.unitto.feature.calculator.components
|
||||
package com.sadellie.unitto.core.ui.common.textfield
|
||||
|
||||
import android.view.ActionMode
|
||||
import android.view.Menu
|
@ -16,7 +16,7 @@
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sadellie.unitto.feature.calculator.components
|
||||
package com.sadellie.unitto.core.ui.common.textfield
|
||||
|
||||
import android.os.Build
|
||||
import android.view.ActionMode
|
||||
@ -25,7 +25,7 @@ import androidx.compose.ui.geometry.Rect
|
||||
import androidx.compose.ui.platform.TextToolbar
|
||||
import androidx.compose.ui.platform.TextToolbarStatus
|
||||
|
||||
internal class UnittoTextToolbar(
|
||||
class UnittoTextToolbar(
|
||||
private val view: View,
|
||||
private val copyCallback: () -> Unit,
|
||||
private val pasteCallback: (() -> Unit)? = null,
|
||||
@ -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) {
|
@ -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,
|
||||
)
|
||||
|
||||
|
@ -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 = {},
|
||||
|
@ -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 {
|
||||
|
@ -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<HistoryItem>,
|
||||
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
|
||||
) {}
|
||||
}
|
||||
|
@ -1,100 +0,0 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 Elshan Agaev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sadellie.unitto.feature.calculator.components
|
||||
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.derivedStateOf
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalTextInputService
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import com.sadellie.unitto.core.base.Separator
|
||||
import com.sadellie.unitto.core.ui.Formatter
|
||||
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
|
||||
|
||||
@Composable
|
||||
internal fun InputTextField(
|
||||
modifier: Modifier,
|
||||
value: TextFieldValue,
|
||||
onCursorChange: (IntRange) -> Unit,
|
||||
pasteCallback: (String) -> Unit,
|
||||
cutCallback: () -> Unit
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
|
||||
val formattedInput: TextFieldValue by remember(value) {
|
||||
derivedStateOf {
|
||||
value.copy(
|
||||
// We replace this because internally input value is already formatted, but uses
|
||||
// comma as separator.
|
||||
Formatter.fromSeparator(value.text, Separator.COMMA)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
fun copyToClipboard() = clipboardManager.setText(
|
||||
AnnotatedString(
|
||||
Formatter.removeGrouping(
|
||||
formattedInput.annotatedString.subSequence(formattedInput.selection).text
|
||||
)
|
||||
)
|
||||
)
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalTextInputService provides null,
|
||||
LocalTextToolbar provides UnittoTextToolbar(
|
||||
view = LocalView.current,
|
||||
copyCallback = ::copyToClipboard,
|
||||
pasteCallback = {
|
||||
pasteCallback(
|
||||
Formatter.toSeparator(
|
||||
clipboardManager.getText()?.text ?: "", Separator.COMMA
|
||||
)
|
||||
)
|
||||
},
|
||||
cutCallback = { copyToClipboard(); cutCallback() }
|
||||
)
|
||||
) {
|
||||
BasicTextField(
|
||||
modifier = modifier,
|
||||
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant),
|
||||
singleLine = true,
|
||||
value = formattedInput,
|
||||
onValueChange = {
|
||||
onCursorChange(it.selection.start..it.selection.end)
|
||||
},
|
||||
textStyle = NumbersTextStyleDisplayLarge.copy(
|
||||
textAlign = TextAlign.End,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant
|
||||
),
|
||||
minLines = 1,
|
||||
maxLines = 1,
|
||||
)
|
||||
}
|
||||
}
|
@ -18,6 +18,7 @@
|
||||
|
||||
package com.sadellie.unitto.feature.converter
|
||||
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.MoreVert
|
||||
@ -30,6 +31,8 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameter
|
||||
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.sadellie.unitto.core.ui.R
|
||||
@ -91,7 +94,7 @@ private fun ConverterScreen(
|
||||
.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
|
||||
content = { padding ->
|
||||
PortraitLandscape(
|
||||
modifier = Modifier.padding(padding),
|
||||
modifier = Modifier.padding(padding).fillMaxSize(),
|
||||
content1 = {
|
||||
TopScreenPart(
|
||||
modifier = it,
|
||||
@ -125,11 +128,26 @@ private fun ConverterScreen(
|
||||
)
|
||||
}
|
||||
|
||||
@Preview
|
||||
class PreviewUIState: PreviewParameterProvider<ConverterUIState> {
|
||||
override val values: Sequence<ConverterUIState>
|
||||
get() = listOf(
|
||||
ConverterUIState(inputValue = "1234", calculatedValue = null, resultValue = "5678", showLoading = false),
|
||||
ConverterUIState(inputValue = "1234", calculatedValue = "234", resultValue = "5678", showLoading = false),
|
||||
).asSequence()
|
||||
}
|
||||
|
||||
@Preview(widthDp = 432, heightDp = 1008, device = "spec:parent=pixel_5,orientation=portrait")
|
||||
@Preview(widthDp = 432, heightDp = 864, device = "spec:parent=pixel_5,orientation=portrait")
|
||||
@Preview(widthDp = 597, heightDp = 1393, device = "spec:parent=pixel_5,orientation=portrait")
|
||||
@Preview(heightDp = 432, widthDp = 1008, device = "spec:parent=pixel_5,orientation=landscape")
|
||||
@Preview(heightDp = 432, widthDp = 864, device = "spec:parent=pixel_5,orientation=landscape")
|
||||
@Preview(heightDp = 597, widthDp = 1393, device = "spec:parent=pixel_5,orientation=landscape")
|
||||
@Composable
|
||||
private fun PreviewConverterScreen() {
|
||||
private fun PreviewConverterScreen(
|
||||
@PreviewParameter(PreviewUIState::class) uiState: ConverterUIState
|
||||
) {
|
||||
ConverterScreen(
|
||||
uiState = ConverterUIState(),
|
||||
uiState = ConverterUIState(inputValue = "1234", calculatedValue = null, resultValue = "5678", showLoading = false),
|
||||
navigateToLeftScreen = {},
|
||||
navigateToRightScreen = {_, _, _ -> },
|
||||
navigateToSettings = {},
|
||||
|
@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
Loading…
x
Reference in New Issue
Block a user