From 176d18492ba224dc6d415330e83135591202d4ec Mon Sep 17 00:00:00 2001 From: Marco Gomiero Date: Fri, 12 Dec 2025 10:36:59 +0100 Subject: [PATCH 1/4] feat: add navigation animations --- .../moneyflow/navigation/MoneyFlowNavHost.kt | 39 +++++++++++++++++++ 1 file changed, 39 insertions(+) diff --git a/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt b/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt index 301b50e4..7b2e14db 100644 --- a/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt +++ b/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt @@ -1,5 +1,10 @@ package com.prof18.moneyflow.navigation +import androidx.compose.animation.AnimatedContentTransitionScope +import androidx.compose.animation.core.tween +import androidx.compose.animation.slideIntoContainer +import androidx.compose.animation.slideOutOfContainer +import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets import androidx.compose.foundation.layout.WindowInsetsSides @@ -30,6 +35,7 @@ import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.ui.NavDisplay +import androidx.navigationevent.NavigationEvent import androidx.savedstate.compose.serialization.serializers.SnapshotStateListSerializer import com.prof18.moneyflow.features.addtransaction.AddTransactionViewModel import com.prof18.moneyflow.features.alltransactions.AllTransactionsViewModel @@ -61,6 +67,8 @@ import org.jetbrains.compose.resources.stringResource import org.koin.compose.koinInject import org.koin.compose.viewmodel.koinViewModel +private const val DEFAULT_ANIMATION_DURATION_MILLIS = 300 + @Composable fun MoneyFlowNavHost(modifier: Modifier = Modifier) { val backStack = rememberSerializable(serializer = SnapshotStateListSerializer(AppRoute.serializer())) { @@ -98,6 +106,37 @@ fun MoneyFlowNavHost(modifier: Modifier = Modifier) { rememberSaveableStateHolderNavEntryDecorator(), rememberViewModelStoreNavEntryDecorator(), ), + transitionSpec = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + tween(DEFAULT_ANIMATION_DURATION_MILLIS), + ) togetherWith slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Left, + tween(DEFAULT_ANIMATION_DURATION_MILLIS), + ) + }, + popTransitionSpec = { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + tween(DEFAULT_ANIMATION_DURATION_MILLIS), + ) togetherWith slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + tween(DEFAULT_ANIMATION_DURATION_MILLIS), + ) + }, + predictivePopTransitionSpec = { edge -> + if (edge == NavigationEvent.EDGE_LEFT) { + slideIntoContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + tween(DEFAULT_ANIMATION_DURATION_MILLIS), + ) togetherWith slideOutOfContainer( + AnimatedContentTransitionScope.SlideDirection.Right, + tween(DEFAULT_ANIMATION_DURATION_MILLIS), + ) + } else { + null + } + }, ) } } From 4be6506c011ce25b3e73fad6e5cbe706c7945a43 Mon Sep 17 00:00:00 2001 From: Marco Gomiero Date: Fri, 12 Dec 2025 15:13:59 +0100 Subject: [PATCH 2/4] fix: use compatible navigation animations --- .../moneyflow/navigation/MoneyFlowNavHost.kt | 47 +++++++++---------- 1 file changed, 21 insertions(+), 26 deletions(-) diff --git a/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt b/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt index 7b2e14db..6a8c4527 100644 --- a/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt +++ b/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt @@ -1,9 +1,8 @@ package com.prof18.moneyflow.navigation -import androidx.compose.animation.AnimatedContentTransitionScope import androidx.compose.animation.core.tween -import androidx.compose.animation.slideIntoContainer -import androidx.compose.animation.slideOutOfContainer +import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideOutHorizontally import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets @@ -107,35 +106,31 @@ fun MoneyFlowNavHost(modifier: Modifier = Modifier) { rememberViewModelStoreNavEntryDecorator(), ), transitionSpec = { - slideIntoContainer( - AnimatedContentTransitionScope.SlideDirection.Left, - tween(DEFAULT_ANIMATION_DURATION_MILLIS), - ) togetherWith slideOutOfContainer( - AnimatedContentTransitionScope.SlideDirection.Left, - tween(DEFAULT_ANIMATION_DURATION_MILLIS), + slideInHorizontally( + animationSpec = tween(DEFAULT_ANIMATION_DURATION_MILLIS), + initialOffsetX = { it }, + ) togetherWith slideOutHorizontally( + animationSpec = tween(DEFAULT_ANIMATION_DURATION_MILLIS), + targetOffsetX = { -it }, ) }, popTransitionSpec = { - slideIntoContainer( - AnimatedContentTransitionScope.SlideDirection.Right, - tween(DEFAULT_ANIMATION_DURATION_MILLIS), - ) togetherWith slideOutOfContainer( - AnimatedContentTransitionScope.SlideDirection.Right, - tween(DEFAULT_ANIMATION_DURATION_MILLIS), + slideInHorizontally( + animationSpec = tween(DEFAULT_ANIMATION_DURATION_MILLIS), + initialOffsetX = { -it }, + ) togetherWith slideOutHorizontally( + animationSpec = tween(DEFAULT_ANIMATION_DURATION_MILLIS), + targetOffsetX = { it }, ) }, predictivePopTransitionSpec = { edge -> - if (edge == NavigationEvent.EDGE_LEFT) { - slideIntoContainer( - AnimatedContentTransitionScope.SlideDirection.Right, - tween(DEFAULT_ANIMATION_DURATION_MILLIS), - ) togetherWith slideOutOfContainer( - AnimatedContentTransitionScope.SlideDirection.Right, - tween(DEFAULT_ANIMATION_DURATION_MILLIS), - ) - } else { - null - } + slideInHorizontally( + animationSpec = tween(DEFAULT_ANIMATION_DURATION_MILLIS), + initialOffsetX = { -it }, + ) togetherWith slideOutHorizontally( + animationSpec = tween(DEFAULT_ANIMATION_DURATION_MILLIS), + targetOffsetX = { it }, + ) }, ) } From 3de8c2e1c7cb8763824b490d8f9933c791cfef7d Mon Sep 17 00:00:00 2001 From: Marco Gomiero Date: Fri, 12 Dec 2025 15:14:05 +0100 Subject: [PATCH 3/4] chore: remove unused navigation import --- .../kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt | 1 - 1 file changed, 1 deletion(-) diff --git a/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt b/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt index 6a8c4527..0d07e307 100644 --- a/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt +++ b/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt @@ -34,7 +34,6 @@ import androidx.navigation3.runtime.EntryProviderScope import androidx.navigation3.runtime.entryProvider import androidx.navigation3.runtime.rememberSaveableStateHolderNavEntryDecorator import androidx.navigation3.ui.NavDisplay -import androidx.navigationevent.NavigationEvent import androidx.savedstate.compose.serialization.serializers.SnapshotStateListSerializer import com.prof18.moneyflow.features.addtransaction.AddTransactionViewModel import com.prof18.moneyflow.features.alltransactions.AllTransactionsViewModel From 1aa82faf8ac6e13b1889ae378963ab05b3632cb1 Mon Sep 17 00:00:00 2001 From: Marco Gomiero Date: Fri, 12 Dec 2025 19:55:48 +0100 Subject: [PATCH 4/4] Update transitions --- androidApp/build.gradle.kts | 7 +++--- .../moneyflow/navigation/MoneyFlowNavHost.kt | 25 +++++++++++++++++-- 2 files changed, 27 insertions(+), 5 deletions(-) diff --git a/androidApp/build.gradle.kts b/androidApp/build.gradle.kts index 10f80933..0d903bee 100644 --- a/androidApp/build.gradle.kts +++ b/androidApp/build.gradle.kts @@ -1,4 +1,3 @@ -import com.github.triplet.gradle.androidpublisher.ReleaseStatus import java.util.Properties plugins { @@ -67,7 +66,10 @@ android { targetCompatibility = javaVersion } - buildFeatures { compose = true } + buildFeatures { + compose = true + buildConfig = true + } packaging { resources { @@ -91,7 +93,6 @@ dependencies { debugImplementation(libs.compose.ui.tooling) } - play { // The play_config.json file will be provided on CI serviceAccountCredentials.set(file("../play_config.json")) diff --git a/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt b/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt index 0d07e307..3ea56abc 100644 --- a/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt +++ b/shared/src/commonMain/kotlin/com/prof18/moneyflow/navigation/MoneyFlowNavHost.kt @@ -1,8 +1,12 @@ package com.prof18.moneyflow.navigation +import androidx.compose.animation.EnterTransition +import androidx.compose.animation.ExitTransition import androidx.compose.animation.core.tween import androidx.compose.animation.slideInHorizontally +import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutHorizontally +import androidx.compose.animation.slideOutVertically import androidx.compose.animation.togetherWith import androidx.compose.foundation.layout.PaddingValues import androidx.compose.foundation.layout.WindowInsets @@ -67,6 +71,23 @@ import org.koin.compose.viewmodel.koinViewModel private const val DEFAULT_ANIMATION_DURATION_MILLIS = 300 +private fun bottomSheetForwardTransition() = + slideInVertically( + animationSpec = tween(DEFAULT_ANIMATION_DURATION_MILLIS), + initialOffsetY = { it }, + ) togetherWith ExitTransition.None + +private fun bottomSheetPopTransition() = + EnterTransition.None togetherWith slideOutVertically( + animationSpec = tween(DEFAULT_ANIMATION_DURATION_MILLIS), + targetOffsetY = { it }, + ) + +private val bottomSheetTransitionMetadata = + NavDisplay.transitionSpec { bottomSheetForwardTransition() } + + NavDisplay.popTransitionSpec { bottomSheetPopTransition() } + + NavDisplay.predictivePopTransitionSpec { _: Int -> bottomSheetPopTransition() } + @Composable fun MoneyFlowNavHost(modifier: Modifier = Modifier) { val backStack = rememberSerializable(serializer = SnapshotStateListSerializer(AppRoute.serializer())) { @@ -238,7 +259,7 @@ private fun EntryProviderScope.screens( ) } - entry { route -> + entry(metadata = bottomSheetTransitionMetadata) { route -> val viewModel = koinViewModel() val categoryModel by viewModel.categories.collectAsState() @@ -255,7 +276,7 @@ private fun EntryProviderScope.screens( ) } - entry { + entry(metadata = bottomSheetTransitionMetadata) { val viewModel = koinViewModel() AllTransactionsScreen( stateFlow = viewModel.state,