Skip to content

Commit f2721e1

Browse files
Merge pull request #479 from MadridSquad/fix/home-pager
Enhance RTL Support and Pager Animation in Media Carousel
2 parents edf6ed2 + d573841 commit f2721e1

File tree

1 file changed

+108
-68
lines changed
  • presentation/src/main/java/com/madrid/presentation/component

1 file changed

+108
-68
lines changed

presentation/src/main/java/com/madrid/presentation/component/MovioPager.kt

Lines changed: 108 additions & 68 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ import androidx.compose.foundation.pager.rememberPagerState
1515
import androidx.compose.foundation.shape.CircleShape
1616
import androidx.compose.foundation.shape.RoundedCornerShape
1717
import androidx.compose.runtime.Composable
18+
import androidx.compose.runtime.CompositionLocalProvider
19+
import androidx.compose.runtime.LaunchedEffect
1820
import androidx.compose.ui.Alignment
1921
import androidx.compose.ui.Modifier
2022
import androidx.compose.ui.draw.BlurredEdgeTreatment.Companion.Unbounded
@@ -23,7 +25,9 @@ import androidx.compose.ui.draw.clip
2325
import androidx.compose.ui.draw.shadow
2426
import androidx.compose.ui.graphics.graphicsLayer
2527
import androidx.compose.ui.layout.ContentScale
28+
import androidx.compose.ui.platform.LocalLayoutDirection
2629
import androidx.compose.ui.tooling.preview.Preview
30+
import androidx.compose.ui.unit.LayoutDirection
2731
import androidx.compose.ui.unit.dp
2832
import androidx.compose.ui.util.lerp
2933
import androidx.compose.ui.zIndex
@@ -32,6 +36,7 @@ import com.madrid.detectImageContent.FilteredImage
3236
import com.madrid.presentation.viewModel.homeViewModel.CategoryUiState
3337
import com.madrid.presentation.viewModel.shared.MediaType
3438
import com.madrid.presentation.viewModel.shared.MediaUiState
39+
import kotlinx.coroutines.delay
3540
import kotlin.math.absoluteValue
3641

3742
@Composable
@@ -43,11 +48,25 @@ fun MovioPager(
4348
onClickMediaButton: (Int) -> Unit = {},
4449
) {
4550
if (medias.isNotEmpty()) {
51+
val currentLayoutDirection = LocalLayoutDirection.current
52+
val isRtl = currentLayoutDirection == LayoutDirection.Rtl
4653
val pagerState = rememberPagerState(
47-
initialPage = medias.size / 2,
54+
initialPage = if (isRtl) medias.size - 1 else 0,
4855
pageCount = { medias.size }
4956
)
5057

58+
LaunchedEffect(medias) {
59+
while (true) {
60+
delay(6000)
61+
val nextPage = if (isRtl) {
62+
if (pagerState.currentPage == 0) medias.size - 1 else pagerState.currentPage - 1
63+
} else {
64+
(pagerState.currentPage + 1) % medias.size
65+
}
66+
pagerState.animateScrollToPage(nextPage)
67+
}
68+
}
69+
5170
Box(
5271
modifier = modifier
5372
.fillMaxWidth()
@@ -68,87 +87,108 @@ fun MovioPager(
6887
modifier = Modifier.fillMaxWidth(),
6988
horizontalAlignment = Alignment.CenterHorizontally
7089
) {
90+
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
91+
HorizontalPager(
92+
modifier = Modifier.fillMaxWidth(),
93+
pageSpacing = (-240).dp,
94+
state = pagerState,
95+
verticalAlignment = Alignment.CenterVertically,
96+
) { page ->
97+
val pageOffset =
98+
((pagerState.currentPage - page) + pagerState.currentPageOffsetFraction)
7199

72-
HorizontalPager(
73-
modifier = Modifier.fillMaxWidth(),
74-
pageSpacing = (-240).dp,
75-
state = pagerState,
76-
verticalAlignment = Alignment.CenterVertically,
77-
) { page ->
78-
val pageOffset =
79-
((pagerState.currentPage - page) + pagerState.currentPageOffsetFraction)
100+
val absOffset = pageOffset.absoluteValue
80101

81-
val absOffset = pageOffset.absoluteValue
102+
val scale = lerp(0.7f, 1f, 1f - absOffset.coerceIn(0f, 1f))
103+
val rotation =
104+
lerp(
105+
20f,
106+
0f,
107+
1f - absOffset.coerceIn(0f, 1f)
108+
) * if (pageOffset < 0) 1f else -1f
109+
val alphaa = lerp(0.4f, 1f, 1f - absOffset.coerceIn(0f, 1f))
110+
val zIndex = if (page == pagerState.currentPage) {
111+
medias.size.toFloat()
112+
} else {
113+
medias.size - absOffset
114+
}
82115

83-
val scale = lerp(0.7f, 1f, 1f - absOffset.coerceIn(0f, 1f))
84-
val rotation =
85-
lerp(
86-
20f,
87-
0f,
88-
1f - absOffset.coerceIn(0f, 1f)
89-
) * if (pageOffset < 0) 1f else -1f
90-
val alphaa = lerp(0.4f, 1f, 1f - absOffset.coerceIn(0f, 1f))
91-
val zIndex = if (page == pagerState.currentPage) {
92-
medias.size.toFloat()
93-
} else {
94-
medias.size - absOffset
95-
}
96-
97-
Box(
98-
modifier = Modifier
99-
.fillMaxWidth()
100-
.then(
101-
if (pagerState.currentPage == page) Modifier.zIndex(1f) else Modifier
102-
),
103-
contentAlignment = Alignment.Center
104-
) {
105-
MovieHomeCard(
116+
Box(
106117
modifier = Modifier
107-
.graphicsLayer {
108-
scaleX = scale
109-
scaleY = scale
110-
rotationZ = rotation
111-
alpha = alphaa
112-
cameraDistance = 12 * density
113-
}
114-
.clip(RoundedCornerShape(8.dp))
115-
.size(width = 200.dp, height = 260.dp),
116-
movieId = medias[page].imageUrl,
117-
name = medias[page].title,
118-
genres = medias[page].category.map { it.name },
119-
onClick = { onClickMediaButton(page) },
120-
)
118+
.fillMaxWidth()
119+
.then(
120+
if (pagerState.currentPage == page) Modifier.zIndex(1f) else Modifier
121+
),
122+
contentAlignment = Alignment.Center
123+
) {
124+
MovieHomeCard(
125+
modifier = Modifier
126+
.graphicsLayer {
127+
scaleX = scale
128+
scaleY = scale
129+
rotationZ = rotation
130+
alpha = alphaa
131+
cameraDistance = 12 * density
132+
}
133+
.clip(RoundedCornerShape(8.dp))
134+
.size(width = 200.dp, height = 260.dp),
135+
movieId = medias[page].imageUrl,
136+
name = medias[page].title,
137+
genres = medias[page].category.map { it.name },
138+
onClick = { onClickMediaButton(page) },
139+
)
140+
}
121141
}
122142
}
123143

124144
Spacer(modifier = Modifier.height(16.dp))
125145

126-
Row(
127-
horizontalArrangement = Arrangement.Center,
128-
verticalAlignment = Alignment.CenterVertically,
129-
) {
130-
repeat(medias.size) { index ->
131-
val isSelected = pagerState.currentPage == index
132-
Box(
133-
modifier = Modifier
134-
.padding(horizontal = 4.dp)
135-
.size(if (isSelected) 15.dp else 5.dp, 5.dp)
136-
.clip(if (isSelected) RoundedCornerShape(50) else CircleShape)
137-
.background(
138-
if (isSelected)
139-
Theme.color.surfaces.onSurfaceAt1
140-
else
141-
Theme.color.surfaces.onSurfaceAt2
142-
)
143-
)
144-
}
145-
}
146-
Spacer(modifier = Modifier.height(4.dp))
146+
MovioPagerIndicator(
147+
pageCount = medias.size,
148+
currentPage = pagerState.currentPage,
149+
isRtl = isRtl,
150+
modifier = Modifier.padding(bottom = 4.dp)
151+
)
147152
}
148153
}
149154
}
155+
156+
150157
}
151158

159+
@Composable
160+
private fun MovioPagerIndicator(
161+
pageCount: Int,
162+
currentPage: Int,
163+
isRtl: Boolean,
164+
modifier: Modifier = Modifier
165+
) {
166+
CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) {
167+
Row(
168+
horizontalArrangement = Arrangement.Center,
169+
verticalAlignment = Alignment.CenterVertically,
170+
modifier = modifier
171+
) {
172+
val indicatorRange =
173+
if (isRtl) (pageCount - 1) downTo 0 else 0 until pageCount
174+
repeat(pageCount) { index ->
175+
val isSelected = currentPage == index
176+
Box(
177+
modifier = Modifier
178+
.padding(horizontal = 4.dp)
179+
.size(if (isSelected) 15.dp else 5.dp, 5.dp)
180+
.clip(if (isSelected) RoundedCornerShape(50) else CircleShape)
181+
.background(
182+
if (isSelected)
183+
Theme.color.surfaces.onSurfaceAt1
184+
else
185+
Theme.color.surfaces.onSurfaceAt2
186+
)
187+
)
188+
}
189+
}
190+
}
191+
}
152192
@Preview(showBackground = true, showSystemUi = true)
153193
@Composable
154194
fun MovioSliderPreview() {

0 commit comments

Comments
 (0)