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

net.peanuuutz.fork.ui.foundation.animation.AnimatedVisibility.kt Maven / Gradle / Ivy

/*
 * Copyright 2020 The Android Open Source Project
 * Modifications Copyright 2022 Peanuuutz
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package net.peanuuutz.fork.ui.foundation.animation

import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.Stable
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.runtime.snapshotFlow
import net.peanuuutz.fork.ui.animation.Transition
import net.peanuuutz.fork.ui.animation.TransitionState
import net.peanuuutz.fork.ui.animation.derive
import net.peanuuutz.fork.ui.animation.rememberUpdatedTransition
import net.peanuuutz.fork.ui.foundation.animation.EnterExitState.PostExit
import net.peanuuutz.fork.ui.foundation.animation.EnterExitState.PreEnter
import net.peanuuutz.fork.ui.foundation.animation.EnterExitState.Visible
import net.peanuuutz.fork.ui.foundation.layout.Box
import net.peanuuutz.fork.ui.ui.layout.Alignment
import net.peanuuutz.fork.ui.ui.modifier.Modifier
import net.peanuuutz.fork.util.common.getValue
import net.peanuuutz.fork.util.common.ref
import net.peanuuutz.fork.util.common.setValue

@Stable
sealed interface AnimatedVisibilityScope {
    val transition: Transition
}

@Composable
fun AnimatedVisibility(
    isVisible: Boolean,
    modifier: Modifier = Modifier,
    effect: EnterExitEffect = remember { Fade() + ExpandShrink(Alignment.CenterOnPlane) },
    contentAlignment: Alignment = Alignment.CenterOnPlane,
    content: @Composable AnimatedVisibilityScope.() -> Unit
) {
    val transition = rememberUpdatedTransition(isVisible)
    transition.AnimatedVisibility(
        visibilityConvertor = { it },
        modifier = modifier,
        effect = effect,
        contentAlignment = contentAlignment,
        content = content
    )
}

@Composable
fun AnimatedVisibility(
    visibilityTransitionState: TransitionState,
    modifier: Modifier = Modifier,
    effect: EnterExitEffect = remember { Fade() + ExpandShrink(Alignment.CenterOnPlane) },
    contentAlignment: Alignment = Alignment.CenterOnPlane,
    content: @Composable AnimatedVisibilityScope.() -> Unit
) {
    val transition = rememberUpdatedTransition(visibilityTransitionState)
    transition.AnimatedVisibility(
        visibilityConvertor = { it },
        modifier = modifier,
        effect = effect,
        contentAlignment = contentAlignment,
        content = content
    )
}

@Composable
fun  Transition.AnimatedVisibility(
    visibilityConvertor: (T) -> Boolean,
    modifier: Modifier = Modifier,
    effect: EnterExitEffect = remember { Fade() + ExpandShrink(Alignment.CenterOnPlane) },
    contentAlignment: Alignment = Alignment.CenterOnPlane,
    content: @Composable AnimatedVisibilityScope.() -> Unit
) {
    var isAnimationVisible by remember { mutableStateOf(visibilityConvertor(state)) }
    if (visibilityConvertor(targetState) || isAnimationVisible) {
        val child = derive { parentState ->
            key(this) {
                var isVisibleBefore by remember { ref(false) }
                if (visibilityConvertor(state)) {
                    isVisibleBefore = true
                }
                when {
                    visibilityConvertor(parentState) -> Visible
                    isVisibleBefore -> PostExit
                    else -> PreEnter
                }
            }
        }

        LaunchedEffect(child) {
            snapshotFlow {
                child.state.isVisible || child.targetState.isVisible
            }.collect { isVisible ->
                isAnimationVisible = isVisible
            }
        }

        child.AnimatedEnterExit(
            modifier = modifier,
            effect = effect,
            contentAlignment = contentAlignment,
            content = content
        )
    }
}

// ======== Internal ========

@Composable
private fun Transition.AnimatedEnterExit(
    modifier: Modifier,
    effect: EnterExitEffect,
    contentAlignment: Alignment,
    content: @Composable AnimatedVisibilityScope.() -> Unit
) {
    val effectModifier = remember(this, effect) {
        effect.fold(Modifier) { acc, element ->
            val elementModifier = with(element) {
                Modifier.transition(this@AnimatedEnterExit)
            }
            acc then elementModifier
        }
    }
    val scope = remember(this) { AnimatedVisibilityScopeImpl(this) }

    Box(
        modifier = modifier then effectModifier,
        contentAlignment = contentAlignment
    ) {
        scope.content()
    }
}

private data class AnimatedVisibilityScopeImpl(
    override val transition: Transition
) : AnimatedVisibilityScope




© 2015 - 2025 Weber Informatics LLC | Privacy Policy