Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 9 additions & 1 deletion Jetsnack/app/build.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ plugins {
alias(libs.plugins.android.application)
alias(libs.plugins.kotlin.parcelize)
alias(libs.plugins.compose)
alias(libs.plugins.kotlin.serialization)
}

android {
Expand Down Expand Up @@ -118,8 +119,15 @@ dependencies {
implementation(libs.androidx.lifecycle.viewmodel.ktx)
implementation(libs.androidx.lifecycle.viewModelCompose)
implementation(libs.androidx.lifecycle.runtime.compose)
implementation(libs.androidx.navigation.compose)
implementation(libs.androidx.constraintlayout.compose)
implementation(libs.androidx.navigation3.ui)
implementation(libs.androidx.navigation3.runtime)
implementation(libs.androidx.lifecycle.viewmodel.navigation3)
implementation(libs.kotlinx.serialization.json)
implementation(libs.androidx.startup)
implementation(libs.androidx.work.runtime.ktx)
implementation(libs.androidx.room.runtime)
implementation(libs.androidx.room.ktx)

implementation(libs.androidx.compose.runtime)
implementation(libs.androidx.compose.foundation)
Expand Down
5 changes: 4 additions & 1 deletion Jetsnack/app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -34,4 +34,7 @@
-dontwarn org.openjsse.javax.net.ssl.SSLSocket
-dontwarn org.openjsse.net.ssl.OpenJSSE

-keep class androidx.compose.ui.platform.AndroidCompositionLocals_androidKt { *; }
-keep class androidx.compose.ui.platform.AndroidCompositionLocals_androidKt { *; }

-keep class androidx.work.** { *; }
-keep class androidx.work.impl.** { *; }
201 changes: 99 additions & 102 deletions Jetsnack/app/src/main/java/com/example/jetsnack/ui/JetsnackApp.kt
Original file line number Diff line number Diff line change
Expand Up @@ -20,38 +20,44 @@

package com.example.jetsnack.ui

import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.AnimatedVisibility
import androidx.compose.animation.ExperimentalSharedTransitionApi
import androidx.compose.animation.SharedTransitionLayout
import androidx.compose.animation.SharedTransitionScope
import androidx.compose.animation.fadeIn
import androidx.compose.animation.fadeOut
import androidx.compose.animation.slideInVertically
import androidx.compose.animation.slideOutVertically
import androidx.compose.animation.togetherWith
import androidx.compose.foundation.layout.consumeWindowInsets
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.systemBarsPadding
import androidx.compose.material3.SnackbarHost
import androidx.compose.runtime.Composable
import androidx.compose.runtime.CompositionLocalProvider
import androidx.compose.runtime.compositionLocalOf
import androidx.compose.runtime.getValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.tooling.preview.Preview
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavType
import androidx.navigation.compose.NavHost
import androidx.navigation.compose.currentBackStackEntryAsState
import androidx.navigation.navArgument
import androidx.navigation3.runtime.entryProvider
import androidx.navigation3.runtime.rememberNavBackStack
import androidx.navigation3.ui.NavDisplay
import com.example.jetsnack.ui.components.JetsnackScaffold
import com.example.jetsnack.ui.components.JetsnackSnackbar
import com.example.jetsnack.ui.components.rememberJetsnackScaffoldState
import com.example.jetsnack.ui.home.Feed
import com.example.jetsnack.ui.home.HomeSections
import com.example.jetsnack.ui.home.JetsnackBottomBar
import com.example.jetsnack.ui.home.addHomeGraph
import com.example.jetsnack.ui.home.composableWithCompositionLocal
import com.example.jetsnack.ui.navigation.MainDestinations
import com.example.jetsnack.ui.navigation.rememberJetsnackNavController
import com.example.jetsnack.ui.home.Profile
import com.example.jetsnack.ui.home.cart.Cart
import com.example.jetsnack.ui.home.search.Search
import com.example.jetsnack.ui.navigation.CartKey
import com.example.jetsnack.ui.navigation.FeedKey
import com.example.jetsnack.ui.navigation.ProfileKey
import com.example.jetsnack.ui.navigation.SearchKey
import com.example.jetsnack.ui.navigation.SnackDetailKey
import com.example.jetsnack.ui.navigation.addHomeSection
import com.example.jetsnack.ui.navigation.addSnackDetail
import com.example.jetsnack.ui.navigation.currentHomeSectionKey
import com.example.jetsnack.ui.snackdetail.SnackDetail
import com.example.jetsnack.ui.snackdetail.nonSpatialExpressiveSpring
import com.example.jetsnack.ui.snackdetail.spatialExpressiveSpring
Expand All @@ -61,110 +67,101 @@ import com.example.jetsnack.ui.theme.JetsnackTheme
@Composable
fun JetsnackApp() {
JetsnackTheme {
val jetsnackNavController = rememberJetsnackNavController()

val backStack = rememberNavBackStack(FeedKey)
val jetsnackScaffoldState = rememberJetsnackScaffoldState()

SharedTransitionLayout {
CompositionLocalProvider(
LocalSharedTransitionScope provides this,
) {
NavHost(
navController = jetsnackNavController.navController,
startDestination = MainDestinations.HOME_ROUTE,
) {
composableWithCompositionLocal(
route = MainDestinations.HOME_ROUTE,
) { backStackEntry ->
MainContainer(
onSnackSelected = jetsnackNavController::navigateToSnackDetail,
)
}
JetsnackScaffold(
bottomBar = {
val showBottomBar = backStack.last() !is SnackDetailKey

composableWithCompositionLocal(
"${MainDestinations.SNACK_DETAIL_ROUTE}/" +
"{${MainDestinations.SNACK_ID_KEY}}" +
"?origin={${MainDestinations.ORIGIN}}",
arguments = listOf(
navArgument(MainDestinations.SNACK_ID_KEY) {
type = NavType.LongType
AnimatedVisibility(
visible = showBottomBar,
enter = fadeIn(nonSpatialExpressiveSpring()) + slideInVertically(
spatialExpressiveSpring(),
) {
it
},
),

) { backStackEntry ->
val arguments = requireNotNull(backStackEntry.arguments)
val snackId = arguments.getLong(MainDestinations.SNACK_ID_KEY)
val origin = arguments.getString(MainDestinations.ORIGIN)
SnackDetail(
snackId,
origin = origin ?: "",
upPress = jetsnackNavController::upPress,
exit = fadeOut(nonSpatialExpressiveSpring()) + slideOutVertically(
spatialExpressiveSpring(),
) {
it
},
) {
JetsnackBottomBar(
tabs = HomeSections.entries.toTypedArray(),
currentKey = backStack.currentHomeSectionKey(),
onItemClick = { navKey -> backStack.addHomeSection(navKey) },
modifier = Modifier
.renderInSharedTransitionScopeOverlay(
zIndexInOverlay = 1f,
),
)
}
},
snackbarHost = {
SnackbarHost(
hostState = it,
modifier = Modifier.systemBarsPadding(),
snackbar = { snackbarData -> JetsnackSnackbar(snackbarData) },
)
}
}
}
}
}
}
},
snackBarHostState = jetsnackScaffoldState.snackBarHostState,
) { padding ->

@Composable
fun MainContainer(modifier: Modifier = Modifier, onSnackSelected: (Long, String, NavBackStackEntry) -> Unit) {
val jetsnackScaffoldState = rememberJetsnackScaffoldState()
val nestedNavController = rememberJetsnackNavController()
val navBackStackEntry by nestedNavController.navController.currentBackStackEntryAsState()
val currentRoute = navBackStackEntry?.destination?.route
val sharedTransitionScope = LocalSharedTransitionScope.current
?: throw IllegalStateException("No SharedElementScope found")
val animatedVisibilityScope = LocalNavAnimatedVisibilityScope.current
?: throw IllegalStateException("No SharedElementScope found")
JetsnackScaffold(
bottomBar = {
with(animatedVisibilityScope) {
with(sharedTransitionScope) {
JetsnackBottomBar(
tabs = HomeSections.entries.toTypedArray(),
currentRoute = currentRoute ?: HomeSections.FEED.route,
navigateToRoute = nestedNavController::navigateToBottomBarRoute,
modifier = Modifier
.renderInSharedTransitionScopeOverlay(
zIndexInOverlay = 1f,
)
.animateEnterExit(
enter = fadeIn(nonSpatialExpressiveSpring()) + slideInVertically(
spatialExpressiveSpring(),
) {
it
},
exit = fadeOut(nonSpatialExpressiveSpring()) + slideOutVertically(
spatialExpressiveSpring(),
) {
it
},
),
val modifier = Modifier
.padding(padding)
.consumeWindowInsets(padding)

val transitionSpec = fadeIn(nonSpatialExpressiveSpring()) togetherWith
fadeOut(nonSpatialExpressiveSpring())

NavDisplay(
backStack = backStack,
onBack = { backStack.removeLastOrNull() },
sharedTransitionScope = this@SharedTransitionLayout,
entryProvider = entryProvider {
entry<FeedKey> {
Feed(
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Within the Feed view, we should add a check on the TopAppBar to only slide in / slide out when its going towards snackdetail and coming back from snack detail, otherwise it now performs the animation between every screen transition, due to the new single instance of a navigation scope.

onSnackClick = backStack.addSnackDetail(),
modifier = modifier,
)
}
entry<CartKey> {
Cart(
onSnackClick = backStack.addSnackDetail(),
modifier = modifier,
)
}
entry<SearchKey> {
Search(
onSnackClick = backStack.addSnackDetail(),
modifier = modifier,
)
}
entry<ProfileKey> {
Profile(modifier)
}
entry<SnackDetailKey> { key ->
SnackDetail(
key.snackId,
origin = key.origin,
upPress = { backStack.removeLastOrNull() },
)
}
},
transitionSpec = { transitionSpec },
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why, but comparing between develop + this branch, the transition between the Feed and Cart composables looks a bit quicker, and doesn't seem like its performing a fadeIn or fadeOut. Doesn't look bad, I'm just wondering if there was a change missed here.
Not blocking on this one :)

popTransitionSpec = { transitionSpec },
predictivePopTransitionSpec = { transitionSpec },
)
}
}
},
modifier = modifier,
snackbarHost = {
SnackbarHost(
hostState = it,
modifier = Modifier.systemBarsPadding(),
snackbar = { snackbarData -> JetsnackSnackbar(snackbarData) },
)
},
snackBarHostState = jetsnackScaffoldState.snackBarHostState,
) { padding ->
NavHost(
navController = nestedNavController.navController,
startDestination = HomeSections.FEED.route,
) {
addHomeGraph(
onSnackSelected = onSnackSelected,
modifier = Modifier
.padding(padding)
.consumeWindowInsets(padding),
)
}
}
}

val LocalNavAnimatedVisibilityScope = compositionLocalOf<AnimatedVisibilityScope?> { null }
val LocalSharedTransitionScope = compositionLocalOf<SharedTransitionScope?> { null }
Loading
Loading