mirror of
https://github.com/bbara04/Purefin.git
synced 2026-07-23 19:26:50 +00:00
feat(app): implement shared transition for search functionality in HomeTopBar and HomeSearchFullScreen
This commit is contained in:
@@ -6,6 +6,8 @@ import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.SharedTransitionLayout
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.animation.togetherWith
|
||||
@@ -41,6 +43,7 @@ import hu.bbara.purefin.jellyfin.JellyfinAuthInterceptor
|
||||
import hu.bbara.purefin.data.UserSessionRepository
|
||||
import hu.bbara.purefin.navigation.LocalNavigationBackStack
|
||||
import hu.bbara.purefin.navigation.LocalNavigationManager
|
||||
import hu.bbara.purefin.navigation.LocalSharedTransitionScope
|
||||
import hu.bbara.purefin.navigation.NavigationCommand
|
||||
import hu.bbara.purefin.navigation.NavigationManager
|
||||
import hu.bbara.purefin.navigation.Route
|
||||
@@ -131,6 +134,7 @@ class PurefinActivity : ComponentActivity() {
|
||||
return (applicationInfo.flags and ApplicationInfo.FLAG_DEBUGGABLE) != 0
|
||||
}
|
||||
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
fun MainApp(
|
||||
userSessionRepository: UserSessionRepository,
|
||||
@@ -172,45 +176,48 @@ class PurefinActivity : ComponentActivity() {
|
||||
}
|
||||
}
|
||||
|
||||
CompositionLocalProvider(
|
||||
LocalNavigationManager provides navigationManager,
|
||||
LocalNavigationBackStack provides backStack.toList()
|
||||
) {
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
onBack = { navigationManager.pop() },
|
||||
modifier = Modifier.fillMaxSize().background(backgroundDark),
|
||||
transitionSpec = {
|
||||
fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = 140
|
||||
)
|
||||
) togetherWith
|
||||
fadeOut(animationSpec = tween(durationMillis = 140))
|
||||
},
|
||||
popTransitionSpec = {
|
||||
fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = 140
|
||||
)
|
||||
) togetherWith
|
||||
fadeOut(animationSpec = tween(durationMillis = 140))
|
||||
},
|
||||
predictivePopTransitionSpec = { _ ->
|
||||
fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = 140
|
||||
)
|
||||
) togetherWith
|
||||
fadeOut(animationSpec = tween(durationMillis = 140))
|
||||
},
|
||||
entryDecorators =
|
||||
listOf(
|
||||
rememberSaveableStateHolderNavEntryDecorator(),
|
||||
rememberViewModelStoreNavEntryDecorator(),
|
||||
),
|
||||
entryProvider = appEntryProvider
|
||||
)
|
||||
SharedTransitionLayout {
|
||||
CompositionLocalProvider(
|
||||
LocalNavigationManager provides navigationManager,
|
||||
LocalNavigationBackStack provides backStack.toList(),
|
||||
LocalSharedTransitionScope provides this
|
||||
) {
|
||||
NavDisplay(
|
||||
backStack = backStack,
|
||||
onBack = { navigationManager.pop() },
|
||||
modifier = Modifier.fillMaxSize().background(backgroundDark),
|
||||
transitionSpec = {
|
||||
fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = 140
|
||||
)
|
||||
) togetherWith
|
||||
fadeOut(animationSpec = tween(durationMillis = 140))
|
||||
},
|
||||
popTransitionSpec = {
|
||||
fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = 140
|
||||
)
|
||||
) togetherWith
|
||||
fadeOut(animationSpec = tween(durationMillis = 140))
|
||||
},
|
||||
predictivePopTransitionSpec = { _ ->
|
||||
fadeIn(
|
||||
animationSpec = tween(
|
||||
durationMillis = 140
|
||||
)
|
||||
) togetherWith
|
||||
fadeOut(animationSpec = tween(durationMillis = 140))
|
||||
},
|
||||
entryDecorators =
|
||||
listOf(
|
||||
rememberSaveableStateHolderNavEntryDecorator(),
|
||||
rememberViewModelStoreNavEntryDecorator(),
|
||||
),
|
||||
entryProvider = appEntryProvider
|
||||
)
|
||||
}
|
||||
}
|
||||
} else {
|
||||
LoginScreen()
|
||||
|
||||
@@ -1,5 +1,8 @@
|
||||
package hu.bbara.purefin.navigation
|
||||
|
||||
import androidx.compose.animation.AnimatedVisibilityScope
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.runtime.ProvidableCompositionLocal
|
||||
import androidx.compose.runtime.compositionLocalOf
|
||||
import androidx.compose.runtime.staticCompositionLocalOf
|
||||
@@ -8,3 +11,11 @@ val LocalNavigationManager: ProvidableCompositionLocal<NavigationManager> =
|
||||
staticCompositionLocalOf { error("NavigationManager not provided") }
|
||||
|
||||
val LocalNavigationBackStack = compositionLocalOf<List<Route>> { emptyList() }
|
||||
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
val LocalSharedTransitionScope = compositionLocalOf<SharedTransitionScope?> { null }
|
||||
|
||||
val LocalNavSharedAnimatedVisibilityScope =
|
||||
compositionLocalOf<AnimatedVisibilityScope?> { null }
|
||||
|
||||
const val HOME_SEARCH_SHARED_BOUNDS_KEY = "home_search_shared_bounds"
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
package hu.bbara.purefin.navigation
|
||||
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.navigation3.runtime.EntryProviderScope
|
||||
import androidx.navigation3.ui.LocalNavAnimatedContentScope
|
||||
import hu.bbara.purefin.ui.screen.library.LibraryScreen
|
||||
import hu.bbara.purefin.ui.screen.login.LoginScreen
|
||||
import hu.bbara.purefin.ui.screen.AppScreen
|
||||
@@ -11,10 +13,18 @@ import hu.bbara.purefin.ui.screen.series.SeriesScreen
|
||||
|
||||
fun EntryProviderScope<Route>.appRouteEntryBuilder() {
|
||||
entry<Route.Home> {
|
||||
AppScreen()
|
||||
CompositionLocalProvider(
|
||||
LocalNavSharedAnimatedVisibilityScope provides LocalNavAnimatedContentScope.current
|
||||
) {
|
||||
AppScreen()
|
||||
}
|
||||
}
|
||||
entry<Route.HomeSearchRoute> {
|
||||
HomeSearchFullScreen()
|
||||
CompositionLocalProvider(
|
||||
LocalNavSharedAnimatedVisibilityScope provides LocalNavAnimatedContentScope.current
|
||||
) {
|
||||
HomeSearchFullScreen()
|
||||
}
|
||||
}
|
||||
entry<Route.MovieRoute> {
|
||||
MovieScreen(movie = it.item)
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package hu.bbara.purefin.ui.screen.home.components
|
||||
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.layout.Spacer
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
@@ -21,7 +25,11 @@ import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.clip
|
||||
import androidx.compose.ui.unit.dp
|
||||
import hu.bbara.purefin.navigation.HOME_SEARCH_SHARED_BOUNDS_KEY
|
||||
import hu.bbara.purefin.navigation.LocalNavSharedAnimatedVisibilityScope
|
||||
import hu.bbara.purefin.navigation.LocalSharedTransitionScope
|
||||
|
||||
@OptIn(ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
fun HomeTopBar(
|
||||
onSearchClick: () -> Unit,
|
||||
@@ -30,11 +38,34 @@ fun HomeTopBar(
|
||||
onLogoutClick: () -> Unit,
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val sharedTransitionScope = LocalSharedTransitionScope.current
|
||||
val animatedVisibilityScope = LocalNavSharedAnimatedVisibilityScope.current
|
||||
|
||||
var isProfileMenuExpanded by remember { mutableStateOf(false) }
|
||||
|
||||
DefaultTopBar()
|
||||
{
|
||||
val searchButtonModifier = if (
|
||||
sharedTransitionScope != null &&
|
||||
animatedVisibilityScope != null
|
||||
) {
|
||||
with(sharedTransitionScope) {
|
||||
Modifier
|
||||
.sharedBounds(
|
||||
sharedContentState = rememberSharedContentState(
|
||||
key = HOME_SEARCH_SHARED_BOUNDS_KEY
|
||||
),
|
||||
animatedVisibilityScope = animatedVisibilityScope,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds()
|
||||
)
|
||||
.size(50.dp)
|
||||
}
|
||||
} else {
|
||||
Modifier.size(50.dp)
|
||||
}
|
||||
|
||||
IconButton(
|
||||
onClick = onSearchClick,
|
||||
colors = IconButtonColors(
|
||||
@@ -43,7 +74,7 @@ fun HomeTopBar(
|
||||
disabledContainerColor = scheme.surface,
|
||||
disabledContentColor = scheme.onSurface
|
||||
),
|
||||
modifier = Modifier.size(50.dp),
|
||||
modifier = searchButtonModifier,
|
||||
) {
|
||||
Icon(
|
||||
imageVector = Icons.Outlined.Search,
|
||||
@@ -97,4 +128,4 @@ fun HomeTopBar(
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,5 +1,9 @@
|
||||
package hu.bbara.purefin.ui.screen.home.components.search
|
||||
|
||||
import androidx.compose.animation.ExperimentalSharedTransitionApi
|
||||
import androidx.compose.animation.SharedTransitionScope
|
||||
import androidx.compose.animation.fadeIn
|
||||
import androidx.compose.animation.fadeOut
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
@@ -51,17 +55,41 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
import hu.bbara.purefin.ui.common.image.PurefinAsyncImage
|
||||
import hu.bbara.purefin.ui.common.image.PurefinLogo
|
||||
import hu.bbara.purefin.navigation.HOME_SEARCH_SHARED_BOUNDS_KEY
|
||||
import hu.bbara.purefin.navigation.LocalNavSharedAnimatedVisibilityScope
|
||||
import hu.bbara.purefin.navigation.LocalSharedTransitionScope
|
||||
import hu.bbara.purefin.ui.theme.AppTheme
|
||||
|
||||
@OptIn(ExperimentalLayoutApi::class)
|
||||
@OptIn(ExperimentalLayoutApi::class, ExperimentalSharedTransitionApi::class)
|
||||
@Composable
|
||||
fun HomeSearchFullScreen(
|
||||
modifier: Modifier = Modifier
|
||||
) {
|
||||
val scheme = MaterialTheme.colorScheme
|
||||
val sharedTransitionScope = LocalSharedTransitionScope.current
|
||||
val animatedVisibilityScope = LocalNavSharedAnimatedVisibilityScope.current
|
||||
val sharedBoundsModifier = if (
|
||||
sharedTransitionScope != null &&
|
||||
animatedVisibilityScope != null
|
||||
) {
|
||||
with(sharedTransitionScope) {
|
||||
Modifier.sharedBounds(
|
||||
sharedContentState = rememberSharedContentState(
|
||||
key = HOME_SEARCH_SHARED_BOUNDS_KEY
|
||||
),
|
||||
animatedVisibilityScope = animatedVisibilityScope,
|
||||
enter = fadeIn(),
|
||||
exit = fadeOut(),
|
||||
resizeMode = SharedTransitionScope.ResizeMode.scaleToBounds()
|
||||
)
|
||||
}
|
||||
} else {
|
||||
Modifier
|
||||
}
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.then(sharedBoundsModifier)
|
||||
.fillMaxSize()
|
||||
.background(scheme.background)
|
||||
.statusBarsPadding()
|
||||
|
||||
Reference in New Issue
Block a user