mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-19 08:45:27 +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
|
package com.sadellie.unitto.core.ui.common
|
||||||
|
|
||||||
|
import android.content.res.Configuration
|
||||||
import android.view.HapticFeedbackConstants
|
import android.view.HapticFeedbackConstants
|
||||||
import androidx.compose.animation.core.FastOutSlowInEasing
|
import androidx.compose.animation.core.FastOutSlowInEasing
|
||||||
import androidx.compose.animation.core.animateIntAsState
|
import androidx.compose.animation.core.animateIntAsState
|
||||||
@ -38,6 +39,7 @@ import androidx.compose.runtime.remember
|
|||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.graphics.Color
|
import androidx.compose.ui.graphics.Color
|
||||||
import androidx.compose.ui.graphics.vector.ImageVector
|
import androidx.compose.ui.graphics.vector.ImageVector
|
||||||
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalView
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
|
|
||||||
@ -50,7 +52,7 @@ fun BasicKeyboardButton(
|
|||||||
icon: ImageVector,
|
icon: ImageVector,
|
||||||
iconColor: Color,
|
iconColor: Color,
|
||||||
allowVibration: Boolean,
|
allowVibration: Boolean,
|
||||||
contentPadding: PaddingValues = PaddingValues(24.dp, 8.dp)
|
contentHeight: Float
|
||||||
) {
|
) {
|
||||||
val view = LocalView.current
|
val view = LocalView.current
|
||||||
val interactionSource = remember { MutableInteractionSource() }
|
val interactionSource = remember { MutableInteractionSource() }
|
||||||
@ -66,10 +68,15 @@ fun BasicKeyboardButton(
|
|||||||
onLongClick = onLongClick,
|
onLongClick = onLongClick,
|
||||||
shape = RoundedCornerShape(cornerRadius),
|
shape = RoundedCornerShape(cornerRadius),
|
||||||
containerColor = containerColor,
|
containerColor = containerColor,
|
||||||
contentPadding = contentPadding,
|
contentPadding = PaddingValues(),
|
||||||
interactionSource = interactionSource
|
interactionSource = interactionSource
|
||||||
) {
|
) {
|
||||||
Icon(icon, null, modifier = Modifier.fillMaxHeight(), tint = iconColor)
|
Icon(
|
||||||
|
imageVector = icon,
|
||||||
|
contentDescription = null,
|
||||||
|
modifier = Modifier.fillMaxHeight(contentHeight),
|
||||||
|
tint = iconColor
|
||||||
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
LaunchedEffect(key1 = isPressed) {
|
LaunchedEffect(key1 = isPressed) {
|
||||||
@ -93,6 +100,7 @@ fun KeyboardButtonLight(
|
|||||||
icon = icon,
|
icon = icon,
|
||||||
iconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
iconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
allowVibration = allowVibration,
|
allowVibration = allowVibration,
|
||||||
|
contentHeight = if (isPortrait()) 0.5f else 0.85f
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -111,7 +119,8 @@ fun KeyboardButtonFilled(
|
|||||||
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
containerColor = MaterialTheme.colorScheme.primaryContainer,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
iconColor = MaterialTheme.colorScheme.onSecondaryContainer,
|
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,
|
onLongClick: (() -> Unit)? = null,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
||||||
BasicKeyboardButton(
|
BasicKeyboardButton(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.minimumInteractiveComponentSize()
|
.minimumInteractiveComponentSize()
|
||||||
.heightIn(max = 48.dp),
|
.heightIn(max = 48.dp),
|
||||||
onClick = onClick,
|
onClick = onClick,
|
||||||
|
onLongClick = onLongClick,
|
||||||
containerColor = Color.Transparent,
|
containerColor = Color.Transparent,
|
||||||
icon = icon,
|
icon = icon,
|
||||||
iconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
iconColor = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
allowVibration = allowVibration,
|
allowVibration = allowVibration,
|
||||||
contentPadding = PaddingValues(12.dp, 2.dp),
|
contentHeight = if (isPortrait()) 0.9f else 0.85f
|
||||||
onLongClick = onLongClick
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun isPortrait() = LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT
|
||||||
|
@ -19,7 +19,6 @@
|
|||||||
package com.sadellie.unitto.core.ui.common
|
package com.sadellie.unitto.core.ui.common
|
||||||
|
|
||||||
import android.content.res.Configuration
|
import android.content.res.Configuration
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
@ -28,7 +27,6 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* When Portrait mode will place [content1] and [content2] in a [Column].
|
* When Portrait mode will place [content1] and [content2] in a [Column].
|
||||||
@ -42,33 +40,14 @@ fun PortraitLandscape(
|
|||||||
content2: @Composable (Modifier) -> Unit,
|
content2: @Composable (Modifier) -> Unit,
|
||||||
) {
|
) {
|
||||||
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
Column(
|
ColumnWithConstraints(modifier) {
|
||||||
modifier = modifier.fillMaxSize(),
|
content1(Modifier.fillMaxHeight(0.34f).padding(horizontal = it.maxWidth * 0.03f))
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
content2(Modifier.fillMaxSize().padding(horizontal = it.maxWidth * 0.03f, vertical = it.maxHeight * 0.015f))
|
||||||
) {
|
|
||||||
content1(Modifier)
|
|
||||||
content2(
|
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Row(
|
RowWithConstraints(modifier) {
|
||||||
modifier = modifier.fillMaxSize(),
|
content1(Modifier.weight(1f).fillMaxHeight().padding(bottom = it.maxWidth * 0.015f, start = it.maxWidth * 0.015f, end = it.maxWidth * 0.015f))
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
content2(Modifier.weight(1f).fillMaxSize().padding(bottom = it.maxWidth * 0.015f, start = it.maxWidth * 0.015f, end = it.maxWidth * 0.015f))
|
||||||
) {
|
|
||||||
content1(
|
|
||||||
Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.fillMaxHeight()
|
|
||||||
)
|
|
||||||
content2(
|
|
||||||
Modifier
|
|
||||||
.weight(1f)
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -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/>.
|
* 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.ActionMode
|
||||||
import android.view.Menu
|
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/>.
|
* 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.ActionMode
|
||||||
import android.view.Menu
|
import android.view.Menu
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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.ActionMode
|
||||||
import android.view.Menu
|
import android.view.Menu
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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.os.Build
|
||||||
import android.view.ActionMode
|
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.TextToolbar
|
||||||
import androidx.compose.ui.platform.TextToolbarStatus
|
import androidx.compose.ui.platform.TextToolbarStatus
|
||||||
|
|
||||||
internal class UnittoTextToolbar(
|
class UnittoTextToolbar(
|
||||||
private val view: View,
|
private val view: View,
|
||||||
private val copyCallback: () -> Unit,
|
private val copyCallback: () -> Unit,
|
||||||
private val pasteCallback: (() -> Unit)? = null,
|
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.Font
|
||||||
import androidx.compose.ui.text.font.FontFamily
|
import androidx.compose.ui.text.font.FontFamily
|
||||||
import androidx.compose.ui.text.font.FontWeight
|
import androidx.compose.ui.text.font.FontWeight
|
||||||
|
import androidx.compose.ui.unit.em
|
||||||
import androidx.compose.ui.unit.sp
|
import androidx.compose.ui.unit.sp
|
||||||
import com.sadellie.unitto.core.ui.R
|
import com.sadellie.unitto.core.ui.R
|
||||||
|
|
||||||
@ -42,7 +43,7 @@ val NumbersTextStyleDisplayLarge = TextStyle(
|
|||||||
fontFamily = Lato,
|
fontFamily = Lato,
|
||||||
fontWeight = FontWeight.W400,
|
fontWeight = FontWeight.W400,
|
||||||
fontSize = 57.sp,
|
fontSize = 57.sp,
|
||||||
lineHeight = 64.sp,
|
lineHeight = (1.4).em,
|
||||||
letterSpacing = (-0.25).sp,
|
letterSpacing = (-0.25).sp,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
@ -26,15 +26,14 @@ import androidx.compose.foundation.background
|
|||||||
import androidx.compose.foundation.gestures.Orientation
|
import androidx.compose.foundation.gestures.Orientation
|
||||||
import androidx.compose.foundation.gestures.draggable
|
import androidx.compose.foundation.gestures.draggable
|
||||||
import androidx.compose.foundation.gestures.rememberDraggableState
|
import androidx.compose.foundation.gestures.rememberDraggableState
|
||||||
import androidx.compose.foundation.horizontalScroll
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
import androidx.compose.foundation.layout.Box
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.foundation.layout.sizeIn
|
import androidx.compose.foundation.layout.sizeIn
|
||||||
import androidx.compose.foundation.rememberScrollState
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.selection.SelectionContainer
|
||||||
import androidx.compose.material.icons.Icons
|
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.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
import androidx.compose.ui.text.input.TextFieldValue
|
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.tooling.preview.Preview
|
||||||
import androidx.compose.ui.unit.dp
|
import androidx.compose.ui.unit.dp
|
||||||
import androidx.hilt.navigation.compose.hiltViewModel
|
import androidx.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
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.Formatter
|
||||||
import com.sadellie.unitto.core.ui.common.MenuButton
|
import com.sadellie.unitto.core.ui.common.MenuButton
|
||||||
import com.sadellie.unitto.core.ui.common.UnittoScreenWithTopBar
|
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.data.model.HistoryItem
|
||||||
import com.sadellie.unitto.feature.calculator.components.CalculatorKeyboard
|
import com.sadellie.unitto.feature.calculator.components.CalculatorKeyboard
|
||||||
import com.sadellie.unitto.feature.calculator.components.DragDownView
|
import com.sadellie.unitto.feature.calculator.components.DragDownView
|
||||||
import com.sadellie.unitto.feature.calculator.components.HistoryList
|
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 kotlinx.coroutines.launch
|
||||||
import java.text.SimpleDateFormat
|
import java.text.SimpleDateFormat
|
||||||
import java.util.*
|
import java.util.*
|
||||||
@ -194,12 +192,13 @@ private fun CalculatorScreen(
|
|||||||
textFields = { maxDragAmount ->
|
textFields = { maxDragAmount ->
|
||||||
Column(
|
Column(
|
||||||
Modifier
|
Modifier
|
||||||
|
.fillMaxHeight(0.25f)
|
||||||
.onPlaced { textThingyHeight = it.size.height }
|
.onPlaced { textThingyHeight = it.size.height }
|
||||||
.background(
|
.background(
|
||||||
MaterialTheme.colorScheme.surfaceVariant,
|
MaterialTheme.colorScheme.surfaceVariant,
|
||||||
RoundedCornerShape(
|
RoundedCornerShape(
|
||||||
topStart = 0.dp, topEnd = 0.dp,
|
topStartPercent = 0, topEndPercent = 0,
|
||||||
bottomStart = 32.dp, bottomEnd = 32.dp
|
bottomStartPercent = 20, bottomEndPercent = 20
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
.draggable(
|
.draggable(
|
||||||
@ -226,31 +225,33 @@ private fun CalculatorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
.padding(top = 8.dp),
|
.padding(top = 12.dp),
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
horizontalAlignment = Alignment.CenterHorizontally,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
InputTextField(
|
InputTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
|
.weight(2f)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 8.dp),
|
.padding(horizontal = 8.dp),
|
||||||
value = uiState.input,
|
value = uiState.input.copy(
|
||||||
onCursorChange = onCursorChange,
|
Formatter.fromSeparator(uiState.input.text, Separator.COMMA)
|
||||||
|
),
|
||||||
|
minRatio = 0.5f,
|
||||||
|
cutCallback = deleteSymbol,
|
||||||
pasteCallback = addSymbol,
|
pasteCallback = addSymbol,
|
||||||
cutCallback = deleteSymbol
|
onCursorChange = onCursorChange
|
||||||
)
|
)
|
||||||
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
if (LocalConfiguration.current.orientation == Configuration.ORIENTATION_PORTRAIT) {
|
||||||
SelectionContainer {
|
SelectionContainer(Modifier.weight(1f)) {
|
||||||
Text(
|
InputTextField(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp),
|
||||||
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
|
value = TextFieldValue(
|
||||||
text = Formatter.format(uiState.output),
|
Formatter.fromSeparator(uiState.output, Separator.COMMA)
|
||||||
textAlign = TextAlign.End,
|
),
|
||||||
softWrap = false,
|
textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.6f)
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
|
|
||||||
style = NumbersTextStyleDisplayMedium,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -268,7 +269,7 @@ private fun CalculatorScreen(
|
|||||||
},
|
},
|
||||||
numPad = {
|
numPad = {
|
||||||
CalculatorKeyboard(
|
CalculatorKeyboard(
|
||||||
modifier = Modifier.padding(horizontal = 8.dp, vertical = 4.dp),
|
modifier = Modifier.fillMaxSize().padding(horizontal = 8.dp, vertical = 4.dp),
|
||||||
angleMode = uiState.angleMode,
|
angleMode = uiState.angleMode,
|
||||||
allowVibration = uiState.allowVibration,
|
allowVibration = uiState.allowVibration,
|
||||||
addSymbol = addSymbol,
|
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
|
@Composable
|
||||||
private fun PreviewCalculatorScreen() {
|
private fun PreviewCalculatorScreen() {
|
||||||
val dtf = SimpleDateFormat("dd.MM.yyyy HH:mm:ss", Locale.getDefault())
|
val dtf = SimpleDateFormat("dd.MM.yyyy HH:mm:ss", Locale.getDefault())
|
||||||
@ -332,14 +338,14 @@ private fun PreviewCalculatorScreen() {
|
|||||||
HistoryItem(
|
HistoryItem(
|
||||||
date = dtf.parse(it)!!,
|
date = dtf.parse(it)!!,
|
||||||
expression = "12345".repeat(10),
|
expression = "12345".repeat(10),
|
||||||
result = "67890"
|
result = "1234"
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
CalculatorScreen(
|
CalculatorScreen(
|
||||||
uiState = CalculatorUIState(
|
uiState = CalculatorUIState(
|
||||||
input = TextFieldValue("12345"),
|
input = TextFieldValue("1.2345"),
|
||||||
output = "12345",
|
output = "1234",
|
||||||
history = historyItems
|
history = historyItems
|
||||||
),
|
),
|
||||||
navigateToMenu = {},
|
navigateToMenu = {},
|
||||||
|
@ -25,11 +25,13 @@ import androidx.compose.animation.core.FastOutSlowInEasing
|
|||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
import androidx.compose.animation.core.tween
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
import androidx.compose.foundation.layout.Arrangement
|
||||||
|
import androidx.compose.foundation.layout.Box
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
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.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.ExpandLess
|
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.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.runtime.setValue
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
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.base.Token
|
||||||
import com.sadellie.unitto.core.ui.Formatter
|
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.KeyboardButtonAdditional
|
||||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled
|
import com.sadellie.unitto.core.ui.common.KeyboardButtonFilled
|
||||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonLight
|
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
|
||||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.AcTan
|
import com.sadellie.unitto.core.ui.common.key.unittoicons.AcTan
|
||||||
import com.sadellie.unitto.core.ui.common.key.unittoicons.ArCos
|
import com.sadellie.unitto.core.ui.common.key.unittoicons.ArCos
|
||||||
@ -150,22 +155,27 @@ private fun PortraitKeyboard(
|
|||||||
animationSpec = tween(easing = FastOutSlowInEasing)
|
animationSpec = tween(easing = FastOutSlowInEasing)
|
||||||
)
|
)
|
||||||
|
|
||||||
Column(
|
ColumnWithConstraints(
|
||||||
modifier = modifier
|
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 weightModifier = Modifier.weight(1f)
|
||||||
val mainButtonModifier = Modifier
|
val mainButtonModifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(4.dp)
|
.padding(horizontalFraction(0.015f), verticalFraction(0.008f))
|
||||||
val additionalButtonModifier = Modifier
|
val additionalButtonModifier = Modifier
|
||||||
.minimumInteractiveComponentSize()
|
.minimumInteractiveComponentSize()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.heightIn(max = 48.dp)
|
.height(verticalFraction(0.075f))
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(verticalFraction(0.015f)))
|
||||||
|
|
||||||
Row(
|
Row(
|
||||||
modifier = Modifier.padding(vertical = 8.dp),
|
modifier = Modifier,
|
||||||
horizontalArrangement = Arrangement.spacedBy(2.dp)
|
horizontalArrangement = Arrangement.spacedBy(horizontalFraction(0.03f))
|
||||||
) {
|
) {
|
||||||
// Additional buttons
|
// Additional buttons
|
||||||
Crossfade(invMode, weightModifier) {
|
Crossfade(invMode, weightModifier) {
|
||||||
@ -192,6 +202,7 @@ private fun PortraitKeyboard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Box(modifier = Modifier.height(verticalFraction(0.075f)), contentAlignment = Alignment.Center) {
|
||||||
// Expand/Collapse
|
// Expand/Collapse
|
||||||
IconButton(
|
IconButton(
|
||||||
onClick = { showAdditional = !showAdditional },
|
onClick = { showAdditional = !showAdditional },
|
||||||
@ -200,6 +211,9 @@ private fun PortraitKeyboard(
|
|||||||
Icon(Icons.Default.ExpandLess, null, Modifier.rotate(expandRotation))
|
Icon(Icons.Default.ExpandLess, null, Modifier.rotate(expandRotation))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(verticalFraction(0.015f)))
|
||||||
|
|
||||||
Row(weightModifier) {
|
Row(weightModifier) {
|
||||||
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.leftBracket) }
|
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.leftBracket) }
|
||||||
@ -232,6 +246,8 @@ private fun PortraitKeyboard(
|
|||||||
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Backspace, allowVibration, clearSymbols) { deleteSymbol() }
|
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Backspace, allowVibration, clearSymbols) { deleteSymbol() }
|
||||||
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Equal, allowVibration) { evaluate() }
|
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Equal, allowVibration) { evaluate() }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(verticalFraction(0.015f)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -246,7 +262,7 @@ private fun AdditionalButtonsPortrait(
|
|||||||
toggleInvMode: () -> Unit
|
toggleInvMode: () -> Unit
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
Row {
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.SquareRootWide, allowVibration) { addSymbol(Token.sqrt) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.SquareRootWide, allowVibration) { addSymbol(Token.sqrt) }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.Pi, allowVibration) { addSymbol(Token.pi) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.Pi, allowVibration) { addSymbol(Token.pi) }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.ExponentWide, allowVibration) { addSymbol(Token.exponent) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.ExponentWide, allowVibration) { addSymbol(Token.exponent) }
|
||||||
@ -254,13 +270,13 @@ private fun AdditionalButtonsPortrait(
|
|||||||
}
|
}
|
||||||
AnimatedVisibility(showAdditional) {
|
AnimatedVisibility(showAdditional) {
|
||||||
Column {
|
Column {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
Row {
|
||||||
KeyboardButtonAdditional(modifier, if (angleMode == AngleMode.DEG) UnittoIcons.Deg else UnittoIcons.Rad, allowVibration) { toggleAngleMode() }
|
KeyboardButtonAdditional(modifier, if (angleMode == AngleMode.DEG) UnittoIcons.Deg else UnittoIcons.Rad, allowVibration) { toggleAngleMode() }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.Sin, allowVibration) { addSymbol(Token.sin) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.Sin, allowVibration) { addSymbol(Token.sin) }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.Cos, allowVibration) { addSymbol(Token.cos) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.Cos, allowVibration) { addSymbol(Token.cos) }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.Tan, allowVibration) { addSymbol(Token.tan) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.Tan, allowVibration) { addSymbol(Token.tan) }
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
Row {
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.Inv, allowVibration) { toggleInvMode() }
|
KeyboardButtonAdditional(modifier, UnittoIcons.Inv, allowVibration) { toggleInvMode() }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.E, allowVibration) { addSymbol(Token.e) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.E, allowVibration) { addSymbol(Token.e) }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.Ln, allowVibration) { addSymbol(Token.ln) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.Ln, allowVibration) { addSymbol(Token.ln) }
|
||||||
@ -282,7 +298,7 @@ private fun AdditionalButtonsPortraitInverse(
|
|||||||
toggleInvMode: () -> Unit
|
toggleInvMode: () -> Unit
|
||||||
) {
|
) {
|
||||||
Column {
|
Column {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
Row {
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.Modulo, allowVibration) { addSymbol(Token.modulo) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.Modulo, allowVibration) { addSymbol(Token.modulo) }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.Pi, allowVibration) { addSymbol(Token.pi) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.Pi, allowVibration) { addSymbol(Token.pi) }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.ExponentWide, allowVibration) { addSymbol(Token.exponent) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.ExponentWide, allowVibration) { addSymbol(Token.exponent) }
|
||||||
@ -290,13 +306,13 @@ private fun AdditionalButtonsPortraitInverse(
|
|||||||
}
|
}
|
||||||
AnimatedVisibility(showAdditional) {
|
AnimatedVisibility(showAdditional) {
|
||||||
Column {
|
Column {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
Row {
|
||||||
KeyboardButtonAdditional(modifier, if (angleMode == AngleMode.DEG) UnittoIcons.Deg else UnittoIcons.Rad, allowVibration) { toggleAngleMode() }
|
KeyboardButtonAdditional(modifier, if (angleMode == AngleMode.DEG) UnittoIcons.Deg else UnittoIcons.Rad, allowVibration) { toggleAngleMode() }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.ArSin, allowVibration) { addSymbol(Token.arSin) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.ArSin, allowVibration) { addSymbol(Token.arSin) }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.ArCos, allowVibration) { addSymbol(Token.arCos) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.ArCos, allowVibration) { addSymbol(Token.arCos) }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.AcTan, allowVibration) { addSymbol(Token.acTan) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.AcTan, allowVibration) { addSymbol(Token.acTan) }
|
||||||
}
|
}
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
Row {
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.Inv, allowVibration) { toggleInvMode() }
|
KeyboardButtonAdditional(modifier, UnittoIcons.Inv, allowVibration) { toggleInvMode() }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.E, allowVibration) { addSymbol(Token.e) }
|
KeyboardButtonAdditional(modifier, UnittoIcons.E, allowVibration) { addSymbol(Token.e) }
|
||||||
KeyboardButtonAdditional(modifier, UnittoIcons.Exp, allowVibration) { addSymbol(Token.exp) }
|
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 }
|
val fractionalIcon = remember { if (Formatter.fractional == Token.dot) UnittoIcons.Dot else UnittoIcons.Comma }
|
||||||
var invMode: Boolean by remember { mutableStateOf(false) }
|
var invMode: Boolean by remember { mutableStateOf(false) }
|
||||||
|
|
||||||
Row(modifier) {
|
RowWithConstraints(modifier) { constraints ->
|
||||||
val buttonModifier = Modifier
|
val buttonModifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(4.dp)
|
.padding(constraints.maxWidth * 0.005f, constraints.maxHeight * 0.02f)
|
||||||
|
|
||||||
Crossfade(invMode, Modifier.weight(3f)) {
|
Crossfade(invMode, Modifier.weight(3f)) {
|
||||||
Row {
|
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
|
package com.sadellie.unitto.feature.converter
|
||||||
|
|
||||||
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
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.graphics.Color
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.tooling.preview.Preview
|
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.hilt.navigation.compose.hiltViewModel
|
||||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||||
import com.sadellie.unitto.core.ui.R
|
import com.sadellie.unitto.core.ui.R
|
||||||
@ -91,7 +94,7 @@ private fun ConverterScreen(
|
|||||||
.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
|
.centerAlignedTopAppBarColors(containerColor = Color.Transparent),
|
||||||
content = { padding ->
|
content = { padding ->
|
||||||
PortraitLandscape(
|
PortraitLandscape(
|
||||||
modifier = Modifier.padding(padding),
|
modifier = Modifier.padding(padding).fillMaxSize(),
|
||||||
content1 = {
|
content1 = {
|
||||||
TopScreenPart(
|
TopScreenPart(
|
||||||
modifier = it,
|
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
|
@Composable
|
||||||
private fun PreviewConverterScreen() {
|
private fun PreviewConverterScreen(
|
||||||
|
@PreviewParameter(PreviewUIState::class) uiState: ConverterUIState
|
||||||
|
) {
|
||||||
ConverterScreen(
|
ConverterScreen(
|
||||||
uiState = ConverterUIState(),
|
uiState = ConverterUIState(inputValue = "1234", calculatedValue = null, resultValue = "5678", showLoading = false),
|
||||||
navigateToLeftScreen = {},
|
navigateToLeftScreen = {},
|
||||||
navigateToRightScreen = {_, _, _ -> },
|
navigateToRightScreen = {_, _, _ -> },
|
||||||
navigateToSettings = {},
|
navigateToSettings = {},
|
||||||
|
@ -19,16 +19,16 @@
|
|||||||
package com.sadellie.unitto.feature.converter.components
|
package com.sadellie.unitto.feature.converter.components
|
||||||
|
|
||||||
import androidx.compose.animation.Crossfade
|
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.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.padding
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.unit.dp
|
|
||||||
import com.sadellie.unitto.core.base.Token
|
import com.sadellie.unitto.core.base.Token
|
||||||
import com.sadellie.unitto.core.ui.Formatter
|
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.KeyboardButtonFilled
|
||||||
import com.sadellie.unitto.core.ui.common.KeyboardButtonLight
|
import com.sadellie.unitto.core.ui.common.KeyboardButtonLight
|
||||||
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
|
import com.sadellie.unitto.core.ui.common.key.UnittoIcons
|
||||||
@ -85,7 +85,6 @@ internal fun Keyboard(
|
|||||||
ConverterMode.BASE -> BaseKeyboard(addDigit, clearInput, deleteDigit, allowVibration)
|
ConverterMode.BASE -> BaseKeyboard(addDigit, clearInput, deleteDigit, allowVibration)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -96,39 +95,39 @@ private fun DefaultKeyboard(
|
|||||||
allowVibration: Boolean
|
allowVibration: Boolean
|
||||||
) {
|
) {
|
||||||
val fractionalIcon = remember { if (Formatter.fractional == Token.dot) UnittoIcons.Dot else UnittoIcons.Comma }
|
val fractionalIcon = remember { if (Formatter.fractional == Token.dot) UnittoIcons.Dot else UnittoIcons.Comma }
|
||||||
Column {
|
ColumnWithConstraints {
|
||||||
// Button modifier
|
// Button modifier
|
||||||
val bModifier = Modifier
|
val bModifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(4.dp)
|
|
||||||
// Column modifier
|
// Column modifier
|
||||||
val cModifier = Modifier.weight(1f)
|
val cModifier = Modifier.weight(1f).padding(vertical = it.maxHeight * 0.01f)
|
||||||
Row(cModifier) {
|
val horizontalArrangement = Arrangement.spacedBy(it.maxWidth * 0.03f)
|
||||||
|
Row(cModifier, horizontalArrangement) {
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.LeftBracket, allowVibration) { addDigit(Token.leftBracket) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.LeftBracket, allowVibration) { addDigit(Token.leftBracket) }
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.RightBracket, allowVibration) { addDigit(Token.rightBracket) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.RightBracket, allowVibration) { addDigit(Token.rightBracket) }
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.Exponent, allowVibration) { addDigit(Token.exponent) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.Exponent, allowVibration) { addDigit(Token.exponent) }
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.SquareRoot, allowVibration) { addDigit(Token.sqrt) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.SquareRoot, allowVibration) { addDigit(Token.sqrt) }
|
||||||
}
|
}
|
||||||
Row(cModifier) {
|
Row(cModifier, horizontalArrangement) {
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key7, allowVibration) { addDigit(Token._7) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key7, allowVibration) { addDigit(Token._7) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key8, allowVibration) { addDigit(Token._8) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key8, allowVibration) { addDigit(Token._8) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key9, allowVibration) { addDigit(Token._9) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key9, allowVibration) { addDigit(Token._9) }
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.Divide, allowVibration) { addDigit(Token.divide) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.Divide, allowVibration) { addDigit(Token.divide) }
|
||||||
}
|
}
|
||||||
Row(cModifier) {
|
Row(cModifier, horizontalArrangement) {
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key4, allowVibration) { addDigit(Token._4) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key4, allowVibration) { addDigit(Token._4) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key5, allowVibration) { addDigit(Token._5) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key5, allowVibration) { addDigit(Token._5) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key6, allowVibration) { addDigit(Token._6) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key6, allowVibration) { addDigit(Token._6) }
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.Multiply, allowVibration) { addDigit(Token.multiply) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.Multiply, allowVibration) { addDigit(Token.multiply) }
|
||||||
}
|
}
|
||||||
Row(cModifier) {
|
Row(cModifier, horizontalArrangement) {
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key1, allowVibration) { addDigit(Token._1) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key1, allowVibration) { addDigit(Token._1) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key2, allowVibration) { addDigit(Token._2) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key2, allowVibration) { addDigit(Token._2) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key3, allowVibration) { addDigit(Token._3) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key3, allowVibration) { addDigit(Token._3) }
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.Minus, allowVibration) { addDigit(Token.minus) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.Minus, allowVibration) { addDigit(Token.minus) }
|
||||||
}
|
}
|
||||||
Row(cModifier) {
|
Row(cModifier, horizontalArrangement) {
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key0, allowVibration) { addDigit(Token._0) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key0, allowVibration) { addDigit(Token._0) }
|
||||||
KeyboardButtonLight(bModifier, fractionalIcon, allowVibration) { addDigit(Token.dot) }
|
KeyboardButtonLight(bModifier, fractionalIcon, allowVibration) { addDigit(Token.dot) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Backspace, allowVibration, clearInput) { deleteDigit() }
|
KeyboardButtonLight(bModifier, UnittoIcons.Backspace, allowVibration, clearInput) { deleteDigit() }
|
||||||
@ -144,43 +143,43 @@ private fun BaseKeyboard(
|
|||||||
deleteDigit: () -> Unit,
|
deleteDigit: () -> Unit,
|
||||||
allowVibration: Boolean
|
allowVibration: Boolean
|
||||||
) {
|
) {
|
||||||
Column {
|
ColumnWithConstraints {
|
||||||
// Button modifier
|
// Button modifier
|
||||||
val bModifier = Modifier
|
val bModifier = Modifier
|
||||||
.fillMaxSize()
|
.fillMaxSize()
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.padding(4.dp)
|
|
||||||
// Column modifier
|
// Column modifier
|
||||||
val cModifier = Modifier.weight(1f)
|
val cModifier = Modifier.weight(1f).padding(vertical = it.maxHeight * 0.01f)
|
||||||
|
val horizontalArrangement = Arrangement.spacedBy(it.maxWidth * 0.03f)
|
||||||
Row(cModifier) {
|
Row(cModifier, horizontalArrangement) {
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.KeyA, allowVibration) { addDigit(Token.baseA) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.KeyA, allowVibration) { addDigit(Token.baseA) }
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.KeyB, allowVibration) { addDigit(Token.baseB) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.KeyB, allowVibration) { addDigit(Token.baseB) }
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.KeyC, allowVibration) { addDigit(Token.baseC) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.KeyC, allowVibration) { addDigit(Token.baseC) }
|
||||||
}
|
}
|
||||||
Row(cModifier) {
|
Row(cModifier, horizontalArrangement) {
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.KeyD, allowVibration) { addDigit(Token.baseD) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.KeyD, allowVibration) { addDigit(Token.baseD) }
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.KeyE, allowVibration) { addDigit(Token.baseE) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.KeyE, allowVibration) { addDigit(Token.baseE) }
|
||||||
KeyboardButtonFilled(bModifier, UnittoIcons.KeyF, allowVibration) { addDigit(Token.baseF) }
|
KeyboardButtonFilled(bModifier, UnittoIcons.KeyF, allowVibration) { addDigit(Token.baseF) }
|
||||||
}
|
}
|
||||||
Row(cModifier) {
|
Row(cModifier, horizontalArrangement) {
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key7, allowVibration) { addDigit(Token._7) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key7, allowVibration) { addDigit(Token._7) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key8, allowVibration) { addDigit(Token._8) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key8, allowVibration) { addDigit(Token._8) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key9, allowVibration) { addDigit(Token._9) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key9, allowVibration) { addDigit(Token._9) }
|
||||||
}
|
}
|
||||||
Row(cModifier) {
|
Row(cModifier, horizontalArrangement) {
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key4, allowVibration) { addDigit(Token._4) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key4, allowVibration) { addDigit(Token._4) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key5, allowVibration) { addDigit(Token._5) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key5, allowVibration) { addDigit(Token._5) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key6, allowVibration) { addDigit(Token._6) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key6, allowVibration) { addDigit(Token._6) }
|
||||||
}
|
}
|
||||||
Row(cModifier) {
|
Row(cModifier, horizontalArrangement) {
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key1, allowVibration) { addDigit(Token._1) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key1, allowVibration) { addDigit(Token._1) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key2, allowVibration) { addDigit(Token._2) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key2, allowVibration) { addDigit(Token._2) }
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key3, allowVibration) { addDigit(Token._3) }
|
KeyboardButtonLight(bModifier, UnittoIcons.Key3, allowVibration) { addDigit(Token._3) }
|
||||||
}
|
}
|
||||||
Row(cModifier) {
|
Row(cModifier, horizontalArrangement) {
|
||||||
KeyboardButtonLight(bModifier, UnittoIcons.Key0, allowVibration) { addDigit(Token._0) }
|
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.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
import androidx.compose.ui.unit.dp
|
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.R
|
||||||
|
import com.sadellie.unitto.core.ui.common.textfield.InputTextField
|
||||||
|
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Component for input and output
|
* Component for input and output
|
||||||
@ -94,7 +95,8 @@ internal fun MyTextField(
|
|||||||
) {
|
) {
|
||||||
LazyRow(
|
LazyRow(
|
||||||
modifier = modifier
|
modifier = modifier
|
||||||
.wrapContentHeight(),
|
.wrapContentHeight()
|
||||||
|
.weight(2f),
|
||||||
reverseLayout = true,
|
reverseLayout = true,
|
||||||
horizontalArrangement = Arrangement.End,
|
horizontalArrangement = Arrangement.End,
|
||||||
contentPadding = PaddingValues(horizontal = 8.dp)
|
contentPadding = PaddingValues(horizontal = 8.dp)
|
||||||
@ -110,19 +112,17 @@ internal fun MyTextField(
|
|||||||
.using(SizeTransform(clip = false))
|
.using(SizeTransform(clip = false))
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(
|
InputTextField(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
// Quick fix to prevent the UI from crashing
|
value = TextFieldValue(it.take(1000)),
|
||||||
text = it.take(1000),
|
textStyle = NumbersTextStyleDisplayLarge.copy(textAlign = TextAlign.End)
|
||||||
textAlign = TextAlign.End,
|
|
||||||
softWrap = false,
|
|
||||||
style = NumbersTextStyleDisplayLarge
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
AnimatedVisibility(
|
AnimatedVisibility(
|
||||||
|
modifier = Modifier.weight(1f),
|
||||||
visible = !secondaryText.isNullOrEmpty(),
|
visible = !secondaryText.isNullOrEmpty(),
|
||||||
enter = expandVertically(),
|
enter = expandVertically(),
|
||||||
exit = shrinkVertically()
|
exit = shrinkVertically()
|
||||||
@ -145,14 +145,14 @@ internal fun MyTextField(
|
|||||||
.using(SizeTransform(clip = false))
|
.using(SizeTransform(clip = false))
|
||||||
}
|
}
|
||||||
) {
|
) {
|
||||||
Text(
|
InputTextField(
|
||||||
modifier = Modifier,
|
modifier = Modifier.fillMaxWidth(),
|
||||||
// Quick fix to prevent the UI from crashing
|
value = TextFieldValue(it?.take(1000) ?: ""),
|
||||||
text = it?.take(1000) ?: "",
|
textStyle = NumbersTextStyleDisplayLarge.copy(
|
||||||
textAlign = TextAlign.End,
|
textAlign = TextAlign.End,
|
||||||
softWrap = false,
|
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
|
),
|
||||||
style = NumbersTextStyleDisplayMedium
|
minRatio = 0.7f
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -162,7 +162,8 @@ internal fun MyTextField(
|
|||||||
AnimatedContent(
|
AnimatedContent(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.align(Alignment.End)
|
.align(Alignment.End)
|
||||||
.padding(horizontal = 8.dp),
|
.padding(horizontal = 8.dp)
|
||||||
|
.weight(1f),
|
||||||
targetState = helperText
|
targetState = helperText
|
||||||
) {
|
) {
|
||||||
Text(
|
Text(
|
||||||
|
@ -18,18 +18,29 @@
|
|||||||
|
|
||||||
package com.sadellie.unitto.feature.converter.components
|
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.FastOutSlowInEasing
|
||||||
import androidx.compose.animation.core.animateFloatAsState
|
import androidx.compose.animation.core.animateFloatAsState
|
||||||
import androidx.compose.animation.core.tween
|
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.Arrangement
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.outlined.SwapHoriz
|
import androidx.compose.material.icons.outlined.SwapHoriz
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
import androidx.compose.runtime.mutableStateOf
|
import androidx.compose.runtime.mutableStateOf
|
||||||
@ -40,9 +51,12 @@ import androidx.compose.ui.Modifier
|
|||||||
import androidx.compose.ui.draw.rotate
|
import androidx.compose.ui.draw.rotate
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
import androidx.compose.ui.res.stringResource
|
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 androidx.compose.ui.unit.dp
|
||||||
import com.sadellie.unitto.core.ui.Formatter
|
import com.sadellie.unitto.core.ui.Formatter
|
||||||
import com.sadellie.unitto.core.ui.R
|
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.AbstractUnit
|
||||||
import com.sadellie.unitto.data.model.UnitGroup
|
import com.sadellie.unitto.data.model.UnitGroup
|
||||||
import com.sadellie.unitto.feature.converter.ConverterMode
|
import com.sadellie.unitto.feature.converter.ConverterMode
|
||||||
@ -95,22 +109,49 @@ internal fun TopScreenPart(
|
|||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
verticalArrangement = Arrangement.spacedBy(8.dp)
|
||||||
) {
|
) {
|
||||||
MyTextField(
|
InputTextField(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.weight(2f),
|
||||||
primaryText = {
|
value = TextFieldValue(
|
||||||
when (converterMode) {
|
when (converterMode) {
|
||||||
ConverterMode.BASE -> inputValue.uppercase()
|
ConverterMode.BASE -> inputValue.uppercase()
|
||||||
else -> Formatter.format(inputValue)
|
else -> Formatter.format(inputValue)
|
||||||
}
|
}
|
||||||
},
|
),
|
||||||
secondaryText = calculatedValue?.let { Formatter.format(it) },
|
minRatio = 0.7f
|
||||||
helperText = stringResource(unitFrom?.shortName ?: R.string.loading_label),
|
|
||||||
textToCopy = calculatedValue ?: inputValue,
|
|
||||||
)
|
)
|
||||||
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(),
|
modifier = Modifier.fillMaxWidth(),
|
||||||
onClick = onOutputTextFieldClick,
|
targetState = stringResource(unitFrom?.shortName ?: R.string.loading_label),
|
||||||
primaryText = {
|
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 {
|
when {
|
||||||
networkLoading -> stringResource(R.string.loading_label)
|
networkLoading -> stringResource(R.string.loading_label)
|
||||||
networkError -> stringResource(R.string.error_label)
|
networkError -> stringResource(R.string.error_label)
|
||||||
@ -124,16 +165,27 @@ internal fun TopScreenPart(
|
|||||||
}
|
}
|
||||||
else -> Formatter.format(outputValue)
|
else -> Formatter.format(outputValue)
|
||||||
}
|
}
|
||||||
},
|
),
|
||||||
secondaryText = null,
|
minRatio = 0.7f
|
||||||
helperText = stringResource(unitTo?.shortName ?: R.string.loading_label),
|
|
||||||
textToCopy = outputValue,
|
|
||||||
)
|
)
|
||||||
// Unit selection buttons
|
AnimatedContent(
|
||||||
Row(
|
modifier = Modifier.fillMaxWidth(),
|
||||||
modifier = Modifier.padding(horizontal = 8.dp),
|
targetState = stringResource(unitTo?.shortName ?: R.string.loading_label),
|
||||||
verticalAlignment = Alignment.Bottom,
|
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(
|
UnitSelectionButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
|
Loading…
x
Reference in New Issue
Block a user