这是indexloc提供的服务,不要输入任何密码
Skip to content

Refactor to use Display term and improve compatibility with Jetpack Navigation3 #24

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 11 commits into from
Feb 12, 2025
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.tunjid.treenav.compose

import androidx.compose.animation.AnimatedContent
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import androidx.compose.runtime.saveable.SaveableStateHolder
import androidx.compose.ui.Modifier
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleOwner
import androidx.lifecycle.ViewModelStoreOwner
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.viewmodel.compose.LocalViewModelStoreOwner
import com.tunjid.treenav.Node

/**
* Scope that provides context about individual panes [Pane] in an [MultiPaneDisplay].
*/
@Stable
interface MultiPaneDisplayScope<Pane, Destination : Node> {

@Composable
fun Destination(
pane: Pane,
)

fun adaptationsIn(
pane: Pane,
): Set<Adaptation>

fun destinationIn(
pane: Pane,
): Destination?
}

/**
* A Display that provides the following for each
* navigation [Destination] that shows up in its panes:
*
* - A single [SaveableStateHolder] for each navigation [Destination] that shows up in its panes.
* [SaveableStateHolder.SaveableStateProvider] is keyed on the [Destination]s [Node.id].
*
* - A [ViewModelStoreOwner] for each [Destination] via [LocalViewModelStoreOwner].
* Once present in the navigation tree, a [Destination] will always use the same
* [ViewModelStoreOwner], regardless of where in the tree it is, until its is removed from the tree.
* [Destination]s are unique based on their [Node.id].
*
* - A [LifecycleOwner] for each [Destination] via [LocalLifecycleOwner]. This [LifecycleOwner]
* follows the [Lifecycle] of its immediate parent, unless it is animating out or placed in the
* backstack. This is defined by [PaneScope.isActive], which is a function of the backing
* [AnimatedContent] for each [Pane] displayed and if the current [Destination]
* matches [MultiPaneDisplayScope.destinationIn] in the visible [Pane].
*
* @param state the driving [MultiPaneDisplayState] that applies adaptive semantics and
* transforms for each navigation destination shown in the [MultiPaneDisplay].
*/
@Composable
fun <Pane, NavigationState : Node, Destination : Node> MultiPaneDisplay(
state: MultiPaneDisplayState<Pane, NavigationState, Destination>,
modifier: Modifier = Modifier,
content: @Composable MultiPaneDisplayScope<Pane, Destination>.() -> Unit,
) {
Box(
modifier = modifier
) {
SlottedMultiPaneDisplayScope(
state = state,
content = content
)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,145 @@
/*
* Copyright 2021 Google LLC
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/

package com.tunjid.treenav.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.State
import androidx.compose.runtime.derivedStateOf
import com.tunjid.treenav.Node
import com.tunjid.treenav.compose.transforms.CompoundTransform
import com.tunjid.treenav.compose.transforms.DestinationTransform
import com.tunjid.treenav.compose.transforms.PaneTransform
import com.tunjid.treenav.compose.transforms.RenderTransform
import com.tunjid.treenav.compose.transforms.Transform

/**
* Class for configuring a [MultiPaneDisplay] for selecting, adapting and placing navigation
* destinations into different panes from an arbitrary [navigationState].
*
* @param panes a list of panes that is possible to show in the [MultiPaneDisplay] in all
* possible configurations. The panes should consist of enum class instances, or a sealed class
* hierarchy of kotlin objects.
* @param navigationState the navigation state to be adapted into various panes.
* @param backStackTransform a transform to read the back stack of the navigation state.
* @param destinationTransform a transform of the [navigationState] to its current destination.
* @param panesToDestinationsTransform provides the strategy used to adapt the current
* [Destination] to the panes available.
* @param renderTransform the transform used to render a [Destination] in its pane.
*/
class MultiPaneDisplayState<Pane, NavigationState : Node, Destination : Node> internal constructor(
val panes: List<Pane>,
val navigationState: State<NavigationState>,
val backStackTransform: (NavigationState) -> List<Destination>,
val destinationTransform: (NavigationState) -> Destination,
val panesToDestinationsTransform: @Composable (Destination) -> Map<Pane, Destination?>,
val renderTransform: @Composable PaneScope<Pane, Destination>.(Destination) -> Unit,
) {
internal val currentDestination: State<Destination> = derivedStateOf {
destinationTransform(navigationState.value)
}
}

/**
* Provides an [MultiPaneDisplayState] for configuring a [MultiPaneDisplay] for
* showing different navigation destinations into different panes from an arbitrary
* [navigationState].
*
* @param panes a list of panes that is possible to show in the [MultiPaneDisplay] in all
* possible configurations. The panes should consist of enum class instances, or a sealed class
* hierarchy of kotlin objects.
* @param navigationState the navigation state to be adapted into various panes.
* @param backStackTransform a transform to read the back stack of the navigation state.
* @param destinationTransform a transform of the [navigationState] to its current destination.
* @param entryProvider provides the [Transform]s and content needed to render
* a [Destination] in its pane.
* @param transforms a list of transforms applied to every [Destination] before it is
* rendered in its pane. Order matters; they are applied from last to first.
*/
fun <Pane, NavigationState : Node, Destination : Node> MultiPaneDisplayState(
panes: List<Pane>,
navigationState: State<NavigationState>,
backStackTransform: (NavigationState) -> List<Destination>,
destinationTransform: (NavigationState) -> Destination,
entryProvider: (Destination) -> PaneEntry<Pane, Destination>,
transforms: List<Transform<Pane, NavigationState, Destination>>,
) = transforms.fold(
initial = MultiPaneDisplayState(
panes = panes,
navigationState = navigationState,
backStackTransform = backStackTransform,
destinationTransform = destinationTransform,
panesToDestinationsTransform = { destination ->
entryProvider(destination).paneTransform(destination)
},
renderTransform = { destination ->
val nav = entryProvider(destination)
with(nav.renderTransform) {
Render(
destination = destination,
previousTransform = nav.content,
)
}
}
),
operation = MultiPaneDisplayState<Pane, NavigationState, Destination>::plus
)

private operator fun <Pane, NavigationState : Node, Destination : Node>
MultiPaneDisplayState<Pane, NavigationState, Destination>.plus(
transform: Transform<Pane, NavigationState, Destination>,
): MultiPaneDisplayState<Pane, NavigationState, Destination> =
if (transform is CompoundTransform) transform.transforms.fold(
initial = this,
operation = MultiPaneDisplayState<Pane, NavigationState, Destination>::plus,
)
else MultiPaneDisplayState(
panes = panes,
navigationState = navigationState,
backStackTransform = backStackTransform,
destinationTransform = when (transform) {
is DestinationTransform -> { destination ->
transform.toDestination(
navigationState = destination,
previousTransform = destinationTransform
)
}

else -> destinationTransform
},
panesToDestinationsTransform = when (transform) {
is PaneTransform -> { destination ->
transform.toPanesAndDestinations(
destination = destination,
previousTransform = panesToDestinationsTransform,
)
}

else -> panesToDestinationsTransform
},
renderTransform = when (transform) {
is RenderTransform -> { destination ->
with(transform) {
Render(
destination = destination,
previousTransform = renderTransform,
)
}
}

else -> renderTransform
},
)
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
package com.tunjid.treenav.compose

import androidx.compose.runtime.Composable
import androidx.compose.runtime.Stable
import com.tunjid.treenav.Node
import com.tunjid.treenav.compose.transforms.RenderTransform

/**
* Provides the logic used to select, configure and place a navigation [Destination] for each
* pane [Pane] for the current active navigation [Destination].
*/
@Stable
class PaneEntry<Pane, Destination : Node>(
internal val renderTransform: RenderTransform<Pane, Destination>,
internal val paneTransform: @Composable (Destination) -> Map<Pane, Destination?>,
internal val content: @Composable PaneScope<Pane, Destination>.(Destination) -> Unit,
)
Original file line number Diff line number Diff line change
Expand Up @@ -91,7 +91,7 @@ internal data class SlotPaneState<Pane, Destination : Node>(
) : PaneState<Pane, Destination>

/**
* A spot taken by an [PaneStrategy] that may be moved in from pane to pane.
* A spot taken by an [PaneEntry] that may be moved in from pane to pane.
*/
@JvmInline
internal value class Slot internal constructor(val index: Int)

This file was deleted.

This file was deleted.

Loading