Skip to content

Commit b7aca77

Browse files
committed
feat: Implement shared element transitions for images
1 parent 94b04b7 commit b7aca77

File tree

12 files changed

+481
-396
lines changed

12 files changed

+481
-396
lines changed

app/src/main/java/org/nsh07/wikireader/ui/AppScreen.kt

Lines changed: 7 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -238,7 +238,7 @@ fun AppScreen(
238238
slideInHorizontally(
239239
initialOffsetX = { it / 4 },
240240
animationSpec = motionScheme.defaultSpatialSpec()
241-
) + fadeIn(motionScheme.defaultEffectsSpec())
241+
) + fadeIn()
242242
else
243243
fadeIn(animationSpec = tween(220, delayMillis = 90)) +
244244
scaleIn(
@@ -251,7 +251,7 @@ fun AppScreen(
251251
slideOutHorizontally(
252252
targetOffsetX = { -it / 4 },
253253
animationSpec = motionScheme.fastSpatialSpec()
254-
) + fadeOut(motionScheme.fastEffectsSpec())
254+
) + fadeOut()
255255
else
256256
fadeOut(animationSpec = tween(90))
257257
},
@@ -260,7 +260,7 @@ fun AppScreen(
260260
slideInHorizontally(
261261
initialOffsetX = { -it / 4 },
262262
animationSpec = motionScheme.defaultSpatialSpec()
263-
) + fadeIn(motionScheme.defaultEffectsSpec())
263+
) + fadeIn()
264264
else
265265
fadeIn(animationSpec = tween(220, delayMillis = 90)) +
266266
scaleIn(
@@ -273,7 +273,7 @@ fun AppScreen(
273273
slideOutHorizontally(
274274
targetOffsetX = { it / 4 },
275275
animationSpec = motionScheme.fastSpatialSpec()
276-
) + fadeOut(motionScheme.fastEffectsSpec())
276+
) + fadeOut()
277277
else
278278
fadeOut(animationSpec = tween(90))
279279
}
@@ -310,7 +310,7 @@ fun AppScreen(
310310
motionScheme.defaultSpatialSpec(),
311311
initialOffsetY = { -it }
312312
) + expandVertically(motionScheme.defaultSpatialSpec()),
313-
exit = fadeOut(motionScheme.fastEffectsSpec())
313+
exit = fadeOut()
314314
) {
315315
AppSearchBar(
316316
appSearchBarState = appSearchBarState,
@@ -388,8 +388,8 @@ fun AppScreen(
388388

389389
AnimatedVisibility(
390390
backStack.last() !is HomeSubscreen.Image,
391-
enter = fadeIn(motionScheme.slowEffectsSpec()),
392-
exit = fadeOut(motionScheme.fastEffectsSpec())
391+
enter = fadeIn(),
392+
exit = fadeOut()
393393
) {
394394
StatusBarProtection()
395395
}
@@ -423,7 +423,6 @@ fun AppScreen(
423423
composable<SettingsScreen> {
424424
SettingsScreenRoot(
425425
preferencesState = preferencesState,
426-
homeScreenState = homeScreenState,
427426
lastBackStackEntry = backStack.last(),
428427
recentLangs = recentLangs,
429428
languageSearchStr = languageSearchStr,

app/src/main/java/org/nsh07/wikireader/ui/homeScreen/AppHomeScreen.kt

Lines changed: 10 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -108,14 +108,12 @@ import org.nsh07.wikireader.ui.shimmer.FeedLoader
108108
* @param languageSearchStr The current search string for languages in the language bottom sheet.
109109
* @param languageSearchQuery The current search query for languages after debouncing.
110110
* @param showLanguageSheet A boolean indicating whether the language selection bottom sheet should be shown.
111-
* @param deepLinkHandled A boolean indicating if a deep link has been processed.
112-
* @param onImageClick A lambda function to be invoked when the main article image is clicked.
113-
* @param onGalleryImageClick A lambda function to be invoked when an image in the gallery is clicked.
114-
* It takes the image URL and description as parameters.
111+
* @param deepLinkHandled A boolean indicating if a deep link has been processed for the initial feed load.
115112
* @param setShowArticleLanguageSheet A lambda function to control the visibility of the article language bottom sheet.
116113
* @param onAction A lambda function to dispatch [HomeAction] events to the ViewModel.
117114
* @param onSettingsAction A lambda function to dispatch [SettingsAction] events to the SettingsViewModel.
118115
* @param insets The [PaddingValues] for handling system window insets.
116+
* This is used to adjust UI elements to avoid overlapping with system bars.
119117
* @param windowSizeClass The [WindowSizeClass] for adapting the layout to different screen sizes.
120118
* @param modifier The [Modifier] to be applied to the root container of the home screen.
121119
*/
@@ -245,20 +243,20 @@ fun AppHomeScreen(
245243
onAction(HomeAction.StopAll)
246244
repeat(it) { backStack.removeAt(backStack.lastIndex) }
247245
},
248-
transitionSpec = { fadeIn(motionScheme.defaultEffectsSpec()).togetherWith(fadeOut()) },
249-
popTransitionSpec = { fadeIn(motionScheme.defaultEffectsSpec()).togetherWith(fadeOut()) },
246+
transitionSpec = { fadeIn().togetherWith(fadeOut()) },
247+
popTransitionSpec = { fadeIn().togetherWith(fadeOut()) },
250248
predictivePopTransitionSpec = {
251-
if (backStack.size > 2)
249+
if (backStack.size > 2 && backStack.last() !is HomeSubscreen.Image)
252250
(slideInHorizontally(
253251
initialOffsetX = { -it / 4 },
254252
animationSpec = motionScheme.defaultSpatialSpec()
255-
) + fadeIn(motionScheme.defaultEffectsSpec())).togetherWith(
253+
) + fadeIn()).togetherWith(
256254
slideOutHorizontally(
257255
targetOffsetX = { it / 4 },
258256
animationSpec = motionScheme.fastSpatialSpec()
259-
) + fadeOut(motionScheme.fastEffectsSpec())
257+
) + fadeOut()
260258
)
261-
else fadeIn(motionScheme.defaultEffectsSpec()).togetherWith(fadeOut())
259+
else fadeIn().togetherWith(fadeOut())
262260
},
263261
entryProvider = entryProvider {
264262
entry<HomeSubscreen.Logo> {
@@ -364,6 +362,7 @@ fun AppHomeScreen(
364362
FullScreenImage(
365363
photo = it.photo,
366364
photoDesc = it.photoDesc,
365+
sharedScope = this@SharedTransitionLayout,
367366
title = it.title,
368367
background = it.background,
369368
imageLoader = it.imageLoader,
@@ -376,6 +375,7 @@ fun AppHomeScreen(
376375
FullScreenArticleImage(
377376
uri = it.uri,
378377
description = it.description,
378+
sharedScope = this@SharedTransitionLayout,
379379
imageLoader = it.imageLoader,
380380
background = it.background,
381381
link = it.link,

app/src/main/java/org/nsh07/wikireader/ui/homeScreen/ArticleFeed.kt

Lines changed: 78 additions & 65 deletions
Original file line numberDiff line numberDiff line change
@@ -489,78 +489,91 @@ fun ArticleFeed(
489489
}
490490
if (feedContent.image != null) {
491491
item {
492-
Text(
493-
stringResource(R.string.picOfTheDay),
494-
style = typography.titleLarge,
495-
modifier = Modifier
496-
.padding(horizontal = 16.dp)
497-
.padding(top = 32.dp)
498-
)
499-
Card(
500-
onClick = onImageClick,
501-
shape = cardShape,
502-
colors = cardColors,
503-
modifier = Modifier
504-
.fillMaxWidth()
505-
.padding(16.dp)
506-
) {
507-
if (!expanded) {
508-
FeedImage(
509-
source = feedContent.image.thumbnail?.source,
510-
description = feedContent.image.description?.text,
511-
width = feedContent.image.thumbnail?.width ?: 1,
512-
height = feedContent.image.thumbnail?.height ?: 1,
513-
imageLoader = imageLoader,
514-
background = imageBackground,
515-
loadingIndicator = false,
516-
modifier = Modifier.clip(cardShape)
517-
)
518-
Text(
519-
feedContent.image.description?.text?.parseAsHtml().toString(),
520-
modifier = Modifier
521-
.padding(horizontal = 16.dp)
522-
.padding(top = 16.dp)
523-
)
524-
Text(
525-
(feedContent.image.artist?.name
526-
?: feedContent.image.artist?.text)?.substringBefore('\n') +
527-
" (" + feedContent.image.credit?.text?.substringBefore(';') + ")",
528-
style = typography.bodyMedium,
529-
modifier = Modifier
530-
.padding(horizontal = 16.dp)
531-
.padding(top = 8.dp, bottom = 16.dp)
532-
)
533-
} else {
534-
Row {
492+
with(sharedScope) {
493+
Text(
494+
stringResource(R.string.picOfTheDay),
495+
style = typography.titleLarge,
496+
modifier = Modifier
497+
.padding(horizontal = 16.dp)
498+
.padding(top = 32.dp)
499+
)
500+
Card(
501+
onClick = onImageClick,
502+
shape = cardShape,
503+
colors = cardColors,
504+
modifier = Modifier
505+
.fillMaxWidth()
506+
.padding(16.dp)
507+
) {
508+
if (!expanded) {
535509
FeedImage(
536-
source = feedContent.image.image?.source,
510+
source = feedContent.image.thumbnail?.source,
537511
description = feedContent.image.description?.text,
538-
width = feedContent.image.image?.width ?: 1,
539-
height = feedContent.image.image?.height ?: 1,
512+
width = feedContent.image.thumbnail?.width ?: 1,
513+
height = feedContent.image.thumbnail?.height ?: 1,
540514
imageLoader = imageLoader,
541515
background = imageBackground,
542516
loadingIndicator = false,
543-
modifier = Modifier.weight(1f)
517+
modifier = Modifier
518+
.sharedBounds(
519+
sharedContentState = rememberSharedContentState(
520+
feedContent.image.thumbnail?.source ?: "imgsrc"
521+
),
522+
animatedVisibilityScope = LocalNavAnimatedContentScope.current
523+
)
524+
.clip(cardShape)
544525
)
545-
Column(Modifier.weight(1f)) {
546-
Text(
547-
feedContent.image.description?.text?.parseAsHtml()
548-
.toString(),
549-
modifier = Modifier
550-
.padding(horizontal = 16.dp)
551-
.padding(top = 16.dp)
552-
)
553-
Text(
554-
(feedContent.image.artist?.name
555-
?: feedContent.image.artist?.text)?.substringBefore('\n') +
556-
" (" + feedContent.image.credit?.text?.substringBefore(
557-
';'
558-
) + ")",
559-
style = typography.bodyMedium,
560-
modifier = Modifier
561-
.padding(horizontal = 16.dp)
562-
.padding(top = 8.dp, bottom = 16.dp)
526+
Text(
527+
feedContent.image.description?.text?.parseAsHtml().toString(),
528+
modifier = Modifier
529+
.padding(horizontal = 16.dp)
530+
.padding(top = 16.dp)
531+
)
532+
Text(
533+
(feedContent.image.artist?.name
534+
?: feedContent.image.artist?.text)?.substringBefore('\n') +
535+
" (" + feedContent.image.credit?.text?.substringBefore(
536+
';'
537+
) + ")",
538+
style = typography.bodyMedium,
539+
modifier = Modifier
540+
.padding(horizontal = 16.dp)
541+
.padding(top = 8.dp, bottom = 16.dp)
542+
)
543+
} else {
544+
Row {
545+
FeedImage(
546+
source = feedContent.image.image?.source,
547+
description = feedContent.image.description?.text,
548+
width = feedContent.image.image?.width ?: 1,
549+
height = feedContent.image.image?.height ?: 1,
550+
imageLoader = imageLoader,
551+
background = imageBackground,
552+
loadingIndicator = false,
553+
modifier = Modifier.weight(1f)
563554
)
555+
Column(Modifier.weight(1f)) {
556+
Text(
557+
feedContent.image.description?.text?.parseAsHtml()
558+
.toString(),
559+
modifier = Modifier
560+
.padding(horizontal = 16.dp)
561+
.padding(top = 16.dp)
562+
)
563+
Text(
564+
(feedContent.image.artist?.name
565+
?: feedContent.image.artist?.text)?.substringBefore(
566+
'\n'
567+
) +
568+
" (" + feedContent.image.credit?.text?.substringBefore(
569+
';'
570+
) + ")",
571+
style = typography.bodyMedium,
572+
modifier = Modifier
573+
.padding(horizontal = 16.dp)
574+
.padding(top = 8.dp, bottom = 16.dp)
575+
)
576+
}
564577
}
565578
}
566579
}

app/src/main/java/org/nsh07/wikireader/ui/homeScreen/ExpandableSection.kt

Lines changed: 26 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package org.nsh07.wikireader.ui.homeScreen
22

33
import androidx.compose.animation.AnimatedVisibility
4+
import androidx.compose.animation.ExperimentalSharedTransitionApi
5+
import androidx.compose.animation.SharedTransitionLayout
6+
import androidx.compose.animation.SharedTransitionScope
47
import androidx.compose.animation.core.animateFloatAsState
58
import androidx.compose.animation.expandVertically
69
import androidx.compose.animation.fadeIn
@@ -41,7 +44,7 @@ import org.nsh07.wikireader.R
4144
import org.nsh07.wikireader.ui.theme.WRShapeDefaults.cardShape
4245
import org.nsh07.wikireader.ui.theme.WikiReaderTheme
4346

44-
@OptIn(ExperimentalMaterial3ExpressiveApi::class)
47+
@OptIn(ExperimentalMaterial3ExpressiveApi::class, ExperimentalSharedTransitionApi::class)
4548
@Composable
4649
fun ExpandableSection(
4750
title: List<AnnotatedString>,
@@ -50,6 +53,7 @@ fun ExpandableSection(
5053
fontSize: Int,
5154
fontFamily: FontFamily,
5255
imageLoader: ImageLoader,
56+
sharedScope: SharedTransitionScope,
5357
expanded: Boolean,
5458
renderMath: Boolean,
5559
darkTheme: Boolean,
@@ -118,6 +122,7 @@ fun ExpandableSection(
118122
ParsedBodyText(
119123
body = body,
120124
lang = lang,
125+
sharedScope = sharedScope,
121126
fontSize = fontSize,
122127
fontFamily = fontFamily,
123128
renderMath = renderMath,
@@ -133,25 +138,29 @@ fun ExpandableSection(
133138
}
134139
}
135140

141+
@OptIn(ExperimentalSharedTransitionApi::class)
136142
@Preview
137143
@Composable
138144
fun ExpandableSectionPreview() {
139145
WikiReaderTheme {
140-
ExpandableSection(
141-
title = listOf(buildAnnotatedString { append("Title") }),
142-
body = listOf(buildAnnotatedString { append("Lorem\nIpsum\nBig\nHonking\nBody\nText") }),
143-
lang = "en",
144-
fontSize = 16,
145-
fontFamily = FontFamily.SansSerif,
146-
imageLoader = ImageLoader(context = LocalContext.current),
147-
expanded = false,
148-
renderMath = true,
149-
darkTheme = false,
150-
false,
151-
false,
152-
onLinkClick = {},
153-
onGalleryImageClick = { _, _ -> },
154-
showRef = {}
155-
)
146+
SharedTransitionLayout {
147+
ExpandableSection(
148+
title = listOf(buildAnnotatedString { append("Title") }),
149+
body = listOf(buildAnnotatedString { append("Lorem\nIpsum\nBig\nHonking\nBody\nText") }),
150+
lang = "en",
151+
fontSize = 16,
152+
fontFamily = FontFamily.SansSerif,
153+
imageLoader = ImageLoader(context = LocalContext.current),
154+
sharedScope = this@SharedTransitionLayout,
155+
expanded = false,
156+
renderMath = true,
157+
darkTheme = false,
158+
dataSaver = false,
159+
imageBackground = false,
160+
onLinkClick = {},
161+
onGalleryImageClick = { _, _ -> },
162+
showRef = {}
163+
)
164+
}
156165
}
157166
}

0 commit comments

Comments
 (0)