Skip to content

Commit

Permalink
Added EmbeddedNavigationDestination as an experimental API, which a…
Browse files Browse the repository at this point in the history
…llows a `NavigationKey.SupportsPush` to be rendered as an embedded destination within another Composable.
  • Loading branch information
isaac-udy committed May 7, 2024
1 parent 43bf3b1 commit e9b8a00
Show file tree
Hide file tree
Showing 3 changed files with 180 additions and 0 deletions.
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
* EnroBackConfiguration.Manual disables all back handling via Enro, and allows developers to set their own back pressed handling for individual destinations
* EnroBackConfiguration.Predictive is experimental, but adds support for predictive back gestures and animations. This is not yet fully implemented, and is not recommended for production use. Once this is stabilised, EnroBackNavigation.Default will be renamed to EnroBackNavigation.Legacy, and EnroBackNavigation.Predictive will become the default.
* Removed `ContainerRegistrationStrategy` from the "core" `rememberNavigationContainer` methods, to stop the requirement to opt-in for `AdvancedEnroApi` when using the standard `rememberNavigationContainer` APIs. This was introduced accidentally with 2.4.0.
* Added `EmbeddedNavigationDestination` as an experimental API, which allows a `NavigationKey.SupportsPush` to be rendered as an embedded destination within another Composable.

## 2.4.0
* Updated dependency versions
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,69 @@
package dev.enro.destination.compose

import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.ui.Modifier
import dev.enro.annotations.ExperimentalEnroApi
import dev.enro.core.NavigationKey
import dev.enro.core.compose.rememberNavigationContainer
import dev.enro.core.container.EmptyBehavior
import dev.enro.core.container.acceptNone

@Composable
@ExperimentalEnroApi
public fun EmbeddedNavigationDestination(
navigationKey: NavigationKey.SupportsPush,
onClosed: (() -> Unit),
modifier: Modifier = Modifier,
) {
val rememberedOnClosed = rememberUpdatedState(onClosed)
val container = rememberNavigationContainer(
root = navigationKey,
emptyBehavior = EmptyBehavior.CloseParent,
filter = acceptNone(),
interceptor = {
onClosed<NavigationKey> {
if (it != navigationKey) return@onClosed continueWithClose()
rememberedOnClosed.value.invoke()
cancelClose()
}
}
)
Box(modifier = modifier) {
container.Render()
}
}

@Composable
@ExperimentalEnroApi
public inline fun <reified T: Any> EmbeddedNavigationDestination(
navigationKey: NavigationKey.SupportsPush.WithResult<T>,
noinline onClosed: (() -> Unit),
modifier: Modifier = Modifier,
noinline onResult: (T) -> Unit = {},
) {
val rememberedOnClosed = rememberUpdatedState(onClosed)
val rememberedOnResult = rememberUpdatedState(onResult)

val container = rememberNavigationContainer(
emptyBehavior = EmptyBehavior.CloseParent,
root = navigationKey,
filter = acceptNone(),
interceptor = {
onClosed<NavigationKey> {
if (it != navigationKey) return@onClosed continueWithClose()
rememberedOnClosed.value.invoke()
cancelClose()
}
onResult<NavigationKey.WithResult<T>, T> { key, result ->
if (key != navigationKey) return@onResult continueWithClose()
rememberedOnResult.value.invoke(result)
cancelResult()
}
}
)
Box(modifier = modifier) {
container.Render()
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,110 @@
package dev.enro.tests.application.compose

import androidx.compose.animation.Crossfade
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.material.Button
import androidx.compose.material.Text
import androidx.compose.runtime.Composable
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 dev.enro.annotations.ExperimentalEnroApi
import dev.enro.annotations.NavigationDestination
import dev.enro.core.NavigationKey
import dev.enro.core.close
import dev.enro.core.closeWithResult
import dev.enro.core.compose.navigationHandle
import dev.enro.destination.compose.EmbeddedNavigationDestination
import dev.enro.tests.application.compose.common.TitledColumn
import kotlinx.parcelize.Parcelize

@Parcelize
object EmbeddedDestination : NavigationKey.SupportsPush {
@Parcelize
internal data object NoResult: NavigationKey.SupportsPush

@Parcelize
internal data object WithResult: NavigationKey.SupportsPush.WithResult<String>
}

@OptIn(ExperimentalEnroApi::class)
@Composable
@NavigationDestination(EmbeddedDestination::class)
fun EmbeddedDestination() {
val navigation = navigationHandle()
var withResult by remember {
mutableStateOf(false)
}

var lastResult by remember {
mutableStateOf("(None)")
}

TitledColumn(title = "Embedded Destination") {

Text(text = "Last result: $lastResult")

Button(onClick = { withResult = !withResult }) {
Text(text = "Toggle Result")
}

Crossfade(
modifier = Modifier.weight(1f),
targetState = withResult
) { withResult ->
if (withResult) {
EmbeddedNavigationDestination(
navigationKey = EmbeddedDestination.WithResult,
modifier = Modifier.fillMaxSize(),
onClosed = {
navigation.close()
},
onResult = {
lastResult = it
}
)
}
else {
EmbeddedNavigationDestination(
navigationKey = EmbeddedDestination.NoResult,
onClosed = {
navigation.close()
},
modifier = Modifier.fillMaxSize(),
)
}
}
}
}

@Composable
@NavigationDestination(EmbeddedDestination.NoResult::class)
fun EmbeddedDestinationNoResult() {
val navigation = navigationHandle()
TitledColumn(title = "No Result Destination") {
Text(text = "This destination has no result")
Button(onClick = { navigation.close() }) {
Text(text = "Close")
}
}
}

@Composable
@NavigationDestination(EmbeddedDestination.WithResult::class)
fun EmbeddedDestinationWithResult() {
val navigation = navigationHandle<EmbeddedDestination.WithResult>()
TitledColumn(title = "No Result Destination") {
Text(text = "This destination can return results")
Button(onClick = { navigation.closeWithResult("A") }) {
Text(text = "Send Result (A)")
}
Button(onClick = { navigation.closeWithResult("B") }) {
Text(text = "Send Result (B)")
}
Button(onClick = { navigation.close() }) {
Text(text = "Close")
}
}
}

0 comments on commit e9b8a00

Please sign in to comment.