diff --git a/library/compose-threepane/src/commonMain/kotlin/com/tunjid/treenav/compose/threepane/panedecorators/ThreePaneMovableSharedElementDecorator.kt b/library/compose-threepane/src/commonMain/kotlin/com/tunjid/treenav/compose/threepane/panedecorators/ThreePaneMovableSharedElementDecorator.kt index f9f7675..62d6d31 100644 --- a/library/compose-threepane/src/commonMain/kotlin/com/tunjid/treenav/compose/threepane/panedecorators/ThreePaneMovableSharedElementDecorator.kt +++ b/library/compose-threepane/src/commonMain/kotlin/com/tunjid/treenav/compose/threepane/panedecorators/ThreePaneMovableSharedElementDecorator.kt @@ -125,9 +125,6 @@ private class ThreePaneMovableSharedElementScope( override val inPredictiveBack: Boolean get() = delegate.paneScope.inPredictiveBack - override val metadata: Map - get() = delegate.paneScope.metadata - @OptIn(ExperimentalSharedTransitionApi::class) override fun movableSharedElementOf( sharedContentState: SharedTransitionScope.SharedContentState, diff --git a/library/compose/src/commonMain/kotlin/com/tunjid/treenav/compose/MultiPaneDisplay.kt b/library/compose/src/commonMain/kotlin/com/tunjid/treenav/compose/MultiPaneDisplay.kt index 4dc718a..6028fb0 100644 --- a/library/compose/src/commonMain/kotlin/com/tunjid/treenav/compose/MultiPaneDisplay.kt +++ b/library/compose/src/commonMain/kotlin/com/tunjid/treenav/compose/MultiPaneDisplay.kt @@ -348,7 +348,6 @@ private class MultiPaneDisplayScene( val scope = remember { AnimatedPaneScope( backStatus = backStatus, - metadata = entry.metadata, paneState = paneState, animatedContentScope = animatedContentScope, ) diff --git a/library/compose/src/commonMain/kotlin/com/tunjid/treenav/compose/PaneScope.kt b/library/compose/src/commonMain/kotlin/com/tunjid/treenav/compose/PaneScope.kt index baa11d3..a9f695e 100644 --- a/library/compose/src/commonMain/kotlin/com/tunjid/treenav/compose/PaneScope.kt +++ b/library/compose/src/commonMain/kotlin/com/tunjid/treenav/compose/PaneScope.kt @@ -52,11 +52,6 @@ interface PaneScope : AnimatedVisibilityScope { * Whether or not a predictive back gesture is in progress */ val inPredictiveBack: Boolean - - /** - * Metadata associated with this [PaneScope] at the creation of its [PaneEntry]. - */ - val metadata: Map } /** @@ -65,7 +60,6 @@ interface PaneScope : AnimatedVisibilityScope { @Stable internal class AnimatedPaneScope( val backStatus: () -> BackStatus, - override val metadata: Map, paneState: PaneState, animatedContentScope: AnimatedContentScope, ) : PaneScope, AnimatedVisibilityScope by animatedContentScope { 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 75c1782..ff4d3bf 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 @@ -46,6 +46,7 @@ import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.DisposableEffect import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.Stable +import androidx.compose.runtime.State import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.movableContentOf @@ -53,6 +54,7 @@ import androidx.compose.runtime.mutableFloatStateOf import androidx.compose.runtime.mutableStateListOf import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberUpdatedState import androidx.compose.runtime.setValue import androidx.compose.runtime.staticCompositionLocalOf import androidx.compose.ui.Alignment @@ -60,6 +62,7 @@ import androidx.compose.ui.Modifier import androidx.compose.ui.draw.alpha import androidx.compose.ui.geometry.Offset import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalWindowInfo import androidx.compose.ui.unit.Density import androidx.compose.ui.unit.Dp import androidx.compose.ui.unit.dp @@ -110,7 +113,11 @@ fun App( sharedTransitionScope = this ) } - + val windowWidth = rememberUpdatedState( + with(density) { + LocalWindowInfo.current.containerSize.width.toDp() + } + ) val displayState = appState.rememberMultiPaneDisplayState( remember { listOf( @@ -121,9 +128,7 @@ fun App( tertiaryPaneBreakPoint = mutableStateOf( TertiaryPaneMinWidthBreakpointDp ), - windowWidthState = derivedStateOf { - appState.splitLayoutState.size - } + windowWidthState = windowWidth ), threePaneMovableSharedElementDecorator( movableSharedElementHostState = movableSharedElementHostState @@ -137,42 +142,55 @@ fun App( modifier = Modifier .fillMaxSize(), ) { - appState.displayScope = this - appState.splitLayoutState.visibleCount = appState.filteredPaneOrder.size - SplitLayout( - state = appState.splitLayoutState, - modifier = Modifier - .fillMaxSize(), - itemSeparators = { paneIndex, offset -> - PaneSeparator( - splitLayoutState = appState.splitLayoutState, - interactionSource = appState.paneInteractionSourceAt(paneIndex), - index = paneIndex, - density = density, - xOffset = offset, - ) - }, - itemContent = { index -> - Destination(appState.filteredPaneOrder[index]) - } - ) - } + val splitPaneDisplayScope = remember { + SplitPaneDisplayScope( + displayScope = this, + windowWidth = windowWidth, + ) + }.also { + it.update( + displayScope = this, + ) + } - NavigationEventHandler( - enabled = displayState::canPop, - passThrough = true, - ) { progress -> - try { - progress.collect { event -> - appState.backPreviewState.progress = event.progress - appState.backPreviewState.atStart = - event.swipeEdge == NavigationEvent.EDGE_LEFT - appState.backPreviewState.pointerOffset = - Offset(event.touchX, event.touchY).round() + CompositionLocalProvider( + LocalSplitPaneDisplayScope provides splitPaneDisplayScope + ) { + SplitLayout( + state = splitPaneDisplayScope.splitLayoutState, + modifier = Modifier + .fillMaxSize(), + itemSeparators = { paneIndex, offset -> + PaneSeparator( + splitLayoutState = splitPaneDisplayScope.splitLayoutState, + interactionSource = appState.paneInteractionSourceAt(paneIndex), + index = paneIndex, + density = density, + xOffset = offset, + ) + }, + itemContent = { index -> + Destination(splitPaneDisplayScope.filteredPaneOrder[index]) + } + ) + } + + NavigationEventHandler( + enabled = displayState::canPop, + passThrough = true, + ) { progress -> + try { + progress.collect { event -> + appState.backPreviewState.progress = event.progress + appState.backPreviewState.atStart = + event.swipeEdge == NavigationEvent.EDGE_LEFT + appState.backPreviewState.pointerOffset = + Offset(event.touchX, event.touchY).round() + } + appState.backPreviewState.progress = 0f + } finally { + appState.backPreviewState.progress = 0f } - appState.backPreviewState.progress = 0f - } finally { - appState.backPreviewState.progress = 0f } } } @@ -251,30 +269,12 @@ class AppState( ) private val paneInteractionSourceList = mutableStateListOf() - private val paneRenderOrder = listOf( - ThreePane.Tertiary, - ThreePane.Secondary, - ThreePane.Primary, - ) + val currentNavigation by navigationState val backPreviewState = BackPreviewState() - val splitLayoutState = SplitLayoutState( - orientation = Orientation.Horizontal, - maxCount = paneRenderOrder.size, - minSize = 10.dp, - keyAtIndex = { index -> - val indexDiff = paneRenderOrder.size - visibleCount - paneRenderOrder[index + indexDiff] - } - ) - internal val dragToPopState = DragToPopState() - - internal val isMediumScreenWidthOrWider get() = splitLayoutState.size >= SecondaryPaneMinWidthBreakpointDp - internal var displayScope by mutableStateOf?>( - null - ) + internal val dragToPopState = DragToPopState() internal val movableNavigationBar = movableContentOf { modifier -> @@ -286,10 +286,6 @@ class AppState( PaneNavigationRail(modifier) } - val filteredPaneOrder: List by derivedStateOf { - paneRenderOrder.filter { displayScope?.destinationIn(it) != null } - } - fun setTab(destination: SampleDestination.NavTabs) { navigationRepository.navigate { if (it.currentIndex == destination.ordinal) it.popToRoot() @@ -350,10 +346,53 @@ class AppState( } } +@Stable +internal class SplitPaneDisplayScope( + displayScope: MultiPaneDisplayScope, + private val windowWidth: State, +) { + + private var displayScope by mutableStateOf(displayScope) + + internal val filteredPaneOrder by derivedStateOf { + PaneRenderOrder.filter { displayScope.destinationIn(it) != null } + } + + internal val splitLayoutState = SplitLayoutState( + orientation = Orientation.Horizontal, + maxCount = filteredPaneOrder.size, + initialVisibleCount = filteredPaneOrder.size, + minSize = 10.dp, + keyAtIndex = { index -> + filteredPaneOrder[index] + } + ) + + internal val isMediumScreenWidthOrWider + get() = windowWidth.value >= SecondaryPaneMinWidthBreakpointDp + + fun update( + displayScope: MultiPaneDisplayScope + ) { + this.displayScope = displayScope + splitLayoutState.visibleCount = filteredPaneOrder.size + } +} + +internal val LocalSplitPaneDisplayScope = staticCompositionLocalOf { + TODO() +} + internal val LocalAppState = staticCompositionLocalOf { TODO() } +private val PaneRenderOrder = listOf( + ThreePane.Tertiary, + ThreePane.Secondary, + ThreePane.Primary, +) + private val PaneSeparatorActiveWidthDp = 56.dp private val PaneSeparatorTouchTargetWidthDp = 16.dp internal val SecondaryPaneMinWidthBreakpointDp = 600.dp 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 e2cb791..8b274c3 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 @@ -40,14 +40,10 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.remember import androidx.compose.runtime.rememberUpdatedState -import androidx.compose.runtime.setValue import androidx.compose.runtime.snapshotFlow import androidx.compose.ui.Modifier import androidx.compose.ui.graphics.Color -import androidx.compose.ui.layout.onSizeChanged -import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp -import androidx.compose.ui.unit.roundToIntSize import androidx.compose.ui.zIndex import com.tunjid.demo.common.ui.data.SampleDestination import com.tunjid.treenav.compose.PaneScope @@ -59,24 +55,23 @@ import kotlinx.coroutines.flow.filterNotNull @Stable class PaneScaffoldState internal constructor( - private val appState: AppState, + private val splitPaneDisplayScope: SplitPaneDisplayScope, threePaneMovableElementSharedTransitionScope: ThreePaneMovableElementSharedTransitionScope, ) : ThreePaneMovableElementSharedTransitionScope by threePaneMovableElementSharedTransitionScope { - internal val canShowNavigationBar get() = !appState.isMediumScreenWidthOrWider + internal val canShowNavigationBar get() = !splitPaneDisplayScope.isMediumScreenWidthOrWider internal val canShowNavigationRail - get() = appState.filteredPaneOrder.firstOrNull() == paneState.pane - && appState.isMediumScreenWidthOrWider + get() = splitPaneDisplayScope.filteredPaneOrder.firstOrNull() == paneState.pane + && splitPaneDisplayScope.isMediumScreenWidthOrWider - val canUseMovableNavigationBar + internal val canUseMovableNavigationBar get() = canShowNavigationBar && isActive && paneState.pane == ThreePane.Primary - val canUseMovableNavigationRail + internal val canUseMovableNavigationRail get() = canShowNavigationRail && isActive - internal var scaffoldTargetSize by mutableStateOf(IntSize.Zero) - internal var scaffoldCurrentSize by mutableStateOf(IntSize.Zero) + internal val hasSiblings get() = splitPaneDisplayScope.filteredPaneOrder.size > 1 internal val defaultContainerColor: Color @Composable get() { @@ -94,12 +89,12 @@ class PaneScaffoldState internal constructor( @Composable fun PaneScope.rememberPaneScaffoldState(): PaneScaffoldState { - val appState = LocalAppState.current + val splitPaneDisplayScope = LocalSplitPaneDisplayScope.current val paneMovableElementSharedTransitionScope = rememberThreePaneMovableElementSharedTransitionScope() - return remember(appState) { + return remember(splitPaneDisplayScope, paneMovableElementSharedTransitionScope) { PaneScaffoldState( - appState = appState, + splitPaneDisplayScope = splitPaneDisplayScope, threePaneMovableElementSharedTransitionScope = paneMovableElementSharedTransitionScope, ) } @@ -144,11 +139,8 @@ fun PaneScaffoldState.PaneScaffold( } ) .padding( - horizontal = if (appState.filteredPaneOrder.size > 1) 8.dp else 0.dp - ) - .onSizeChanged { - scaffoldCurrentSize = it - }, + horizontal = if (hasSiblings) 8.dp else 0.dp + ), containerColor = containerColor, topBar = { topBar() @@ -216,10 +208,7 @@ private inline fun PaneNavigationRailScaffold( private fun scaffoldBoundsTransform( paneScaffoldState: PaneScaffoldState, canAnimatePane: () -> Boolean, -): BoundsTransform = BoundsTransform { _, targetBounds -> - paneScaffoldState.scaffoldTargetSize = - targetBounds.size.roundToIntSize() - +): BoundsTransform = BoundsTransform { _, _ -> when (paneScaffoldState.paneState.pane) { ThreePane.Primary, ThreePane.Secondary,