mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-19 00:35:26 +02:00
The most responsive UI
- Auto-size text field (animated) - Auto-size keyboard - Auto-size buttons - Fix cursor
This commit is contained in:
parent
bfafed4501
commit
07fdb03ab3
@ -18,6 +18,7 @@
|
||||
|
||||
package com.sadellie.unitto.core.ui.common
|
||||
|
||||
import android.content.res.Configuration
|
||||
import android.view.HapticFeedbackConstants
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateIntAsState
|
||||
@ -38,6 +39,7 @@ import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
@ -50,7 +52,7 @@ fun BasicKeyboardButton(
|
||||
icon: ImageVector,
|
||||
iconColor: Color,
|
||||
allowVibration: Boolean,
|
||||
contentPadding: PaddingValues = PaddingValues(24.dp, 8.dp)
|
||||
contentHeight: Float
|
||||
) {
|
||||
val view = LocalView.current
|
||||
val interactionSource = remember { MutableInteractionSource() }
|
||||
@ -66,10 +68,15 @@ fun BasicKeyboardButton(
|
||||
onLongClick = onLongClick,
|
||||
shape = RoundedCornerShape(cornerRadius),
|
||||
containerColor = containerColor,
|
||||
contentPadding = contentPadding,
|
||||
contentPadding = PaddingValues(),
|
||||
interactionSource = interactionSource
|
||||
) {
|
||||
Icon(icon, null, modifier = Modifier.fillMaxHeight(), tint = iconColor)
|
||||
Icon(
|
||||
imageVector = icon,
|
||||
contentDescription = null,
|
||||
modifier = Modifier.fillMaxHeight(contentHeight),
|
||||
tint = iconColor
|
||||
)
|
||||
}
|
||||
|
||||
LaunchedEffect(key1 = isPressed) {
|
||||
@ -93,6 +100,7 @@ fun KeyboardButtonLight(
|
||||
icon = icon,
|
||||
iconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
allowVibration = allowVibration,
|
||||
contentHeight = if (isPortrait()) 0.5f else 0.85f
|
||||
)
|
||||
}
|
||||
|
||||
@ -111,7 +119,8 @@ fun KeyboardButtonFilled(
|
||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||
icon = icon,
|
||||
iconColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
||||
allowVibration = allowVibration
|
||||
allowVibration = allowVibration,
|
||||
contentHeight = if (isPortrait()) 0.5f else 0.85f
|
||||
)
|
||||
}
|
||||
|
||||
@ -123,17 +132,19 @@ fun KeyboardButtonAdditional(
|
||||
onLongClick: (() -> Unit)? = null,
|
||||
onClick: () -> Unit
|
||||
) {
|
||||
|
||||
BasicKeyboardButton(
|
||||
modifier = modifier
|
||||
.minimumInteractiveComponentSize()
|
||||
.heightIn(max = 48.dp),
|
||||
onClick = onClick,
|
||||
onLongClick = onLongClick,
|
||||
containerColor = Color.Transparent,
|
||||
icon = icon,
|
||||
iconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
allowVibration = allowVibration,
|
||||
contentPadding = PaddingValues(12.dp, 2.dp),
|
||||
onLongClick = onLongClick
|
||||
contentHeight = if (isPortrait()) 0.9f else 0.85f
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun isPortrait() = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||
|
@ -19,7 +19,6 @@
|
||||
package com.sadellie.unitto.core.ui.common
|
||||
|
||||
import android.content.res.Configuration
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
@ -28,7 +27,6 @@ import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.unit.dp
|
||||
|
||||
/**
|
||||
* When Portrait mode will place [content1] and [content2] in a [Column].
|
||||
@ -42,33 +40,14 @@ fun PortraitLandscape(
|
||||
content2: @Composable (Modifier) -> Unit,
|
||||
) {
|
||||
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
Column(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
||||
) {
|
||||
content1(Modifier)
|
||||
content2(
|
||||
Modifier
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 8.dp)
|
||||
)
|
||||
ColumnWithConstraints(modifier) {
|
||||
content1(Modifier.fillMaxHeight(0.34f).padding(horizontal = it.maxWidth * 0.03f))
|
||||
content2(Modifier.fillMaxSize().padding(horizontal = it.maxWidth * 0.03f, vertical = it.maxHeight * 0.015f))
|
||||
}
|
||||
} else {
|
||||
Row(
|
||||
modifier = modifier.fillMaxSize(),
|
||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
content1(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxHeight()
|
||||
)
|
||||
content2(
|
||||
Modifier
|
||||
.weight(1f)
|
||||
.fillMaxSize()
|
||||
.padding(horizontal = 8.dp)
|
||||
)
|
||||
RowWithConstraints(modifier) {
|
||||
content1(Modifier.weight(1f).fillMaxHeight().padding(bottom = it.maxWidth * 0.015f, start = it.maxWidth * 0.015f, end = it.maxWidth * 0.015f))
|
||||
content2(Modifier.weight(1f).fillMaxSize().padding(bottom = it.maxWidth * 0.015f, start = it.maxWidth * 0.015f, end = it.maxWidth * 0.015f))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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,210 @@
|
||||
/*
|
||||
* Unitto is a unit converter for Android
|
||||
* Copyright (c) 2023 Elshan Agaev
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package com.sadellie.unitto.core.ui.common.textfield
|
||||
|
||||
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||
import androidx.compose.foundation.layout.widthIn
|
||||
import androidx.compose.foundation.text.BasicTextField
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Brush
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.SolidColor
|
||||
import androidx.compose.ui.layout.layout
|
||||
import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.platform.LocalDensity
|
||||
import androidx.compose.ui.platform.LocalTextInputService
|
||||
import androidx.compose.ui.platform.LocalTextToolbar
|
||||
import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.Paragraph
|
||||
import androidx.compose.ui.text.TextStyle
|
||||
import androidx.compose.ui.text.font.createFontFamilyResolver
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.unit.Constraints
|
||||
import androidx.compose.ui.unit.TextUnit
|
||||
import androidx.compose.ui.unit.sp
|
||||
import com.sadellie.unitto.core.base.Separator
|
||||
import com.sadellie.unitto.core.ui.Formatter
|
||||
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun InputTextField(
|
||||
modifier: Modifier,
|
||||
value: TextFieldValue,
|
||||
textStyle: TextStyle = NumbersTextStyleDisplayLarge,
|
||||
minRatio: Float = 1f,
|
||||
cutCallback: () -> Unit,
|
||||
pasteCallback: (String) -> Unit,
|
||||
onCursorChange: (IntRange) -> Unit,
|
||||
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
) {
|
||||
val clipboardManager = LocalClipboardManager.current
|
||||
fun copyCallback() {
|
||||
clipboardManager.setText(
|
||||
AnnotatedString(
|
||||
Formatter.removeGrouping(
|
||||
value.annotatedString.subSequence(value.selection).text
|
||||
)
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalTextInputService provides null,
|
||||
LocalTextToolbar provides UnittoTextToolbar(
|
||||
view = LocalView.current,
|
||||
copyCallback = ::copyCallback,
|
||||
pasteCallback = {
|
||||
pasteCallback(
|
||||
Formatter.toSeparator(
|
||||
clipboardManager.getText()?.text ?: "", Separator.COMMA
|
||||
)
|
||||
)
|
||||
},
|
||||
cutCallback = { copyCallback(); cutCallback() }
|
||||
)
|
||||
) {
|
||||
AutoSizableTextField(
|
||||
modifier = modifier,
|
||||
value = value,
|
||||
onValueChange = {
|
||||
onCursorChange(it.selection.start..it.selection.end)
|
||||
},
|
||||
textStyle = textStyle,
|
||||
minRatio = minRatio,
|
||||
readOnly = false
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun InputTextField(
|
||||
modifier: Modifier = Modifier,
|
||||
value: TextFieldValue,
|
||||
textStyle: TextStyle = NumbersTextStyleDisplayLarge,
|
||||
minRatio: Float = 1f,
|
||||
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
) {
|
||||
AutoSizableTextField(
|
||||
modifier = modifier,
|
||||
value = value,
|
||||
textStyle = textStyle,
|
||||
minRatio = minRatio,
|
||||
readOnly = true
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun AutoSizableTextField(
|
||||
modifier: Modifier = Modifier,
|
||||
value: TextFieldValue,
|
||||
onValueChange: (TextFieldValue) -> Unit = {},
|
||||
textStyle: TextStyle = TextStyle(),
|
||||
scaleFactor: Float = 0.95f,
|
||||
minRatio: Float = 1f,
|
||||
readOnly: Boolean = false,
|
||||
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||
cursorBrush: Brush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant)
|
||||
) {
|
||||
// FIXME Acts strange when minRatio is set to 0 (still scales down).
|
||||
|
||||
val density = LocalDensity.current
|
||||
|
||||
var nFontSize: TextUnit by remember { mutableStateOf(0.sp) }
|
||||
var minFontSize: TextUnit
|
||||
|
||||
BoxWithConstraints(
|
||||
modifier = modifier,
|
||||
contentAlignment = Alignment.BottomStart
|
||||
) {
|
||||
with(density) {
|
||||
// Cursor handle is not visible without this, 0.836f is the minimum required factor here
|
||||
nFontSize = maxHeight.toSp() * 0.836f
|
||||
minFontSize = nFontSize * minRatio
|
||||
}
|
||||
|
||||
// Modified: https://blog.canopas.com/autosizing-textfield-in-jetpack-compose-7a80f0270853
|
||||
val calculateParagraph = @Composable {
|
||||
Paragraph(
|
||||
text = value.text,
|
||||
style = textStyle.copy(fontSize = nFontSize),
|
||||
constraints = Constraints(
|
||||
maxWidth = ceil(with(density) { maxWidth.toPx() }).toInt()
|
||||
),
|
||||
density = density,
|
||||
fontFamilyResolver = createFontFamilyResolver(LocalContext.current),
|
||||
spanStyles = listOf(),
|
||||
placeholders = listOf(),
|
||||
maxLines = 1,
|
||||
ellipsis = false
|
||||
)
|
||||
}
|
||||
|
||||
var intrinsics = calculateParagraph()
|
||||
with(density) {
|
||||
while ((intrinsics.maxIntrinsicWidth > maxWidth.toPx()) && nFontSize >= minFontSize) {
|
||||
nFontSize *= scaleFactor
|
||||
intrinsics = calculateParagraph()
|
||||
}
|
||||
}
|
||||
|
||||
val nTextStyle = textStyle.copy(
|
||||
// https://issuetracker.google.com/issues/266470454
|
||||
// textAlign = TextAlign.End,
|
||||
color = textColor,
|
||||
fontSize = nFontSize
|
||||
)
|
||||
|
||||
BasicTextField(
|
||||
value = value,
|
||||
singleLine = true,
|
||||
onValueChange = onValueChange,
|
||||
modifier = Modifier
|
||||
.widthIn(
|
||||
max = with(density) { intrinsics.width.toDp() }
|
||||
)
|
||||
.layout { measurable, constraints ->
|
||||
val placeable = measurable.measure(constraints)
|
||||
// TextField size is changed with a delay (text jumps). Here we correct it.
|
||||
layout(placeable.width, placeable.height) {
|
||||
placeable.place(
|
||||
x = (intrinsics.width - intrinsics.maxIntrinsicWidth)
|
||||
.coerceAtLeast(0f)
|
||||
.roundToInt(),
|
||||
y = (placeable.height - intrinsics.height).roundToInt()
|
||||
)
|
||||
}
|
||||
},
|
||||
textStyle = nTextStyle,
|
||||
readOnly = readOnly,
|
||||
cursorBrush = cursorBrush
|
||||
)
|
||||
}
|
||||
}
|
@ -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,
|
@ -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,15 +26,14 @@ import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.gestures.Orientation
|
||||
import androidx.compose.foundation.gestures.draggable
|
||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxHeight
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.sizeIn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||
import androidx.compose.material.icons.Icons
|
||||
@ -65,21 +64,20 @@ import androidx.compose.ui.platform.LocalView
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.sadellie.unitto.core.base.Separator
|
||||
import com.sadellie.unitto.core.ui.Formatter
|
||||
import com.sadellie.unitto.core.ui.common.MenuButton
|
||||
import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar
|
||||
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium
|
||||
import com.sadellie.unitto.core.ui.common.textfield.InputTextField
|
||||
import com.sadellie.unitto.core.ui.common.textfield.UnittoTextToolbar
|
||||
import com.sadellie.unitto.data.model.HistoryItem
|
||||
import com.sadellie.unitto.feature.calculator.components.CalculatorKeyboard
|
||||
import com.sadellie.unitto.feature.calculator.components.DragDownView
|
||||
import com.sadellie.unitto.feature.calculator.components.HistoryList
|
||||
import com.sadellie.unitto.feature.calculator.components.InputTextField
|
||||
import com.sadellie.unitto.feature.calculator.components.UnittoTextToolbar
|
||||
import kotlinx.coroutines.launch
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
@ -194,12 +192,13 @@ private fun CalculatorScreen(
|
||||
textFields = { maxDragAmount ->
|
||||
Column(
|
||||
Modifier
|
||||
.fillMaxHeight(0.25f)
|
||||
.onPlaced { textThingyHeight = it.size.height }
|
||||
.background(
|
||||
MaterialTheme.colorScheme.surfaceVariant,
|
||||
RoundedCornerShape(
|
||||
topStart = 0.dp, topEnd = 0.dp,
|
||||
bottomStart = 32.dp, bottomEnd = 32.dp
|
||||
topStartPercent = 0, topEndPercent = 0,
|
||||
bottomStartPercent = 20, bottomEndPercent = 20
|
||||
)
|
||||
)
|
||||
.draggable(
|
||||
@ -226,31 +225,33 @@ private fun CalculatorScreen(
|
||||
}
|
||||
}
|
||||
)
|
||||
.padding(top = 8.dp),
|
||||
.padding(top = 12.dp),
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
InputTextField(
|
||||
modifier = Modifier
|
||||
.weight(2f)
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp),
|
||||
value = uiState.input,
|
||||
onCursorChange = onCursorChange,
|
||||
value = uiState.input.copy(
|
||||
Formatter.fromSeparator(uiState.input.text, Separator.COMMA)
|
||||
),
|
||||
minRatio = 0.5f,
|
||||
cutCallback = deleteSymbol,
|
||||
pasteCallback = addSymbol,
|
||||
cutCallback = deleteSymbol
|
||||
onCursorChange = onCursorChange
|
||||
)
|
||||
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||
SelectionContainer {
|
||||
Text(
|
||||
SelectionContainer(Modifier.weight(1f)) {
|
||||
InputTextField(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(horizontal = 8.dp)
|
||||
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
|
||||
text = Formatter.format(uiState.output),
|
||||
textAlign = TextAlign.End,
|
||||
softWrap = false,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
|
||||
style = NumbersTextStyleDisplayMedium,
|
||||
.padding(horizontal = 8.dp),
|
||||
value = TextFieldValue(
|
||||
Formatter.fromSeparator(uiState.output, Separator.COMMA)
|
||||
),
|
||||
textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.6f)
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -268,7 +269,7 @@ private fun CalculatorScreen(
|
||||
},
|
||||
numPad = {
|
||||
CalculatorKeyboard(
|
||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp, vertical = 4.dp),
|
||||
angleMode = uiState.angleMode,
|
||||
allowVibration = uiState.allowVibration,
|
||||
addSymbol = addSymbol,
|
||||
@ -314,7 +315,12 @@ private fun CalculatorScreen(
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Preview(widthDp = 432, heightDp = 1008, device = "spec:parent=pixel_5,orientation=portrait")
|
||||
@Preview(widthDp = 432, heightDp = 864, device = "spec:parent=pixel_5,orientation=portrait")
|
||||
@Preview(widthDp = 597, heightDp = 1393, device = "spec:parent=pixel_5,orientation=portrait")
|
||||
@Preview(heightDp = 432, widthDp = 1008, device = "spec:parent=pixel_5,orientation=landscape")
|
||||
@Preview(heightDp = 432, widthDp = 864, device = "spec:parent=pixel_5,orientation=landscape")
|
||||
@Preview(heightDp = 597, widthDp = 1393, device = "spec:parent=pixel_5,orientation=landscape")
|
||||
@Composable
|
||||
private fun PreviewCalculatorScreen() {
|
||||
val dtf = SimpleDateFormat("dd.MM.yyyy HH:mm:ss", Locale.getDefault())
|
||||
@ -332,14 +338,14 @@ private fun PreviewCalculatorScreen() {
|
||||
HistoryItem(
|
||||
date = dtf.parse(it)!!,
|
||||
expression = "12345".repeat(10),
|
||||
result = "67890"
|
||||
result = "1234"
|
||||
)
|
||||
}
|
||||
|
||||
CalculatorScreen(
|
||||
uiState = CalculatorUIState(
|
||||
input = TextFieldValue("12345"),
|
||||
output = "12345",
|
||||
input = TextFieldValue("1.2345"),
|
||||
output = "1234",
|
||||
history = historyItems
|
||||
),
|
||||
navigateToMenu = {},
|
||||
|
@ -25,11 +25,13 @@ import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.heightIn
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.ExpandLess
|
||||
@ -43,16 +45,19 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.platform.LocalConfiguration
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.Dp
|
||||
import com.sadellie.unitto.core.base.Token
|
||||
import com.sadellie.unitto.core.ui.Formatter
|
||||
import com.sadellie.unitto.core.ui.common.ColumnWithConstraints
|
||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonAdditional
|
||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled
|
||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonLight
|
||||
import com.sadellie.unitto.core.ui.common.RowWithConstraints
|
||||
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.AcTan
|
||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.ArCos
|
||||
@ -150,22 +155,27 @@ private fun PortraitKeyboard(
|
||||
animationSpec = tween(easing = FastOutSlowInEasing)
|
||||
)
|
||||
|
||||
Column(
|
||||
ColumnWithConstraints(
|
||||
modifier = modifier
|
||||
) {
|
||||
) { constraints ->
|
||||
fun verticalFraction(fraction: Float): Dp = constraints.maxHeight * fraction
|
||||
fun horizontalFraction(fraction: Float): Dp = constraints.maxWidth * fraction
|
||||
|
||||
val weightModifier = Modifier.weight(1f)
|
||||
val mainButtonModifier = Modifier
|
||||
.fillMaxSize()
|
||||
.weight(1f)
|
||||
.padding(4.dp)
|
||||
.padding(horizontalFraction(0.015f), verticalFraction(0.008f))
|
||||
val additionalButtonModifier = Modifier
|
||||
.minimumInteractiveComponentSize()
|
||||
.weight(1f)
|
||||
.heightIn(max = 48.dp)
|
||||
.height(verticalFraction(0.075f))
|
||||
|
||||
Spacer(modifier = Modifier.height(verticalFraction(0.015f)))
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(vertical = 8.dp),
|
||||
horizontalArrangement = Arrangement.spacedBy(2.dp)
|
||||
modifier = Modifier,
|
||||
horizontalArrangement = Arrangement.spacedBy(horizontalFraction(0.03f))
|
||||
) {
|
||||
// Additional buttons
|
||||
Crossfade(invMode, weightModifier) {
|
||||
@ -192,14 +202,18 @@ private fun PortraitKeyboard(
|
||||
}
|
||||
}
|
||||
|
||||
// Expand/Collapse
|
||||
IconButton(
|
||||
onClick = { showAdditional = !showAdditional },
|
||||
colors = IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.inverseOnSurface)
|
||||
) {
|
||||
Icon(Icons.Default.ExpandLess, null, Modifier.rotate(expandRotation))
|
||||
Box(modifier = Modifier.height(verticalFraction(0.075f)), contentAlignment = Alignment.Center) {
|
||||
// Expand/Collapse
|
||||
IconButton(
|
||||
onClick = { showAdditional = !showAdditional },
|
||||
colors = IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.inverseOnSurface)
|
||||
) {
|
||||
Icon(Icons.Default.ExpandLess, null, Modifier.rotate(expandRotation))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(verticalFraction(0.015f)))
|
||||
|
||||
Row(weightModifier) {
|
||||
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.leftBracket) }
|
||||
@ -232,6 +246,8 @@ private fun PortraitKeyboard(
|
||||
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Backspace, allowVibration, clearSymbols) { deleteSymbol() }
|
||||
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Equal, allowVibration) { evaluate() }
|
||||
}
|
||||
|
||||
Spacer(modifier = Modifier.height(verticalFraction(0.015f)))
|
||||
}
|
||||
}
|
||||
|
||||
@ -246,7 +262,7 @@ private fun AdditionalButtonsPortrait(
|
||||
toggleInvMode: () -> Unit
|
||||
) {
|
||||
Column {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Row {
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.SquareRootWide, allowVibration) { addSymbol(Token.sqrt) }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.Pi, allowVibration) { addSymbol(Token.pi) }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.ExponentWide, allowVibration) { addSymbol(Token.exponent) }
|
||||
@ -254,13 +270,13 @@ private fun AdditionalButtonsPortrait(
|
||||
}
|
||||
AnimatedVisibility(showAdditional) {
|
||||
Column {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Row {
|
||||
KeyboardButtonAdditional(modifier, if (angleMode == AngleMode.DEG) UnittoIcons.Deg else UnittoIcons.Rad, allowVibration) { toggleAngleMode() }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.Sin, allowVibration) { addSymbol(Token.sin) }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.Cos, allowVibration) { addSymbol(Token.cos) }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.Tan, allowVibration) { addSymbol(Token.tan) }
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Row {
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.Inv, allowVibration) { toggleInvMode() }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.E, allowVibration) { addSymbol(Token.e) }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.Ln, allowVibration) { addSymbol(Token.ln) }
|
||||
@ -282,7 +298,7 @@ private fun AdditionalButtonsPortraitInverse(
|
||||
toggleInvMode: () -> Unit
|
||||
) {
|
||||
Column {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Row {
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.Modulo, allowVibration) { addSymbol(Token.modulo) }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.Pi, allowVibration) { addSymbol(Token.pi) }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.ExponentWide, allowVibration) { addSymbol(Token.exponent) }
|
||||
@ -290,13 +306,13 @@ private fun AdditionalButtonsPortraitInverse(
|
||||
}
|
||||
AnimatedVisibility(showAdditional) {
|
||||
Column {
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Row {
|
||||
KeyboardButtonAdditional(modifier, if (angleMode == AngleMode.DEG) UnittoIcons.Deg else UnittoIcons.Rad, allowVibration) { toggleAngleMode() }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.ArSin, allowVibration) { addSymbol(Token.arSin) }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.ArCos, allowVibration) { addSymbol(Token.arCos) }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.AcTan, allowVibration) { addSymbol(Token.acTan) }
|
||||
}
|
||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
||||
Row {
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.Inv, allowVibration) { toggleInvMode() }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.E, allowVibration) { addSymbol(Token.e) }
|
||||
KeyboardButtonAdditional(modifier, UnittoIcons.Exp, allowVibration) { addSymbol(Token.exp) }
|
||||
@ -321,11 +337,11 @@ private fun LandscapeKeyboard(
|
||||
val fractionalIcon = remember { if (Formatter.fractional == Token.dot) UnittoIcons.Dot else UnittoIcons.Comma }
|
||||
var invMode: Boolean by remember { mutableStateOf(false) }
|
||||
|
||||
Row(modifier) {
|
||||
RowWithConstraints(modifier) { constraints ->
|
||||
val buttonModifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.weight(1f)
|
||||
.padding(4.dp)
|
||||
.padding(constraints.maxWidth * 0.005f, constraints.maxHeight * 0.02f)
|
||||
|
||||
Crossfade(invMode, Modifier.weight(3f)) {
|
||||
Row {
|
||||
|
@ -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() }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -48,11 +48,12 @@ import androidx.compose.ui.platform.LocalClipboardManager
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.AnnotatedString
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
|
||||
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium
|
||||
import com.sadellie.unitto.core.ui.R
|
||||
import com.sadellie.unitto.core.ui.common.textfield.InputTextField
|
||||
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
|
||||
|
||||
/**
|
||||
* Component for input and output
|
||||
@ -94,7 +95,8 @@ internal fun MyTextField(
|
||||
) {
|
||||
LazyRow(
|
||||
modifier = modifier
|
||||
.wrapContentHeight(),
|
||||
.wrapContentHeight()
|
||||
.weight(2f),
|
||||
reverseLayout = true,
|
||||
horizontalArrangement = Arrangement.End,
|
||||
contentPadding = PaddingValues(horizontal = 8.dp)
|
||||
@ -110,19 +112,17 @@ internal fun MyTextField(
|
||||
.using(SizeTransform(clip = false))
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
InputTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
// Quick fix to prevent the UI from crashing
|
||||
text = it.take(1000),
|
||||
textAlign = TextAlign.End,
|
||||
softWrap = false,
|
||||
style = NumbersTextStyleDisplayLarge
|
||||
value = TextFieldValue(it.take(1000)),
|
||||
textStyle = NumbersTextStyleDisplayLarge.copy(textAlign = TextAlign.End)
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
AnimatedVisibility(
|
||||
modifier = Modifier.weight(1f),
|
||||
visible = !secondaryText.isNullOrEmpty(),
|
||||
enter = expandVertically(),
|
||||
exit = shrinkVertically()
|
||||
@ -145,14 +145,14 @@ internal fun MyTextField(
|
||||
.using(SizeTransform(clip = false))
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
modifier = Modifier,
|
||||
// Quick fix to prevent the UI from crashing
|
||||
text = it?.take(1000) ?: "",
|
||||
textAlign = TextAlign.End,
|
||||
softWrap = false,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
|
||||
style = NumbersTextStyleDisplayMedium
|
||||
InputTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
value = TextFieldValue(it?.take(1000) ?: ""),
|
||||
textStyle = NumbersTextStyleDisplayLarge.copy(
|
||||
textAlign = TextAlign.End,
|
||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)
|
||||
),
|
||||
minRatio = 0.7f
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -162,7 +162,8 @@ internal fun MyTextField(
|
||||
AnimatedContent(
|
||||
modifier = Modifier
|
||||
.align(Alignment.End)
|
||||
.padding(horizontal = 8.dp),
|
||||
.padding(horizontal = 8.dp)
|
||||
.weight(1f),
|
||||
targetState = helperText
|
||||
) {
|
||||
Text(
|
||||
|
@ -18,18 +18,29 @@
|
||||
|
||||
package com.sadellie.unitto.feature.converter.components
|
||||
|
||||
import androidx.compose.animation.AnimatedContent
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.SizeTransform
|
||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.expandHorizontally
|
||||
import androidx.compose.animation.expandVertically
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.shrinkVertically
|
||||
import androidx.compose.animation.with
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.outlined.SwapHoriz
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
@ -40,9 +51,12 @@ import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.rotate
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.input.TextFieldValue
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.unit.dp
|
||||
import com.sadellie.unitto.core.ui.Formatter
|
||||
import com.sadellie.unitto.core.ui.R
|
||||
import com.sadellie.unitto.core.ui.common.textfield.InputTextField
|
||||
import com.sadellie.unitto.data.model.AbstractUnit
|
||||
import com.sadellie.unitto.data.model.UnitGroup
|
||||
import com.sadellie.unitto.feature.converter.ConverterMode
|
||||
@ -95,22 +109,49 @@ internal fun TopScreenPart(
|
||||
modifier = modifier,
|
||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||
) {
|
||||
MyTextField(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
primaryText = {
|
||||
InputTextField(
|
||||
modifier = Modifier.weight(2f),
|
||||
value = TextFieldValue(
|
||||
when (converterMode) {
|
||||
ConverterMode.BASE -> inputValue.uppercase()
|
||||
else -> Formatter.format(inputValue)
|
||||
}
|
||||
},
|
||||
secondaryText = calculatedValue?.let { Formatter.format(it) },
|
||||
helperText = stringResource(unitFrom?.shortName ?: R.string.loading_label),
|
||||
textToCopy = calculatedValue ?: inputValue,
|
||||
),
|
||||
minRatio = 0.7f
|
||||
)
|
||||
MyTextField(
|
||||
AnimatedVisibility(
|
||||
visible = !calculatedValue.isNullOrEmpty(),
|
||||
modifier = Modifier.weight(1f),
|
||||
enter = expandVertically(clip = false),
|
||||
exit = shrinkVertically(clip = false)
|
||||
) {
|
||||
InputTextField(
|
||||
value = TextFieldValue(calculatedValue?.let { Formatter.format(it) } ?: ""),
|
||||
minRatio = 0.7f
|
||||
)
|
||||
}
|
||||
AnimatedContent(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
onClick = onOutputTextFieldClick,
|
||||
primaryText = {
|
||||
targetState = stringResource(unitFrom?.shortName ?: R.string.loading_label),
|
||||
transitionSpec = {
|
||||
// Enter animation
|
||||
(expandHorizontally(clip = false, expandFrom = Alignment.Start) + fadeIn()
|
||||
// Exit animation
|
||||
with fadeOut())
|
||||
.using(SizeTransform(clip = false))
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.End)
|
||||
)
|
||||
}
|
||||
|
||||
InputTextField(
|
||||
modifier = Modifier
|
||||
.weight(2f)
|
||||
.clickable { onOutputTextFieldClick() },
|
||||
value = TextFieldValue(
|
||||
when {
|
||||
networkLoading -> stringResource(R.string.loading_label)
|
||||
networkError -> stringResource(R.string.error_label)
|
||||
@ -124,16 +165,27 @@ internal fun TopScreenPart(
|
||||
}
|
||||
else -> Formatter.format(outputValue)
|
||||
}
|
||||
},
|
||||
secondaryText = null,
|
||||
helperText = stringResource(unitTo?.shortName ?: R.string.loading_label),
|
||||
textToCopy = outputValue,
|
||||
),
|
||||
minRatio = 0.7f
|
||||
)
|
||||
// Unit selection buttons
|
||||
Row(
|
||||
modifier = Modifier.padding(horizontal = 8.dp),
|
||||
verticalAlignment = Alignment.Bottom,
|
||||
AnimatedContent(
|
||||
modifier = Modifier.fillMaxWidth(),
|
||||
targetState = stringResource(unitTo?.shortName ?: R.string.loading_label),
|
||||
transitionSpec = {
|
||||
// Enter animation
|
||||
(expandHorizontally(clip = false, expandFrom = Alignment.Start) + fadeIn()
|
||||
// Exit animation
|
||||
with fadeOut())
|
||||
.using(SizeTransform(clip = false))
|
||||
}
|
||||
) {
|
||||
Text(
|
||||
text = it,
|
||||
style = MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.End)
|
||||
)
|
||||
}
|
||||
|
||||
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||
UnitSelectionButton(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
|
Loading…
x
Reference in New Issue
Block a user