Hello, <@UHAJKUSTU>! Do you have plans to support ...
# decompose
p
Hello, @Arkadii Ivanov! Do you have plans to support built-in mechanism of "screen drag" (back navigation) on compose ios? This feature is typical for the iOS platform. I would like it to be built into the decompose too. Flutter, for example, supports such a feature when building an application for iOS.
a
This should be already doable with the latest version 2.1.0-compose-experimental-alpha-02 See https://arkivanov.github.io/Decompose/extensions/compose/#predictive-back-gesture-on-other-platforms
p
Thank you! It works! Almost like i wanted is there a way to make the predictive back animation also like a drag?
p
It's was easy, thank you!
Copy code
private fun Modifier.slideExitModifier(progress: Float, maxWidth: Dp): Modifier {
    return offset {
        IntOffset(x = (maxWidth * progress).roundToPx(), y = 0)
    }
}
a
Cool! I think it should be possible to get the maximum width from
this
scope inside
offset {}
.
p
indeed, but we should use graphicsLayer than
Copy code
private fun Modifier.slideExitModifier(progress: Float): Modifier {
    return graphicsLayer {
        translationX = size.width * progress
    }
}
a
Thanks for sharing!
p
Maybe it will be useful for someone This animation configuration makes the navigation look native to iOS.
Copy code
@Composable
internal fun RootContent(
    component: RootComponent,
    modifier: Modifier = Modifier,
) {
    Children(
        stack = component.stack,
        modifier = modifier,
        animation = predictiveBackAnimation(
            backHandler = component.backHandler,
            exitModifier = { progress, _ ->
                Modifier.slideExitModifier(
                    progress = progress,
                )
            },
            enterModifier = { progress, _ ->
                Modifier.slideEnterModifier(
                    progress = progress,
                )
            },
            animation = stackAnimation(iosLikeSlide()),
            onBack = component::onBackClicked,
        ),
    ) {
        SomeScreenHere()
    }
}

fun iosLikeSlide(animationSpec: FiniteAnimationSpec<Float> = tween()): StackAnimator =
    stackAnimator(animationSpec = animationSpec) { factor, direction, content ->
        val newFactor = when (direction) {
            Direction.ENTER_FRONT -> factor
            Direction.EXIT_FRONT -> factor
            Direction.ENTER_BACK -> factor * 0.5f
            Direction.EXIT_BACK -> factor * 0.5f
        }
        content(Modifier.offsetXFactor(factor = newFactor))
    }

fun Modifier.slideExitModifier(progress: Float): Modifier {
    return offsetXFactor(progress)
}

fun Modifier.slideEnterModifier(progress: Float): Modifier {
    return offsetXFactor((progress - 1f) * 0.5f)
}

private fun Modifier.offsetXFactor(factor: Float): Modifier =
    layout { measurable, constraints ->
        val placeable = measurable.measure(constraints)

        layout(placeable.width, placeable.height) {
            placeable.placeRelative(x = (placeable.width.toFloat() * factor).toInt(), y = 0)
        }
    }
a
Looks really cool!
c
Decompose should have some sort of recipes section or something. This looks really cool!
a
Interesting idea! Let me think about it!
p
I think it would be cool to have a separate section on the site
c
i know okhttp has a recipes section. i always reference that. 😄
a
Yeah! Although, I feel it would be cool to have it community maintained. Having a pinned discussion where everyone can post a recipe, then comment and upvote/downvote. WDYT?
Eventually, most upvoted recipes could be added to a dedicated section in docs.
p
Sounds good)
a
Here is a slightly enhanced version of the back gesture transition on iOS, with a fade effect.
👍 2
so beautiful 1
Copy code
@OptIn(ExperimentalDecomposeApi::class)
@Composable
fun RootContent(
    component: RootComponent,
    modifier: Modifier = Modifier,
) {
    Children(
        stack = component.childStack,
        animation = predictiveBackAnimation(
            backHandler = component.backHandler,
            animation = stackAnimation(iosLikeSlide()),
            exitModifier = { progress, _ -> Modifier.slideExitModifier(progress = progress) },
            enterModifier = { progress, _ -> Modifier.slideEnterModifier(progress = progress) },
            onBack = component::onBackPressed,
        ),
        modifier = modifier,
    ) {
        Surface {
            when (val child = it.instance) {
                is RootComponent.Child.Main -> MainContent(component = child.component)
                is RootComponent.Child.Details -> DetailsContent(component = child.component)
            }
        }
    }
}

fun iosLikeSlide(animationSpec: FiniteAnimationSpec<Float> = tween()): StackAnimator =
    stackAnimator(animationSpec = animationSpec) { factor, direction, content ->
        content(
            Modifier
                .then(if (direction.isFront) Modifier else Modifier.fade(factor + 1F))
                .offsetXFactor(factor = if (direction.isFront) factor else factor * 0.5F)
        )
    }

fun Modifier.slideExitModifier(progress: Float): Modifier =
    offsetXFactor(progress)

fun Modifier.slideEnterModifier(progress: Float): Modifier =
    fade(progress).offsetXFactor((progress - 1f) * 0.5f)

private fun Modifier.fade(factor: Float) =
    drawWithContent {
        drawContent()
        drawRect(color = Color(red = 0F, green = 0F, blue = 0F, alpha = (1F - factor) / 4F))
    }

private fun Modifier.offsetXFactor(factor: Float): Modifier =
    layout { measurable, constraints ->
        val placeable = measurable.measure(constraints)

        layout(placeable.width, placeable.height) {
            placeable.placeRelative(x = (placeable.width.toFloat() * factor).toInt(), y = 0)
        }
    }