diff --git a/.idea/kotlinc.xml b/.idea/kotlinc.xml
index 6d0ee1c..d4b7acc 100644
--- a/.idea/kotlinc.xml
+++ b/.idea/kotlinc.xml
@@ -1,6 +1,6 @@
-
+
\ No newline at end of file
diff --git a/demo/src/main/kotlin/dev/hrach/navigation/demo/NavHost.kt b/demo/src/main/kotlin/dev/hrach/navigation/demo/NavHost.kt
index b3f8217..8cf3494 100644
--- a/demo/src/main/kotlin/dev/hrach/navigation/demo/NavHost.kt
+++ b/demo/src/main/kotlin/dev/hrach/navigation/demo/NavHost.kt
@@ -1,10 +1,23 @@
package dev.hrach.navigation.demo
+import androidx.compose.animation.EnterTransition
+import androidx.compose.animation.ExitTransition
+import androidx.compose.animation.core.FastOutLinearInEasing
+import androidx.compose.animation.core.LinearOutSlowInEasing
+import androidx.compose.animation.core.tween
+import androidx.compose.animation.fadeIn
+import androidx.compose.animation.fadeOut
+import androidx.compose.animation.slideInHorizontally
+import androidx.compose.animation.slideInVertically
+import androidx.compose.animation.slideOutHorizontally
+import androidx.compose.animation.slideOutVertically
import androidx.compose.foundation.shape.CornerSize
import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalDensity
+import androidx.compose.ui.unit.Density
import androidx.compose.ui.unit.dp
import androidx.navigation.NavHostController
import androidx.navigation.compose.NavHost
@@ -29,9 +42,14 @@ internal fun NavHost(
modalSheetNavigator: ModalSheetNavigator,
bottomSheetNavigator: BottomSheetNavigator,
) {
+ val density = LocalDensity.current
NavHost(
navController = navController,
startDestination = Destinations.Home,
+ enterTransition = { SharedXAxisEnterTransition(density) },
+ exitTransition = { SharedXAxisExitTransition(density) },
+ popEnterTransition = { SharedXAxisPopEnterTransition(density) },
+ popExitTransition = { SharedXAxisPopExitTransition(density) },
) {
composable { Home(navController) }
composable { List() }
@@ -40,10 +58,16 @@ internal fun NavHost(
modalSheet { Modal2() }
bottomSheet { BottomSheet(navController) }
}
- ModalSheetHost(modalSheetNavigator, containerColor = MaterialTheme.colorScheme.background)
+ ModalSheetHost(
+ modalSheetNavigator = modalSheetNavigator,
+ containerColor = MaterialTheme.colorScheme.background,
+ enterTransition = { SharedYAxisEnterTransition(density) },
+ exitTransition = { SharedYAxisExitTransition(density) },
+ )
BottomSheetHost(
- bottomSheetNavigator,
- shape = RoundedCornerShape( // optional, just an example of bottom sheet custom property
+ navigator = bottomSheetNavigator,
+ shape = RoundedCornerShape(
+ // optional, just an example of bottom sheet custom property
topStart = CornerSize(12.dp),
topEnd = CornerSize(12.dp),
bottomStart = CornerSize(0.dp),
@@ -51,3 +75,45 @@ internal fun NavHost(
),
)
}
+
+private val SharedXAxisEnterTransition: (Density) -> EnterTransition = { density ->
+ fadeIn(animationSpec = tween(durationMillis = 210, delayMillis = 90, easing = LinearOutSlowInEasing)) +
+ slideInHorizontally(animationSpec = tween(durationMillis = 300)) {
+ with(density) { 30.dp.roundToPx() }
+ }
+}
+
+private val SharedXAxisPopEnterTransition: (Density) -> EnterTransition = { density ->
+ fadeIn(animationSpec = tween(durationMillis = 210, delayMillis = 90, easing = LinearOutSlowInEasing)) +
+ slideInHorizontally(animationSpec = tween(durationMillis = 300)) {
+ with(density) { (-30).dp.roundToPx() }
+ }
+}
+
+private val SharedXAxisExitTransition: (Density) -> ExitTransition = { density ->
+ fadeOut(animationSpec = tween(durationMillis = 90, easing = FastOutLinearInEasing)) +
+ slideOutHorizontally(animationSpec = tween(durationMillis = 300)) {
+ with(density) { (-30).dp.roundToPx() }
+ }
+}
+
+private val SharedXAxisPopExitTransition: (Density) -> ExitTransition = { density ->
+ fadeOut(animationSpec = tween(durationMillis = 90, easing = FastOutLinearInEasing)) +
+ slideOutHorizontally(animationSpec = tween(durationMillis = 300)) {
+ with(density) { 30.dp.roundToPx() }
+ }
+}
+
+private val SharedYAxisEnterTransition: (Density) -> EnterTransition = { density ->
+ fadeIn(animationSpec = tween(durationMillis = 210, delayMillis = 90, easing = LinearOutSlowInEasing)) +
+ slideInVertically(animationSpec = tween(durationMillis = 300)) {
+ with(density) { 30.dp.roundToPx() }
+ }
+}
+
+private val SharedYAxisExitTransition: (Density) -> ExitTransition = { density ->
+ fadeOut(animationSpec = tween(durationMillis = 210, delayMillis = 90, easing = LinearOutSlowInEasing)) +
+ slideOutVertically(animationSpec = tween(durationMillis = 300)) {
+ with(density) { 30.dp.roundToPx() }
+ }
+}
diff --git a/demo/src/main/kotlin/dev/hrach/navigation/demo/screens/Modal1.kt b/demo/src/main/kotlin/dev/hrach/navigation/demo/screens/Modal1.kt
index 20aa530..456fc98 100644
--- a/demo/src/main/kotlin/dev/hrach/navigation/demo/screens/Modal1.kt
+++ b/demo/src/main/kotlin/dev/hrach/navigation/demo/screens/Modal1.kt
@@ -1,22 +1,30 @@
package dev.hrach.navigation.demo.screens
import android.annotation.SuppressLint
+import androidx.activity.compose.BackHandler
import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
import androidx.compose.foundation.layout.WindowInsets
import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.systemBars
import androidx.compose.foundation.layout.windowInsetsPadding
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.OutlinedButton
import androidx.compose.material3.Surface
+import androidx.compose.material3.Switch
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
+import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
import androidx.navigation.NavController
import dev.hrach.navigation.demo.Destinations
import dev.hrach.navigation.results.NavigationResultEffect
@@ -33,6 +41,7 @@ internal fun Modal1(navController: NavController) {
}
Modal1(
navigate = navController::navigate,
+ close = navController::popBackStack,
bottomSheetResult = bottomSheetResult,
)
}
@@ -40,8 +49,14 @@ internal fun Modal1(navController: NavController) {
@Composable
private fun Modal1(
navigate: (Any) -> Unit,
+ close: () -> Unit,
bottomSheetResult: Int,
) {
+ var disableBackHandling by rememberSaveable { mutableStateOf(false) }
+ BackHandler(disableBackHandling) {
+ // no-op
+ }
+
Surface(
color = MaterialTheme.colorScheme.inverseSurface,
) {
@@ -58,6 +73,18 @@ private fun Modal1(
Text("BottomSheet")
}
Text("BottomSheetResult: $bottomSheetResult")
+
+ Spacer(Modifier.height(32.dp))
+
+ Row(verticalAlignment = Alignment.CenterVertically) {
+ Text("Disable back handling")
+ Switch(disableBackHandling, onCheckedChange = { disableBackHandling = it })
+ }
+
+ Spacer(Modifier.height(32.dp))
+ OutlinedButton(onClick = close) {
+ Text("Close")
+ }
}
}
}
diff --git a/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetDialog.kt b/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetDialog.kt
index 70edf41..582c25d 100644
--- a/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetDialog.kt
+++ b/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetDialog.kt
@@ -7,16 +7,10 @@ import android.view.View
import android.view.ViewOutlineProvider
import android.view.Window
import android.view.WindowManager
-import android.window.BackEvent
-import android.window.OnBackAnimationCallback
-import android.window.OnBackInvokedCallback
-import android.window.OnBackInvokedDispatcher
import androidx.activity.BackEventCompat
import androidx.activity.ComponentDialog
-import androidx.activity.addCallback
+import androidx.activity.compose.PredictiveBackHandler
import androidx.activity.setViewTreeOnBackPressedDispatcherOwner
-import androidx.annotation.DoNotInline
-import androidx.annotation.RequiresApi
import androidx.appcompat.view.ContextThemeWrapper
import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.Box
@@ -29,7 +23,6 @@ import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCompositionContext
-import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.rememberUpdatedState
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
@@ -54,16 +47,7 @@ import androidx.lifecycle.setViewTreeViewModelStoreOwner
import androidx.savedstate.findViewTreeSavedStateRegistryOwner
import androidx.savedstate.setViewTreeSavedStateRegistryOwner
import java.util.UUID
-import java.util.concurrent.CancellationException
-import kotlinx.coroutines.CoroutineScope
-import kotlinx.coroutines.channels.BufferOverflow.SUSPEND
-import kotlinx.coroutines.channels.Channel
-import kotlinx.coroutines.channels.Channel.Factory.BUFFERED
import kotlinx.coroutines.flow.Flow
-import kotlinx.coroutines.flow.consumeAsFlow
-import kotlinx.coroutines.flow.flowOf
-import kotlinx.coroutines.flow.onCompletion
-import kotlinx.coroutines.launch
@Composable
internal fun ModalSheetDialog(
@@ -79,18 +63,16 @@ internal fun ModalSheetDialog(
val dialogId = rememberSaveable { UUID.randomUUID() }
val darkThemeEnabled = isSystemInDarkTheme()
val currentOnPredictiveBack = rememberUpdatedState(onPredictiveBack)
- val scope = rememberCoroutineScope()
val dialog = remember(view, density) {
ModalSheetDialogWrapper(
- currentOnPredictiveBack,
- view,
- scope,
- securePolicy,
- layoutDirection,
- density,
- dialogId,
- darkThemeEnabled,
+ onPredictiveBack = currentOnPredictiveBack,
+ composeView = view,
+ securePolicy = securePolicy,
+ layoutDirection = layoutDirection,
+ density = density,
+ dialogId = dialogId,
+ darkThemeEnabled = darkThemeEnabled,
).apply {
setContent(composition) {
Box(
@@ -124,10 +106,8 @@ private class ModalSheetDialogLayout(
context: Context,
override val window: Window,
private val onPredictiveBack: State) -> Unit>,
- private val scope: CoroutineScope,
) : AbstractComposeView(context), DialogWindowProvider {
private var content: @Composable () -> Unit by mutableStateOf({})
- private var backCallback: Any? = null
override var shouldCreateCompositionOnAttachedToWindow: Boolean = false
private set
@@ -140,111 +120,9 @@ private class ModalSheetDialogLayout(
@Composable
override fun Content() {
+ PredictiveBackHandler(onBack = onPredictiveBack.value)
content()
}
-
- override fun onAttachedToWindow() {
- super.onAttachedToWindow()
- maybeRegisterBackCallback()
- }
-
- override fun onDetachedFromWindow() {
- super.onDetachedFromWindow()
- maybeUnregisterBackCallback()
- }
-
- private fun maybeRegisterBackCallback() {
- if (Build.VERSION.SDK_INT < 33) return
- if (backCallback == null) {
- backCallback = when {
- Build.VERSION.SDK_INT >= 34 -> Api34Impl.createBackCallback(onPredictiveBack, scope)
- else -> Api33Impl.createBackCallback(onPredictiveBack, scope)
- }
- }
- Api33Impl.maybeRegisterBackCallback(this, backCallback)
- }
-
- private fun maybeUnregisterBackCallback() {
- if (Build.VERSION.SDK_INT >= 33) {
- Api33Impl.maybeUnregisterBackCallback(this, backCallback)
- }
- backCallback = null
- }
-
- @RequiresApi(34)
- private object Api34Impl {
- @JvmStatic
- @DoNotInline
- fun createBackCallback(
- currentOnBack: State) -> Unit>,
- scope: CoroutineScope,
- ) = object : OnBackAnimationCallback {
- var onBackInstance: OnBackInstance? = null
-
- override fun onBackStarted(backEvent: BackEvent) {
- onBackInstance?.cancel()
- onBackInstance = OnBackInstance(scope, true, currentOnBack.value)
- }
-
- override fun onBackProgressed(backEvent: BackEvent) {
- onBackInstance?.send(BackEventCompat(backEvent))
- }
-
- override fun onBackInvoked() {
- onBackInstance?.apply {
- if (!isPredictiveBack) {
- cancel()
- onBackInstance = null
- }
- }
- if (onBackInstance == null) {
- onBackInstance = OnBackInstance(scope, false, currentOnBack.value)
- }
- onBackInstance?.close()
- onBackInstance?.isPredictiveBack = false
- }
-
- override fun onBackCancelled() {
- onBackInstance?.cancel()
- onBackInstance = null
- onBackInstance?.isPredictiveBack = false
- }
- }
- }
-
- @RequiresApi(33)
- private object Api33Impl {
- @JvmStatic
- @DoNotInline
- fun createBackCallback(
- currentOnBack: State) -> Unit>,
- scope: CoroutineScope,
- ) {
- OnBackInvokedCallback {
- scope.launch {
- currentOnBack.value.invoke(flowOf())
- }
- }
- }
-
- @JvmStatic
- @DoNotInline
- fun maybeRegisterBackCallback(view: View, backCallback: Any?) {
- if (backCallback !is OnBackInvokedCallback) return
- val dispatcher = view.findOnBackInvokedDispatcher() ?: return
- dispatcher.registerOnBackInvokedCallback(
- OnBackInvokedDispatcher.PRIORITY_OVERLAY,
- backCallback,
- )
- }
-
- @JvmStatic
- @DoNotInline
- fun maybeUnregisterBackCallback(view: View, backCallback: Any?) {
- if (backCallback !is OnBackInvokedCallback) return
- view.findOnBackInvokedDispatcher()?.unregisterOnBackInvokedCallback(backCallback)
- }
- }
}
// Fork of androidx.compose.ui.window.DialogWrapper.
@@ -253,7 +131,6 @@ private class ModalSheetDialogLayout(
internal class ModalSheetDialogWrapper(
onPredictiveBack: State) -> Unit>,
private val composeView: View,
- scope: CoroutineScope,
securePolicy: SecureFlagPolicy,
layoutDirection: LayoutDirection,
density: Density,
@@ -274,10 +151,9 @@ internal class ModalSheetDialogWrapper(
window.setBackgroundDrawableResource(android.R.color.transparent)
WindowCompat.setDecorFitsSystemWindows(window, false)
dialogLayout = ModalSheetDialogLayout(
- context,
- window,
- onPredictiveBack,
- scope,
+ context = context,
+ window = window,
+ onPredictiveBack = onPredictiveBack,
).apply {
// Set unique id for AbstractComposeView. This allows state restoration for the state
// defined inside the Dialog via rememberSaveable()
@@ -310,17 +186,6 @@ internal class ModalSheetDialogWrapper(
dialogLayout.setViewTreeOnBackPressedDispatcherOwner(this)
// Initial setup
updateParameters(securePolicy, layoutDirection, darkThemeEnabled)
-
- // Due to how the onDismissRequest callback works
- // (it enforces a just-in-time decision on whether to update the state to hide the dialog)
- // we need to unconditionally add a callback here that is always enabled,
- // meaning we'll never get a system UI controlled predictive back animation
- // for these dialogs
- onBackPressedDispatcher.addCallback(this) {
- scope.launch {
- onPredictiveBack.value.invoke(flowOf())
- }
- }
}
private fun setLayoutDirection(layoutDirection: LayoutDirection) {
@@ -383,35 +248,6 @@ internal class ModalSheetDialogWrapper(
}
}
-private class OnBackInstance(
- scope: CoroutineScope,
- var isPredictiveBack: Boolean,
- onBack: suspend (progress: Flow) -> Unit,
-) {
- val channel = Channel(capacity = BUFFERED, onBufferOverflow = SUSPEND)
- val job = scope.launch {
- var completed = false
- onBack(
- channel.consumeAsFlow().onCompletion {
- completed = true
- },
- )
- check(completed) {
- "You must collect the progress flow"
- }
- }
-
- fun send(backEvent: BackEventCompat) = channel.trySend(backEvent)
-
- // idempotent if invoked more than once
- fun close() = channel.close()
-
- fun cancel() {
- channel.cancel(CancellationException("onBack cancelled"))
- job.cancel()
- }
-}
-
internal fun View.isFlagSecureEnabled(): Boolean {
val windowParams = rootView.layoutParams as? WindowManager.LayoutParams
if (windowParams != null) {
diff --git a/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetHost.kt b/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetHost.kt
index 8ecb759..3c0d77a 100644
--- a/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetHost.kt
+++ b/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetHost.kt
@@ -21,19 +21,14 @@ import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableFloatStateOf
-import androidx.compose.runtime.mutableStateListOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.saveable.rememberSaveableStateHolder
import androidx.compose.runtime.setValue
-import androidx.compose.runtime.snapshots.SnapshotStateList
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
-import androidx.compose.ui.platform.LocalInspectionMode
import androidx.compose.ui.window.SecureFlagPolicy
-import androidx.lifecycle.Lifecycle
-import androidx.lifecycle.LifecycleEventObserver
import androidx.navigation.NavBackStackEntry
import androidx.navigation.NavDestination.Companion.hierarchy
import androidx.navigation.compose.LocalOwnersProvider
@@ -57,24 +52,14 @@ public fun ModalSheetHost(
sizeTransform: (AnimatedContentTransitionScope.() -> @JvmSuppressWildcards SizeTransform?)? =
null,
) {
- val modalBackStack by modalSheetNavigator.backStack.collectAsState(listOf())
-
var progress by remember { mutableFloatStateOf(0f) }
var inPredictiveBack by remember { mutableStateOf(false) }
-
val zIndices = remember { mutableMapOf() }
val saveableStateHolder = rememberSaveableStateHolder()
- val visibleEntries = rememberVisibleList(modalBackStack)
- visibleEntries.PopulateVisibleList(modalBackStack)
-
- val currentBackStack = if (LocalInspectionMode.current) {
- modalSheetNavigator.backStack.collectAsState(emptyList()).value
- } else {
- visibleEntries
- }
- val backStackEntry: NavBackStackEntry? = currentBackStack.lastOrNull()
+ val modalBackStack by modalSheetNavigator.backStack.collectAsState(listOf())
+ val backStackEntry: NavBackStackEntry? = modalBackStack.lastOrNull()
val finalEnter: AnimatedContentTransitionScope.() -> EnterTransition = {
val targetDestination = targetState.destination as ModalSheetNavigator.Destination
@@ -90,7 +75,6 @@ public fun ModalSheetHost(
}
val finalExit: AnimatedContentTransitionScope.() -> ExitTransition = {
val initialDestination = initialState.destination as ModalSheetNavigator.Destination
-
if (modalSheetNavigator.isPop.value) {
initialDestination.hierarchy.firstNotNullOfOrNull { destination ->
null // destination.createPopExitTransition(this)
@@ -103,7 +87,6 @@ public fun ModalSheetHost(
}
val finalSizeTransform: AnimatedContentTransitionScope.() -> SizeTransform? = {
val targetDestination = targetState.destination as ModalSheetNavigator.Destination
-
targetDestination.hierarchy.firstNotNullOfOrNull { destination ->
null // destination.createSizeTransform(this)
} ?: sizeTransform?.invoke(this)
@@ -115,14 +98,16 @@ public fun ModalSheetHost(
// scope exposed by the transitions on the NavHost and composable APIs.
SeekableTransitionState(backStackEntry)
}
+ val transitionsInProgress = modalSheetNavigator.transitionsInProgress.collectAsState().value
val transition = rememberTransition(transitionState, label = "entry")
val nothingToShow = transition.currentState == transition.targetState &&
transition.currentState == null &&
- backStackEntry == null
+ backStackEntry == null &&
+ transitionsInProgress.isEmpty()
if (inPredictiveBack) {
LaunchedEffect(progress) {
- val previousEntry = currentBackStack.getOrNull(currentBackStack.size - 2)
+ val previousEntry = modalBackStack.getOrNull(modalBackStack.size - 2)
transitionState.seekTo(progress, previousEntry)
}
} else {
@@ -163,10 +148,12 @@ public fun ModalSheetHost(
?: SecureFlagPolicy.Inherit
ModalSheetDialog(
- onPredictiveBack = { backEvent ->
+ onPredictiveBack = onPredictBack@{ backEvent ->
progress = 0f
- val currentBackStackEntry = modalBackStack.lastOrNull()
- modalSheetNavigator.prepareForTransition(currentBackStackEntry!!)
+ // early return: already animating backstack out, repeated back handling
+ // probably reproducible only with slowed animations
+ val currentBackStackEntry = modalBackStack.lastOrNull() ?: return@onPredictBack
+ modalSheetNavigator.prepareForTransition(currentBackStackEntry)
val previousEntry = modalBackStack.getOrNull(modalBackStack.size - 2)
if (previousEntry != null) {
modalSheetNavigator.prepareForTransition(previousEntry)
@@ -186,17 +173,20 @@ public fun ModalSheetHost(
) {
transition.AnimatedContent(
modifier = modifier
- .background(if (transition.targetState == null) Color.Unspecified else containerColor),
+ .background(if (transition.targetState == null || transition.currentState == null) Color.Transparent else containerColor),
contentAlignment = Alignment.TopStart,
transitionSpec = block@{
+ @Suppress("UNCHECKED_CAST")
val initialState = initialState ?: return@block ContentTransform(
- fadeIn(),
+ enterTransition(this as AnimatedContentTransitionScope),
fadeOut(), // irrelevant
0f,
)
+
+ @Suppress("UNCHECKED_CAST")
val targetState = targetState ?: return@block ContentTransform(
fadeIn(), // irrelevant
- fadeOut(),
+ exitTransition(this as AnimatedContentTransitionScope),
0f,
)
@@ -218,13 +208,7 @@ public fun ModalSheetHost(
sizeTransform = finalSizeTransform(this),
)
},
- ) {
- val currentEntry = if (inPredictiveBack) {
- it
- } else {
- visibleEntries.lastOrNull { entry -> it == entry }
- }
-
+ ) { currentEntry ->
if (currentEntry == null) {
Box(Modifier.fillMaxSize()) {}
return@AnimatedContent
@@ -251,58 +235,3 @@ public fun ModalSheetHost(
}
}
}
-
-@Suppress("ComposeUnstableCollections")
-@Composable
-internal fun MutableList.PopulateVisibleList(
- transitionsInProgress: List,
-) {
- val isInspecting = LocalInspectionMode.current
- transitionsInProgress.forEach { entry ->
- DisposableEffect(entry.lifecycle) {
- val observer = LifecycleEventObserver { _, event ->
- // show dialog in preview
- if (isInspecting && !contains(entry)) {
- add(entry)
- }
- // ON_START -> add to visibleBackStack, ON_STOP -> remove from visibleBackStack
- if (event == Lifecycle.Event.ON_START) {
- // We want to treat the visible lists as sets, but we want to keep
- // the functionality of mutableStateListOf() so that we recompose in response
- // to adds and removes.
- if (!contains(entry)) {
- add(entry)
- }
- }
- if (event == Lifecycle.Event.ON_STOP) {
- remove(entry)
- }
- }
- entry.lifecycle.addObserver(observer)
- onDispose {
- entry.lifecycle.removeObserver(observer)
- }
- }
- }
-}
-
-@Composable
-internal fun rememberVisibleList(
- transitionsInProgress: List,
-): SnapshotStateList {
- // show dialog in preview
- val isInspecting = LocalInspectionMode.current
- return remember(transitionsInProgress) {
- mutableStateListOf().also {
- it.addAll(
- transitionsInProgress.filter { entry ->
- if (isInspecting) {
- true
- } else {
- entry.lifecycle.currentState.isAtLeast(Lifecycle.State.STARTED)
- }
- },
- )
- }
- }
-}
diff --git a/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetNavigator.kt b/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetNavigator.kt
index 083faec..774b4b6 100644
--- a/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetNavigator.kt
+++ b/modalsheet/src/main/kotlin/dev/hrach/navigation/modalsheet/ModalSheetNavigator.kt
@@ -18,6 +18,7 @@ import dev.hrach.navigation.modalsheet.ModalSheetNavigator.Destination
@Navigator.Name("ModalSheetNavigator")
public class ModalSheetNavigator : Navigator() {
internal val backStack get() = state.backStack
+ internal val transitionsInProgress get() = state.transitionsInProgress
internal val isPop = mutableStateOf(false)