From 5799953cee39158396297dc17c336d3231e401b9 Mon Sep 17 00:00:00 2001 From: Suhas Dissanayake Date: Fri, 3 May 2024 12:21:59 +0530 Subject: [PATCH] feat: support for predictive back animation --- app/src/main/AndroidManifest.xml | 1 + .../app/suhasdissa/vibeyou/MainActivity.kt | 2 +- .../vibeyou/navigation/Destination.kt | 2 +- .../suhasdissa/vibeyou/navigation/NavHost.kt | 194 ++++++++++++++++-- .../components/NavDrawerContent.kt | 4 +- .../presentation/screens/home/HomeScreen.kt | 6 +- 6 files changed, 188 insertions(+), 21 deletions(-) diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index e4b49e7..ca08cec 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -25,6 +25,7 @@ android:label="@string/app_name" android:supportsRtl="true" android:theme="@style/Theme.VibeYou" + android:enableOnBackInvokedCallback="true" tools:targetApi="31"> (Destination.PipedMusic) + mutableStateOf(Destination.Home) } ModalNavigationDrawer( drawerState = drawerState, diff --git a/app/src/main/java/app/suhasdissa/vibeyou/navigation/Destination.kt b/app/src/main/java/app/suhasdissa/vibeyou/navigation/Destination.kt index ee51116..e5ffa33 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/navigation/Destination.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/navigation/Destination.kt @@ -7,7 +7,7 @@ import kotlinx.serialization.Serializable @Serializable sealed class Destination { @Serializable - object PipedMusic : Destination() + object Home : Destination() @Serializable object LocalMusic : Destination() diff --git a/app/src/main/java/app/suhasdissa/vibeyou/navigation/NavHost.kt b/app/src/main/java/app/suhasdissa/vibeyou/navigation/NavHost.kt index 078ae95..77b5df5 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/navigation/NavHost.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/navigation/NavHost.kt @@ -1,9 +1,15 @@ package app.suhasdissa.vibeyou.navigation +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.scaleIn +import androidx.compose.animation.scaleOut import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner import androidx.lifecycle.viewmodel.compose.viewModel +import androidx.navigation.NavDestination.Companion.hasRoute import androidx.navigation.NavHostController import androidx.navigation.compose.NavHost import androidx.navigation.compose.composable @@ -36,9 +42,22 @@ fun AppNavHost(navHostController: NavHostController) { val settingsModel: SettingsModel = viewModel(factory = SettingsModel.Factory) NavHost( navController = navHostController, - startDestination = Destination.PipedMusic + startDestination = Destination.Home ) { - composable { + composable( + enterTransition = { + scaleIn(initialScale = 3f / 4) + fadeIn() + }, + exitTransition = { + if (listOf(Destination.OnlineSearch::class, Destination.LocalSearch::class) + .any { targetState.destination.hasRoute(it) } + ) { + fadeOut() + } else { + scaleOut(targetScale = 0f) + fadeOut() + } + } + ) { CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { HomeScreen(onNavigate = { destination -> navHostController.navigate(destination) @@ -46,7 +65,16 @@ fun AppNavHost(navHostController: NavHostController) { } } - composable { + composable( + enterTransition = { + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it }) + fadeOut() + } + ) { val pipedSearchViewModel: PipedSearchViewModel = viewModel(factory = PipedSearchViewModel.Factory) CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { @@ -55,7 +83,16 @@ fun AppNavHost(navHostController: NavHostController) { }, pipedSearchViewModel) } } - composable { + composable( + enterTransition = { + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it }) + fadeOut() + } + ) { val localSearchViewModel: LocalSearchViewModel = viewModel(factory = LocalSearchViewModel.Factory) CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { @@ -65,30 +102,119 @@ fun AppNavHost(navHostController: NavHostController) { } } - composable { + composable( + enterTransition = { + if (listOf( + Destination.About::class, + Destination.NetworkSettings::class, + Destination.DatabaseSettings::class, + Destination.AppearanceSettings::class + ) + .any { initialState.destination.hasRoute(it) } + ) { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + initialOffset = { it }) + fadeIn() + } else { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + } + }, + exitTransition = { + if (listOf( + Destination.About::class, + Destination.NetworkSettings::class, + Destination.DatabaseSettings::class, + Destination.AppearanceSettings::class + ) + .any { targetState.destination.hasRoute(it) } + ) { + slideOutOfContainer(AnimatedContentTransitionScope.SlideDirection.Left, + targetOffset = { it / 4 }) + fadeOut() + } else { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } + }) { SettingsScreen(onNavigate = { route -> navHostController.navigate(route) }) } - composable { + composable( + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + initialOffset = { it }) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + targetOffset = { it }) + fadeOut() + } + ) { AboutScreen() } - composable { + composable( + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + initialOffset = { it }) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + targetOffset = { it }) + fadeOut() + } + ) { NetworkSettingsScreen() } - composable { + composable( + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + initialOffset = { it }) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + targetOffset = { it }) + fadeOut() + } + ) { DatabaseSettingsScreen() } - composable { + composable( + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + initialOffset = { it }) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + targetOffset = { it }) + fadeOut() + } + ) { AppearanceSettingsScreen(settingsModel) } composable( - typeMap = mapOf(typeOf() to AlbumType) + typeMap = mapOf(typeOf() to AlbumType), + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } ) { val onlinePlaylistViewModel: OnlinePlaylistViewModel = viewModel(factory = OnlinePlaylistViewModel.Factory) @@ -98,7 +224,17 @@ fun AppNavHost(navHostController: NavHostController) { } composable( - typeMap = mapOf(typeOf() to AlbumType) + typeMap = mapOf(typeOf() to AlbumType), + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } ) { val localPlaylistViewModel: LocalPlaylistViewModel = viewModel(factory = LocalPlaylistViewModel.Factory) @@ -108,7 +244,17 @@ fun AppNavHost(navHostController: NavHostController) { } composable( - typeMap = mapOf(typeOf() to AlbumType) + typeMap = mapOf(typeOf() to AlbumType), + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } ) { val playlistInfoViewModel: PlaylistInfoViewModel = viewModel(factory = PlaylistInfoViewModel.Factory) @@ -118,7 +264,17 @@ fun AppNavHost(navHostController: NavHostController) { } composable( - typeMap = mapOf(typeOf() to ArtistType) + typeMap = mapOf(typeOf() to ArtistType), + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } ) { val onlineArtistViewModel: OnlineArtistViewModel = viewModel(factory = OnlineArtistViewModel.Factory) @@ -130,7 +286,17 @@ fun AppNavHost(navHostController: NavHostController) { } composable( - typeMap = mapOf(typeOf() to ArtistType) + typeMap = mapOf(typeOf() to ArtistType), + enterTransition = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Up, + initialOffset = { it / 4 }) + scaleIn(initialScale = 3f / 4) + fadeIn() + }, + exitTransition = { + slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Down, + targetOffset = { it / 4 }) + scaleOut(targetScale = 3f / 4) + fadeOut() + } ) { val localArtistViewModel: LocalArtistViewModel = viewModel(factory = LocalArtistViewModel.Factory) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/NavDrawerContent.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/NavDrawerContent.kt index 12651ae..cbe8d73 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/NavDrawerContent.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/components/NavDrawerContent.kt @@ -73,10 +73,10 @@ fun NavDrawerContent( ) }, label = { Text(text = stringResource(id = R.string.piped_music)) }, - selected = currentDestination == Destination.PipedMusic, + selected = currentDestination == Destination.Home, onClick = { view.playSoundEffect(SoundEffectConstants.CLICK) - onDestinationSelected(Destination.PipedMusic) + onDestinationSelected(Destination.Home) } ) Spacer(Modifier.height(16.dp)) diff --git a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/home/HomeScreen.kt b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/home/HomeScreen.kt index 2eba577..e7a4cea 100644 --- a/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/home/HomeScreen.kt +++ b/app/src/main/java/app/suhasdissa/vibeyou/presentation/screens/home/HomeScreen.kt @@ -104,7 +104,7 @@ fun HomeScreen( .padding(end = 8.dp) .clickable { view.playSoundEffect(SoundEffectConstants.CLICK) - if (currentDestination == Destination.PipedMusic) { + if (currentDestination == Destination.Home) { onNavigate(Destination.OnlineSearch) } else { onNavigate(Destination.LocalSearch) @@ -144,11 +144,11 @@ fun HomeScreen( enterTransition = { EnterTransition.None }, exitTransition = { ExitTransition.None } ) { - composable { + composable { CompositionLocalProvider(LocalViewModelStoreOwner provides viewModelStoreOwner) { MusicScreen(onNavigate) LaunchedEffect(Unit) { - currentDestination = Destination.PipedMusic + currentDestination = Destination.Home } } }