Updated on 2026-08-14

This commit is contained in:
Tangem 2026-03-12 13:11:51 +05:00
commit 1ea13a7484

View file

@ -1,5 +1,6 @@
package com.tangem.core.ui.ds
import android.content.res.Configuration
import androidx.compose.animation.animateColorAsState
import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.animateDpAsState
@ -8,6 +9,7 @@ import androidx.compose.foundation.background
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.pager.PagerState
import androidx.compose.foundation.pager.rememberPagerState
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
@ -16,10 +18,13 @@ import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalDensity
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.tooling.preview.PreviewParameter
import androidx.compose.ui.tooling.preview.PreviewParameterProvider
import androidx.compose.ui.unit.Dp
import androidx.compose.ui.unit.DpSize
import androidx.compose.ui.unit.IntOffset
import androidx.compose.ui.unit.dp
import com.tangem.core.ui.extensions.conditionalCompose
import com.tangem.core.ui.res.TangemTheme
import com.tangem.core.ui.res.TangemThemePreviewRedesign
import kotlinx.coroutines.Job
@ -34,30 +39,31 @@ private const val MIN_HIDDEN_FOR_SMALL_DOT = 2
private const val MIN_DISTANCE_FOR_SMALL_DOT = 3
private const val MIN_DISTANCE_FOR_HINT_DOT = 2
private val SPACING = 4.dp
private val CURRENT_DOT_SIZE = DpSize(16.dp, 8.dp)
private val SPACING = 8.dp
private val NORMAL_DOT_SIZE = DpSize(8.dp, 8.dp)
private val HINT_DOT_SIZE = DpSize(6.dp, 6.dp)
private val SMALL_DOT_SIZE = DpSize(4.dp, 4.dp)
/**
* // TODO Cleanup and document this code, it's quite complex and has some "magic numbers" that need explanation.
*
* A pager indicator that adapts to the number of pages and the current page index.
*
* [Figma]("https://www.figma.com/design/RU7AIgwHtGdMfy83T5UOoR/Core-Library?node-id=8452-16489&m=dev")
*
* For 5 or fewer pages, it shows all dots with the current page highlighted.
* For more than 5 pages, it shows a sliding window of 5 dots with size and opacity indicating position.
*
* @param pagerState state of the pager to observe
* @param activeIndicatorColor color for the active page indicator
* @param pagerState state of the pager to observe
* @param modifier modifier for styling
* @param hasBackground whether to show a background behind the indicators
* @param activeIndicatorColor color for the active page indicator
* @param inactiveIndicatorColor color for the inactive page indicators
* @param modifier modifier for styling
*/
@Suppress("LongMethod", "CyclomaticComplexMethod")
@Composable
fun TangemPagerIndicator(
pagerState: PagerState,
modifier: Modifier = Modifier,
hasBackground: Boolean = false,
activeIndicatorColor: Color = TangemTheme.colors2.graphic.neutral.primary,
inactiveIndicatorColor: Color = TangemTheme.colors2.graphic.neutral.tertiary,
) {
@ -124,7 +130,14 @@ fun TangemPagerIndicator(
val visibleIndices = (displayLower until displayUpper).toList()
Box(
modifier = modifier,
modifier = modifier
.conditionalCompose(hasBackground) {
background(
color = TangemTheme.colors2.tabs.backgroundSecondary,
shape = CircleShape,
)
}
.padding(TangemTheme.dimens2.x3),
contentAlignment = Alignment.Center,
) {
Row(
@ -173,9 +186,6 @@ private fun getWindowBounds(totalPages: Int, currentIndex: Int): Pair<Int, Int>
}
private fun getDotSize(index: Int, currentIndex: Int, totalPages: Int): DpSize {
if (index == currentIndex) {
return CURRENT_DOT_SIZE
}
if (totalPages <= MAX_VISIBLE_DOTS) {
return NORMAL_DOT_SIZE
}
@ -276,95 +286,49 @@ private fun Dot(
val shape = RoundedCornerShape(animatedHeight / 2)
Box(
modifier = modifier
.width(animatedWidth)
.height(animatedHeight)
.background(animatedColor, shape),
)
Box(modifier.size(8.dp)) {
Box(
modifier = Modifier
.align(Alignment.Center)
.width(animatedWidth)
.height(animatedHeight)
.background(animatedColor, shape),
)
}
}
@Preview(showBackground = true)
// region Preview
@Composable
private fun PagerIndicatorPreview() {
@Preview(showBackground = true)
@Preview(showBackground = true, uiMode = Configuration.UI_MODE_NIGHT_YES)
private fun TangemPagerIndicator_Preview(@PreviewParameter(TangemPagerIndicatorPreviewProvider::class) params: Int) {
TangemThemePreviewRedesign {
Column(
Modifier
.background(TangemTheme.colors.background.primary)
.background(TangemTheme.colors2.surface.level1)
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
listOf(0, 1, 2, 3, 4).forEach { page ->
TangemPagerIndicator(rememberPagerState(page) { 5 })
repeat(params) { index ->
TangemPagerIndicator(
pagerState = rememberPagerState(index) { params },
hasBackground = index % 2 == 0,
)
}
}
}
}
@Preview(showBackground = true)
@Composable
private fun PagerIndicator6ItemsPreview() {
TangemThemePreviewRedesign {
Column(
Modifier
.background(TangemTheme.colors.background.primary)
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
listOf(0, 1, 2, 3, 4, 5).forEach { page ->
TangemPagerIndicator(rememberPagerState(page) { 6 })
}
}
}
private class TangemPagerIndicatorPreviewProvider : PreviewParameterProvider<Int> {
override val values: Sequence<Int>
get() = sequenceOf(
1,
2,
3,
5,
6,
7,
10,
)
}
@Preview(showBackground = true)
@Composable
private fun PagerIndicator7ItemsPreview() {
TangemThemePreviewRedesign {
Column(
Modifier
.background(TangemTheme.colors.background.primary)
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
listOf(0, 1, 2, 3, 4, 5, 6).forEach { page ->
TangemPagerIndicator(rememberPagerState(page) { 7 })
}
}
}
}
@Preview(showBackground = true)
@Composable
private fun PagerIndicator10ItemsPreview() {
TangemThemePreviewRedesign {
Column(
Modifier
.background(TangemTheme.colors.background.primary)
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
listOf(0, 1, 2, 3, 4, 5, 6, 7, 8, 9).forEach { page ->
TangemPagerIndicator(rememberPagerState(page) { 10 })
}
}
}
}
@Preview(showBackground = true)
@Composable
private fun PagerIndicatorSmallCountsPreview() {
TangemThemePreviewRedesign {
Column(
Modifier
.background(TangemTheme.colors.background.primary)
.padding(20.dp),
verticalArrangement = Arrangement.spacedBy(10.dp),
) {
TangemPagerIndicator(rememberPagerState(0) { 1 })
TangemPagerIndicator(rememberPagerState(1) { 2 })
TangemPagerIndicator(rememberPagerState(1) { 3 })
}
}
}
// endregion