From e31857a1cb6387fef87286d2c3f21bbbb40c464d Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Mon, 2 Dec 2024 16:14:09 +0100 Subject: [PATCH 1/3] feat: Color and size page indicator according to the current pager state --- .../onboarding/HorizontalPagerIndicator.kt | 101 +++++++++++++++--- 1 file changed, 87 insertions(+), 14 deletions(-) diff --git a/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt b/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt index 282e5dc0b..87447b060 100644 --- a/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt +++ b/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt @@ -26,30 +26,103 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.lerp import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp +import androidx.compose.ui.unit.lerp +import kotlin.math.abs -// TODO: Simple placeholder code for now. Will be styled correctly in the next PR @Composable -fun HorizontalPagerIndicator(modifier: Modifier = Modifier, pagerState: PagerState) { - Row( - modifier - .wrapContentHeight() - .fillMaxWidth() - .padding(bottom = 8.dp), - horizontalArrangement = Arrangement.Center - ) { - repeat(pagerState.pageCount) { iteration -> - val color = if (pagerState.currentPage == iteration) Color.DarkGray else Color.LightGray +fun HorizontalPagerIndicator( + modifier: Modifier = Modifier, + pagerState: PagerState, + inactiveIndicatorColor: Color = Color.LightGray, + activeIndicatorColor: Color = Color.Green, + inactiveIndicatorSize: Dp = 8.dp, + activeIndicatorWidth: Dp = 16.dp, + indicatorPadding: Dp = 8.dp, +) { + Row(modifier, horizontalArrangement = Arrangement.Center) { + repeat(pagerState.pageCount) { index -> + val (indicatorWidth, indicatorColor: Color) = computeIndicatorProperties( + index, + pagerState, + inactiveIndicatorSize, + activeIndicatorWidth, + inactiveIndicatorColor, + activeIndicatorColor + ) + Box( modifier = Modifier - .padding(2.dp) .clip(CircleShape) - .background(color) - .size(16.dp) + .background(indicatorColor) + .size(height = inactiveIndicatorSize, width = indicatorWidth) ) + + if (index < pagerState.pageCount - 1) Spacer(modifier = Modifier.width(indicatorPadding)) + } + } +} + +@Composable +private fun computeIndicatorProperties( + index: Int, + pagerState: PagerState, + inactiveIndicatorSize: Dp, + activeIndicatorWidth: Dp, + inactiveIndicatorColor: Color, + activeIndicatorColor: Color +): Pair { + val (extendedCurrentPageOffsetFraction, pageVisibilityProgress) = computePageProgresses(index, pagerState) + + val indicatorWidth = lerp(inactiveIndicatorSize, activeIndicatorWidth, pageVisibilityProgress) + + val isTransitioningToSelected = extendedCurrentPageOffsetFraction < 0 + val indicatorColor: Color = when { + index == pagerState.currentPage && isTransitioningToSelected -> { + lerp(inactiveIndicatorColor, activeIndicatorColor, pageVisibilityProgress) } + index == pagerState.currentPage + 1 && isTransitioningToSelected -> { + lerp(inactiveIndicatorColor, activeIndicatorColor, pageVisibilityProgress) + } + index <= pagerState.currentPage -> activeIndicatorColor + else -> inactiveIndicatorColor + } + + return indicatorWidth to indicatorColor +} + +@Composable +private fun computePageProgresses(index: Int, pagerState: PagerState): Pair { + // Extended offset fraction of the current page relative to the screen + // Range: [-1, 1] + // 0: Page is centered on the screen + // -1: Page is completely off-screen to the left + // 1: Page is completely off-screen to the right + val extendedCurrentPageOffsetFraction = when { + // Page is one step behind the current page and partially visible on the left + index == pagerState.currentPage - 1 && pagerState.currentPageOffsetFraction < 0 -> { + 1 + pagerState.currentPageOffsetFraction + } + // Current page itself (centered or partially moved) + index == pagerState.currentPage -> pagerState.currentPageOffsetFraction + // Page is one step ahead of the current page and partially visible on the right + index == pagerState.currentPage + 1 && pagerState.currentPageOffsetFraction >= 0 -> { + -1 + pagerState.currentPageOffsetFraction + } + // Completely off-screen + else -> 1f } + + // Progress of the page visibility + // Range: [0, 1] + // 0: Page is completely off-screen + // 1: Page is fully centered on the screen + val pageVisibilityProgress = 1 - abs(extendedCurrentPageOffsetFraction) + + return extendedCurrentPageOffsetFraction to pageVisibilityProgress } @Preview From 9bc4b9ac51590d6bd9d712b5c57d407765e75e2e Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Thu, 5 Dec 2024 14:38:05 +0100 Subject: [PATCH 2/3] feat: Clean code and correctly style the indicator inside OnboardingScreen --- .../onboarding/HorizontalPagerIndicator.kt | 57 ++++++++++--------- .../library/onboarding/OnboardingScaffold.kt | 16 +++++- .../ui/screen/onboarding/OnboardingScreen.kt | 8 +++ 3 files changed, 51 insertions(+), 30 deletions(-) diff --git a/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt b/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt index 87447b060..81342cfc0 100644 --- a/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt +++ b/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt @@ -37,64 +37,48 @@ import kotlin.math.abs fun HorizontalPagerIndicator( modifier: Modifier = Modifier, pagerState: PagerState, - inactiveIndicatorColor: Color = Color.LightGray, - activeIndicatorColor: Color = Color.Green, - inactiveIndicatorSize: Dp = 8.dp, - activeIndicatorWidth: Dp = 16.dp, - indicatorPadding: Dp = 8.dp, + indicatorStyle: IndicatorStyle, ) { Row(modifier, horizontalArrangement = Arrangement.Center) { repeat(pagerState.pageCount) { index -> - val (indicatorWidth, indicatorColor: Color) = computeIndicatorProperties( - index, - pagerState, - inactiveIndicatorSize, - activeIndicatorWidth, - inactiveIndicatorColor, - activeIndicatorColor - ) + val (indicatorWidth, indicatorColor: Color) = computeIndicatorProperties(index, pagerState, indicatorStyle) Box( modifier = Modifier .clip(CircleShape) .background(indicatorColor) - .size(height = inactiveIndicatorSize, width = indicatorWidth) + .size(height = indicatorStyle.inactiveSize, width = indicatorWidth) ) - if (index < pagerState.pageCount - 1) Spacer(modifier = Modifier.width(indicatorPadding)) + if (index < pagerState.pageCount - 1) Spacer(modifier = Modifier.width(indicatorStyle.indicatorSpacing)) } } } -@Composable private fun computeIndicatorProperties( index: Int, pagerState: PagerState, - inactiveIndicatorSize: Dp, - activeIndicatorWidth: Dp, - inactiveIndicatorColor: Color, - activeIndicatorColor: Color -): Pair { + indicatorStyle: IndicatorStyle, +): Pair = with(indicatorStyle) { val (extendedCurrentPageOffsetFraction, pageVisibilityProgress) = computePageProgresses(index, pagerState) - val indicatorWidth = lerp(inactiveIndicatorSize, activeIndicatorWidth, pageVisibilityProgress) + val indicatorWidth = lerp(inactiveSize, activeWidth, pageVisibilityProgress) val isTransitioningToSelected = extendedCurrentPageOffsetFraction < 0 val indicatorColor: Color = when { index == pagerState.currentPage && isTransitioningToSelected -> { - lerp(inactiveIndicatorColor, activeIndicatorColor, pageVisibilityProgress) + lerp(inactiveColor, activeColor, pageVisibilityProgress) } index == pagerState.currentPage + 1 && isTransitioningToSelected -> { - lerp(inactiveIndicatorColor, activeIndicatorColor, pageVisibilityProgress) + lerp(inactiveColor, activeColor, pageVisibilityProgress) } - index <= pagerState.currentPage -> activeIndicatorColor - else -> inactiveIndicatorColor + index <= pagerState.currentPage -> activeColor + else -> inactiveColor } return indicatorWidth to indicatorColor } -@Composable private fun computePageProgresses(index: Int, pagerState: PagerState): Pair { // Extended offset fraction of the current page relative to the screen // Range: [-1, 1] @@ -125,8 +109,25 @@ private fun computePageProgresses(index: Int, pagerState: PagerState): Pair, bottomContent: @Composable (PaddingValues) -> Unit, + indicatorStyle: IndicatorStyle, ) { Scaffold { paddingValues -> Column { @@ -61,8 +62,12 @@ fun OnboardingScaffold( } HorizontalPagerIndicator( - modifier = Modifier.padding(PaddingValues(start = startPadding, end = endPadding)), + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp) + .padding(PaddingValues(start = startPadding, end = endPadding)), pagerState = pagerState, + indicatorStyle = indicatorStyle, ) bottomContent( @@ -147,6 +152,13 @@ private fun Preview() { ) { Text("Bottom content") } - } + }, + indicatorStyle = IndicatorStyle( + inactiveColor = Color.LightGray, + activeColor = Color.DarkGray, + inactiveSize = 8.dp, + activeWidth = 16.dp, + indicatorSpacing = 8.dp, + ) ) } diff --git a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/onboarding/OnboardingScreen.kt b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/onboarding/OnboardingScreen.kt index b926cd102..b4325f0c2 100644 --- a/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/onboarding/OnboardingScreen.kt +++ b/app/src/main/java/com/infomaniak/swisstransfer/ui/screen/onboarding/OnboardingScreen.kt @@ -31,6 +31,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextAlign import androidx.compose.ui.unit.dp +import com.infomaniak.library.onboarding.IndicatorStyle import com.infomaniak.library.onboarding.OnboardingPage import com.infomaniak.library.onboarding.OnboardingScaffold import com.infomaniak.swisstransfer.R @@ -83,6 +84,13 @@ fun OnboardingScreen(goToMainActivity: () -> Unit) { goToNextPage = { coroutineScope.launch { pagerState.animateScrollToPage(pagerState.currentPage + 1) } }, ) }, + indicatorStyle = IndicatorStyle( + inactiveColor = SwissTransferTheme.materialColors.outlineVariant, + activeColor = SwissTransferTheme.materialColors.primary, + inactiveSize = 8.dp, + activeWidth = 16.dp, + indicatorSpacing = Margin.Mini, + ) ) } From 77e6386859965eb2351ba40afd081b4b32bdd8d5 Mon Sep 17 00:00:00 2001 From: Gibran Chevalley Date: Thu, 5 Dec 2024 14:39:48 +0100 Subject: [PATCH 3/3] refactor: Clarify comments --- .../library/onboarding/HorizontalPagerIndicator.kt | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt b/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt index 81342cfc0..3dc54f4d0 100644 --- a/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt +++ b/Core2/Onboarding/src/main/java/com/infomaniak/library/onboarding/HorizontalPagerIndicator.kt @@ -80,7 +80,9 @@ private fun computeIndicatorProperties( } private fun computePageProgresses(index: Int, pagerState: PagerState): Pair { - // Extended offset fraction of the current page relative to the screen + // Extended offset fraction of the current page relative to the screen. It's as if pagerState.currentPageOffsetFraction went + // beyond -0.5 up to -1 and beyond 0.5 up to 1 + // // Range: [-1, 1] // 0: Page is centered on the screen // -1: Page is completely off-screen to the left @@ -100,10 +102,10 @@ private fun computePageProgresses(index: Int, pagerState: PagerState): Pair 1f } - // Progress of the page visibility + // Progress of the page visibility i.e. what fraction of the page is currently visible // Range: [0, 1] // 0: Page is completely off-screen - // 1: Page is fully centered on the screen + // 1: Page is fully centered on the screen and therefore fully visible val pageVisibilityProgress = 1 - abs(extendedCurrentPageOffsetFraction) return extendedCurrentPageOffsetFraction to pageVisibilityProgress