2
2
// SPDX-License-Identifier: Apache-2.0
3
3
package com.slack.circuit.star.petdetail
4
4
5
+ import android.content.res.Configuration
5
6
import android.view.KeyEvent
6
7
import androidx.compose.animation.animateContentSize
7
8
import androidx.compose.animation.core.AnimationConstants
8
9
import androidx.compose.foundation.ExperimentalFoundationApi
9
10
import androidx.compose.foundation.focusable
10
11
import androidx.compose.foundation.layout.Column
12
+ import androidx.compose.foundation.layout.ColumnScope
11
13
import androidx.compose.foundation.layout.PaddingValues
12
14
import androidx.compose.foundation.layout.aspectRatio
13
15
import androidx.compose.foundation.layout.fillMaxSize
14
16
import androidx.compose.foundation.layout.fillMaxWidth
15
17
import androidx.compose.foundation.layout.padding
16
18
import androidx.compose.foundation.pager.HorizontalPager
17
19
import androidx.compose.foundation.pager.PagerState
20
+ import androidx.compose.foundation.pager.VerticalPager
18
21
import androidx.compose.foundation.pager.rememberPagerState
19
22
import androidx.compose.material3.Card
20
23
import androidx.compose.material3.MaterialTheme
@@ -29,6 +32,7 @@ import androidx.compose.ui.focus.focusRequester
29
32
import androidx.compose.ui.graphics.graphicsLayer
30
33
import androidx.compose.ui.input.key.onKeyEvent
31
34
import androidx.compose.ui.layout.ContentScale
35
+ import androidx.compose.ui.platform.LocalConfiguration
32
36
import androidx.compose.ui.platform.LocalContext
33
37
import androidx.compose.ui.platform.testTag
34
38
import androidx.compose.ui.unit.dp
@@ -37,6 +41,7 @@ import coil.compose.AsyncImage
37
41
import coil.imageLoader
38
42
import coil.request.ImageRequest
39
43
import com.google.accompanist.pager.HorizontalPagerIndicator
44
+ import com.google.accompanist.pager.VerticalPagerIndicator
40
45
import com.slack.circuit.codegen.annotations.CircuitInject
41
46
import com.slack.circuit.runtime.CircuitUiState
42
47
import com.slack.circuit.runtime.Screen
@@ -145,6 +150,7 @@ internal fun PetPhotoCarousel(state: PetPhotoCarouselScreen.State, modifier: Mod
145
150
.focusable()
146
151
.onKeyEvent { event ->
147
152
if (event.nativeKeyEvent.action != KeyEvent .ACTION_UP ) return @onKeyEvent false
153
+ // TODO vert
148
154
val index =
149
155
when (event.nativeKeyEvent.keyCode) {
150
156
KeyEvent .KEYCODE_DPAD_RIGHT -> {
@@ -163,20 +169,26 @@ internal fun PetPhotoCarousel(state: PetPhotoCarouselScreen.State, modifier: Mod
163
169
}
164
170
}
165
171
) {
166
- PhotoPager (
167
- count = totalPhotos,
168
- pagerState = pagerState,
169
- photoUrls = photoUrls,
170
- name = name,
171
- photoUrlMemoryCacheKey = photoUrlMemoryCacheKey,
172
- )
173
-
174
- HorizontalPagerIndicator (
175
- pagerState = pagerState,
176
- pageCount = totalPhotos,
177
- modifier = Modifier .align(Alignment .CenterHorizontally ).padding(16 .dp),
178
- activeColor = MaterialTheme .colorScheme.onBackground
179
- )
172
+ when (LocalConfiguration .current.orientation == Configuration .ORIENTATION_LANDSCAPE ) {
173
+ true -> {
174
+ VerticalPhotoPager (
175
+ count = totalPhotos,
176
+ pagerState = pagerState,
177
+ photoUrls = photoUrls,
178
+ name = name,
179
+ photoUrlMemoryCacheKey = photoUrlMemoryCacheKey,
180
+ )
181
+ }
182
+ false -> {
183
+ HorizontalPhotoPager (
184
+ count = totalPhotos,
185
+ pagerState = pagerState,
186
+ photoUrls = photoUrls,
187
+ name = name,
188
+ photoUrlMemoryCacheKey = photoUrlMemoryCacheKey,
189
+ )
190
+ }
191
+ }
180
192
}
181
193
182
194
// Focus the pager so we can cycle through it with arrow keys
@@ -188,10 +200,9 @@ private fun PagerState.calculateCurrentOffsetForPage(page: Int): Float {
188
200
return (currentPage - page) + currentPageOffsetFraction
189
201
}
190
202
191
- @Suppress(" LongParameterList" )
192
203
@OptIn(ExperimentalFoundationApi ::class )
193
204
@Composable
194
- private fun PhotoPager (
205
+ private fun ColumnScope. HorizontalPhotoPager (
195
206
count : Int ,
196
207
pagerState : PagerState ,
197
208
photoUrls : ImmutableList <String >,
@@ -206,40 +217,84 @@ private fun PhotoPager(
206
217
modifier = modifier,
207
218
contentPadding = PaddingValues (16 .dp),
208
219
) { page ->
209
- Card (
210
- modifier =
211
- Modifier .aspectRatio(1f ).graphicsLayer {
212
- // Calculate the absolute offset for the current page from the
213
- // scroll position. We use the absolute value which allows us to mirror
214
- // any effects for both directions
215
- val pageOffset = pagerState.calculateCurrentOffsetForPage(page).absoluteValue
216
-
217
- // We animate the scaleX + scaleY, between 85% and 100%
218
- lerp(start = 0.85f , stop = 1f , fraction = 1f - pageOffset.coerceIn(0f , 1f )).also { scale
219
- ->
220
- scaleX = scale
221
- scaleY = scale
222
- }
220
+ PhotoPage (page, pagerState, photoUrls, name, photoUrlMemoryCacheKey)
221
+ }
223
222
224
- // We animate the alpha, between 50% and 100%
225
- alpha = lerp(start = 0.5f , stop = 1f , fraction = 1f - pageOffset.coerceIn(0f , 1f ))
223
+ HorizontalPagerIndicator (
224
+ pagerState = pagerState,
225
+ pageCount = count,
226
+ modifier = Modifier .align(Alignment .CenterHorizontally ).padding(16 .dp),
227
+ activeColor = MaterialTheme .colorScheme.onBackground
228
+ )
229
+ }
230
+
231
+ @OptIn(ExperimentalFoundationApi ::class )
232
+ @Composable
233
+ private fun ColumnScope.VerticalPhotoPager (
234
+ count : Int ,
235
+ pagerState : PagerState ,
236
+ photoUrls : ImmutableList <String >,
237
+ name : String ,
238
+ photoUrlMemoryCacheKey : String? = null,
239
+ ) {
240
+ VerticalPager (
241
+ pageCount = count,
242
+ state = pagerState,
243
+ key = photoUrls::get,
244
+ contentPadding = PaddingValues (16 .dp),
245
+ ) { page ->
246
+ PhotoPage (page, pagerState, photoUrls, name, photoUrlMemoryCacheKey)
247
+ }
248
+
249
+ VerticalPagerIndicator (
250
+ pagerState = pagerState,
251
+ pageCount = count,
252
+ modifier = Modifier .align(Alignment .CenterHorizontally ).padding(16 .dp),
253
+ activeColor = MaterialTheme .colorScheme.onBackground
254
+ )
255
+ }
256
+
257
+ @OptIn(ExperimentalFoundationApi ::class )
258
+ @Composable
259
+ private fun PhotoPage (
260
+ page : Int ,
261
+ pagerState : PagerState ,
262
+ photoUrls : ImmutableList <String >,
263
+ name : String ,
264
+ photoUrlMemoryCacheKey : String? = null,
265
+ ) {
266
+ Card (
267
+ modifier =
268
+ Modifier .aspectRatio(1f ).graphicsLayer {
269
+ // Calculate the absolute offset for the current page from the
270
+ // scroll position. We use the absolute value which allows us to mirror
271
+ // any effects for both directions
272
+ val pageOffset = pagerState.calculateCurrentOffsetForPage(page).absoluteValue
273
+
274
+ // We animate the scaleX + scaleY, between 85% and 100%
275
+ lerp(start = 0.85f , stop = 1f , fraction = 1f - pageOffset.coerceIn(0f , 1f )).also { scale ->
276
+ scaleX = scale
277
+ scaleY = scale
226
278
}
227
- ) {
228
- AsyncImage (
229
- modifier = Modifier .fillMaxWidth(),
230
- model =
231
- ImageRequest .Builder (LocalContext .current)
232
- .data(photoUrls[page].takeIf (String ::isNotBlank))
233
- .apply {
234
- if (page == 0 ) {
235
- placeholderMemoryCacheKey(photoUrlMemoryCacheKey)
236
- crossfade(AnimationConstants .DefaultDurationMillis )
237
- }
279
+
280
+ // We animate the alpha, between 50% and 100%
281
+ alpha = lerp(start = 0.5f , stop = 1f , fraction = 1f - pageOffset.coerceIn(0f , 1f ))
282
+ }
283
+ ) {
284
+ AsyncImage (
285
+ modifier = Modifier .fillMaxWidth(),
286
+ model =
287
+ ImageRequest .Builder (LocalContext .current)
288
+ .data(photoUrls[page].takeIf (String ::isNotBlank))
289
+ .apply {
290
+ if (page == 0 ) {
291
+ placeholderMemoryCacheKey(photoUrlMemoryCacheKey)
292
+ crossfade(AnimationConstants .DefaultDurationMillis )
238
293
}
239
- .build(),
240
- contentDescription = name ,
241
- contentScale = ContentScale . Crop ,
242
- )
243
- }
294
+ }
295
+ .build() ,
296
+ contentDescription = name ,
297
+ contentScale = ContentScale . Crop ,
298
+ )
244
299
}
245
300
}
0 commit comments