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-jvm Show documentation
Show all versions of extensions-compose-jvm Show documentation
Kotlin Multiplatform lifecycle-aware business logic components
package com.arkivanov.decompose.extensions.compose.stack.animation
import androidx.compose.foundation.layout.Box
import androidx.compose.runtime.Composable
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 androidx.compose.ui.input.pointer.pointerInput
import com.arkivanov.decompose.Child
import com.arkivanov.decompose.router.stack.ChildStack
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)) }
if (stack.active.configuration != currentStack.active.configuration) {
val oldStack = currentStack
currentStack = stack
items = getAnimationItems(newStack = currentStack, oldStack = oldStack)
}
Box(modifier = modifier) {
items.forEach { (configuration, item) ->
key(configuration) {
Child(
item = item,
onFinished = {
if (item.direction.isExit) {
items -= configuration
} else {
items += (configuration to item.copy(otherChild = null))
}
},
content = content,
)
}
}
// A workaround until https://issuetracker.google.com/issues/214231672.
// Normally only the exiting child should be disabled.
if (disableInputDuringAnimation && (items.size > 1)) {
Overlay(modifier = Modifier.matchParentSize())
}
}
}
@Composable
private fun Overlay(modifier: Modifier) {
Box(
modifier = modifier.pointerInput(Unit) {
awaitPointerEventScope {
while (true) {
val event = awaitPointerEvent()
event.changes.forEach { it.consume() }
}
}
}
)
}
private fun getAnimationItems(newStack: ChildStack, oldStack: ChildStack?): Map> =
when {
oldStack == null ->
listOf(AnimationItem(child = newStack.active, direction = Direction.ENTER_FRONT, isInitial = true))
(newStack.size < oldStack.size) && (newStack.active.configuration 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.configuration }
private val ChildStack<*, *>.size: Int
get() = items.size
private operator fun Iterable>.contains(config: C): Boolean =
any { it.configuration == config }
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