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

commonMain.io.github.alexzhirkevich.cupertino.decompose.CupertinoPredictiveBack.kt Maven / Gradle / Ivy

/*
 * Copyright (c) 2023-2024. Compose Cupertino project and open source contributors.
 *
 *  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 io.github.alexzhirkevich.cupertino.decompose

import androidx.compose.animation.core.Animatable
import androidx.compose.animation.core.FiniteAnimationSpec
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.composed
import androidx.compose.ui.draw.drawWithContent
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.graphicsLayer
import androidx.compose.ui.platform.LocalLayoutDirection
import androidx.compose.ui.unit.LayoutDirection
import com.arkivanov.decompose.ExperimentalDecomposeApi
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.Direction
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimation
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.StackAnimator
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.predictiveback.PredictiveBackAnimatable
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.predictiveback.predictiveBackAnimation
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimation
import com.arkivanov.decompose.extensions.compose.jetbrains.stack.animation.stackAnimator
import com.arkivanov.essenty.backhandler.BackEvent
import com.arkivanov.essenty.backhandler.BackHandler
import io.github.alexzhirkevich.cupertino.cupertinoTween

@ExperimentalDecomposeApi
@Composable
fun  cupertinoPredictiveBackAnimation(
    backHandler: BackHandler,
    onBack : () -> Unit,
    animation: StackAnimation? = stackAnimation(
        animator = cupertinoStackAnimator(),
        disableInputDuringAnimation = true
    )
) : StackAnimation = predictiveBackAnimation(
    backHandler = backHandler,
    animation = animation,
    onBack = onBack,
    selector = { initialBackEvent, _, _ ->
        cupertinoPredictiveBackAnimatable(
            initialBackEvent = initialBackEvent
        )
    }
)


@ExperimentalDecomposeApi
fun cupertinoPredictiveBackAnimatable(
    initialBackEvent: BackEvent
) : PredictiveBackAnimatable =  CupertinoPredictiveBackAnimatable(
    initialBackEvent = initialBackEvent
)

@OptIn(ExperimentalDecomposeApi::class)
internal class CupertinoPredictiveBackAnimatable(
    initialBackEvent: BackEvent,
) : PredictiveBackAnimatable {

    private val progressAnimatable = Animatable(initialValue = initialBackEvent.progress)

    private var swipeEdge by mutableStateOf(initialBackEvent.swipeEdge)

    override val exitModifier: Modifier = Modifier
        .cupertinoPredictiveExit { progressAnimatable.value }
    override val enterModifier: Modifier get() = Modifier
        .cupertinoPredictiveEnter { progressAnimatable.value }

    override suspend fun animate(event: BackEvent) {
        swipeEdge = event.swipeEdge

        progressAnimatable.snapTo(targetValue = event.progress)
    }

    override suspend fun finish() {
        progressAnimatable.animateTo(targetValue = 1F)
    }
}

fun cupertinoStackAnimator(
    animationSpec: FiniteAnimationSpec = cupertinoTween()
) : StackAnimator = stackAnimator(
    animationSpec = animationSpec,
) { factor, direction, content ->
    content(
        Modifier.composed {

            val layoutDirection = LocalLayoutDirection.current

            graphicsLayer {
                translationX = size.width * when (direction) {
                    Direction.ENTER_FRONT,
                    Direction.EXIT_FRONT -> factor

                    else -> factor * SlideFactor
                }

                if (layoutDirection == LayoutDirection.Rtl)
                    translationX = -translationX
            }
        }
    )
}

fun Modifier.cupertinoPredictiveEnter(progress: () -> Float) : Modifier = composed {

    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl

    graphicsLayer {
        translationX = (progress() - 1f) * SlideFactor * size.width

        if (isRtl)
            translationX = -translationX

    }.drawWithContent {
        drawContent()
        drawRect(Color.Black, alpha = ((1f - progress()) * SlideFactor).coerceIn(0f, 1f))
    }
}

fun Modifier.cupertinoPredictiveExit(progress: () -> Float) : Modifier = composed {

    val isRtl = LocalLayoutDirection.current == LayoutDirection.Rtl

    graphicsLayer {
        translationX = progress() * size.width

        if (isRtl)
            translationX = -translationX
    }
}


private const val SlideFactor =.25f




© 2015 - 2025 Weber Informatics LLC | Privacy Policy