Skip to content

Commit

Permalink
feat: Create and style correctly the HorizontalPagerIndicator for the…
Browse files Browse the repository at this point in the history
… onboarding (#237)
  • Loading branch information
LunarX authored Dec 13, 2024
2 parents 421dbf6 + 77e6386 commit a892dc5
Show file tree
Hide file tree
Showing 3 changed files with 113 additions and 17 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -26,34 +26,110 @@ 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,
indicatorStyle: IndicatorStyle,
) {
Row(modifier, horizontalArrangement = Arrangement.Center) {
repeat(pagerState.pageCount) { index ->
val (indicatorWidth, indicatorColor: Color) = computeIndicatorProperties(index, pagerState, indicatorStyle)

Box(
modifier = Modifier
.padding(2.dp)
.clip(CircleShape)
.background(color)
.size(16.dp)
.background(indicatorColor)
.size(height = indicatorStyle.inactiveSize, width = indicatorWidth)
)

if (index < pagerState.pageCount - 1) Spacer(modifier = Modifier.width(indicatorStyle.indicatorSpacing))
}
}
}

private fun computeIndicatorProperties(
index: Int,
pagerState: PagerState,
indicatorStyle: IndicatorStyle,
): Pair<Dp, Color> = with(indicatorStyle) {
val (extendedCurrentPageOffsetFraction, pageVisibilityProgress) = computePageProgresses(index, pagerState)

val indicatorWidth = lerp(inactiveSize, activeWidth, pageVisibilityProgress)

val isTransitioningToSelected = extendedCurrentPageOffsetFraction < 0
val indicatorColor: Color = when {
index == pagerState.currentPage && isTransitioningToSelected -> {
lerp(inactiveColor, activeColor, pageVisibilityProgress)
}
index == pagerState.currentPage + 1 && isTransitioningToSelected -> {
lerp(inactiveColor, activeColor, pageVisibilityProgress)
}
index <= pagerState.currentPage -> activeColor
else -> inactiveColor
}

return indicatorWidth to indicatorColor
}

private fun computePageProgresses(index: Int, pagerState: PagerState): Pair<Float, Float> {
// 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
// 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 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 and therefore fully visible
val pageVisibilityProgress = 1 - abs(extendedCurrentPageOffsetFraction)

return extendedCurrentPageOffsetFraction to pageVisibilityProgress
}

data class IndicatorStyle(
val inactiveColor: Color,
val activeColor: Color,
val inactiveSize: Dp,
val activeWidth: Dp,
val indicatorSpacing: Dp,
)

@Preview
@Composable
private fun Preview() {
HorizontalPagerIndicator(pagerState = rememberPagerState { 3 })
HorizontalPagerIndicator(
pagerState = rememberPagerState(pageCount = { 3 }, initialPage = 1),
indicatorStyle = IndicatorStyle(
inactiveColor = Color.LightGray,
activeColor = Color.DarkGray,
inactiveSize = 8.dp,
activeWidth = 16.dp,
indicatorSpacing = 8.dp,
),
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -43,6 +43,7 @@ fun OnboardingScaffold(
pagerState: PagerState,
onboardingPages: List<OnboardingPage>,
bottomContent: @Composable (PaddingValues) -> Unit,
indicatorStyle: IndicatorStyle,
) {
Scaffold { paddingValues ->
Column {
Expand All @@ -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(
Expand Down Expand Up @@ -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,
)
)
}
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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,
)
)
}

Expand Down

0 comments on commit a892dc5

Please sign in to comment.