diff --git a/libraryVersion.properties b/libraryVersion.properties index e854783..e89f8ed 100644 --- a/libraryVersion.properties +++ b/libraryVersion.properties @@ -14,7 +14,7 @@ # limitations under the License. # groupId=com.tunjid.treenav -treenav_version=0.0.25 -strings_version=0.0.25 -compose_version=0.0.25 -compose-threepane_version=0.0.25 \ No newline at end of file +treenav_version=0.0.26 +strings_version=0.0.26 +compose_version=0.0.26 +compose-threepane_version=0.0.26 \ No newline at end of file diff --git a/sample/android/build.gradle.kts b/sample/android/build.gradle.kts index b53fd89..7dfc9cc 100644 --- a/sample/android/build.gradle.kts +++ b/sample/android/build.gradle.kts @@ -37,6 +37,7 @@ android { getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro" ) + signingConfig = signingConfigs.getByName("debug") } } } diff --git a/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/DemoApp.kt b/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/DemoApp.kt index 6d79122..c2680c4 100644 --- a/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/DemoApp.kt +++ b/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/DemoApp.kt @@ -48,7 +48,7 @@ import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue -import androidx.compose.runtime.movableContentWithReceiverOf +import androidx.compose.runtime.movableContentOf import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf @@ -67,28 +67,28 @@ import com.tunjid.composables.backpreview.backPreview import com.tunjid.composables.splitlayout.SplitLayout import com.tunjid.composables.splitlayout.SplitLayoutState import com.tunjid.demo.common.ui.AppState.Companion.rememberMultiPaneDisplayState +import com.tunjid.demo.common.ui.avatar.avatarPaneEntry import com.tunjid.demo.common.ui.chat.chatPaneEntry import com.tunjid.demo.common.ui.chatrooms.chatRoomPaneEntry import com.tunjid.demo.common.ui.data.NavigationRepository import com.tunjid.demo.common.ui.data.SampleDestination import com.tunjid.demo.common.ui.me.mePaneEntry -import com.tunjid.demo.common.ui.avatar.avatarPaneEntry import com.tunjid.demo.common.ui.profile.profilePaneEntry import com.tunjid.treenav.MultiStackNav import com.tunjid.treenav.compose.MultiPaneDisplay import com.tunjid.treenav.compose.MultiPaneDisplayScope import com.tunjid.treenav.compose.MultiPaneDisplayState import com.tunjid.treenav.compose.moveablesharedelement.MovableSharedElementHostState -import com.tunjid.treenav.compose.threepane.ThreePane import com.tunjid.treenav.compose.multiPaneDisplayBackstack +import com.tunjid.treenav.compose.threepane.ThreePane import com.tunjid.treenav.compose.threepane.transforms.backPreviewTransform import com.tunjid.treenav.compose.threepane.transforms.threePanedAdaptiveTransform import com.tunjid.treenav.compose.threepane.transforms.threePanedMovableSharedElementTransform import com.tunjid.treenav.compose.transforms.Transform import com.tunjid.treenav.compose.transforms.paneModifierTransform -import com.tunjid.treenav.requireCurrent import com.tunjid.treenav.pop import com.tunjid.treenav.popToRoot +import com.tunjid.treenav.requireCurrent import com.tunjid.treenav.switch import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers @@ -109,7 +109,6 @@ fun App( sharedTransitionScope = this ) } - appState.movableSharedElementHostState = movableSharedElementHostState MultiPaneDisplay( modifier = Modifier .fillMaxSize(), @@ -241,8 +240,6 @@ class AppState( private val navigationRepository: NavigationRepository = NavigationRepository, ) { - internal lateinit var movableSharedElementHostState: MovableSharedElementHostState - private val navigationState = mutableStateOf( navigationRepository.navigationStateFlow.value ) @@ -278,12 +275,12 @@ class AppState( ) internal val movableNavigationBar = - movableContentWithReceiverOf { modifier -> + movableContentOf { modifier -> PaneNavigationBar(modifier) } internal val movableNavigationRail = - movableContentWithReceiverOf { modifier -> + movableContentOf { modifier -> PaneNavigationRail(modifier) } diff --git a/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/PaneNavigation.kt b/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/PaneNavigation.kt index 8c042e6..99042dd 100644 --- a/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/PaneNavigation.kt +++ b/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/PaneNavigation.kt @@ -19,10 +19,11 @@ package com.tunjid.demo.common.ui import androidx.compose.animation.AnimatedVisibility +import androidx.compose.animation.BoundsTransform import androidx.compose.animation.EnterTransition import androidx.compose.animation.ExitTransition import androidx.compose.animation.ExperimentalSharedTransitionApi -import androidx.compose.animation.SharedTransitionScope +import androidx.compose.animation.core.snap import androidx.compose.animation.slideInHorizontally import androidx.compose.animation.slideInVertically import androidx.compose.animation.slideOutHorizontally @@ -33,17 +34,8 @@ import androidx.compose.material3.NavigationBarItem import androidx.compose.material3.NavigationRail import androidx.compose.material3.NavigationRailItem import androidx.compose.runtime.Composable -import androidx.compose.runtime.Stable -import androidx.compose.runtime.getValue -import androidx.compose.runtime.mutableStateOf -import androidx.compose.runtime.remember -import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import com.tunjid.demo.common.ui.data.SampleDestination -import com.tunjid.treenav.compose.moveablesharedelement.rememberPaneMovableSharedElementScope -import com.tunjid.treenav.compose.rememberPaneMovableElementSharedTransitionScope -import com.tunjid.treenav.compose.threepane.ThreePane -import com.tunjid.treenav.compose.threepane.ThreePaneMovableElementSharedTransitionScope import com.tunjid.treenav.current @Composable @@ -51,171 +43,96 @@ fun PaneScaffoldState.PaneNavigationBar( modifier: Modifier = Modifier, enterTransition: EnterTransition = slideInVertically(initialOffsetY = { it }), exitTransition: ExitTransition = slideOutVertically(targetOffsetY = { it }), -) = withUpdatedPaneScaffoldNavigationState( - enterTransition = enterTransition, - exitTransition = exitTransition, - canShow = canShowBottomNavigation, - content = { - val finalModifier = modifier - .navigationSharedElement( - sharedContentState = rememberSharedContentState(BottomNavSharedElementKey), - ) - - if (canUseMovableContent) LocalAppState.current.movableNavigationBar( - this, - finalModifier - ) - else PaneNavigationBar(finalModifier) - } -) - -@Composable -fun PaneScaffoldState.PaneNavigationRail( - modifier: Modifier = Modifier, - enterTransition: EnterTransition = slideInHorizontally(initialOffsetX = { -it }), - exitTransition: ExitTransition = slideOutHorizontally(targetOffsetX = { -it }), -) = withUpdatedPaneScaffoldNavigationState( - enterTransition = enterTransition, - exitTransition = exitTransition, - canShow = canShowNavRail, - content = { - val finalModifier = modifier - .navigationSharedElement( - sharedContentState = rememberSharedContentState(NavRailSharedElementKey), - ) - - if (canUseMovableContent) LocalAppState.current.movableNavigationRail( - this, - finalModifier - ) - else PaneNavigationRail(finalModifier) - } -) - -@Composable -internal fun NavigationBarState.PaneNavigationBar( - modifier: Modifier = Modifier, ) { - val appState = LocalAppState.current AnimatedVisibility( - modifier = modifier, - visible = canShow, + modifier = modifier + .sharedElement( + sharedContentState = rememberSharedContentState(NavigationBarSharedElementKey), + animatedVisibilityScope = this, + zIndexInOverlay = NavigationSharedElementZIndex, + ), + visible = canShowNavigationBar, enter = enterTransition, exit = exitTransition, content = { - NavigationBar { - SampleDestination.NavTabs.entries.forEach { item -> - NavigationBarItem( - icon = { - Icon( - imageVector = item.icon, - contentDescription = item.title, - ) - }, - selected = item == appState.currentNavigation.current, - onClick = { appState.setTab(item) } - ) - } - } - }, + val appState = LocalAppState.current + if (canUseMovableNavigationBar) appState.movableNavigationBar(Modifier) + else appState.PaneNavigationBar(Modifier) + } ) } @Composable -internal fun NavigationBarState.PaneNavigationRail( +fun PaneScaffoldState.PaneNavigationRail( modifier: Modifier = Modifier, + enterTransition: EnterTransition = slideInHorizontally(initialOffsetX = { -it }), + exitTransition: ExitTransition = slideOutHorizontally(targetOffsetX = { -it }), ) { - val appState = LocalAppState.current AnimatedVisibility( - modifier = modifier, - visible = canShow, - enter = enterTransition, - exit = exitTransition, - content = { - NavigationRail { - SampleDestination.NavTabs.entries.forEach { item -> - NavigationRailItem( - selected = item == appState.currentNavigation.current, - icon = { - Icon( - imageVector = item.icon, - contentDescription = item.title, - ) - }, - onClick = { appState.setTab(item) } - ) - } - } - } - ) + modifier = modifier + .sharedElementWithCallerManagedVisibility( + sharedContentState = rememberSharedContentState(NavigationRailSharedElementKey), + visible = canShowNavigationRail, + zIndexInOverlay = NavigationSharedElementZIndex, + boundsTransform = NavigationRailBoundsTransform, + ), + visible = canShowNavigationRail, + enter = if (canShowNavigationRail) enterTransition else EnterTransition.None, + exit = if (canShowNavigationRail) exitTransition else ExitTransition.None, + ) { + val appState = LocalAppState.current + if (canUseMovableNavigationRail) appState.movableNavigationRail(Modifier) + else appState.PaneNavigationRail(Modifier) + } } @Composable -private fun PaneScaffoldState.withUpdatedPaneScaffoldNavigationState( - enterTransition: EnterTransition, - exitTransition: ExitTransition, - canShow: Boolean, - content: @Composable NavigationBarState.() -> Unit +internal fun AppState.PaneNavigationBar( + modifier: Modifier = Modifier ) { - val appState = LocalAppState.current - - val paneMovableElementSharedTransitionScope = - rememberPaneMovableElementSharedTransitionScope( - paneSharedTransitionScope = this, - movableSharedElementScope = rememberPaneMovableSharedElementScope( - movableSharedElementHostState = appState.movableSharedElementHostState - ), - ) - - val state = remember { - NavigationBarState( - delegate = paneMovableElementSharedTransitionScope, - enterTransition = enterTransition, - exitTransition = exitTransition, - canShow = canShow, - ) - }.also { - it.enterTransition = enterTransition - it.exitTransition = exitTransition - it.canShow = canShow + NavigationBar( + modifier = modifier + ) { + SampleDestination.NavTabs.entries.forEach { item -> + NavigationBarItem( + icon = { + Icon( + imageVector = item.icon, + contentDescription = item.title, + ) + }, + selected = item == currentNavigation.current, + onClick = { setTab(item) } + ) + } } - - state.content() } -@Stable -internal class NavigationBarState( - private val delegate: ThreePaneMovableElementSharedTransitionScope, - enterTransition: EnterTransition, - exitTransition: ExitTransition, - canShow: Boolean, -) : ThreePaneMovableElementSharedTransitionScope by delegate { - var enterTransition by mutableStateOf(enterTransition) - var exitTransition by mutableStateOf(exitTransition) - var canShow by mutableStateOf(canShow) - - val canUseMovableContent - get() = when { - isActive && isPreviewingBack && paneState.pane == ThreePane.TransientPrimary -> true - isActive && !isPreviewingBack && paneState.pane == ThreePane.Primary -> true - else -> false +@Composable +internal fun AppState.PaneNavigationRail( + modifier: Modifier = Modifier +) { + NavigationRail( + modifier = modifier + ) { + SampleDestination.NavTabs.entries.forEach { item -> + NavigationRailItem( + selected = item == currentNavigation.current, + icon = { + Icon( + imageVector = item.icon, + contentDescription = item.title, + ) + }, + onClick = { setTab(item) } + ) } - - private val isPreviewingBack: Boolean - get() = paneState.adaptations.contains(ThreePane.PrimaryToTransient) - - - @OptIn(ExperimentalSharedTransitionApi::class) - fun Modifier.navigationSharedElement( - sharedContentState: SharedTransitionScope.SharedContentState, - ) = sharedElement( - sharedContentState = sharedContentState, - animatedVisibilityScope = delegate, - zIndexInOverlay = NavigationSharedElementZIndex, - ) + } } -private data object BottomNavSharedElementKey -private data object NavRailSharedElementKey +private data object NavigationBarSharedElementKey +private data object NavigationRailSharedElementKey private const val NavigationSharedElementZIndex = 2f + +private val NavigationRailBoundsTransform = BoundsTransform { _, _ -> snap() } diff --git a/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/PaneScaffold.kt b/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/PaneScaffold.kt index 6f7d66b..373904f 100644 --- a/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/PaneScaffold.kt +++ b/sample/common/src/commonMain/kotlin/com/tunjid/demo/common/ui/PaneScaffold.kt @@ -64,12 +64,22 @@ class PaneScaffoldState internal constructor( threePaneMovableElementSharedTransitionScope: ThreePaneMovableElementSharedTransitionScope, ) : ThreePaneMovableElementSharedTransitionScope by threePaneMovableElementSharedTransitionScope { - internal val canShowBottomNavigation get() = !appState.isMediumScreenWidthOrWider + internal val canShowNavigationBar get() = !appState.isMediumScreenWidthOrWider - internal val canShowNavRail + internal val canShowNavigationRail get() = appState.filteredPaneOrder.firstOrNull() == paneState.pane && appState.isMediumScreenWidthOrWider + val canUseMovableNavigationBar + get() = canShowNavigationBar && when { + isActive && isPreviewingBack && paneState.pane == ThreePane.TransientPrimary -> true + isActive && !isPreviewingBack && paneState.pane == ThreePane.Primary -> true + else -> false + } + + val canUseMovableNavigationRail + get() = canShowNavigationRail && isActive + internal val canShowFab get() = when (paneState.pane) { ThreePane.Primary -> true @@ -86,6 +96,9 @@ class PaneScaffoldState internal constructor( internal fun hasMatchedSize(): Boolean = abs(scaffoldCurrentSize.width - scaffoldTargetSize.width) <= 2 && abs(scaffoldCurrentSize.height - scaffoldTargetSize.height) <= 2 + + private val isPreviewingBack: Boolean + get() = paneState.adaptations.contains(ThreePane.PrimaryToTransient) } @Composable