diff --git a/app/src/main/java/com/javernaut/whatthecodec/compose/navigation/MaterialNavHost.kt b/app/src/main/java/com/javernaut/whatthecodec/compose/navigation/MaterialNavHost.kt new file mode 100644 index 00000000..0b63e16a --- /dev/null +++ b/app/src/main/java/com/javernaut/whatthecodec/compose/navigation/MaterialNavHost.kt @@ -0,0 +1,40 @@ +package com.javernaut.whatthecodec.compose.navigation + +import androidx.compose.runtime.Composable +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.navigation.NavGraphBuilder +import androidx.navigation.NavHostController +import androidx.navigation.compose.NavHost + +@Composable +fun MaterialNavHost( + navController: NavHostController, + startDestination: String, + modifier: Modifier = Modifier, + contentAlignment: Alignment = Alignment.Center, + route: String? = null, + builder: NavGraphBuilder.() -> Unit +) { + val defaultSlideDistance = rememberSlideDistance() + NavHost( + navController = navController, + startDestination = startDestination, + modifier = modifier, + contentAlignment = contentAlignment, + route = route, + enterTransition = { + materialSharedAxisXIn(true, defaultSlideDistance) + }, + exitTransition = { + materialSharedAxisXOut(true, defaultSlideDistance) + }, + popEnterTransition = { + materialSharedAxisXIn(false, defaultSlideDistance) + }, + popExitTransition = { + materialSharedAxisXOut(false, defaultSlideDistance) + }, + builder = builder + ) +} diff --git a/app/src/main/java/com/javernaut/whatthecodec/compose/navigation/MaterialSharedAxis.kt b/app/src/main/java/com/javernaut/whatthecodec/compose/navigation/MaterialSharedAxis.kt new file mode 100644 index 00000000..46133ee4 --- /dev/null +++ b/app/src/main/java/com/javernaut/whatthecodec/compose/navigation/MaterialSharedAxis.kt @@ -0,0 +1,103 @@ +package com.javernaut.whatthecodec.compose.navigation + +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition +import androidx.compose.animation.core.FastOutLinearInEasing +import androidx.compose.animation.core.FastOutSlowInEasing +import androidx.compose.animation.core.LinearOutSlowInEasing +import androidx.compose.animation.core.tween +import androidx.compose.animation.fadeIn +import androidx.compose.animation.fadeOut +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally +import androidx.compose.runtime.Composable +import androidx.compose.runtime.remember +import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.dp + +// Original implementation was taken from here +// https://github.com/fornewid/material-motion-compose/blob/main/core/src/main/java/soup/compose/material/motion/animation/MaterialSharedAxis.kt + +private object MotionConstants { + const val DefaultMotionDuration: Int = 300 + val DefaultSlideDistance: Dp = 30.dp +} + + +/** + * Returns the provided [Dp] as an [Int] value by the [LocalDensity]. + * + * @param slideDistance Value to the slide distance dimension, 30dp by default. + */ +@Composable +fun rememberSlideDistance( + slideDistance: Dp = MotionConstants.DefaultSlideDistance, +): Int { + val density = LocalDensity.current + return remember(density, slideDistance) { + with(density) { slideDistance.roundToPx() } + } +} + +private const val ProgressThreshold = 0.35f + +private val Int.ForOutgoing: Int + get() = (this * ProgressThreshold).toInt() + +private val Int.ForIncoming: Int + get() = this - this.ForOutgoing + +/** + * [materialSharedAxisXIn] allows to switch a layout with shared X-axis enter transition. + * + * @param forward whether the direction of the animation is forward. + * @param slideDistance the slide distance of the enter transition. + * @param durationMillis the duration of the enter transition. + */ +fun materialSharedAxisXIn( + forward: Boolean, + slideDistance: Int, + durationMillis: Int = MotionConstants.DefaultMotionDuration, +): EnterTransition = slideInHorizontally( + animationSpec = tween( + durationMillis = durationMillis, + easing = FastOutSlowInEasing + ), + initialOffsetX = { + if (forward) slideDistance else -slideDistance + } +) + fadeIn( + animationSpec = tween( + durationMillis = durationMillis.ForIncoming, + delayMillis = durationMillis.ForOutgoing, + easing = LinearOutSlowInEasing + ) +) + +/** + * [materialSharedAxisXOut] allows to switch a layout with shared X-axis exit transition. + * + * @param forward whether the direction of the animation is forward. + * @param slideDistance the slide distance of the exit transition. + * @param durationMillis the duration of the exit transition. + */ +fun materialSharedAxisXOut( + forward: Boolean, + slideDistance: Int, + durationMillis: Int = MotionConstants.DefaultMotionDuration, +): ExitTransition = slideOutHorizontally( + animationSpec = tween( + durationMillis = durationMillis, + easing = FastOutSlowInEasing + ), + targetOffsetX = { + if (forward) -slideDistance else slideDistance + } +) + fadeOut( + animationSpec = tween( + durationMillis = durationMillis.ForOutgoing, + delayMillis = 0, + easing = FastOutLinearInEasing + ) +) diff --git a/app/src/main/java/com/javernaut/whatthecodec/home/ui/WhatTheCodecApp.kt b/app/src/main/java/com/javernaut/whatthecodec/home/ui/WhatTheCodecApp.kt index 79d8e38c..b8556fb3 100644 --- a/app/src/main/java/com/javernaut/whatthecodec/home/ui/WhatTheCodecApp.kt +++ b/app/src/main/java/com/javernaut/whatthecodec/home/ui/WhatTheCodecApp.kt @@ -4,8 +4,8 @@ import androidx.compose.foundation.background import androidx.compose.material3.MaterialTheme import androidx.compose.runtime.Composable import androidx.compose.ui.Modifier -import androidx.navigation.compose.NavHost import androidx.navigation.compose.rememberNavController +import com.javernaut.whatthecodec.compose.navigation.MaterialNavHost import com.javernaut.whatthecodec.home.ui.navigation.HomeRoute import com.javernaut.whatthecodec.home.ui.navigation.homeScreen import com.javernaut.whatthecodec.settings.navigation.navigateToSettings @@ -14,10 +14,10 @@ import com.javernaut.whatthecodec.settings.navigation.settingsScreen @Composable fun WhatTheCodecApp() { val navController = rememberNavController() - NavHost( + MaterialNavHost( navController = navController, startDestination = HomeRoute, - modifier = Modifier.background(MaterialTheme.colorScheme.background) + modifier = Modifier.background(MaterialTheme.colorScheme.background), ) { homeScreen( onSettingsClicked = navController::navigateToSettings