commonMain.com.arkivanov.decompose.extensions.compose.stack.animation.AbstractStackAnimation.kt Maven / Gradle / Ivy
Go to download
Show more of this group Show more artifacts with this name
Show all versions of extensions-compose Show documentation
Show all versions of extensions-compose Show documentation
Kotlin Multiplatform lifecycle-aware business logic components
The newest version!
package com.arkivanov.decompose.extensions.compose.stack.animation
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
import androidx.compose.runtime.DisposableEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.key
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import com.arkivanov.decompose.Child
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.utils.InputConsumingOverlay
import com.arkivanov.decompose.router.stack.ChildStack
@OptIn(ExperimentalDecomposeApi::class)
internal abstract class AbstractStackAnimation(
private val disableInputDuringAnimation: Boolean,
) : StackAnimation {
@Composable
protected abstract fun Child(
item: AnimationItem,
onFinished: () -> Unit,
content: @Composable (child: Child.Created) -> Unit,
)
@Composable
override operator fun invoke(stack: ChildStack, modifier: Modifier, content: @Composable (child: Child.Created) -> Unit) {
var currentStack by remember { mutableStateOf(stack) }
var items by remember { mutableStateOf(getAnimationItems(newStack = currentStack, oldStack = null)) }
var nextItems: Map>? by remember { mutableStateOf(null) }
if (stack.active.key != currentStack.active.key || stack.active.instance != currentStack.active.instance) {
val oldStack = currentStack
currentStack = stack
val newItems = getAnimationItems(newStack = currentStack, oldStack = oldStack)
if (items.size == 1) {
items = newItems
} else {
nextItems = newItems
}
}
Box(modifier = modifier) {
items.forEach { (key, item) ->
key(key) {
Child(
item = item,
onFinished = {
if (item.direction.isExit) {
items -= key
} else {
items += (key to item.copy(otherChild = null))
}
},
content = content,
)
if (item.direction.isExit) {
DisposableEffect(Unit) {
onDispose {
nextItems?.also { items = it }
nextItems = null
}
}
}
}
}
// A workaround until https://issuetracker.google.com/issues/214231672.
// Normally only the exiting child should be disabled.
if (disableInputDuringAnimation && ((items.size > 1) || (nextItems != null))) {
InputConsumingOverlay(modifier = Modifier.matchParentSize())
}
}
}
private fun getAnimationItems(newStack: ChildStack, oldStack: ChildStack?): Map> =
when {
(oldStack == null) || (newStack.active.key == oldStack.active.key) ->
listOf(AnimationItem(child = newStack.active, direction = Direction.ENTER_FRONT, isInitial = true))
(newStack.size < oldStack.size) && (newStack.active.key in oldStack.backStack) ->
listOf(
AnimationItem(child = newStack.active, direction = Direction.ENTER_BACK, otherChild = oldStack.active),
AnimationItem(child = oldStack.active, direction = Direction.EXIT_FRONT, otherChild = newStack.active),
)
else ->
listOf(
AnimationItem(child = oldStack.active, direction = Direction.EXIT_BACK, otherChild = newStack.active),
AnimationItem(child = newStack.active, direction = Direction.ENTER_FRONT, otherChild = oldStack.active),
)
}.associateBy { it.child.key }
private val ChildStack<*, *>.size: Int
get() = items.size
private operator fun Iterable>.contains(key: Any): Boolean =
any { it.key == key }
protected data class AnimationItem(
val child: Child.Created,
val direction: Direction,
val isInitial: Boolean = false,
val otherChild: Child.Created? = null,
)
}
© 2015 - 2024 Weber Informatics LLC | Privacy Policy