From a58cb01f58b5f9010edb391fee2857064c6008a0 Mon Sep 17 00:00:00 2001 From: Sad Ellie Date: Sat, 25 Nov 2023 20:41:53 +0300 Subject: [PATCH] Use Pager in PagedIsland --- .../unitto/core/ui/common/PagedIsland.kt | 110 +++++++++------ .../components/DateTimeResultBlock.kt | 133 ++++++++++-------- .../difference/DateDifferencePage.kt | 88 +++++++----- .../settings/formatting/FormattingScreen.kt | 3 +- 4 files changed, 203 insertions(+), 131 deletions(-) diff --git a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/PagedIsland.kt b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/PagedIsland.kt index 4ffedd94..d04c9c8c 100644 --- a/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/PagedIsland.kt +++ b/core/ui/src/main/java/com/sadellie/unitto/core/ui/common/PagedIsland.kt @@ -18,76 +18,97 @@ package com.sadellie.unitto.core.ui.common -import androidx.annotation.IntRange -import androidx.compose.animation.AnimatedContent +import androidx.compose.animation.animateContentSize import androidx.compose.foundation.Canvas 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.Column -import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding 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.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.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview 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 fun PagedIsland( modifier: Modifier = Modifier, - @IntRange(from = 1) pagesCount: Int, - onPageChange: (currentPage: Int) -> Unit = {}, + pagerState: PagerState, 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 disabledContentColor = contentColor.copy(alpha = 0.5f) + val corScope = rememberCoroutineScope() - 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) + ProvideColor(color = contentColor) { + Column( + modifier = modifier + .clip(RoundedCornerShape(32.dp)) + .clickable { + onClick() + if (pagerState.currentPage == (pagerState.pageCount - 1)) return@clickable + + corScope.launch { + pagerState.animateScrollToPage(pagerState.currentPage + 1) } } - 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 -private fun ADot( +private fun PageDot( color: Color, ) { Canvas(modifier = Modifier.size(4.dp)) { @@ -98,11 +119,16 @@ private fun ADot( @Preview @Composable private fun PreviewPagedIsland() { - PagedIsland(pagesCount = 5) { currentPage -> - Text("Current page: $currentPage") + PagedIsland( + modifier = Modifier.size(400.dp, 250.dp), + pagerState = rememberPagerState { 5 } + ) { currentPage -> + Column { + Text("Current page: $currentPage") - if (currentPage == 3) { - Text("Middle in: $currentPage") + if (currentPage == 3) { + Text("Middle in: $currentPage") + } } } } diff --git a/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/components/DateTimeResultBlock.kt b/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/components/DateTimeResultBlock.kt index 3e139440..c9999201 100644 --- a/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/components/DateTimeResultBlock.kt +++ b/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/components/DateTimeResultBlock.kt @@ -20,11 +20,14 @@ package com.sadellie.unitto.feature.datecalculator.components import androidx.annotation.StringRes 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.material3.MaterialTheme import androidx.compose.material3.Text import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalFocusManager import androidx.compose.ui.res.stringResource import androidx.compose.ui.tooling.preview.Preview import com.sadellie.unitto.core.base.R @@ -38,94 +41,112 @@ internal fun DateTimeResultBlock( diff: ZonedDateTimeDifference.Default, format: (BigDecimal) -> String ) { + val focusManager = LocalFocusManager.current + PagedIsland( modifier = modifier, - pagesCount = 6, + pagerState = rememberPagerState { 6 }, + onClick = { focusManager.clearFocus() }, backgroundColor = MaterialTheme.colorScheme.tertiaryContainer ) { currentPage -> when(currentPage) { 0 -> { - Text( - text = stringResource(R.string.date_calculator_difference), - style = MaterialTheme.typography.labelMedium - ) - SelectionContainer { - Column { - // Years - if (diff.years > 0) { - DateText(R.string.date_calculator_years, diff.years.toBigDecimal(), format) - } + Column( + modifier = Modifier + .fillMaxWidth() + ) { + Text( + text = stringResource(R.string.date_calculator_difference), + style = MaterialTheme.typography.labelMedium + ) + SelectionContainer { + Column { + // Years + if (diff.years > 0) { + DateText(R.string.date_calculator_years, diff.years.toBigDecimal(), format) + } - // Months - if (diff.months > 0) { - DateText(R.string.date_calculator_months, diff.months.toBigDecimal(), format) - } + // Months + if (diff.months > 0) { + DateText(R.string.date_calculator_months, diff.months.toBigDecimal(), format) + } - // Days - if (diff.days > 0) { - DateText(R.string.date_calculator_days, diff.days.toBigDecimal(), format) - } + // Days + if (diff.days > 0) { + DateText(R.string.date_calculator_days, diff.days.toBigDecimal(), format) + } - // Hours - if (diff.hours > 0) { - DateText(R.string.date_calculator_hours, diff.hours.toBigDecimal(), format) - } + // Hours + if (diff.hours > 0) { + DateText(R.string.date_calculator_hours, diff.hours.toBigDecimal(), format) + } - // Minutes - if (diff.minutes > 0) { - DateText(R.string.date_calculator_minutes, diff.minutes.toBigDecimal(), format) + // Minutes + if (diff.minutes > 0) { + DateText(R.string.date_calculator_minutes, diff.minutes.toBigDecimal(), format) + } } } } } 1 -> { - Text( - text = stringResource(R.string.date_calculator_years), - style = MaterialTheme.typography.labelMedium - ) - SelectionContainer { - DateText(diff.sumYears, format) + Column { + Text( + text = stringResource(R.string.date_calculator_years), + style = MaterialTheme.typography.labelMedium + ) + SelectionContainer { + DateText(diff.sumYears, format) + } } } 2 -> { - Text( - text = stringResource(R.string.date_calculator_months), - style = MaterialTheme.typography.labelMedium - ) - SelectionContainer { - DateText(diff.sumMonths, format) + Column { + Text( + text = stringResource(R.string.date_calculator_months), + style = MaterialTheme.typography.labelMedium + ) + SelectionContainer { + DateText(diff.sumMonths, format) + } } } 3 -> { - Text( - text = stringResource(R.string.date_calculator_days), - style = MaterialTheme.typography.labelMedium - ) - SelectionContainer { - DateText(diff.sumDays, format) + Column { + Text( + text = stringResource(R.string.date_calculator_days), + style = MaterialTheme.typography.labelMedium + ) + SelectionContainer { + DateText(diff.sumDays, format) + } } } 4 -> { - Text( - text = stringResource(R.string.date_calculator_hours), - style = MaterialTheme.typography.labelMedium - ) - SelectionContainer { - DateText(diff.sumHours, format) + Column { + Text( + text = stringResource(R.string.date_calculator_hours), + style = MaterialTheme.typography.labelMedium + ) + SelectionContainer { + DateText(diff.sumHours, format) + } } } 5 -> { - Text( - text = stringResource(R.string.date_calculator_minutes), - style = MaterialTheme.typography.labelMedium - ) - SelectionContainer { - DateText(diff.sumMinutes, format) + Column { + Text( + text = stringResource(R.string.date_calculator_minutes), + style = MaterialTheme.typography.labelMedium + ) + SelectionContainer { + DateText(diff.sumMinutes, format) + } } } } diff --git a/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/difference/DateDifferencePage.kt b/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/difference/DateDifferencePage.kt index 9734bd3b..6d7b6f85 100644 --- a/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/difference/DateDifferencePage.kt +++ b/feature/datecalculator/src/main/java/com/sadellie/unitto/feature/datecalculator/difference/DateDifferencePage.kt @@ -21,10 +21,13 @@ package com.sadellie.unitto.feature.datecalculator.difference import androidx.compose.animation.AnimatedContent import androidx.compose.foundation.background import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column import androidx.compose.foundation.layout.FlowRow import androidx.compose.foundation.layout.fillMaxSize import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.rememberScrollState +import androidx.compose.foundation.verticalScroll import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable 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.DateTimeSelectorBlock import com.sadellie.unitto.feature.datecalculator.components.DialogState +import java.math.BigDecimal import java.time.ZonedDateTime import java.time.temporal.ChronoUnit @@ -72,50 +76,59 @@ private fun DateDifferenceView( ) { var dialogState by remember { mutableStateOf(DialogState.NONE) } - FlowRow( + Column( modifier = Modifier .fillMaxSize() - .padding(16.dp), - maxItemsInEachRow = 2, - verticalArrangement = Arrangement.spacedBy(8.dp), - horizontalArrangement = Arrangement.spacedBy(8.dp) + .verticalScroll(rememberScrollState()), + verticalArrangement = Arrangement.spacedBy(8.dp) ) { - DateTimeSelectorBlock( + FlowRow( modifier = Modifier - .background(MaterialTheme.colorScheme.secondaryContainer) - .weight(1f) + .padding(top = 16.dp, start = 16.dp, end = 16.dp) .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 - ) + maxItemsInEachRow = 2, + verticalArrangement = Arrangement.spacedBy(8.dp), + horizontalArrangement = Arrangement.spacedBy(8.dp) + ) { + DateTimeSelectorBlock( + modifier = Modifier + .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( - modifier = Modifier - .background(MaterialTheme.colorScheme.secondaryContainer) - .weight(1f) - .fillMaxWidth(), - title = stringResource(R.string.date_calculator_end), - dateTime = uiState.end, - onClick = { dialogState = DialogState.TO }, - onLongClick = { setEndDate(ZonedDateTimeUtils.nowWithMinutes()) }, - onTimeClick = { dialogState = DialogState.TO_TIME }, - onDateClick = { dialogState = DialogState.TO_DATE }, - containerColor = MaterialTheme.colorScheme.secondaryContainer - ) + DateTimeSelectorBlock( + modifier = Modifier + .background(MaterialTheme.colorScheme.secondaryContainer) + .weight(1f) + .fillMaxWidth(), + title = stringResource(R.string.date_calculator_end), + dateTime = uiState.end, + onClick = { dialogState = DialogState.TO }, + onLongClick = { setEndDate(ZonedDateTimeUtils.nowWithMinutes()) }, + onTimeClick = { dialogState = DialogState.TO_TIME }, + onDateClick = { dialogState = DialogState.TO_DATE }, + containerColor = MaterialTheme.colorScheme.secondaryContainer + ) + } AnimatedContent( targetState = uiState.result, - modifier = Modifier.weight(2f) + modifier = Modifier.fillMaxWidth() ) { result -> when (result) { is ZonedDateTimeDifference.Default -> { DateTimeResultBlock( - modifier = Modifier.fillMaxWidth(), + modifier = Modifier + .padding(horizontal = 16.dp) + .fillMaxWidth(), diff = result, format = { it.format(uiState.precision, uiState.outputFormat) @@ -157,7 +170,18 @@ fun DateDifferenceViewPreview() { uiState = DifferenceUIState.Ready( start = ZonedDateTimeUtils.nowWithMinutes(), 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, outputFormat = OutputFormat.PLAIN, formatterSymbols = FormatterSymbols.Spaces diff --git a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/formatting/FormattingScreen.kt b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/formatting/FormattingScreen.kt index c048e4ea..5036fa6f 100644 --- a/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/formatting/FormattingScreen.kt +++ b/feature/settings/src/main/java/com/sadellie/unitto/feature/settings/formatting/FormattingScreen.kt @@ -25,6 +25,7 @@ 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.pager.rememberPagerState import androidx.compose.foundation.rememberScrollState import androidx.compose.material.icons.Icons import androidx.compose.material.icons.filled.Architecture @@ -122,7 +123,7 @@ fun FormattingScreen( modifier = Modifier .fillMaxWidth() .padding(16.dp), - pagesCount = 2, + pagerState = rememberPagerState { 2 }, ) { currentPage -> val preview = when (currentPage) { 0 -> "123456.${"789123456".repeat(ceil(uiState.precision.toDouble() / 9.0).toInt())}"