All Downloads are FREE. Search and download functionalities are using the official Maven repository.

commonMain.cafe.adriel.voyager.transitions.ScreenTransition.kt Maven / Gradle / Ivy

The newest version!
package cafe.adriel.voyager.transitions

import androidx.compose.animation.AnimatedContent
import androidx.compose.animation.AnimatedContentTransitionScope
import androidx.compose.animation.AnimatedVisibilityScope
import androidx.compose.animation.ContentTransform
import androidx.compose.animation.EnterTransition
import androidx.compose.animation.ExitTransition
import androidx.compose.animation.ExperimentalAnimationApi
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.MutableState
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.saveable.Saver
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import cafe.adriel.voyager.core.annotation.ExperimentalVoyagerApi
import cafe.adriel.voyager.core.screen.Screen
import cafe.adriel.voyager.core.stack.StackEvent
import cafe.adriel.voyager.navigator.Navigator

@ExperimentalVoyagerApi
public interface ScreenTransition {

    /**
     * Defines the enter transition for the Screen.
     *
     * @param lastEvent - lastEvent in the navigation stack.
     * @return EnterTransition or null when it should not define a transition for this screen.
     */
    public fun enter(lastEvent: StackEvent): EnterTransition? = null

    /**
     * Defines the exit transition for the Screen.
     *
     * @param lastEvent - lastEvent in the navigation stack.
     * @return ExitTransition or null when it should not define a transition for this screen.
     */
    public fun exit(lastEvent: StackEvent): ExitTransition? = null

    /**
     * Defines the z-index for the Screen.
     *
     * @param lastEvent - lastEvent in the navigation stack.
     * @return Float value for the z-index.
     */
    public fun zIndex(lastEvent: StackEvent): Float? = null
}

public typealias ScreenTransitionContent = @Composable AnimatedVisibilityScope.(Screen) -> Unit

@ExperimentalVoyagerApi
@Composable
public fun ScreenTransition(
    navigator: Navigator,
    enterTransition: AnimatedContentTransitionScope.() -> ContentTransform,
    exitTransition: AnimatedContentTransitionScope.() -> ContentTransform,
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    disposeScreenAfterTransitionEnd: Boolean = false,
    contentKey: (Screen) -> Any = { it.key },
    content: ScreenTransitionContent = { it.Content() }
) {
    ScreenTransition(
        navigator = navigator,
        modifier = modifier,
        contentAlignment = contentAlignment,
        disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd,
        contentKey = contentKey,
        content = content,
        transition = {
            when (navigator.lastEvent) {
                StackEvent.Pop -> exitTransition()
                else -> enterTransition()
            }
        }
    )
}

@Composable
public fun ScreenTransition(
    navigator: Navigator,
    enterTransition: AnimatedContentTransitionScope.() -> ContentTransform,
    exitTransition: AnimatedContentTransitionScope.() -> ContentTransform,
    modifier: Modifier = Modifier,
    content: ScreenTransitionContent = { it.Content() }
) {
    ScreenTransition(
        navigator = navigator,
        transition = {
            when (navigator.lastEvent) {
                StackEvent.Pop -> exitTransition()
                else -> enterTransition()
            }
        },
        modifier = modifier,
        content = content
    )
}

@ExperimentalVoyagerApi
@Composable
public fun ScreenTransition(
    navigator: Navigator,
    defaultTransition: ScreenTransition,
    modifier: Modifier = Modifier,
    contentZIndex: Float = 0f,
    contentAlignment: Alignment = Alignment.TopStart,
    disposeScreenAfterTransitionEnd: Boolean = false,
    contentKey: (Screen) -> Any = { it.key },
    content: ScreenTransitionContent = { it.Content() }
) {
    ScreenTransition(
        navigator = navigator,
        transition = {
            val enter = defaultTransition.enter(navigator.lastEvent) ?: EnterTransition.None
            val exit = defaultTransition.exit(navigator.lastEvent) ?: ExitTransition.None
            ContentTransform(
                targetContentEnter = enter,
                initialContentExit = exit,
                targetContentZIndex = contentZIndex
            )
        },
        modifier = modifier,
        contentAlignment = contentAlignment,
        disposeScreenAfterTransitionEnd = disposeScreenAfterTransitionEnd,
        contentKey = contentKey,
        content = content
    )
}

@Composable
public fun ScreenTransition(
    navigator: Navigator,
    transition: AnimatedContentTransitionScope.() -> ContentTransform,
    modifier: Modifier = Modifier,
    content: ScreenTransitionContent = { it.Content() }
) {
    ScreenTransition(
        navigator = navigator,
        transition = transition,
        modifier = modifier,
        disposeScreenAfterTransitionEnd = false,
        content = content
    )
}

@ExperimentalVoyagerApi
@OptIn(ExperimentalAnimationApi::class)
@Composable
public fun ScreenTransition(
    navigator: Navigator,
    transition: AnimatedContentTransitionScope.() -> ContentTransform,
    modifier: Modifier = Modifier,
    contentAlignment: Alignment = Alignment.TopStart,
    disposeScreenAfterTransitionEnd: Boolean = false,
    contentKey: (Screen) -> Any = { it.key },
    content: ScreenTransitionContent = { it.Content() }
) {
    val screenCandidatesToDispose = rememberSaveable(saver = screenCandidatesToDisposeSaver()) {
        mutableStateOf(emptySet())
    }

    val currentScreens = navigator.items

    if (disposeScreenAfterTransitionEnd) {
        DisposableEffect(currentScreens) {
            onDispose {
                val newScreenKeys = navigator.items.map { it.key }
                screenCandidatesToDispose.value += currentScreens.filter { it.key !in newScreenKeys }
            }
        }
    }

    AnimatedContent(
        targetState = navigator.lastItem,
        transitionSpec = {
            val contentTransform = transition()

            val sourceScreenTransition = when (navigator.lastEvent) {
                StackEvent.Pop, StackEvent.Replace -> initialState
                else -> targetState
            } as? ScreenTransition

            val screenEnterTransition = sourceScreenTransition?.enter(navigator.lastEvent)
                ?: contentTransform.targetContentEnter

            val screenExitTransition = sourceScreenTransition?.exit(navigator.lastEvent)
                ?: contentTransform.initialContentExit

            val screenContentZIndex = sourceScreenTransition?.zIndex(navigator.lastEvent)
                ?: contentTransform.targetContentZIndex

            ContentTransform(
                targetContentEnter = screenEnterTransition,
                initialContentExit = screenExitTransition,
                targetContentZIndex = screenContentZIndex,
                sizeTransform = contentTransform.sizeTransform
            )
        },
        contentAlignment = contentAlignment,
        contentKey = contentKey,
        modifier = modifier
    ) { screen ->
        if (this.transition.targetState == this.transition.currentState && disposeScreenAfterTransitionEnd) {
            LaunchedEffect(Unit) {
                val newScreens = navigator.items.map { it.key }
                val screensToDispose = screenCandidatesToDispose.value.filterNot { it.key in newScreens }
                if (screensToDispose.isNotEmpty()) {
                    screensToDispose.forEach { navigator.dispose(it) }
                    navigator.clearEvent()
                }
                screenCandidatesToDispose.value = emptySet()
            }
        }

        navigator.saveableState("transition", screen) {
            content(screen)
        }
    }
}

private fun screenCandidatesToDisposeSaver(): Saver>, List> {
    return Saver(
        save = { it.value.toList() },
        restore = { mutableStateOf(it.toSet()) }
    )
}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy