mirror of
https://github.com/Myzel394/NumberHub.git
synced 2025-06-19 08:45:27 +02:00
Interactive formatting settings
This commit is contained in:
parent
9444e55d71
commit
7ec8cf934a
@ -22,28 +22,3 @@ package com.sadellie.unitto.core.base
|
||||
* Current maximum scale that will be used in app. Used in various place in code
|
||||
*/
|
||||
const val MAX_PRECISION: Int = 1_000
|
||||
|
||||
/**
|
||||
* Currently available scale options
|
||||
*/
|
||||
val PRECISIONS: Map<Int, Int> by lazy {
|
||||
mapOf(
|
||||
0 to R.string.precision_zero,
|
||||
1 to R.string.precision_one,
|
||||
2 to R.string.precision_two,
|
||||
3 to R.string.precision_three,
|
||||
4 to R.string.precision_four,
|
||||
5 to R.string.precision_five,
|
||||
6 to R.string.precision_six,
|
||||
7 to R.string.precision_seven,
|
||||
8 to R.string.precision_eight,
|
||||
9 to R.string.precision_nine,
|
||||
10 to R.string.precision_ten,
|
||||
11 to R.string.precision_eleven,
|
||||
12 to R.string.precision_twelve,
|
||||
13 to R.string.precision_thirteen,
|
||||
14 to R.string.precision_fourteen,
|
||||
15 to R.string.precision_fifteen,
|
||||
MAX_PRECISION to R.string.max_precision
|
||||
)
|
||||
}
|
||||
|
@ -1258,6 +1258,7 @@
|
||||
<string name="third_party_licenses">Third party licenses</string>
|
||||
<string name="rate_this_app">Rate this app</string>
|
||||
<string name="formatting_settings_group">Formatting</string>
|
||||
<string name="formatting_settings_support">Precision and numbers appearance</string>
|
||||
<string name="additional_settings_group">Additional</string>
|
||||
|
||||
<!--Tools-->
|
||||
@ -1306,9 +1307,9 @@
|
||||
|
||||
<!--Separator-->
|
||||
<string name="separator_setting_support">Group separator symbol</string>
|
||||
<string name="period">Period (42.069,12)</string>
|
||||
<string name="comma">Comma (42,069.12)</string>
|
||||
<string name="spaces">Spaces (42 069.12)</string>
|
||||
<string name="period">Period</string>
|
||||
<string name="comma">Comma</string>
|
||||
<string name="spaces">Spaces</string>
|
||||
|
||||
<!--Output format-->
|
||||
<string name="output_format_setting_support">Result value formatting</string>
|
||||
|
@ -66,7 +66,7 @@ fun RowScope.SegmentedButton(
|
||||
label: String,
|
||||
onClick: () -> Unit,
|
||||
selected: Boolean,
|
||||
icon: ImageVector
|
||||
icon: ImageVector? = null
|
||||
) {
|
||||
val containerColor =
|
||||
if (selected) MaterialTheme.colorScheme.secondaryContainer else MaterialTheme.colorScheme.surface
|
||||
@ -81,6 +81,7 @@ fun RowScope.SegmentedButton(
|
||||
),
|
||||
contentPadding = PaddingValues(horizontal = 12.dp)
|
||||
) {
|
||||
if (icon != null) {
|
||||
Crossfade(targetState = selected) {
|
||||
if (it) {
|
||||
Icon(Icons.Default.Check, null, Modifier.size(18.dp))
|
||||
@ -89,6 +90,7 @@ fun RowScope.SegmentedButton(
|
||||
}
|
||||
}
|
||||
Spacer(Modifier.width(8.dp))
|
||||
}
|
||||
Text(label)
|
||||
}
|
||||
}
|
||||
|
@ -0,0 +1,157 @@
|
||||
/*
|
||||
* 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.animation.core.animateFloatAsState
|
||||
import androidx.compose.animation.core.spring
|
||||
import androidx.compose.foundation.Canvas
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SliderPositions
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.geometry.Offset
|
||||
import androidx.compose.ui.graphics.ClipOp
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.Path
|
||||
import androidx.compose.ui.graphics.StrokeCap
|
||||
import androidx.compose.ui.graphics.drawscope.Stroke
|
||||
import androidx.compose.ui.graphics.drawscope.clipRect
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import kotlinx.coroutines.delay
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlin.math.ceil
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun UnittoSlider(
|
||||
modifier: Modifier = Modifier,
|
||||
value: Float,
|
||||
valueRange: ClosedFloatingPointRange<Float>,
|
||||
onValueChange: (Float) -> Unit,
|
||||
onValueChangeFinished: (Float) -> Unit = {}
|
||||
) {
|
||||
val animated = animateFloatAsState(targetValue = value)
|
||||
|
||||
Slider(
|
||||
value = animated.value,
|
||||
onValueChange = onValueChange,
|
||||
modifier = modifier,
|
||||
valueRange = valueRange,
|
||||
onValueChangeFinished = { onValueChangeFinished(animated.value) },
|
||||
track = { sliderPosition -> SquigglyTrack(sliderPosition) },
|
||||
steps = valueRange.endInclusive.roundToInt(),
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun SquigglyTrack(
|
||||
sliderPosition: SliderPositions,
|
||||
eachWaveWidth: Float = 80f,
|
||||
strokeWidth: Float = 15f,
|
||||
filledColor: Color = MaterialTheme.colorScheme.primary,
|
||||
unfilledColor: Color = MaterialTheme.colorScheme.surfaceVariant
|
||||
) {
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
var direct by remember { mutableStateOf(1f) }
|
||||
val animatedDirect = animateFloatAsState(direct, spring())
|
||||
val slider = sliderPosition.activeRange.endInclusive
|
||||
|
||||
LaunchedEffect(sliderPosition.activeRange.endInclusive) {
|
||||
coroutineScope.launch {
|
||||
delay(300L)
|
||||
direct = if (direct == 1f) -1f else 1f
|
||||
}
|
||||
}
|
||||
|
||||
Canvas(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.height(20.dp)
|
||||
) {
|
||||
val width = size.width
|
||||
val height = size.height
|
||||
|
||||
val path = Path().apply {
|
||||
moveTo(
|
||||
x = strokeWidth / 2,
|
||||
y = height.times(0.5f)
|
||||
)
|
||||
val amount = ceil(width.div(eachWaveWidth))
|
||||
|
||||
repeat(amount.toInt()) {
|
||||
val peek = if (it % 2 == 0) animatedDirect.value else -animatedDirect.value
|
||||
|
||||
relativeQuadraticBezierTo(
|
||||
dx1 = eachWaveWidth * 0.5f,
|
||||
// 0.75, because 1.0 was clipping out of bound for some reason
|
||||
dy1 = height.times(0.75f) * peek,
|
||||
dx2 = eachWaveWidth,
|
||||
dy2 = 0f
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
clipRect(
|
||||
top = 0f,
|
||||
left = 0f,
|
||||
right = width.times(slider),
|
||||
bottom = height,
|
||||
clipOp = ClipOp.Intersect
|
||||
) {
|
||||
drawPath(
|
||||
path = path,
|
||||
color = filledColor,
|
||||
style = Stroke(strokeWidth, cap = StrokeCap.Round)
|
||||
)
|
||||
}
|
||||
|
||||
drawLine(
|
||||
color = unfilledColor,
|
||||
start = Offset(width.times(slider), height.times(0.5f)),
|
||||
end = Offset(width, height.times(0.5f)),
|
||||
strokeWidth = strokeWidth,
|
||||
cap = StrokeCap.Round
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
@Preview(device = "spec:width=411dp,height=891dp")
|
||||
@Preview(device = "spec:width=673.5dp,height=841dp,dpi=480")
|
||||
@Preview(device = "spec:width=1280dp,height=800dp,dpi=480")
|
||||
@Preview(device = "spec:width=1920dp,height=1080dp,dpi=480")
|
||||
@Composable
|
||||
private fun PreviewNewSlider() {
|
||||
var currentValue by remember { mutableStateOf(0.9f) }
|
||||
|
||||
UnittoSlider(
|
||||
value = currentValue,
|
||||
valueRange = 0f..1f,
|
||||
onValueChange = { currentValue = it }
|
||||
)
|
||||
}
|
@ -31,6 +31,7 @@ dependencies {
|
||||
implementation(libs.com.github.sadellie.themmo)
|
||||
implementation(libs.org.burnoutcrew.composereorderable)
|
||||
|
||||
implementation(project(mapOf("path" to ":data:common")))
|
||||
implementation(project(mapOf("path" to ":data:model")))
|
||||
implementation(project(mapOf("path" to ":data:unitgroups")))
|
||||
implementation(project(mapOf("path" to ":data:userprefs")))
|
||||
|
@ -19,7 +19,6 @@
|
||||
package com.sadellie.unitto.feature.settings
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Home
|
||||
@ -42,14 +41,10 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.sadellie.unitto.core.base.BuildConfig
|
||||
import com.sadellie.unitto.core.base.OUTPUT_FORMAT
|
||||
import com.sadellie.unitto.core.base.PRECISIONS
|
||||
import com.sadellie.unitto.core.base.R
|
||||
import com.sadellie.unitto.core.base.SEPARATORS
|
||||
import com.sadellie.unitto.core.base.TOP_LEVEL_DESTINATIONS
|
||||
import com.sadellie.unitto.core.ui.common.Header
|
||||
import com.sadellie.unitto.core.ui.common.MenuButton
|
||||
@ -59,6 +54,7 @@ import com.sadellie.unitto.core.ui.openLink
|
||||
import com.sadellie.unitto.data.model.UnitsListSorting
|
||||
import com.sadellie.unitto.feature.settings.components.AlertDialogWithList
|
||||
import com.sadellie.unitto.feature.settings.navigation.aboutRoute
|
||||
import com.sadellie.unitto.feature.settings.navigation.formattingRoute
|
||||
import com.sadellie.unitto.feature.settings.navigation.themesRoute
|
||||
import com.sadellie.unitto.feature.settings.navigation.unitsGroupRoute
|
||||
|
||||
@ -110,43 +106,18 @@ internal fun SettingsScreen(
|
||||
)
|
||||
}
|
||||
|
||||
// GENERAL GROUP
|
||||
item { Header(stringResource(R.string.formatting_settings_group)) }
|
||||
|
||||
// PRECISION
|
||||
// FORMATTING
|
||||
item {
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(
|
||||
Icons.Default._123,
|
||||
stringResource(R.string.precision_setting),
|
||||
stringResource(R.string.formatting_settings_group),
|
||||
)
|
||||
},
|
||||
headlineContent = { Text(stringResource(R.string.precision_setting)) },
|
||||
supportingContent = { Text(stringResource(R.string.precision_setting_support)) },
|
||||
modifier = Modifier.clickable { dialogState = DialogState.PRECISION }
|
||||
)
|
||||
}
|
||||
|
||||
// SEPARATOR
|
||||
item {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.separator_setting)) },
|
||||
supportingContent = { Text(stringResource(R.string.separator_setting_support)) },
|
||||
modifier = Modifier
|
||||
.clickable { dialogState = DialogState.SEPARATOR }
|
||||
.padding(start = 40.dp)
|
||||
)
|
||||
}
|
||||
|
||||
// OUTPUT FORMAT
|
||||
item {
|
||||
ListItem(
|
||||
headlineContent = { Text(stringResource(R.string.output_format_setting)) },
|
||||
supportingContent = { Text(stringResource(R.string.output_format_setting_support)) },
|
||||
modifier = Modifier
|
||||
.clickable { dialogState = DialogState.OUTPUT_FORMAT }
|
||||
.padding(start = 40.dp)
|
||||
headlineContent = { Text(stringResource(R.string.formatting_settings_group)) },
|
||||
supportingContent = { Text(stringResource(R.string.formatting_settings_support)) },
|
||||
modifier = Modifier.clickable { navControllerAction(formattingRoute) }
|
||||
)
|
||||
}
|
||||
|
||||
@ -260,35 +231,6 @@ internal fun SettingsScreen(
|
||||
|
||||
// Showing dialog
|
||||
when (dialogState) {
|
||||
DialogState.PRECISION -> {
|
||||
AlertDialogWithList(
|
||||
title = stringResource(R.string.precision_setting),
|
||||
listItems = PRECISIONS,
|
||||
selectedItemIndex = userPrefs.value.digitsPrecision,
|
||||
selectAction = viewModel::updatePrecision,
|
||||
dismissAction = { resetDialog() },
|
||||
supportText = stringResource(R.string.precision_setting_info)
|
||||
)
|
||||
}
|
||||
DialogState.SEPARATOR -> {
|
||||
AlertDialogWithList(
|
||||
title = stringResource(R.string.separator_setting),
|
||||
listItems = SEPARATORS,
|
||||
selectedItemIndex = userPrefs.value.separator,
|
||||
selectAction = viewModel::updateSeparator,
|
||||
dismissAction = { resetDialog() }
|
||||
)
|
||||
}
|
||||
DialogState.OUTPUT_FORMAT -> {
|
||||
AlertDialogWithList(
|
||||
title = stringResource(R.string.output_format_setting),
|
||||
listItems = OUTPUT_FORMAT,
|
||||
selectedItemIndex = userPrefs.value.outputFormat,
|
||||
selectAction = viewModel::updateOutputFormat,
|
||||
dismissAction = { resetDialog() },
|
||||
supportText = stringResource(R.string.output_format_setting_info)
|
||||
)
|
||||
}
|
||||
DialogState.START_SCREEN -> {
|
||||
AlertDialogWithList(
|
||||
title = stringResource(R.string.starting_screen_setting),
|
||||
@ -321,5 +263,5 @@ internal fun SettingsScreen(
|
||||
* All possible states for alert dialog that opens when user clicks on settings.
|
||||
*/
|
||||
private enum class DialogState {
|
||||
NONE, PRECISION, SEPARATOR, OUTPUT_FORMAT, START_SCREEN, UNIT_LIST_SORTING
|
||||
NONE, START_SCREEN, UNIT_LIST_SORTING
|
||||
}
|
||||
|
@ -40,33 +40,6 @@ class SettingsViewModel @Inject constructor(
|
||||
UserPreferences()
|
||||
)
|
||||
|
||||
/**
|
||||
* @see UserPreferencesRepository.updateDigitsPrecision
|
||||
*/
|
||||
fun updatePrecision(precision: Int) {
|
||||
viewModelScope.launch {
|
||||
userPrefsRepository.updateDigitsPrecision(precision)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserPreferencesRepository.updateSeparator
|
||||
*/
|
||||
fun updateSeparator(separator: Int) {
|
||||
viewModelScope.launch {
|
||||
userPrefsRepository.updateSeparator(separator)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserPreferencesRepository.updateOutputFormat
|
||||
*/
|
||||
fun updateOutputFormat(outputFormat: Int) {
|
||||
viewModelScope.launch {
|
||||
userPrefsRepository.updateOutputFormat(outputFormat)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserPreferencesRepository.updateVibrations
|
||||
*/
|
||||
|
@ -0,0 +1,242 @@
|
||||
/*
|
||||
* 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.settings.formatting
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.horizontalScroll
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.layout.wrapContentWidth
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.rememberScrollState
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Architecture
|
||||
import androidx.compose.material.icons.filled.EMobiledata
|
||||
import androidx.compose.material.icons.filled._123
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.ListItem
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextAlign
|
||||
import androidx.compose.ui.tooling.preview.Preview
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.hilt.navigation.compose.hiltViewModel
|
||||
import androidx.lifecycle.compose.collectAsStateWithLifecycle
|
||||
import com.sadellie.unitto.core.base.OUTPUT_FORMAT
|
||||
import com.sadellie.unitto.core.base.OutputFormat
|
||||
import com.sadellie.unitto.core.base.R
|
||||
import com.sadellie.unitto.core.base.SEPARATORS
|
||||
import com.sadellie.unitto.core.base.Separator
|
||||
import com.sadellie.unitto.core.ui.common.NavigateUpButton
|
||||
import com.sadellie.unitto.core.ui.common.UnittoSlider
|
||||
import com.sadellie.unitto.core.ui.common.SegmentedButton
|
||||
import com.sadellie.unitto.core.ui.common.SegmentedButtonsRow
|
||||
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
|
||||
import com.sadellie.unitto.core.ui.common.squashable
|
||||
import com.sadellie.unitto.core.ui.theme.NumbersTextStyleDisplayMedium
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@Composable
|
||||
fun FormattingRoute(
|
||||
viewModel: FormattingViewModel = hiltViewModel(),
|
||||
navigateUpAction: () -> Unit,
|
||||
) {
|
||||
val uiState = viewModel.uiState.collectAsStateWithLifecycle()
|
||||
|
||||
FormattingScreen(
|
||||
navigateUpAction = navigateUpAction,
|
||||
uiState = uiState.value,
|
||||
onPrecisionChange = viewModel::updatePrecision,
|
||||
onSeparatorChange = viewModel::updateSeparator,
|
||||
onOutputFormatChange = viewModel::updateOutputFormat,
|
||||
togglePreview = viewModel::togglePreview
|
||||
)
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun FormattingScreen(
|
||||
navigateUpAction: () -> Unit,
|
||||
uiState: FormattingUIState,
|
||||
onPrecisionChange: (Int) -> Unit,
|
||||
onSeparatorChange: (Int) -> Unit,
|
||||
onOutputFormatChange: (Int) -> Unit,
|
||||
togglePreview: () -> Unit,
|
||||
precisions: ClosedFloatingPointRange<Float> = 0f..16f, // 16th is a MAX_PRECISION (1000)
|
||||
) {
|
||||
UnittoScreenWithLargeTopBar(
|
||||
title = stringResource(R.string.formatting_settings_group),
|
||||
navigationIcon = { NavigateUpButton(navigateUpAction) },
|
||||
) { paddingValues ->
|
||||
LazyColumn(
|
||||
modifier = Modifier
|
||||
.padding(paddingValues)
|
||||
) {
|
||||
item("preview") {
|
||||
Column(
|
||||
Modifier
|
||||
.padding(16.dp)
|
||||
.squashable(
|
||||
onClick = togglePreview,
|
||||
cornerRadiusRange = 8.dp..32.dp,
|
||||
interactionSource = remember { MutableInteractionSource() }
|
||||
)
|
||||
.background(MaterialTheme.colorScheme.secondaryContainer)
|
||||
.fillMaxWidth()
|
||||
.padding(16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = "Preview (click to switch)",
|
||||
style = MaterialTheme.typography.labelMedium,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
Text(
|
||||
text = uiState.preview,
|
||||
style = NumbersTextStyleDisplayMedium,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.horizontalScroll(rememberScrollState()),
|
||||
textAlign = TextAlign.End,
|
||||
color = MaterialTheme.colorScheme.onSecondaryContainer
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
item("precision_label") {
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(Icons.Default.Architecture, stringResource(R.string.precision_setting))
|
||||
},
|
||||
headlineContent = {
|
||||
Row(
|
||||
horizontalArrangement = Arrangement.SpaceBetween,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
) {
|
||||
Text(stringResource(R.string.precision_setting))
|
||||
Text(if (uiState.precision >= precisions.endInclusive) stringResource(R.string.max_precision) else uiState.precision.toString())
|
||||
}
|
||||
},
|
||||
supportingContent = {
|
||||
Text(stringResource(R.string.precision_setting_support))
|
||||
}
|
||||
)
|
||||
}
|
||||
|
||||
item("precision_slider") {
|
||||
UnittoSlider(
|
||||
modifier = Modifier.padding(start = 56.dp, end = 16.dp),
|
||||
value = uiState.precision.toFloat(),
|
||||
valueRange = precisions,
|
||||
onValueChange = { onPrecisionChange(it.roundToInt()) },
|
||||
)
|
||||
}
|
||||
|
||||
item("separator_label") {
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(Icons.Default._123, stringResource(R.string.precision_setting))
|
||||
},
|
||||
headlineContent = { Text(stringResource(R.string.separator_setting)) },
|
||||
supportingContent = { Text(stringResource(R.string.separator_setting_support)) },
|
||||
)
|
||||
}
|
||||
|
||||
item("separator") {
|
||||
Row(
|
||||
Modifier
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.wrapContentWidth()
|
||||
.padding(start = 56.dp)
|
||||
) {
|
||||
SegmentedButtonsRow {
|
||||
SEPARATORS.forEach { (separator, stringRes) ->
|
||||
SegmentedButton(
|
||||
label = stringResource(stringRes),
|
||||
onClick = { onSeparatorChange(separator) },
|
||||
selected = separator == uiState.separator
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
item("output_format_label") {
|
||||
ListItem(
|
||||
leadingContent = {
|
||||
Icon(Icons.Default.EMobiledata, stringResource(R.string.precision_setting))
|
||||
},
|
||||
headlineContent = { Text(stringResource(R.string.output_format_setting)) },
|
||||
supportingContent = { Text(stringResource(R.string.output_format_setting_support)) }
|
||||
)
|
||||
}
|
||||
|
||||
item("output_format") {
|
||||
Row(
|
||||
Modifier
|
||||
.horizontalScroll(rememberScrollState())
|
||||
.wrapContentWidth()
|
||||
.padding(start = 56.dp)
|
||||
) {
|
||||
SegmentedButtonsRow {
|
||||
OUTPUT_FORMAT.forEach { (outputFormat, stringRes) ->
|
||||
SegmentedButton(
|
||||
label = stringResource(stringRes),
|
||||
onClick = { onOutputFormatChange(outputFormat) },
|
||||
selected = outputFormat == uiState.outputFormat
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Preview
|
||||
@Composable
|
||||
private fun PreviewFormattingScreen() {
|
||||
var currentPrecision by remember { mutableStateOf(6) }
|
||||
var currentSeparator by remember { mutableStateOf(Separator.COMMA) }
|
||||
var currentOutputFormat by remember { mutableStateOf(OutputFormat.PLAIN) }
|
||||
|
||||
FormattingScreen(
|
||||
uiState = FormattingUIState(
|
||||
preview = "",
|
||||
precision = 3,
|
||||
separator = Separator.SPACES,
|
||||
outputFormat = OutputFormat.PLAIN
|
||||
),
|
||||
onPrecisionChange = { currentPrecision = it },
|
||||
onSeparatorChange = { currentSeparator = it },
|
||||
onOutputFormatChange = { currentOutputFormat = it },
|
||||
navigateUpAction = {},
|
||||
togglePreview = {}
|
||||
)
|
||||
}
|
@ -0,0 +1,26 @@
|
||||
/*
|
||||
* 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.settings.formatting
|
||||
|
||||
data class FormattingUIState(
|
||||
val preview: String = "",
|
||||
val precision: Int = 0,
|
||||
val separator: Int? = null,
|
||||
val outputFormat: Int? = null
|
||||
)
|
@ -0,0 +1,113 @@
|
||||
/*
|
||||
* 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.settings.formatting
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import com.sadellie.unitto.core.base.MAX_PRECISION
|
||||
import com.sadellie.unitto.core.ui.common.textfield.AllFormatterSymbols
|
||||
import com.sadellie.unitto.core.ui.common.textfield.formatExpression
|
||||
import com.sadellie.unitto.data.common.setMinimumRequiredScale
|
||||
import com.sadellie.unitto.data.common.toStringWith
|
||||
import com.sadellie.unitto.data.common.trimZeros
|
||||
import com.sadellie.unitto.data.userprefs.UserPreferencesRepository
|
||||
import dagger.hilt.android.lifecycle.HiltViewModel
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.SharingStarted
|
||||
import kotlinx.coroutines.flow.combine
|
||||
import kotlinx.coroutines.flow.stateIn
|
||||
import kotlinx.coroutines.flow.update
|
||||
import kotlinx.coroutines.launch
|
||||
import java.math.BigDecimal
|
||||
import javax.inject.Inject
|
||||
import kotlin.math.ceil
|
||||
|
||||
@HiltViewModel
|
||||
class FormattingViewModel @Inject constructor(
|
||||
private val userPreferencesRepository: UserPreferencesRepository
|
||||
) : ViewModel() {
|
||||
private val _mainPreferences = userPreferencesRepository.mainPreferencesFlow
|
||||
private val _fractional = MutableStateFlow(false)
|
||||
|
||||
val uiState = combine(_mainPreferences, _fractional) { mainPrefs, fractional ->
|
||||
|
||||
return@combine FormattingUIState(
|
||||
preview = updatePreview(
|
||||
fractional = fractional,
|
||||
precision = mainPrefs.digitsPrecision,
|
||||
outputFormat = mainPrefs.outputFormat,
|
||||
separator = mainPrefs.separator
|
||||
),
|
||||
precision = mainPrefs.digitsPrecision,
|
||||
separator = mainPrefs.separator,
|
||||
outputFormat = mainPrefs.outputFormat
|
||||
)
|
||||
}
|
||||
.stateIn(viewModelScope, SharingStarted.WhileSubscribed(5000L), FormattingUIState())
|
||||
|
||||
fun togglePreview() = _fractional.update { !it }
|
||||
|
||||
private fun updatePreview(
|
||||
fractional: Boolean,
|
||||
precision: Int,
|
||||
outputFormat: Int,
|
||||
separator: Int,
|
||||
): String {
|
||||
val bigD = when {
|
||||
fractional -> "0.${"0000001".padStart(precision, '0')}"
|
||||
precision > 0 -> "123456.${"7890123456".repeat(ceil(precision.toDouble() / 10.0).toInt())}"
|
||||
else -> "123456"
|
||||
}
|
||||
|
||||
return BigDecimal(bigD)
|
||||
.setMinimumRequiredScale(precision)
|
||||
.trimZeros()
|
||||
.toStringWith(outputFormat)
|
||||
.formatExpression(AllFormatterSymbols.getById(separator))
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserPreferencesRepository.updateDigitsPrecision
|
||||
*/
|
||||
fun updatePrecision(precision: Int) {
|
||||
viewModelScope.launch {
|
||||
// In UI the slider for precision goes from 0 to 16, where 16 is treated as 1000 (MAX)
|
||||
val newPrecision = if (precision > 15) MAX_PRECISION else precision
|
||||
userPreferencesRepository.updateDigitsPrecision(newPrecision)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserPreferencesRepository.updateSeparator
|
||||
*/
|
||||
fun updateSeparator(separator: Int) {
|
||||
viewModelScope.launch {
|
||||
userPreferencesRepository.updateSeparator(separator)
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* @see UserPreferencesRepository.updateOutputFormat
|
||||
*/
|
||||
fun updateOutputFormat(outputFormat: Int) {
|
||||
viewModelScope.launch {
|
||||
userPreferencesRepository.updateOutputFormat(outputFormat)
|
||||
}
|
||||
}
|
||||
}
|
@ -27,8 +27,9 @@ import androidx.navigation.compose.navigation
|
||||
import com.sadellie.unitto.core.base.TopLevelDestinations
|
||||
import com.sadellie.unitto.feature.settings.AboutScreen
|
||||
import com.sadellie.unitto.feature.settings.SettingsScreen
|
||||
import com.sadellie.unitto.feature.settings.themes.ThemesRoute
|
||||
import com.sadellie.unitto.feature.settings.ThirdPartyLicensesScreen
|
||||
import com.sadellie.unitto.feature.settings.formatting.FormattingRoute
|
||||
import com.sadellie.unitto.feature.settings.themes.ThemesRoute
|
||||
import com.sadellie.unitto.feature.settings.unitgroups.UnitGroupsScreen
|
||||
import io.github.sadellie.themmo.ThemmoController
|
||||
|
||||
@ -38,6 +39,7 @@ internal const val themesRoute = "themes_route"
|
||||
internal const val unitsGroupRoute = "units_group_route"
|
||||
internal const val thirdPartyRoute = "third_party_route"
|
||||
internal const val aboutRoute = "about_route"
|
||||
internal const val formattingRoute = "formatting_route"
|
||||
|
||||
fun NavController.navigateToSettings(builder: NavOptionsBuilder.() -> Unit) {
|
||||
navigate(settingsRoute, builder)
|
||||
@ -85,5 +87,11 @@ fun NavGraphBuilder.settingGraph(
|
||||
navigateUpAction = navController::navigateUp,
|
||||
)
|
||||
}
|
||||
|
||||
composable(formattingRoute) {
|
||||
FormattingRoute(
|
||||
navigateUpAction = navController::navigateUp
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user