Use Pager in PagedIsland

This commit is contained in:
Sad Ellie 2023-11-25 20:41:53 +03:00
parent 93404c1fac
commit a58cb01f58
4 changed files with 203 additions and 131 deletions

View File

@ -18,76 +18,97 @@
package com.sadellie.unitto.core.ui.common package com.sadellie.unitto.core.ui.common
import androidx.annotation.IntRange import androidx.compose.animation.animateContentSize
import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.Canvas import androidx.compose.foundation.Canvas
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.interaction.MutableInteractionSource import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.ColumnScope
import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size import androidx.compose.foundation.layout.size
import androidx.compose.foundation.pager.HorizontalPager
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.material3.contentColorFor import androidx.compose.material3.contentColorFor
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.mutableIntStateOf import androidx.compose.ui.Alignment
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.graphics.Color import androidx.compose.ui.graphics.Color
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 kotlinx.coroutines.launch
/**
* [HorizontalPager] with a background and a page indicator.
*
* @param modifier [Modifier] that will be applied to the surround [Column].
* @param pagerState [PagerState] that will passed [HorizontalPager].
* @param backgroundColor [Color] for background.
* @param pageIndicatorAlignment [Alignment.Horizontal] for page indicator.
* @param onClick Called on all clicks, even if the page didn't change.
* @param pageContent Page content. Use passed `currentPage` value.
*/
@Composable @Composable
fun PagedIsland( fun PagedIsland(
modifier: Modifier = Modifier, modifier: Modifier = Modifier,
@IntRange(from = 1) pagesCount: Int, pagerState: PagerState,
onPageChange: (currentPage: Int) -> Unit = {},
backgroundColor: Color = MaterialTheme.colorScheme.secondaryContainer, backgroundColor: Color = MaterialTheme.colorScheme.secondaryContainer,
pageContent: @Composable ColumnScope.(currentPage: Int) -> Unit, pageIndicatorAlignment: Alignment.Horizontal = Alignment.End,
onClick: () -> Unit = {},
pageContent: @Composable (currentPage: Int) -> Unit,
) { ) {
var currentPage: Int by remember { mutableIntStateOf(0) }
val contentColor = MaterialTheme.colorScheme.contentColorFor(backgroundColor) val contentColor = MaterialTheme.colorScheme.contentColorFor(backgroundColor)
val disabledContentColor = contentColor.copy(alpha = 0.5f) val disabledContentColor = contentColor.copy(alpha = 0.5f)
val corScope = rememberCoroutineScope()
AnimatedContent( ProvideColor(color = contentColor) {
modifier = modifier Column(
.squashable( modifier = modifier
onClick = { .clip(RoundedCornerShape(32.dp))
if (currentPage == pagesCount - 1) currentPage = 0 else currentPage++ .clickable {
onPageChange(currentPage) onClick()
}, if (pagerState.currentPage == (pagerState.pageCount - 1)) return@clickable
cornerRadiusRange = 8.dp..32.dp,
interactionSource = remember { MutableInteractionSource() } corScope.launch {
) pagerState.animateScrollToPage(pagerState.currentPage + 1)
.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) .background(backgroundColor)
.padding(16.dp)
) {
Row(
modifier = Modifier
.fillMaxWidth()
.padding(vertical = 8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp, pageIndicatorAlignment),
) {
repeat(pagerState.pageCount) {
PageDot(if (it == pagerState.currentPage) contentColor else disabledContentColor)
}
}
HorizontalPager(
modifier = Modifier
.animateContentSize()
.fillMaxWidth(),
verticalAlignment = Alignment.Top,
state = pagerState
) { page ->
pageContent(page)
} }
} }
} }
} }
@Composable @Composable
private fun ADot( private fun PageDot(
color: Color, color: Color,
) { ) {
Canvas(modifier = Modifier.size(4.dp)) { Canvas(modifier = Modifier.size(4.dp)) {
@ -98,11 +119,16 @@ private fun ADot(
@Preview @Preview
@Composable @Composable
private fun PreviewPagedIsland() { private fun PreviewPagedIsland() {
PagedIsland(pagesCount = 5) { currentPage -> PagedIsland(
Text("Current page: $currentPage") modifier = Modifier.size(400.dp, 250.dp),
pagerState = rememberPagerState { 5 }
) { currentPage ->
Column {
Text("Current page: $currentPage")
if (currentPage == 3) { if (currentPage == 3) {
Text("Middle in: $currentPage") Text("Middle in: $currentPage")
}
} }
} }
} }

View File

@ -20,11 +20,14 @@ package com.sadellie.unitto.feature.datecalculator.components
import androidx.annotation.StringRes import androidx.annotation.StringRes
import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.text.selection.SelectionContainer import androidx.compose.foundation.text.selection.SelectionContainer
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.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalFocusManager
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 com.sadellie.unitto.core.base.R import com.sadellie.unitto.core.base.R
@ -38,94 +41,112 @@ internal fun DateTimeResultBlock(
diff: ZonedDateTimeDifference.Default, diff: ZonedDateTimeDifference.Default,
format: (BigDecimal) -> String format: (BigDecimal) -> String
) { ) {
val focusManager = LocalFocusManager.current
PagedIsland( PagedIsland(
modifier = modifier, modifier = modifier,
pagesCount = 6, pagerState = rememberPagerState { 6 },
onClick = { focusManager.clearFocus() },
backgroundColor = MaterialTheme.colorScheme.tertiaryContainer backgroundColor = MaterialTheme.colorScheme.tertiaryContainer
) { currentPage -> ) { currentPage ->
when(currentPage) { when(currentPage) {
0 -> { 0 -> {
Text( Column(
text = stringResource(R.string.date_calculator_difference), modifier = Modifier
style = MaterialTheme.typography.labelMedium .fillMaxWidth()
) ) {
SelectionContainer { Text(
Column { text = stringResource(R.string.date_calculator_difference),
// Years style = MaterialTheme.typography.labelMedium
if (diff.years > 0) { )
DateText(R.string.date_calculator_years, diff.years.toBigDecimal(), format) SelectionContainer {
} Column {
// Years
if (diff.years > 0) {
DateText(R.string.date_calculator_years, diff.years.toBigDecimal(), format)
}
// Months // Months
if (diff.months > 0) { if (diff.months > 0) {
DateText(R.string.date_calculator_months, diff.months.toBigDecimal(), format) DateText(R.string.date_calculator_months, diff.months.toBigDecimal(), format)
} }
// Days // Days
if (diff.days > 0) { if (diff.days > 0) {
DateText(R.string.date_calculator_days, diff.days.toBigDecimal(), format) DateText(R.string.date_calculator_days, diff.days.toBigDecimal(), format)
} }
// Hours // Hours
if (diff.hours > 0) { if (diff.hours > 0) {
DateText(R.string.date_calculator_hours, diff.hours.toBigDecimal(), format) DateText(R.string.date_calculator_hours, diff.hours.toBigDecimal(), format)
} }
// Minutes // Minutes
if (diff.minutes > 0) { if (diff.minutes > 0) {
DateText(R.string.date_calculator_minutes, diff.minutes.toBigDecimal(), format) DateText(R.string.date_calculator_minutes, diff.minutes.toBigDecimal(), format)
}
} }
} }
} }
} }
1 -> { 1 -> {
Text( Column {
text = stringResource(R.string.date_calculator_years), Text(
style = MaterialTheme.typography.labelMedium text = stringResource(R.string.date_calculator_years),
) style = MaterialTheme.typography.labelMedium
SelectionContainer { )
DateText(diff.sumYears, format) SelectionContainer {
DateText(diff.sumYears, format)
}
} }
} }
2 -> { 2 -> {
Text( Column {
text = stringResource(R.string.date_calculator_months), Text(
style = MaterialTheme.typography.labelMedium text = stringResource(R.string.date_calculator_months),
) style = MaterialTheme.typography.labelMedium
SelectionContainer { )
DateText(diff.sumMonths, format) SelectionContainer {
DateText(diff.sumMonths, format)
}
} }
} }
3 -> { 3 -> {
Text( Column {
text = stringResource(R.string.date_calculator_days), Text(
style = MaterialTheme.typography.labelMedium text = stringResource(R.string.date_calculator_days),
) style = MaterialTheme.typography.labelMedium
SelectionContainer { )
DateText(diff.sumDays, format) SelectionContainer {
DateText(diff.sumDays, format)
}
} }
} }
4 -> { 4 -> {
Text( Column {
text = stringResource(R.string.date_calculator_hours), Text(
style = MaterialTheme.typography.labelMedium text = stringResource(R.string.date_calculator_hours),
) style = MaterialTheme.typography.labelMedium
SelectionContainer { )
DateText(diff.sumHours, format) SelectionContainer {
DateText(diff.sumHours, format)
}
} }
} }
5 -> { 5 -> {
Text( Column {
text = stringResource(R.string.date_calculator_minutes), Text(
style = MaterialTheme.typography.labelMedium text = stringResource(R.string.date_calculator_minutes),
) style = MaterialTheme.typography.labelMedium
SelectionContainer { )
DateText(diff.sumMinutes, format) SelectionContainer {
DateText(diff.sumMinutes, format)
}
} }
} }
} }

View File

@ -21,10 +21,13 @@ package com.sadellie.unitto.feature.datecalculator.difference
import androidx.compose.animation.AnimatedContent import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.background import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.FlowRow
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.rememberScrollState
import androidx.compose.foundation.verticalScroll
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue import androidx.compose.runtime.getValue
@ -47,6 +50,7 @@ import com.sadellie.unitto.feature.datecalculator.components.DateTimeDialogs
import com.sadellie.unitto.feature.datecalculator.components.DateTimeResultBlock import com.sadellie.unitto.feature.datecalculator.components.DateTimeResultBlock
import com.sadellie.unitto.feature.datecalculator.components.DateTimeSelectorBlock import com.sadellie.unitto.feature.datecalculator.components.DateTimeSelectorBlock
import com.sadellie.unitto.feature.datecalculator.components.DialogState import com.sadellie.unitto.feature.datecalculator.components.DialogState
import java.math.BigDecimal
import java.time.ZonedDateTime import java.time.ZonedDateTime
import java.time.temporal.ChronoUnit import java.time.temporal.ChronoUnit
@ -72,50 +76,59 @@ private fun DateDifferenceView(
) { ) {
var dialogState by remember { mutableStateOf(DialogState.NONE) } var dialogState by remember { mutableStateOf(DialogState.NONE) }
FlowRow( Column(
modifier = Modifier modifier = Modifier
.fillMaxSize() .fillMaxSize()
.padding(16.dp), .verticalScroll(rememberScrollState()),
maxItemsInEachRow = 2, verticalArrangement = Arrangement.spacedBy(8.dp)
verticalArrangement = Arrangement.spacedBy(8.dp),
horizontalArrangement = Arrangement.spacedBy(8.dp)
) { ) {
DateTimeSelectorBlock( FlowRow(
modifier = Modifier modifier = Modifier
.background(MaterialTheme.colorScheme.secondaryContainer) .padding(top = 16.dp, start = 16.dp, end = 16.dp)
.weight(1f)
.fillMaxWidth(), .fillMaxWidth(),
title = stringResource(R.string.date_calculator_start), maxItemsInEachRow = 2,
dateTime = uiState.start, verticalArrangement = Arrangement.spacedBy(8.dp),
onClick = { dialogState = DialogState.FROM }, horizontalArrangement = Arrangement.spacedBy(8.dp)
onLongClick = { setStartDate(ZonedDateTimeUtils.nowWithMinutes()) }, ) {
onTimeClick = { dialogState = DialogState.FROM_TIME }, DateTimeSelectorBlock(
onDateClick = { dialogState = DialogState.FROM_DATE }, modifier = Modifier
containerColor = MaterialTheme.colorScheme.secondaryContainer .background(MaterialTheme.colorScheme.secondaryContainer)
) .weight(1f)
.fillMaxWidth(),
title = stringResource(R.string.date_calculator_start),
dateTime = uiState.start,
onClick = { dialogState = DialogState.FROM },
onLongClick = { setStartDate(ZonedDateTimeUtils.nowWithMinutes()) },
onTimeClick = { dialogState = DialogState.FROM_TIME },
onDateClick = { dialogState = DialogState.FROM_DATE },
containerColor = MaterialTheme.colorScheme.secondaryContainer
)
DateTimeSelectorBlock( DateTimeSelectorBlock(
modifier = Modifier modifier = Modifier
.background(MaterialTheme.colorScheme.secondaryContainer) .background(MaterialTheme.colorScheme.secondaryContainer)
.weight(1f) .weight(1f)
.fillMaxWidth(), .fillMaxWidth(),
title = stringResource(R.string.date_calculator_end), title = stringResource(R.string.date_calculator_end),
dateTime = uiState.end, dateTime = uiState.end,
onClick = { dialogState = DialogState.TO }, onClick = { dialogState = DialogState.TO },
onLongClick = { setEndDate(ZonedDateTimeUtils.nowWithMinutes()) }, onLongClick = { setEndDate(ZonedDateTimeUtils.nowWithMinutes()) },
onTimeClick = { dialogState = DialogState.TO_TIME }, onTimeClick = { dialogState = DialogState.TO_TIME },
onDateClick = { dialogState = DialogState.TO_DATE }, onDateClick = { dialogState = DialogState.TO_DATE },
containerColor = MaterialTheme.colorScheme.secondaryContainer containerColor = MaterialTheme.colorScheme.secondaryContainer
) )
}
AnimatedContent( AnimatedContent(
targetState = uiState.result, targetState = uiState.result,
modifier = Modifier.weight(2f) modifier = Modifier.fillMaxWidth()
) { result -> ) { result ->
when (result) { when (result) {
is ZonedDateTimeDifference.Default -> { is ZonedDateTimeDifference.Default -> {
DateTimeResultBlock( DateTimeResultBlock(
modifier = Modifier.fillMaxWidth(), modifier = Modifier
.padding(horizontal = 16.dp)
.fillMaxWidth(),
diff = result, diff = result,
format = { format = {
it.format(uiState.precision, uiState.outputFormat) it.format(uiState.precision, uiState.outputFormat)
@ -157,7 +170,18 @@ fun DateDifferenceViewPreview() {
uiState = DifferenceUIState.Ready( uiState = DifferenceUIState.Ready(
start = ZonedDateTimeUtils.nowWithMinutes(), start = ZonedDateTimeUtils.nowWithMinutes(),
end = ZonedDateTimeUtils.nowWithMinutes().truncatedTo(ChronoUnit.MINUTES), end = ZonedDateTimeUtils.nowWithMinutes().truncatedTo(ChronoUnit.MINUTES),
result = ZonedDateTimeDifference.Zero, result = ZonedDateTimeDifference.Default(
years = 1,
months = 2,
days = 3,
hours = 4,
minutes = 5,
sumYears = BigDecimal("0.083"),
sumMonths = BigDecimal("1.000"),
sumDays = BigDecimal("30.000"),
sumHours = BigDecimal("720.000"),
sumMinutes = BigDecimal("43200.000"),
),
precision = 3, precision = 3,
outputFormat = OutputFormat.PLAIN, outputFormat = OutputFormat.PLAIN,
formatterSymbols = FormatterSymbols.Spaces formatterSymbols = FormatterSymbols.Spaces

View File

@ -25,6 +25,7 @@ import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.padding import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.wrapContentWidth import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.lazy.LazyColumn import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.rememberScrollState import androidx.compose.foundation.rememberScrollState
import androidx.compose.material.icons.Icons import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Architecture import androidx.compose.material.icons.filled.Architecture
@ -122,7 +123,7 @@ fun FormattingScreen(
modifier = Modifier modifier = Modifier
.fillMaxWidth() .fillMaxWidth()
.padding(16.dp), .padding(16.dp),
pagesCount = 2, pagerState = rememberPagerState { 2 },
) { currentPage -> ) { currentPage ->
val preview = when (currentPage) { val preview = when (currentPage) {
0 -> "123456.${"789123456".repeat(ceil(uiState.precision.toDouble() / 9.0).toInt())}" 0 -> "123456.${"789123456".repeat(ceil(uiState.precision.toDouble() / 9.0).toInt())}"