Use dots to indicate pages

This commit is contained in:
Sad Ellie 2023-11-24 13:25:22 +03:00
parent 4305354931
commit 4e77eaf01c
4 changed files with 142 additions and 71 deletions

View File

@ -0,0 +1,108 @@
/*
* 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.annotation.IntRange
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
@Composable
fun PagedIsland(
modifier: Modifier = Modifier,
@IntRange(from = 1) pagesCount: Int,
onPageChange: (currentPage: Int) -> Unit = {},
backgroundColor: Color = MaterialTheme.colorScheme.secondaryContainer,
pageContent: @Composable ColumnScope.(currentPage: Int) -> Unit,
) {
var currentPage: Int by remember { mutableIntStateOf(0) }
val contentColor = MaterialTheme.colorScheme.contentColorFor(backgroundColor)
val disabledContentColor = contentColor.copy(alpha = 0.5f)
AnimatedContent(
modifier = modifier
.squashable(
onClick = {
if (currentPage == pagesCount - 1) currentPage = 0 else currentPage++
onPageChange(currentPage)
},
cornerRadiusRange = 8.dp..32.dp,
interactionSource = remember { MutableInteractionSource() }
)
.background(backgroundColor),
targetState = currentPage
) { state ->
ProvideColor(color = contentColor) {
Column(
modifier = Modifier.padding(16.dp)
) {
Row(
modifier = Modifier
.padding(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) {
repeat(pagesCount) {
ADot(color = if (it == state) contentColor else disabledContentColor)
}
}
pageContent(state)
}
}
}
}
@Composable
private fun ADot(
color: Color,
) {
Canvas(modifier = Modifier.size(4.dp)) {
drawCircle(color)
}
}
@Preview
@Composable
private fun PreviewPagedIsland() {
PagedIsland(pagesCount = 5) { currentPage ->
Text("Current page: $currentPage")
if (currentPage == 3) {
Text("Middle in: $currentPage")
}
}
}

View File

@ -18,11 +18,8 @@
package com.sadellie.unitto.feature.settings.formatting package com.sadellie.unitto.feature.settings.formatting
import androidx.compose.foundation.background
import androidx.compose.foundation.horizontalScroll import androidx.compose.foundation.horizontalScroll
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
@ -55,15 +52,18 @@ import com.sadellie.unitto.core.base.OutputFormat
import com.sadellie.unitto.core.base.R import com.sadellie.unitto.core.base.R
import com.sadellie.unitto.core.base.Separator import com.sadellie.unitto.core.base.Separator
import com.sadellie.unitto.core.ui.common.NavigateUpButton import com.sadellie.unitto.core.ui.common.NavigateUpButton
import com.sadellie.unitto.core.ui.common.PagedIsland
import com.sadellie.unitto.core.ui.common.SegmentedButton import com.sadellie.unitto.core.ui.common.SegmentedButton
import com.sadellie.unitto.core.ui.common.SegmentedButtonsRow import com.sadellie.unitto.core.ui.common.SegmentedButtonsRow
import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen import com.sadellie.unitto.core.ui.common.UnittoEmptyScreen
import com.sadellie.unitto.core.ui.common.UnittoListItem import com.sadellie.unitto.core.ui.common.UnittoListItem
import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar import com.sadellie.unitto.core.ui.common.UnittoScreenWithLargeTopBar
import com.sadellie.unitto.core.ui.common.UnittoSlider import com.sadellie.unitto.core.ui.common.UnittoSlider
import com.sadellie.unitto.core.ui.common.squashable import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.formatExpression import com.sadellie.unitto.core.ui.common.textfield.formatExpression
import com.sadellie.unitto.core.ui.theme.NumberTypographyUnitto import com.sadellie.unitto.core.ui.theme.NumberTypographyUnitto
import com.sadellie.unitto.data.common.format
import kotlin.math.ceil
import kotlin.math.roundToInt import kotlin.math.roundToInt
@Composable @Composable
@ -80,7 +80,6 @@ fun FormattingRoute(
onPrecisionChange = viewModel::updatePrecision, onPrecisionChange = viewModel::updatePrecision,
onSeparatorChange = viewModel::updateSeparator, onSeparatorChange = viewModel::updateSeparator,
onOutputFormatChange = viewModel::updateOutputFormat, onOutputFormatChange = viewModel::updateOutputFormat,
togglePreview = viewModel::togglePreview
) )
} }
} }
@ -93,7 +92,6 @@ fun FormattingScreen(
onPrecisionChange: (Int) -> Unit, onPrecisionChange: (Int) -> Unit,
onSeparatorChange: (Int) -> Unit, onSeparatorChange: (Int) -> Unit,
onOutputFormatChange: (Int) -> Unit, onOutputFormatChange: (Int) -> Unit,
togglePreview: () -> Unit,
precisions: ClosedFloatingPointRange<Float> = 0f..16f, // 16th is a MAX_PRECISION (1000) precisions: ClosedFloatingPointRange<Float> = 0f..16f, // 16th is a MAX_PRECISION (1000)
) { ) {
val resources = LocalContext.current.resources val resources = LocalContext.current.resources
@ -120,25 +118,24 @@ fun FormattingScreen(
.padding(paddingValues) .padding(paddingValues)
) { ) {
item("preview") { item("preview") {
Column( PagedIsland(
Modifier modifier = Modifier
.padding(16.dp)
.squashable(
onClick = togglePreview,
cornerRadiusRange = 8.dp..32.dp,
interactionSource = remember { MutableInteractionSource() }
)
.background(MaterialTheme.colorScheme.secondaryContainer)
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp) .padding(16.dp),
) { pagesCount = 2,
) { currentPage ->
val preview = when (currentPage) {
0 -> "123456.${"789123456".repeat(ceil(uiState.precision.toDouble() / 9.0).toInt())}"
1 -> "0.${"1".padStart(uiState.precision, '0')}"
else -> ""
}
.toBigDecimalOrNull()
?.format(uiState.precision, uiState.outputFormat)
?.formatExpression(uiState.formatterSymbols)
?: ""
Text( Text(
text = stringResource(R.string.settings_formatting_preview), text = preview,
style = MaterialTheme.typography.labelMedium,
color = MaterialTheme.colorScheme.onSecondaryContainer
)
Text(
text = uiState.preview,
style = NumberTypographyUnitto.displayMedium, style = NumberTypographyUnitto.displayMedium,
maxLines = 1, maxLines = 1,
modifier = Modifier modifier = Modifier
@ -153,7 +150,10 @@ fun FormattingScreen(
item("precision_label") { item("precision_label") {
UnittoListItem( UnittoListItem(
leadingContent = { leadingContent = {
Icon(Icons.Default.Architecture, stringResource(R.string.settings_precision)) Icon(
Icons.Default.Architecture,
stringResource(R.string.settings_precision)
)
}, },
headlineContent = { headlineContent = {
Row( Row(
@ -265,15 +265,14 @@ private fun PreviewFormattingScreen() {
FormattingScreen( FormattingScreen(
uiState = FormattingUIState( uiState = FormattingUIState(
preview = "123456.789",
precision = 16, precision = 16,
separator = Separator.SPACE, separator = Separator.SPACE,
outputFormat = OutputFormat.PLAIN outputFormat = OutputFormat.PLAIN,
formatterSymbols = FormatterSymbols.Spaces
), ),
onPrecisionChange = { currentPrecision = it }, onPrecisionChange = { currentPrecision = it },
onSeparatorChange = { currentSeparator = it }, onSeparatorChange = { currentSeparator = it },
onOutputFormatChange = { currentOutputFormat = it }, onOutputFormatChange = { currentOutputFormat = it },
navigateUpAction = {}, navigateUpAction = {},
togglePreview = {}
) )
} }

View File

@ -21,9 +21,8 @@ package com.sadellie.unitto.feature.settings.formatting
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
data class FormattingUIState( data class FormattingUIState(
val preview: String = "", val precision: Int,
val precision: Int = 0, val separator: Int,
val separator: Int? = null, val outputFormat: Int,
val outputFormat: Int? = null, val formatterSymbols: FormatterSymbols,
val formatterSymbols: FormatterSymbols = FormatterSymbols.Spaces
) )

View File

@ -22,64 +22,29 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import com.sadellie.unitto.core.base.MAX_PRECISION 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.AllFormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.FormatterSymbols
import com.sadellie.unitto.core.ui.common.textfield.formatExpression
import com.sadellie.unitto.data.common.format
import com.sadellie.unitto.data.common.stateIn import com.sadellie.unitto.data.common.stateIn
import com.sadellie.unitto.data.model.repository.UserPreferencesRepository import com.sadellie.unitto.data.model.repository.UserPreferencesRepository
import dagger.hilt.android.lifecycle.HiltViewModel import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.combine
import kotlinx.coroutines.flow.update
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.math.BigDecimal
import javax.inject.Inject import javax.inject.Inject
import kotlin.math.ceil
@HiltViewModel @HiltViewModel
class FormattingViewModel @Inject constructor( class FormattingViewModel @Inject constructor(
private val userPreferencesRepository: UserPreferencesRepository private val userPreferencesRepository: UserPreferencesRepository
) : ViewModel() { ) : ViewModel() {
private val _prefs = userPreferencesRepository.formattingPrefs private val _prefs = userPreferencesRepository.formattingPrefs
private val _fractional = MutableStateFlow(false)
val uiState = combine(_prefs, _fractional) { mainPrefs, fractional -> val uiState = _prefs.map { mainPrefs ->
val formatterSymbols = AllFormatterSymbols.getById(mainPrefs.separator) FormattingUIState(
return@combine FormattingUIState(
preview = updatePreview(
fractional = fractional,
precision = mainPrefs.digitsPrecision,
outputFormat = mainPrefs.outputFormat,
formatterSymbols = formatterSymbols
),
precision = mainPrefs.digitsPrecision, precision = mainPrefs.digitsPrecision,
separator = mainPrefs.separator, separator = mainPrefs.separator,
outputFormat = mainPrefs.outputFormat, outputFormat = mainPrefs.outputFormat,
formatterSymbols = formatterSymbols formatterSymbols = AllFormatterSymbols.getById(mainPrefs.separator)
) )
} }
.stateIn(viewModelScope, null) .stateIn(viewModelScope, null)
fun togglePreview() = _fractional.update { !it }
private fun updatePreview(
fractional: Boolean,
precision: Int,
outputFormat: Int,
formatterSymbols: FormatterSymbols
): String {
val bigD = when {
fractional -> "0.${"1".padStart(precision, '0')}"
precision > 0 -> "123456.${"789123456".repeat(ceil(precision.toDouble() / 9.0).toInt())}"
else -> "123456"
}
return BigDecimal(bigD)
.format(precision, outputFormat)
.formatExpression(formatterSymbols)
}
/** /**
* @see UserPreferencesRepository.updateDigitsPrecision * @see UserPreferencesRepository.updateDigitsPrecision
*/ */