diff --git a/design/api/current.api b/design/api/current.api index 0e9da44..b175bfa 100644 --- a/design/api/current.api +++ b/design/api/current.api @@ -203,6 +203,51 @@ package com.urlaunched.android.design.ui.modifiers { } +package com.urlaunched.android.design.ui.onboardingcontainer { + + public final class OnboardingContainerKt { + method @androidx.compose.runtime.Composable public static void OnboardingContainer(optional androidx.compose.ui.Modifier modifier, androidx.compose.foundation.pager.PagerState pagerState, optional com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarColors stepProgressBarColors, optional com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarStyle stepProgressBarStyle, optional androidx.compose.foundation.layout.PaddingValues stepProgressPadding, optional androidx.compose.foundation.layout.Arrangement.Vertical contentArrangement, optional androidx.compose.ui.Alignment.Horizontal contentAlignment, kotlin.jvm.functions.Function1 footerContent, kotlin.jvm.functions.Function2 pageContent); + method @androidx.compose.runtime.Composable public static void OnboardingContainer(optional androidx.compose.ui.Modifier modifier, java.util.List pages, optional int initialPageIndex, optional com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarColors stepProgressBarColors, optional com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarStyle stepProgressBarStyle, optional androidx.compose.foundation.layout.PaddingValues stepProgressPadding, optional androidx.compose.foundation.layout.Arrangement.Vertical contentArrangement, optional androidx.compose.ui.Alignment.Horizontal contentAlignment, optional kotlin.jvm.functions.Function1? onPageChange, kotlin.jvm.functions.Function4,kotlin.Unit> footerContent, kotlin.jvm.functions.Function2 pageContent); + } + + public final class StepProgressBarKt { + method @androidx.compose.runtime.Composable public static void StepProgressBar(optional androidx.compose.ui.Modifier modifier, int activeStepIndex, int stepsCount, com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarColors stepProgressBarColors, com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarStyle stepProgressBarStyle); + } + +} + +package com.urlaunched.android.design.ui.onboardingcontainer.models { + + public final class StepProgressBarColors { + ctor public StepProgressBarColors(optional long selectedStepColor, optional long unselectedStepColor); + method public long component1-0d7_KjU(); + method public long component2-0d7_KjU(); + method public com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarColors copy--OWjLjI(long selectedStepColor, long unselectedStepColor); + method public long getSelectedStepColor(); + method public long getUnselectedStepColor(); + property public final long selectedStepColor; + property public final long unselectedStepColor; + } + + public final class StepProgressBarStyle { + ctor public StepProgressBarStyle(optional float stepHeight, optional float stepWidth, optional float stepsSpacing, optional androidx.compose.ui.graphics.Shape stepShape); + method public float component1-D9Ej5fM(); + method public float component2-D9Ej5fM(); + method public float component3-D9Ej5fM(); + method public androidx.compose.ui.graphics.Shape component4(); + method public com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarStyle copy-DRUOcmI(float stepHeight, float stepWidth, float stepsSpacing, androidx.compose.ui.graphics.Shape stepShape); + method public float getStepHeight(); + method public androidx.compose.ui.graphics.Shape getStepShape(); + method public float getStepWidth(); + method public float getStepsSpacing(); + property public final float stepHeight; + property public final androidx.compose.ui.graphics.Shape stepShape; + property public final float stepWidth; + property public final float stepsSpacing; + } + +} + package com.urlaunched.android.design.ui.paging { public final class PagingColumnKt { diff --git a/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/OnboardingContainer.kt b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/OnboardingContainer.kt new file mode 100644 index 0000000..d5470b9 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/OnboardingContainer.kt @@ -0,0 +1,195 @@ +package com.urlaunched.android.design.ui.onboardingcontainer + +import androidx.compose.animation.core.TweenSpec +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.ColumnScope +import androidx.compose.foundation.layout.PaddingValues +import androidx.compose.foundation.layout.fillMaxSize +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.foundation.layout.width +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.Button +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.Text +import androidx.compose.material3.TextButton +import androidx.compose.runtime.Composable +import androidx.compose.runtime.LaunchedEffect +import androidx.compose.runtime.rememberCoroutineScope +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.text.style.TextAlign +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.urlaunched.android.design.resources.dimens.Dimens +import com.urlaunched.android.design.ui.onboardingcontainer.constants.OnboardingConstants +import com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarColors +import com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarStyle +import kotlinx.coroutines.launch + +private const val FIRST_PAGE_INDEX = 0 +private const val ONE_PAGE = 1 + +@Composable +fun OnboardingContainer( + modifier: Modifier = Modifier, + pages: List, + initialPageIndex: Int = FIRST_PAGE_INDEX, + stepProgressBarColors: StepProgressBarColors = StepProgressBarColors(), + stepProgressBarStyle: StepProgressBarStyle = StepProgressBarStyle(), + stepProgressPadding: PaddingValues = PaddingValues(top = Dimens.spacingNormal), + contentArrangement: Arrangement.Vertical = Arrangement.Top, + contentAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, + onPageChange: ((page: T) -> Unit)? = null, + footerContent: @Composable ColumnScope.(page: T, isLastPage: Boolean, nextPage: () -> Unit) -> Unit, + pageContent: @Composable ColumnScope.(page: T) -> Unit +) { + val coroutineScope = rememberCoroutineScope() + val pagerState = rememberPagerState( + initialPage = initialPageIndex, + pageCount = { pages.size } + ) + + onPageChange?.let { onChange -> + LaunchedEffect(pagerState.currentPage) { + onChange(pages[pagerState.currentPage]) + } + } + + val goToNextPage: () -> Unit = { + coroutineScope.launch { + pagerState.animateScrollToPage( + page = pagerState.currentPage + ONE_PAGE, + animationSpec = TweenSpec(durationMillis = OnboardingConstants.PAGE_ANIMATION_DURATION_MILLIS) + ) + } + } + + OnboardingContainer( + modifier = modifier, + pagerState = pagerState, + stepProgressBarColors = stepProgressBarColors, + stepProgressBarStyle = stepProgressBarStyle, + stepProgressPadding = stepProgressPadding, + contentArrangement = contentArrangement, + contentAlignment = contentAlignment, + pageContent = { pageIndex -> + pageContent(pages[pageIndex]) + }, + footerContent = { + footerContent(pages[pagerState.currentPage], pagerState.currentPage == pages.lastIndex, goToNextPage) + } + ) +} + +@Composable +fun OnboardingContainer( + modifier: Modifier = Modifier, + pagerState: PagerState, + stepProgressBarColors: StepProgressBarColors = StepProgressBarColors(), + stepProgressBarStyle: StepProgressBarStyle = StepProgressBarStyle(), + stepProgressPadding: PaddingValues = PaddingValues(top = Dimens.spacingNormal), + contentArrangement: Arrangement.Vertical = Arrangement.Top, + contentAlignment: Alignment.Horizontal = Alignment.CenterHorizontally, + footerContent: @Composable ColumnScope.() -> Unit, + pageContent: @Composable ColumnScope.(page: Int) -> Unit +) { + Column( + modifier = modifier + ) { + HorizontalPager( + modifier = Modifier.weight(1f), + verticalAlignment = Alignment.Top, + state = pagerState + ) { page -> + Column( + modifier = Modifier.fillMaxSize(), + horizontalAlignment = contentAlignment, + verticalArrangement = contentArrangement + ) { + pageContent(page) + } + } + + Column( + modifier = Modifier.fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + StepProgressBar( + activeStepIndex = pagerState.currentPage, + stepsCount = pagerState.pageCount, + stepProgressBarColors = stepProgressBarColors, + stepProgressBarStyle = stepProgressBarStyle, + modifier = Modifier.padding(stepProgressPadding) + ) + + footerContent() + } + } +} + +@Preview(showBackground = true) +@Composable +private fun OnboardingContainerPreview() { + val pagerState = rememberPagerState(pageCount = { 4 }) + + OnboardingContainer( + pagerState = pagerState, + stepProgressBarColors = StepProgressBarColors( + selectedStepColor = MaterialTheme.colorScheme.primary, + unselectedStepColor = MaterialTheme.colorScheme.primaryContainer + ), + pageContent = { page -> + Box( + modifier = Modifier + .padding(top = 100.dp) + .size(270.dp) + .background(Color(0xFF4CAF50), RoundedCornerShape(24.dp)) + ) + + Text( + text = "Welcome to the App", + style = MaterialTheme.typography.displaySmall, + modifier = Modifier.padding(top = 32.dp) + ) + + Text( + text = "Your go-to application for everything.\nEmbark on an exciting journey with us.", + style = MaterialTheme.typography.labelLarge, + textAlign = TextAlign.Center, + color = Color(0xA3000000), + modifier = Modifier.padding(top = 8.dp) + ) + }, + footerContent = { + Button( + onClick = {}, + modifier = Modifier + .padding(top = Dimens.spacingBig) + .width(200.dp) + ) { + Text("Next") + } + + TextButton( + onClick = {}, + modifier = Modifier.width(200.dp) + ) { + Text("Skip") + } + + Text( + text = "Privacy Policy, Terms & Conditions", + modifier = Modifier.padding(vertical = Dimens.spacingNormal) + ) + } + ) +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/StepProgressBar.kt b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/StepProgressBar.kt new file mode 100644 index 0000000..f3e5ec3 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/StepProgressBar.kt @@ -0,0 +1,64 @@ +package com.urlaunched.android.design.ui.onboardingcontainer + +import androidx.compose.animation.animateColorAsState +import androidx.compose.animation.core.LinearEasing +import androidx.compose.animation.core.tween +import androidx.compose.foundation.background +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Box +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.layout.size +import androidx.compose.runtime.Composable +import androidx.compose.runtime.getValue +import androidx.compose.ui.Modifier +import androidx.compose.ui.draw.clip +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.urlaunched.android.design.ui.onboardingcontainer.constants.OnboardingConstants +import com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarColors +import com.urlaunched.android.design.ui.onboardingcontainer.models.StepProgressBarStyle + +@Composable +fun StepProgressBar( + modifier: Modifier = Modifier, + activeStepIndex: Int, + stepsCount: Int, + stepProgressBarColors: StepProgressBarColors, + stepProgressBarStyle: StepProgressBarStyle +) { + Row( + modifier = modifier, + horizontalArrangement = Arrangement.spacedBy(stepProgressBarStyle.stepsSpacing) + ) { + for (stepIndex in OnboardingConstants.PROGRESS_BAR_FIRST_STEP until stepsCount) { + val tabColor by animateColorAsState( + targetValue = if (stepIndex == activeStepIndex) { + stepProgressBarColors.selectedStepColor + } else { + stepProgressBarColors.unselectedStepColor + }, + animationSpec = tween(OnboardingConstants.PAGE_ANIMATION_DURATION_MILLIS, easing = LinearEasing) + ) + + Box( + modifier = Modifier + .size(height = stepProgressBarStyle.stepHeight, width = stepProgressBarStyle.stepWidth) + .clip(stepProgressBarStyle.stepShape) + .background(tabColor) + ) + } + } +} + +@Preview(showBackground = true) +@Composable +private fun StepProgressBarPreview() { + StepProgressBar( + modifier = Modifier.padding(16.dp), + activeStepIndex = 1, + stepsCount = 4, + stepProgressBarColors = StepProgressBarColors(), + stepProgressBarStyle = StepProgressBarStyle() + ) +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/constants/OnboardingConstants.kt b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/constants/OnboardingConstants.kt new file mode 100644 index 0000000..dd78b61 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/constants/OnboardingConstants.kt @@ -0,0 +1,6 @@ +package com.urlaunched.android.design.ui.onboardingcontainer.constants + +internal object OnboardingConstants { + const val PROGRESS_BAR_FIRST_STEP = 0 + const val PAGE_ANIMATION_DURATION_MILLIS = 300 +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/constants/StepProgressBarDimens.kt b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/constants/StepProgressBarDimens.kt new file mode 100644 index 0000000..d8184e9 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/constants/StepProgressBarDimens.kt @@ -0,0 +1,10 @@ +package com.urlaunched.android.design.ui.onboardingcontainer.constants + +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +internal object StepProgressBarDimens { + val defaultStepHeight: Dp = 5.dp + val defaultStepWidth: Dp = 24.dp + val defaultStepsSpacing: Dp = 4.dp +} \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/models/StepProgressBarColors.kt b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/models/StepProgressBarColors.kt new file mode 100644 index 0000000..3baa1b4 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/models/StepProgressBarColors.kt @@ -0,0 +1,8 @@ +package com.urlaunched.android.design.ui.onboardingcontainer.models + +import androidx.compose.ui.graphics.Color + +data class StepProgressBarColors( + val selectedStepColor: Color = Color.Black, + val unselectedStepColor: Color = Color.LightGray +) \ No newline at end of file diff --git a/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/models/StepProgressBarStyle.kt b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/models/StepProgressBarStyle.kt new file mode 100644 index 0000000..4a19ad9 --- /dev/null +++ b/design/src/main/java/com/urlaunched/android/design/ui/onboardingcontainer/models/StepProgressBarStyle.kt @@ -0,0 +1,13 @@ +package com.urlaunched.android.design.ui.onboardingcontainer.models + +import androidx.compose.foundation.shape.CircleShape +import androidx.compose.ui.graphics.Shape +import androidx.compose.ui.unit.Dp +import com.urlaunched.android.design.ui.onboardingcontainer.constants.StepProgressBarDimens + +data class StepProgressBarStyle( + val stepHeight: Dp = StepProgressBarDimens.defaultStepHeight, + val stepWidth: Dp = StepProgressBarDimens.defaultStepWidth, + val stepsSpacing: Dp = StepProgressBarDimens.defaultStepsSpacing, + val stepShape: Shape = CircleShape +) \ No newline at end of file