mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-20 09:15:26 +02:00
Merge branch 'feature/responsive-ui'
This commit is contained in:
commit
3a620452c2
@ -18,6 +18,7 @@
|
|||||||
|
|
||||||
package com.sadellie.unitto.core.ui.common
|
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
|
||||||
@ -26,11 +27,9 @@ import androidx.compose.foundation.interaction.MutableInteractionSource
|
|||||||
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
import androidx.compose.foundation.interaction.collectIsPressedAsState
|
||||||
import androidx.compose.foundation.layout.PaddingValues
|
import androidx.compose.foundation.layout.PaddingValues
|
||||||
import androidx.compose.foundation.layout.fillMaxHeight
|
import androidx.compose.foundation.layout.fillMaxHeight
|
||||||
import androidx.compose.foundation.layout.heightIn
|
|
||||||
import androidx.compose.foundation.shape.RoundedCornerShape
|
import androidx.compose.foundation.shape.RoundedCornerShape
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.LaunchedEffect
|
import androidx.compose.runtime.LaunchedEffect
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
@ -38,8 +37,8 @@ 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
|
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun BasicKeyboardButton(
|
fun BasicKeyboardButton(
|
||||||
@ -50,7 +49,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 +65,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 +97,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 +116,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 +129,17 @@ fun KeyboardButtonAdditional(
|
|||||||
onLongClick: (() -> Unit)? = null,
|
onLongClick: (() -> Unit)? = null,
|
||||||
onClick: () -> Unit
|
onClick: () -> Unit
|
||||||
) {
|
) {
|
||||||
|
|
||||||
BasicKeyboardButton(
|
BasicKeyboardButton(
|
||||||
modifier = modifier
|
modifier = modifier,
|
||||||
.minimumInteractiveComponentSize()
|
|
||||||
.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.8f 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
|
||||||
@ -42,33 +41,20 @@ 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.38f).padding(horizontal = it.maxWidth * 0.03f))
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp),
|
content2(Modifier.fillMaxSize().padding(it.maxWidth * 0.03f, it.maxHeight * 0.015f))
|
||||||
) {
|
|
||||||
content1(Modifier)
|
|
||||||
content2(
|
|
||||||
Modifier
|
|
||||||
.fillMaxSize()
|
|
||||||
.padding(horizontal = 8.dp)
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
Row(
|
RowWithConstraints(modifier) {
|
||||||
modifier = modifier.fillMaxSize(),
|
val contentModifier = Modifier
|
||||||
horizontalArrangement = Arrangement.spacedBy(8.dp)
|
.weight(1f)
|
||||||
) {
|
.fillMaxSize()
|
||||||
content1(
|
.padding(
|
||||||
Modifier
|
it.maxWidth * 0.015f, 0.dp,
|
||||||
.weight(1f)
|
it.maxHeight * 0.03f, it.maxHeight * 0.03f)
|
||||||
.fillMaxHeight()
|
content1(contentModifier)
|
||||||
)
|
content2(contentModifier)
|
||||||
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,267 @@
|
|||||||
|
/*
|
||||||
|
* Unitto is a unit converter for Android
|
||||||
|
* Copyright (c) 2023 Elshan Agaev
|
||||||
|
*
|
||||||
|
* This program is free software: you can redistribute it and/or modify
|
||||||
|
* it under the terms of the GNU General Public License as published by
|
||||||
|
* the Free Software Foundation, either version 3 of the License, or
|
||||||
|
* (at your option) any later version.
|
||||||
|
*
|
||||||
|
* This program is distributed in the hope that it will be useful,
|
||||||
|
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||||
|
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||||
|
* GNU General Public License for more details.
|
||||||
|
*
|
||||||
|
* You should have received a copy of the GNU General Public License
|
||||||
|
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package com.sadellie.unitto.core.ui.common.textfield
|
||||||
|
|
||||||
|
import androidx.compose.foundation.clickable
|
||||||
|
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||||
|
import androidx.compose.foundation.layout.BoxWithConstraints
|
||||||
|
import androidx.compose.foundation.layout.widthIn
|
||||||
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
|
import androidx.compose.material3.MaterialTheme
|
||||||
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
|
import androidx.compose.ui.Alignment
|
||||||
|
import androidx.compose.ui.Modifier
|
||||||
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
|
import androidx.compose.ui.focus.focusRequester
|
||||||
|
import androidx.compose.ui.geometry.Offset
|
||||||
|
import androidx.compose.ui.geometry.Rect
|
||||||
|
import androidx.compose.ui.graphics.Color
|
||||||
|
import androidx.compose.ui.graphics.SolidColor
|
||||||
|
import androidx.compose.ui.layout.layout
|
||||||
|
import androidx.compose.ui.layout.onGloballyPositioned
|
||||||
|
import androidx.compose.ui.layout.positionInWindow
|
||||||
|
import androidx.compose.ui.platform.ClipboardManager
|
||||||
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
import androidx.compose.ui.platform.LocalDensity
|
||||||
|
import androidx.compose.ui.platform.LocalTextInputService
|
||||||
|
import androidx.compose.ui.platform.LocalTextToolbar
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
|
import androidx.compose.ui.text.Paragraph
|
||||||
|
import androidx.compose.ui.text.TextRange
|
||||||
|
import androidx.compose.ui.text.TextStyle
|
||||||
|
import androidx.compose.ui.text.font.createFontFamilyResolver
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
|
import androidx.compose.ui.unit.Constraints
|
||||||
|
import androidx.compose.ui.unit.TextUnit
|
||||||
|
import androidx.compose.ui.unit.sp
|
||||||
|
import com.sadellie.unitto.core.base.Separator
|
||||||
|
import com.sadellie.unitto.core.ui.Formatter
|
||||||
|
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayLarge
|
||||||
|
import kotlin.math.ceil
|
||||||
|
import kotlin.math.roundToInt
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InputTextField(
|
||||||
|
modifier: Modifier,
|
||||||
|
value: TextFieldValue,
|
||||||
|
textStyle: TextStyle = NumbersTextStyleDisplayLarge,
|
||||||
|
minRatio: Float = 1f,
|
||||||
|
cutCallback: () -> Unit,
|
||||||
|
pasteCallback: (String) -> Unit,
|
||||||
|
onCursorChange: (IntRange) -> Unit,
|
||||||
|
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
) {
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
fun copyCallback() = clipboardManager.copyWithoutGrouping(value)
|
||||||
|
|
||||||
|
val textToolbar = UnittoTextToolbar(
|
||||||
|
view = LocalView.current,
|
||||||
|
copyCallback = ::copyCallback,
|
||||||
|
pasteCallback = {
|
||||||
|
pasteCallback(
|
||||||
|
Formatter.toSeparator(
|
||||||
|
clipboardManager.getText()?.text ?: "", Separator.COMMA
|
||||||
|
)
|
||||||
|
)
|
||||||
|
},
|
||||||
|
cutCallback = {
|
||||||
|
copyCallback()
|
||||||
|
cutCallback()
|
||||||
|
onCursorChange(value.selection.end..value.selection.end)
|
||||||
|
}
|
||||||
|
)
|
||||||
|
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalTextInputService provides null,
|
||||||
|
LocalTextToolbar provides textToolbar
|
||||||
|
) {
|
||||||
|
AutoSizableTextField(
|
||||||
|
modifier = modifier,
|
||||||
|
value = value,
|
||||||
|
textStyle = textStyle.copy(color = textColor),
|
||||||
|
minRatio = minRatio,
|
||||||
|
onValueChange = {
|
||||||
|
onCursorChange(it.selection.start..it.selection.end)
|
||||||
|
},
|
||||||
|
showToolbar = textToolbar::showMenu,
|
||||||
|
hideToolbar = textToolbar::hide
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
fun InputTextField(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
value: String,
|
||||||
|
textStyle: TextStyle = NumbersTextStyleDisplayLarge,
|
||||||
|
minRatio: Float = 1f,
|
||||||
|
textColor: Color = MaterialTheme.colorScheme.onSurfaceVariant,
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
var textFieldValue by remember(value) {
|
||||||
|
mutableStateOf(TextFieldValue(value, selection = TextRange(value.length)))
|
||||||
|
}
|
||||||
|
val clipboardManager = LocalClipboardManager.current
|
||||||
|
fun copyCallback() {
|
||||||
|
clipboardManager.copyWithoutGrouping(textFieldValue)
|
||||||
|
textFieldValue = textFieldValue.copy(selection = TextRange(textFieldValue.selection.end))
|
||||||
|
}
|
||||||
|
|
||||||
|
CompositionLocalProvider(
|
||||||
|
LocalTextInputService provides null,
|
||||||
|
LocalTextToolbar provides UnittoTextToolbar(
|
||||||
|
view = LocalView.current,
|
||||||
|
copyCallback = ::copyCallback,
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
AutoSizableTextField(
|
||||||
|
modifier = modifier,
|
||||||
|
value = textFieldValue,
|
||||||
|
onValueChange = { textFieldValue = it },
|
||||||
|
textStyle = textStyle.copy(color = textColor),
|
||||||
|
minRatio = minRatio,
|
||||||
|
readOnly = true,
|
||||||
|
interactionSource = interactionSource
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Composable
|
||||||
|
private fun AutoSizableTextField(
|
||||||
|
modifier: Modifier = Modifier,
|
||||||
|
value: TextFieldValue,
|
||||||
|
textStyle: TextStyle = TextStyle(),
|
||||||
|
scaleFactor: Float = 0.95f,
|
||||||
|
minRatio: Float = 1f,
|
||||||
|
onValueChange: (TextFieldValue) -> Unit,
|
||||||
|
readOnly: Boolean = false,
|
||||||
|
showToolbar: (rect: Rect) -> Unit = {},
|
||||||
|
hideToolbar: () -> Unit = {},
|
||||||
|
interactionSource: MutableInteractionSource = remember { MutableInteractionSource() }
|
||||||
|
) {
|
||||||
|
val focusRequester = remember { FocusRequester() }
|
||||||
|
val density = LocalDensity.current
|
||||||
|
|
||||||
|
var nFontSize: TextUnit by remember { mutableStateOf(0.sp) }
|
||||||
|
var minFontSize: TextUnit
|
||||||
|
|
||||||
|
BoxWithConstraints(
|
||||||
|
modifier = modifier,
|
||||||
|
contentAlignment = Alignment.BottomStart
|
||||||
|
) {
|
||||||
|
with(density) {
|
||||||
|
// Cursor handle is not visible without this, 0.836f is the minimum required factor here
|
||||||
|
nFontSize = maxHeight.toSp() * 0.836f
|
||||||
|
minFontSize = nFontSize * minRatio
|
||||||
|
}
|
||||||
|
|
||||||
|
// Modified: https://blog.canopas.com/autosizing-textfield-in-jetpack-compose-7a80f0270853
|
||||||
|
val calculateParagraph = @Composable {
|
||||||
|
Paragraph(
|
||||||
|
text = value.text,
|
||||||
|
style = textStyle.copy(fontSize = nFontSize),
|
||||||
|
constraints = Constraints(
|
||||||
|
maxWidth = ceil(with(density) { maxWidth.toPx() }).toInt()
|
||||||
|
),
|
||||||
|
density = density,
|
||||||
|
fontFamilyResolver = createFontFamilyResolver(LocalContext.current),
|
||||||
|
spanStyles = listOf(),
|
||||||
|
placeholders = listOf(),
|
||||||
|
maxLines = 1,
|
||||||
|
ellipsis = false
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
var intrinsics = calculateParagraph()
|
||||||
|
with(density) {
|
||||||
|
while ((intrinsics.maxIntrinsicWidth > maxWidth.toPx()) && nFontSize >= minFontSize) {
|
||||||
|
nFontSize *= scaleFactor
|
||||||
|
intrinsics = calculateParagraph()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
val nTextStyle = textStyle.copy(
|
||||||
|
// https://issuetracker.google.com/issues/266470454
|
||||||
|
// textAlign = TextAlign.End,
|
||||||
|
fontSize = nFontSize
|
||||||
|
)
|
||||||
|
var offset = Offset.Zero
|
||||||
|
|
||||||
|
BasicTextField(
|
||||||
|
value = value,
|
||||||
|
onValueChange = {
|
||||||
|
showToolbar(Rect(offset, 0f))
|
||||||
|
hideToolbar()
|
||||||
|
onValueChange(it)
|
||||||
|
},
|
||||||
|
modifier = Modifier
|
||||||
|
.focusRequester(focusRequester)
|
||||||
|
.clickable(
|
||||||
|
interactionSource = remember { MutableInteractionSource() },
|
||||||
|
indication = null,
|
||||||
|
onClick = {
|
||||||
|
hideToolbar()
|
||||||
|
focusRequester.requestFocus()
|
||||||
|
onValueChange(value.copy(selection = TextRange.Zero))
|
||||||
|
showToolbar(Rect(offset, 0f))
|
||||||
|
}
|
||||||
|
)
|
||||||
|
.widthIn(max = with(density) { intrinsics.width.toDp() })
|
||||||
|
.layout { measurable, constraints ->
|
||||||
|
val placeable = measurable.measure(constraints)
|
||||||
|
// TextField size is changed with a delay (text jumps). Here we correct it.
|
||||||
|
layout(placeable.width, placeable.height) {
|
||||||
|
placeable.place(
|
||||||
|
x = (intrinsics.width - intrinsics.maxIntrinsicWidth)
|
||||||
|
.coerceAtLeast(0f)
|
||||||
|
.roundToInt(),
|
||||||
|
y = (placeable.height - intrinsics.height).roundToInt()
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.onGloballyPositioned { layoutCoords -> offset = layoutCoords.positionInWindow() },
|
||||||
|
textStyle = nTextStyle,
|
||||||
|
cursorBrush = SolidColor(MaterialTheme.colorScheme.onSurfaceVariant),
|
||||||
|
singleLine = true,
|
||||||
|
readOnly = readOnly,
|
||||||
|
interactionSource = interactionSource
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copy value to clipboard without grouping symbols.
|
||||||
|
*
|
||||||
|
* Example:
|
||||||
|
* "123.456,789" will be copied as "123456,789"
|
||||||
|
*
|
||||||
|
* @param value Formatted value that has grouping symbols.
|
||||||
|
*/
|
||||||
|
fun ClipboardManager.copyWithoutGrouping(value: TextFieldValue) = this.setText(
|
||||||
|
AnnotatedString(
|
||||||
|
Formatter.removeGrouping(value.annotatedString.subSequence(value.selection).text)
|
||||||
|
)
|
||||||
|
)
|
@ -16,7 +16,7 @@
|
|||||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
* 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,
|
||||||
@ -45,10 +45,8 @@ internal class UnittoTextToolbar(
|
|||||||
onSelectAllRequested: (() -> Unit)?
|
onSelectAllRequested: (() -> Unit)?
|
||||||
) {
|
) {
|
||||||
textActionModeCallback.rect = rect
|
textActionModeCallback.rect = rect
|
||||||
textActionModeCallback.onCopyRequested = { onCopyRequested?.invoke(); copyCallback.invoke() }
|
textActionModeCallback.onCopyRequested = copyCallback
|
||||||
textActionModeCallback.onCutRequested = cutCallback?.let {
|
textActionModeCallback.onCutRequested = cutCallback
|
||||||
{ it.invoke(); onCutRequested?.invoke() }
|
|
||||||
}
|
|
||||||
textActionModeCallback.onPasteRequested = pasteCallback
|
textActionModeCallback.onPasteRequested = pasteCallback
|
||||||
textActionModeCallback.onSelectAllRequested = onSelectAllRequested
|
textActionModeCallback.onSelectAllRequested = onSelectAllRequested
|
||||||
if (actionMode == null) {
|
if (actionMode == null) {
|
@ -23,6 +23,7 @@ import androidx.compose.ui.text.TextStyle
|
|||||||
import androidx.compose.ui.text.font.Font
|
import androidx.compose.ui.text.font.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,17 +26,15 @@ 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.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.Delete
|
import androidx.compose.material.icons.filled.Delete
|
||||||
import androidx.compose.material.icons.outlined.MoreVert
|
import androidx.compose.material.icons.outlined.MoreVert
|
||||||
@ -48,7 +46,6 @@ import androidx.compose.material3.Text
|
|||||||
import androidx.compose.material3.TextButton
|
import androidx.compose.material3.TextButton
|
||||||
import androidx.compose.material3.TopAppBarDefaults
|
import androidx.compose.material3.TopAppBarDefaults
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import androidx.compose.runtime.CompositionLocalProvider
|
|
||||||
import androidx.compose.runtime.getValue
|
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
|
||||||
@ -58,28 +55,22 @@ import androidx.compose.runtime.setValue
|
|||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.onPlaced
|
import androidx.compose.ui.layout.onPlaced
|
||||||
import androidx.compose.ui.platform.LocalClipboardManager
|
|
||||||
import androidx.compose.ui.platform.LocalConfiguration
|
import androidx.compose.ui.platform.LocalConfiguration
|
||||||
import androidx.compose.ui.platform.LocalTextToolbar
|
|
||||||
import androidx.compose.ui.platform.LocalView
|
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
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.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.*
|
||||||
@ -92,39 +83,20 @@ internal fun CalculatorRoute(
|
|||||||
navigateToSettings: () -> Unit,
|
navigateToSettings: () -> Unit,
|
||||||
viewModel: CalculatorViewModel = hiltViewModel()
|
viewModel: CalculatorViewModel = hiltViewModel()
|
||||||
) {
|
) {
|
||||||
val clipboardManager = LocalClipboardManager.current
|
|
||||||
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
|
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
|
||||||
|
|
||||||
fun copyToClipboard() {
|
CalculatorScreen(
|
||||||
val clipboardText = clipboardManager.getText() ?: return
|
uiState = uiState.value,
|
||||||
// This method is called immediately after copying formatted text, we replace it with the
|
navigateToMenu = navigateToMenu,
|
||||||
// the unformatted version.
|
navigateToSettings = navigateToSettings,
|
||||||
clipboardManager.setText(
|
addSymbol = viewModel::addSymbol,
|
||||||
AnnotatedString(
|
clearSymbols = viewModel::clearSymbols,
|
||||||
clipboardText.text.replace(Formatter.grouping, "")
|
deleteSymbol = viewModel::deleteSymbol,
|
||||||
)
|
onCursorChange = viewModel::onCursorChange,
|
||||||
)
|
toggleAngleMode = viewModel::toggleCalculatorMode,
|
||||||
}
|
evaluate = viewModel::evaluate,
|
||||||
|
clearHistory = viewModel::clearHistory
|
||||||
CompositionLocalProvider(
|
)
|
||||||
LocalTextToolbar provides UnittoTextToolbar(
|
|
||||||
view = LocalView.current,
|
|
||||||
copyCallback = ::copyToClipboard
|
|
||||||
)
|
|
||||||
) {
|
|
||||||
CalculatorScreen(
|
|
||||||
uiState = uiState.value,
|
|
||||||
navigateToMenu = navigateToMenu,
|
|
||||||
navigateToSettings = navigateToSettings,
|
|
||||||
addSymbol = viewModel::addSymbol,
|
|
||||||
clearSymbols = viewModel::clearSymbols,
|
|
||||||
deleteSymbol = viewModel::deleteSymbol,
|
|
||||||
onCursorChange = viewModel::onCursorChange,
|
|
||||||
toggleAngleMode = viewModel::toggleCalculatorMode,
|
|
||||||
evaluate = viewModel::evaluate,
|
|
||||||
clearHistory = viewModel::clearHistory
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
@ -188,18 +160,18 @@ private fun CalculatorScreen(
|
|||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
historyItems = uiState.history,
|
historyItems = uiState.history,
|
||||||
historyItemHeightCallback = { historyItemHeight = it },
|
historyItemHeightCallback = { historyItemHeight = it },
|
||||||
onTextClick = addSymbol
|
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
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,33 +198,32 @@ 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 {
|
InputTextField(
|
||||||
Text(
|
modifier = Modifier
|
||||||
modifier = Modifier
|
.weight(1f)
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.padding(horizontal = 8.dp)
|
.padding(horizontal = 8.dp),
|
||||||
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
|
value = Formatter.format(uiState.output),
|
||||||
text = Formatter.format(uiState.output),
|
textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(0.6f)
|
||||||
textAlign = TextAlign.End,
|
)
|
||||||
softWrap = false,
|
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f),
|
|
||||||
style = NumbersTextStyleDisplayMedium,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Handle
|
// Handle
|
||||||
Box(
|
Box(
|
||||||
@ -268,7 +239,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),
|
||||||
radianMode = uiState.radianMode,
|
radianMode = uiState.radianMode,
|
||||||
allowVibration = uiState.allowVibration,
|
allowVibration = uiState.allowVibration,
|
||||||
addSymbol = addSymbol,
|
addSymbol = addSymbol,
|
||||||
@ -314,7 +285,12 @@ private fun CalculatorScreen(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Preview
|
@Preview(widthDp = 432, heightDp = 1008, device = "spec:parent=pixel_5,orientation=portrait")
|
||||||
|
@Preview(widthDp = 432, heightDp = 864, device = "spec:parent=pixel_5,orientation=portrait")
|
||||||
|
@Preview(widthDp = 597, heightDp = 1393, device = "spec:parent=pixel_5,orientation=portrait")
|
||||||
|
@Preview(heightDp = 432, widthDp = 1008, device = "spec:parent=pixel_5,orientation=landscape")
|
||||||
|
@Preview(heightDp = 432, widthDp = 864, device = "spec:parent=pixel_5,orientation=landscape")
|
||||||
|
@Preview(heightDp = 597, widthDp = 1393, device = "spec:parent=pixel_5,orientation=landscape")
|
||||||
@Composable
|
@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 +308,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,34 +25,39 @@ 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.foundation.layout.size
|
||||||
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
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.IconButton
|
import androidx.compose.material3.IconButton
|
||||||
import androidx.compose.material3.IconButtonDefaults
|
import androidx.compose.material3.IconButtonDefaults
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.minimumInteractiveComponentSize
|
|
||||||
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
|
||||||
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
|
||||||
@ -149,22 +154,26 @@ 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.009f))
|
||||||
val additionalButtonModifier = Modifier
|
val additionalButtonModifier = Modifier
|
||||||
.minimumInteractiveComponentSize()
|
|
||||||
.weight(1f)
|
.weight(1f)
|
||||||
.heightIn(max = 48.dp)
|
.height(verticalFraction(0.09f))
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(verticalFraction(0.025f)))
|
||||||
|
|
||||||
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) {
|
||||||
@ -191,22 +200,28 @@ private fun PortraitKeyboard(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Expand/Collapse
|
Box(
|
||||||
IconButton(
|
modifier = Modifier.size(verticalFraction(0.09f)),
|
||||||
onClick = { showAdditional = !showAdditional },
|
contentAlignment = Alignment.Center
|
||||||
colors = IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.inverseOnSurface)
|
|
||||||
) {
|
) {
|
||||||
Icon(Icons.Default.ExpandLess, null, Modifier.rotate(expandRotation))
|
// Expand/Collapse
|
||||||
|
IconButton(
|
||||||
|
onClick = { showAdditional = !showAdditional },
|
||||||
|
colors = IconButtonDefaults.iconButtonColors(containerColor = MaterialTheme.colorScheme.inverseOnSurface)
|
||||||
|
) {
|
||||||
|
Icon(Icons.Default.ExpandLess, null, Modifier.rotate(expandRotation))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(verticalFraction(0.025f)))
|
||||||
|
|
||||||
Row(weightModifier) {
|
Row(weightModifier) {
|
||||||
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.leftBracket) }
|
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.LeftBracket, allowVibration) { addSymbol(Token.leftBracket) }
|
||||||
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.RightBracket, allowVibration) { addSymbol(Token.rightBracket) }
|
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.RightBracket, allowVibration) { addSymbol(Token.rightBracket) }
|
||||||
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Percent, allowVibration) { addSymbol(Token.percent) }
|
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Percent, allowVibration) { addSymbol(Token.percent) }
|
||||||
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Divide, allowVibration) { addSymbol(Token.divideDisplay) }
|
KeyboardButtonFilled(mainButtonModifier, UnittoIcons.Divide, allowVibration) { addSymbol(Token.divideDisplay) }
|
||||||
}
|
}
|
||||||
|
|
||||||
Row(weightModifier) {
|
Row(weightModifier) {
|
||||||
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key7, allowVibration) { addSymbol(Token._7) }
|
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key7, allowVibration) { addSymbol(Token._7) }
|
||||||
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key8, allowVibration) { addSymbol(Token._8) }
|
KeyboardButtonLight(mainButtonModifier, UnittoIcons.Key8, allowVibration) { addSymbol(Token._8) }
|
||||||
@ -231,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)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -245,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) }
|
||||||
@ -253,13 +270,13 @@ private fun AdditionalButtonsPortrait(
|
|||||||
}
|
}
|
||||||
AnimatedVisibility(showAdditional) {
|
AnimatedVisibility(showAdditional) {
|
||||||
Column {
|
Column {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
Row {
|
||||||
KeyboardButtonAdditional(modifier, if (radianMode) UnittoIcons.Rad else UnittoIcons.Deg, allowVibration) { toggleAngleMode() }
|
KeyboardButtonAdditional(modifier, if (radianMode) UnittoIcons.Rad else UnittoIcons.Deg, 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) }
|
||||||
@ -281,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) }
|
||||||
@ -289,13 +306,13 @@ private fun AdditionalButtonsPortraitInverse(
|
|||||||
}
|
}
|
||||||
AnimatedVisibility(showAdditional) {
|
AnimatedVisibility(showAdditional) {
|
||||||
Column {
|
Column {
|
||||||
Row(horizontalArrangement = Arrangement.spacedBy(2.dp)) {
|
Row {
|
||||||
KeyboardButtonAdditional(modifier, if (radianMode) UnittoIcons.Rad else UnittoIcons.Deg, allowVibration) { toggleAngleMode() }
|
KeyboardButtonAdditional(modifier, if (radianMode) UnittoIcons.Rad else UnittoIcons.Deg, 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) }
|
||||||
@ -320,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 {
|
||||||
|
@ -19,10 +19,8 @@
|
|||||||
package com.sadellie.unitto.feature.calculator.components
|
package com.sadellie.unitto.feature.calculator.components
|
||||||
|
|
||||||
import androidx.compose.foundation.background
|
import androidx.compose.foundation.background
|
||||||
import androidx.compose.foundation.clickable
|
|
||||||
import androidx.compose.foundation.horizontalScroll
|
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.Column
|
import androidx.compose.foundation.layout.Column
|
||||||
import androidx.compose.foundation.layout.fillMaxSize
|
import androidx.compose.foundation.layout.fillMaxSize
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
@ -30,24 +28,35 @@ import androidx.compose.foundation.layout.padding
|
|||||||
import androidx.compose.foundation.lazy.LazyColumn
|
import androidx.compose.foundation.lazy.LazyColumn
|
||||||
import androidx.compose.foundation.lazy.items
|
import androidx.compose.foundation.lazy.items
|
||||||
import androidx.compose.foundation.rememberScrollState
|
import androidx.compose.foundation.rememberScrollState
|
||||||
import androidx.compose.foundation.text.selection.SelectionContainer
|
import androidx.compose.foundation.text.BasicTextField
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.Icons
|
||||||
import androidx.compose.material.icons.filled.History
|
import androidx.compose.material.icons.filled.History
|
||||||
import androidx.compose.material3.Icon
|
import androidx.compose.material3.Icon
|
||||||
import androidx.compose.material3.MaterialTheme
|
import androidx.compose.material3.MaterialTheme
|
||||||
import androidx.compose.material3.Text
|
import androidx.compose.material3.Text
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
|
import androidx.compose.runtime.CompositionLocalProvider
|
||||||
import androidx.compose.runtime.derivedStateOf
|
import androidx.compose.runtime.derivedStateOf
|
||||||
import androidx.compose.runtime.getValue
|
import androidx.compose.runtime.getValue
|
||||||
|
import androidx.compose.runtime.mutableStateOf
|
||||||
import androidx.compose.runtime.remember
|
import androidx.compose.runtime.remember
|
||||||
|
import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.layout.onPlaced
|
import androidx.compose.ui.layout.onPlaced
|
||||||
|
import androidx.compose.ui.platform.LocalClipboardManager
|
||||||
|
import androidx.compose.ui.platform.LocalTextInputService
|
||||||
|
import androidx.compose.ui.platform.LocalTextToolbar
|
||||||
|
import androidx.compose.ui.platform.LocalView
|
||||||
import androidx.compose.ui.res.stringResource
|
import androidx.compose.ui.res.stringResource
|
||||||
|
import androidx.compose.ui.text.TextRange
|
||||||
|
import androidx.compose.ui.text.input.TextFieldValue
|
||||||
import androidx.compose.ui.text.style.TextAlign
|
import androidx.compose.ui.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 com.sadellie.unitto.core.ui.Formatter
|
import com.sadellie.unitto.core.ui.Formatter
|
||||||
|
import com.sadellie.unitto.core.ui.common.textfield.UnittoTextToolbar
|
||||||
|
import com.sadellie.unitto.core.ui.common.textfield.copyWithoutGrouping
|
||||||
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium
|
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium
|
||||||
import com.sadellie.unitto.data.model.HistoryItem
|
import com.sadellie.unitto.data.model.HistoryItem
|
||||||
import com.sadellie.unitto.feature.calculator.R
|
import com.sadellie.unitto.feature.calculator.R
|
||||||
@ -59,7 +68,6 @@ internal fun HistoryList(
|
|||||||
modifier: Modifier,
|
modifier: Modifier,
|
||||||
historyItems: List<HistoryItem>,
|
historyItems: List<HistoryItem>,
|
||||||
historyItemHeightCallback: (Int) -> Unit,
|
historyItemHeightCallback: (Int) -> Unit,
|
||||||
onTextClick: (String) -> Unit
|
|
||||||
) {
|
) {
|
||||||
val verticalArrangement by remember(historyItems) {
|
val verticalArrangement by remember(historyItems) {
|
||||||
derivedStateOf {
|
derivedStateOf {
|
||||||
@ -95,15 +103,13 @@ internal fun HistoryList(
|
|||||||
item {
|
item {
|
||||||
HistoryListItem(
|
HistoryListItem(
|
||||||
modifier = Modifier.onPlaced { historyItemHeightCallback(it.size.height) },
|
modifier = Modifier.onPlaced { historyItemHeightCallback(it.size.height) },
|
||||||
historyItem = historyItems.first(),
|
historyItem = historyItems.first()
|
||||||
onTextClick = onTextClick
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
items(historyItems.drop(1)) { historyItem ->
|
items(historyItems.drop(1)) { historyItem ->
|
||||||
HistoryListItem(
|
HistoryListItem(
|
||||||
modifier = Modifier,
|
modifier = Modifier,
|
||||||
historyItem = historyItem,
|
historyItem = historyItem
|
||||||
onTextClick = onTextClick
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -114,40 +120,56 @@ internal fun HistoryList(
|
|||||||
private fun HistoryListItem(
|
private fun HistoryListItem(
|
||||||
modifier: Modifier = Modifier,
|
modifier: Modifier = Modifier,
|
||||||
historyItem: HistoryItem,
|
historyItem: HistoryItem,
|
||||||
onTextClick: (String) -> Unit
|
|
||||||
) {
|
) {
|
||||||
SelectionContainer {
|
val clipboardManager = LocalClipboardManager.current
|
||||||
Column(modifier = modifier) {
|
val expr = Formatter.format(historyItem.expression)
|
||||||
Box(
|
var textFieldexpr by remember(expr) {
|
||||||
Modifier.clickable { onTextClick(historyItem.expression) }
|
mutableStateOf(TextFieldValue(expr, selection = TextRange(expr.length)))
|
||||||
) {
|
}
|
||||||
Text(
|
val res = Formatter.format(historyItem.expression)
|
||||||
text = Formatter.format(historyItem.expression),
|
var textFieldRes by remember(res) {
|
||||||
maxLines = 1,
|
mutableStateOf(TextFieldValue(res, selection = TextRange(res.length)))
|
||||||
modifier = Modifier
|
}
|
||||||
.fillMaxWidth()
|
|
||||||
.padding(horizontal = 8.dp)
|
Column(modifier = modifier) {
|
||||||
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
|
CompositionLocalProvider(
|
||||||
style = NumbersTextStyleDisplayMedium,
|
LocalTextInputService provides null,
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant,
|
LocalTextToolbar provides UnittoTextToolbar(
|
||||||
textAlign = TextAlign.End
|
view = LocalView.current,
|
||||||
)
|
copyCallback = { clipboardManager.copyWithoutGrouping(textFieldexpr) }
|
||||||
}
|
)
|
||||||
Box(
|
) {
|
||||||
Modifier.clickable { onTextClick(historyItem.result) }
|
BasicTextField(
|
||||||
) {
|
value = textFieldexpr,
|
||||||
Text(
|
onValueChange = { textFieldexpr = it },
|
||||||
text = Formatter.format(historyItem.result),
|
maxLines = 1,
|
||||||
maxLines = 1,
|
modifier = Modifier
|
||||||
modifier = Modifier
|
.fillMaxWidth()
|
||||||
.fillMaxWidth()
|
.padding(horizontal = 8.dp)
|
||||||
.padding(horizontal = 8.dp)
|
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
|
||||||
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
|
textStyle = NumbersTextStyleDisplayMedium.copy(color = MaterialTheme.colorScheme.onSurfaceVariant, textAlign = TextAlign.End),
|
||||||
style = NumbersTextStyleDisplayMedium,
|
readOnly = true
|
||||||
color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f),
|
)
|
||||||
textAlign = TextAlign.End
|
}
|
||||||
)
|
|
||||||
}
|
CompositionLocalProvider(
|
||||||
|
LocalTextInputService provides null,
|
||||||
|
LocalTextToolbar provides UnittoTextToolbar(
|
||||||
|
view = LocalView.current,
|
||||||
|
copyCallback = { clipboardManager.copyWithoutGrouping(textFieldRes) }
|
||||||
|
)
|
||||||
|
) {
|
||||||
|
BasicTextField(
|
||||||
|
value = textFieldRes,
|
||||||
|
onValueChange = { textFieldRes = it },
|
||||||
|
maxLines = 1,
|
||||||
|
modifier = Modifier
|
||||||
|
.fillMaxWidth()
|
||||||
|
.padding(horizontal = 8.dp)
|
||||||
|
.horizontalScroll(rememberScrollState(), reverseScrolling = true),
|
||||||
|
textStyle = NumbersTextStyleDisplayMedium.copy(color = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.5f), textAlign = TextAlign.End),
|
||||||
|
readOnly = true
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -178,8 +200,6 @@ private fun PreviewHistoryList() {
|
|||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f))
|
.background(MaterialTheme.colorScheme.surfaceVariant.copy(alpha = 0.5f))
|
||||||
.fillMaxSize(),
|
.fillMaxSize(),
|
||||||
historyItems = historyItems,
|
historyItems = historyItems
|
||||||
historyItemHeightCallback = {},
|
) {}
|
||||||
onTextClick = {}
|
|
||||||
)
|
|
||||||
}
|
}
|
||||||
|
@ -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() }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,9 +50,9 @@ import androidx.compose.ui.res.stringResource
|
|||||||
import androidx.compose.ui.text.AnnotatedString
|
import androidx.compose.ui.text.AnnotatedString
|
||||||
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 +94,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 +111,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 = 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 +144,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 = 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 +161,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,28 @@
|
|||||||
|
|
||||||
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.foundation.layout.Arrangement
|
import androidx.compose.animation.expandHorizontally
|
||||||
import androidx.compose.foundation.layout.Column
|
import androidx.compose.animation.expandVertically
|
||||||
|
import androidx.compose.animation.fadeIn
|
||||||
|
import androidx.compose.animation.fadeOut
|
||||||
|
import androidx.compose.animation.shrinkVertically
|
||||||
|
import androidx.compose.animation.with
|
||||||
import androidx.compose.foundation.layout.Row
|
import androidx.compose.foundation.layout.Row
|
||||||
|
import androidx.compose.foundation.layout.Spacer
|
||||||
import androidx.compose.foundation.layout.fillMaxWidth
|
import androidx.compose.foundation.layout.fillMaxWidth
|
||||||
import androidx.compose.foundation.layout.padding
|
import androidx.compose.foundation.layout.height
|
||||||
import androidx.compose.material.icons.Icons
|
import androidx.compose.material.icons.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 +50,11 @@ 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.unit.dp
|
import androidx.compose.ui.text.style.TextAlign
|
||||||
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.ColumnWithConstraints
|
||||||
|
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
|
||||||
@ -91,54 +103,89 @@ internal fun TopScreenPart(
|
|||||||
)
|
)
|
||||||
val mContext = LocalContext.current
|
val mContext = LocalContext.current
|
||||||
|
|
||||||
Column(
|
ColumnWithConstraints(
|
||||||
modifier = modifier,
|
modifier = modifier,
|
||||||
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
||||||
) {
|
) {
|
||||||
MyTextField(
|
InputTextField(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
modifier = Modifier.weight(2f),
|
||||||
primaryText = {
|
value = 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(
|
||||||
modifier = Modifier.fillMaxWidth(),
|
visible = !calculatedValue.isNullOrEmpty(),
|
||||||
onClick = onOutputTextFieldClick,
|
modifier = Modifier.weight(1f),
|
||||||
primaryText = {
|
enter = expandVertically(clip = false),
|
||||||
when {
|
exit = shrinkVertically(clip = false)
|
||||||
networkLoading -> stringResource(R.string.loading_label)
|
|
||||||
networkError -> stringResource(R.string.error_label)
|
|
||||||
converterMode == ConverterMode.BASE -> outputValue.uppercase()
|
|
||||||
formatTime and (unitTo?.group == UnitGroup.TIME) -> {
|
|
||||||
Formatter.formatTime(
|
|
||||||
context = mContext,
|
|
||||||
input = calculatedValue ?: inputValue,
|
|
||||||
basicUnit = unitFrom?.basicUnit
|
|
||||||
)
|
|
||||||
}
|
|
||||||
else -> Formatter.format(outputValue)
|
|
||||||
}
|
|
||||||
},
|
|
||||||
secondaryText = null,
|
|
||||||
helperText = stringResource(unitTo?.shortName ?: R.string.loading_label),
|
|
||||||
textToCopy = outputValue,
|
|
||||||
)
|
|
||||||
// Unit selection buttons
|
|
||||||
Row(
|
|
||||||
modifier = Modifier.padding(horizontal = 8.dp),
|
|
||||||
verticalAlignment = Alignment.Bottom,
|
|
||||||
) {
|
) {
|
||||||
|
InputTextField(
|
||||||
|
value = calculatedValue?.let { value -> Formatter.format(value) } ?: "",
|
||||||
|
minRatio = 0.7f,
|
||||||
|
textColor = MaterialTheme.colorScheme.onSurfaceVariant.copy(alpha = 0.6f)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
AnimatedContent(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
targetState = stringResource(unitFrom?.shortName ?: R.string.loading_label),
|
||||||
|
transitionSpec = {
|
||||||
|
// Enter animation
|
||||||
|
(expandHorizontally(clip = false, expandFrom = Alignment.Start) + fadeIn()
|
||||||
|
// Exit animation
|
||||||
|
with fadeOut())
|
||||||
|
.using(SizeTransform(clip = false))
|
||||||
|
}
|
||||||
|
) { value ->
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.End)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
InputTextField(
|
||||||
|
modifier = Modifier
|
||||||
|
.weight(2f),
|
||||||
|
value = when {
|
||||||
|
networkLoading -> stringResource(R.string.loading_label)
|
||||||
|
networkError -> stringResource(R.string.error_label)
|
||||||
|
converterMode == ConverterMode.BASE -> outputValue.uppercase()
|
||||||
|
formatTime and (unitTo?.group == UnitGroup.TIME) -> {
|
||||||
|
Formatter.formatTime(
|
||||||
|
context = mContext,
|
||||||
|
input = calculatedValue ?: inputValue,
|
||||||
|
basicUnit = unitFrom?.basicUnit
|
||||||
|
)
|
||||||
|
}
|
||||||
|
else -> Formatter.format(outputValue)
|
||||||
|
},
|
||||||
|
minRatio = 0.7f,
|
||||||
|
)
|
||||||
|
AnimatedContent(
|
||||||
|
modifier = Modifier.fillMaxWidth(),
|
||||||
|
targetState = stringResource(unitTo?.shortName ?: R.string.loading_label),
|
||||||
|
transitionSpec = {
|
||||||
|
// Enter animation
|
||||||
|
(expandHorizontally(clip = false, expandFrom = Alignment.Start) + fadeIn()
|
||||||
|
// Exit animation
|
||||||
|
with fadeOut())
|
||||||
|
.using(SizeTransform(clip = false))
|
||||||
|
}
|
||||||
|
) { value ->
|
||||||
|
Text(
|
||||||
|
text = value,
|
||||||
|
style = MaterialTheme.typography.bodyMedium.copy(textAlign = TextAlign.End)
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
Spacer(modifier = Modifier.height(it.maxHeight * 0.03f))
|
||||||
|
|
||||||
|
Row(verticalAlignment = Alignment.CenterVertically) {
|
||||||
UnitSelectionButton(
|
UnitSelectionButton(
|
||||||
modifier = Modifier
|
modifier = Modifier
|
||||||
.fillMaxWidth()
|
.fillMaxWidth()
|
||||||
.weight(1f),
|
.weight(1f),
|
||||||
onClick = { unitFrom?.let { navigateToLeftScreen(it.unitId) } },
|
onClick = { unitFrom?.let { unit -> navigateToLeftScreen(unit.unitId) } },
|
||||||
label = unitFrom?.displayName ?: R.string.loading_label,
|
label = unitFrom?.displayName ?: R.string.loading_label,
|
||||||
)
|
)
|
||||||
IconButton(
|
IconButton(
|
||||||
|
Loading…
x
Reference in New Issue
Block a user