From 9f07cba5c643cb2f6a441a3096c4ca9a771e1472 Mon Sep 17 00:00:00 2001 From: Zsolt Kocsi Date: Tue, 5 Sep 2023 16:00:39 +0100 Subject: [PATCH 01/10] Revert change 87d80a446c6f855c32b14694e7ca91abacf27b4e --- .../interactions/core/DraggableChildren.kt | 129 +++++++++--------- 1 file changed, 64 insertions(+), 65 deletions(-) diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt index dc82db6b0..c92455fcf 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt @@ -19,8 +19,11 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds +import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect +import androidx.compose.ui.graphics.Color +import androidx.compose.ui.graphics.Color.Companion import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.boundsInParent @@ -100,77 +103,73 @@ fun DraggableAppyxComponent( .fillMaxSize() ) { CompositionLocalProvider(LocalBoxScope provides this) { - elementUiModels - .forEach { elementUiModel -> - key(elementUiModel.element.id) { - var transformedBoundingBox by remember(elementUiModel.element.id) { - mutableStateOf(Rect.Zero) - } - var size by remember(elementUiModel.element.id) { mutableStateOf(IntSize.Zero) } - var offsetCenter by remember(elementUiModel.element.id) { - mutableStateOf( - Offset.Zero - ) - } - val isVisible by elementUiModel.visibleState.collectAsState() - elementUiModel.persistentContainer() - if (isVisible) { - Box( - modifier = Modifier - .offset { offsetCenter.round() } - .width(with(density) { size.width.toDp() }) - .height(with(density) { size.height.toDp() }) - .pointerInput(appyxComponent) { - detectDragGesturesOrCancellation( - onDragStart = { position -> - appyxComponent.onStartDrag(position) - }, - onDrag = { change, dragAmount -> - if (gestureValidator.isGestureValid( - change.position, - transformedBoundingBox.translate(-offsetCenter) - ) - ) { - change.consume() - appyxComponent.onDrag(dragAmount, density) - true - } else { + elementUiModels.forEach { elementUiModel -> + key(elementUiModel.element.id) { + var transformedBoundingBox by remember(elementUiModel.element.id) { + mutableStateOf(Rect.Zero) + } + var size by remember(elementUiModel.element.id) { mutableStateOf(IntSize.Zero) } + var offsetCenter by remember(elementUiModel.element.id) { + mutableStateOf( + Offset.Zero + ) + } + val isVisible by elementUiModel.visibleState.collectAsState() + elementUiModel.persistentContainer() + if (isVisible) { + CompositionLocalProvider( + LocalMotionProperties provides elementUiModel.motionProperties + ) { + element.invoke( + elementUiModel.copy( + modifier = Modifier + .offset { offsetCenter.round() } + .width(with(density) { size.width.toDp() }) + .height(with(density) { size.height.toDp() }) + .pointerInput(appyxComponent) { + detectDragGesturesOrCancellation( + onDragStart = { position -> + appyxComponent.onStartDrag(position) + }, + onDrag = { change, dragAmount -> + if (gestureValidator.isGestureValid( + change.position, + transformedBoundingBox.translate(-offsetCenter) + ) + ) { + change.consume() + appyxComponent.onDrag(dragAmount, density) + true + } else { + appyxComponent.onDragEnd() + false + } + }, + onDragEnd = { appyxComponent.onDragEnd() - false - } - }, - onDragEnd = { - appyxComponent.onDragEnd() - }, - ) - } - ) - CompositionLocalProvider( - LocalMotionProperties provides elementUiModel.motionProperties - ) { - element.invoke( - elementUiModel.copy( - modifier = Modifier - .then(elementUiModel.modifier) - .onPlaced { - size = it.size - val localCenter = Offset( - it.size.width.toFloat(), - it.size.height.toFloat() - ) / 2f + }, + ) + } + .offset { -offsetCenter.round() } + .then(elementUiModel.modifier) + .onPlaced { + size = it.size + val localCenter = Offset( + it.size.width.toFloat(), + it.size.height.toFloat() + ) / 2f - transformedBoundingBox = - it.boundsInParent() - .inflate(gestureExtraTouchAreaPx) - offsetCenter = - transformedBoundingBox.center - localCenter - } - ) + transformedBoundingBox = + it.boundsInParent().inflate(gestureExtraTouchAreaPx) + offsetCenter = + transformedBoundingBox.center - localCenter + } ) - } + ) } } } + } } } } From ba1229284e1fbec6f23269c01417058156aa5006 Mon Sep 17 00:00:00 2001 From: Zsolt Kocsi Date: Tue, 5 Sep 2023 18:36:13 +0100 Subject: [PATCH 02/10] Account for offset originating from alignment --- .../interactions/core/DraggableChildren.kt | 66 +++++-- .../navigation/composable/AppyxComponent.kt | 168 +++++++++++------- 2 files changed, 151 insertions(+), 83 deletions(-) diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt index c92455fcf..2864aa3e3 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt @@ -10,6 +10,7 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf @@ -23,13 +24,14 @@ import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect import androidx.compose.ui.graphics.Color -import androidx.compose.ui.graphics.Color.Companion import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.boundsInParent import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round @@ -43,6 +45,10 @@ import com.bumble.appyx.interactions.core.ui.LocalMotionProperties import com.bumble.appyx.interactions.core.ui.context.TransitionBounds import com.bumble.appyx.interactions.core.ui.context.UiContext import com.bumble.appyx.interactions.core.ui.output.ElementUiModel +import com.bumble.appyx.interactions.core.ui.property.impl.position.PositionInside +import com.bumble.appyx.interactions.core.ui.property.impl.position.PositionInside.Value +import com.bumble.appyx.interactions.core.ui.property.impl.position.PositionOutside +import com.bumble.appyx.interactions.core.ui.property.motionPropertyRenderValue private val defaultExtraTouch = 48f.dp @@ -73,6 +79,7 @@ fun DraggableAppyxComponent( val elementUiModels by appyxComponent.uiModels.collectAsState() val coroutineScope = rememberCoroutineScope() val gestureExtraTouchAreaPx = with(density) { gestureExtraTouchArea.toPx() } + var containerSize by remember { mutableStateOf(IntSize.Zero) } var uiContext by remember { mutableStateOf(null) } LaunchedEffect(uiContext) { @@ -83,6 +90,7 @@ fun DraggableAppyxComponent( .fillMaxSize() .then(if (clipToBounds) Modifier.clipToBounds() else Modifier) .onPlaced { + containerSize = it.size uiContext = UiContext( coroutineScope = coroutineScope, transitionBounds = TransitionBounds( @@ -100,32 +108,31 @@ fun DraggableAppyxComponent( appyxComponent.onRelease() } } - .fillMaxSize() ) { CompositionLocalProvider(LocalBoxScope provides this) { elementUiModels.forEach { elementUiModel -> - key(elementUiModel.element.id) { - var transformedBoundingBox by remember(elementUiModel.element.id) { - mutableStateOf(Rect.Zero) - } - var size by remember(elementUiModel.element.id) { mutableStateOf(IntSize.Zero) } - var offsetCenter by remember(elementUiModel.element.id) { - mutableStateOf( - Offset.Zero - ) - } + val id = elementUiModel.element.id + + key(id) { + var transformedBoundingBox by remember(id) { mutableStateOf(Rect.Zero) } + var elementSize by remember(id) { mutableStateOf(IntSize.Zero) } + var offsetCenter by remember(id) { mutableStateOf(Offset.Zero) } val isVisible by elementUiModel.visibleState.collectAsState() + elementUiModel.persistentContainer() + if (isVisible) { CompositionLocalProvider( LocalMotionProperties provides elementUiModel.motionProperties ) { + val elementOffset = offsetCenter.round() - elementOffset(elementSize, containerSize) + element.invoke( elementUiModel.copy( modifier = Modifier - .offset { offsetCenter.round() } - .width(with(density) { size.width.toDp() }) - .height(with(density) { size.height.toDp() }) + .offset { elementOffset } + .width(with(density) { elementSize.width.toDp() }) + .height(with(density) { elementSize.height.toDp() }) .pointerInput(appyxComponent) { detectDragGesturesOrCancellation( onDragStart = { position -> @@ -150,10 +157,10 @@ fun DraggableAppyxComponent( }, ) } - .offset { -offsetCenter.round() } + .offset { -elementOffset } .then(elementUiModel.modifier) .onPlaced { - size = it.size + elementSize = it.size val localCenter = Offset( it.size.width.toFloat(), it.size.height.toFloat() @@ -173,3 +180,28 @@ fun DraggableAppyxComponent( } } } + + +@Composable +fun elementOffset( + elementSize: IntSize, + containerSize: IntSize, +): IntOffset { + + val positionInside = motionPropertyRenderValue() + val positionOutside = motionPropertyRenderValue() + val layoutDirection = LocalLayoutDirection.current + + val positionInsideOffset by derivedStateOf { + positionInside?.let { + it.alignment.align(elementSize, containerSize, layoutDirection) + } ?: IntOffset.Zero + } + val positionOutsideOffset by derivedStateOf { + positionOutside?.let { + it.alignment.align(elementSize, containerSize, layoutDirection) + } ?: IntOffset.Zero + } + + return positionInsideOffset - positionOutsideOffset +} diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt index b602435ea..b8a81b0d7 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt @@ -2,13 +2,12 @@ package com.bumble.appyx.navigation.composable import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.height import androidx.compose.foundation.layout.offset -import androidx.compose.foundation.layout.width import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState +import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf @@ -25,7 +24,9 @@ import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.boundsInParent import androidx.compose.ui.layout.onPlaced import androidx.compose.ui.platform.LocalDensity +import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.unit.Dp +import androidx.compose.ui.unit.IntOffset import androidx.compose.ui.unit.IntSize import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.round @@ -35,9 +36,14 @@ import com.bumble.appyx.interactions.core.model.BaseAppyxComponent import com.bumble.appyx.interactions.core.model.removedElements import com.bumble.appyx.interactions.core.modifiers.onPointerEvent import com.bumble.appyx.interactions.core.ui.LocalBoxScope +import com.bumble.appyx.interactions.core.ui.LocalMotionProperties import com.bumble.appyx.interactions.core.ui.context.TransitionBounds import com.bumble.appyx.interactions.core.ui.context.UiContext import com.bumble.appyx.interactions.core.ui.output.ElementUiModel +import com.bumble.appyx.interactions.core.ui.property.impl.position.PositionInside +import com.bumble.appyx.interactions.core.ui.property.impl.position.PositionInside.Value +import com.bumble.appyx.interactions.core.ui.property.impl.position.PositionOutside +import com.bumble.appyx.interactions.core.ui.property.motionPropertyRenderValue import com.bumble.appyx.navigation.integration.LocalScreenSize import com.bumble.appyx.navigation.node.ParentNode import kotlin.math.roundToInt @@ -52,27 +58,29 @@ fun ParentNode.Ap clipToBounds: Boolean = false, gestureValidator: GestureValidator = GestureValidator.defaultValidator, gestureExtraTouchArea: Dp = defaultExtraTouch, - block: @Composable (ChildrenTransitionScope.() -> Unit)? = null, + block: @Composable ChildrenTransitionScope.() -> Unit = { + children { child, elementUiModel -> + child(elementUiModel.modifier) + } + } ) { val density = LocalDensity.current val screenWidthPx = (LocalScreenSize.current.widthDp * density.density).value.roundToInt() val screenHeightPx = (LocalScreenSize.current.heightDp * density.density).value.roundToInt() val coroutineScope = rememberCoroutineScope() + var containerSize by remember { mutableStateOf(IntSize.Zero) } var uiContext by remember { mutableStateOf(null) } - val childrenBlock = block ?: { - children { child, _ -> - child() - } - } LaunchedEffect(uiContext) { uiContext?.let { appyxComponent.updateContext(it) } } + Box( modifier = modifier .fillMaxSize() .then(if (clipToBounds) Modifier.clipToBounds() else Modifier) .onPlaced { + containerSize = it.size uiContext = UiContext( coroutineScope = coroutineScope, transitionBounds = TransitionBounds( @@ -90,18 +98,17 @@ fun ParentNode.Ap appyxComponent.onRelease() } } - .fillMaxSize() ) { CompositionLocalProvider(LocalBoxScope provides this) { - childrenBlock( - ChildrenTransitionScope(appyxComponent, gestureExtraTouchArea, gestureValidator) + block( + ChildrenTransitionScope(containerSize, appyxComponent, gestureExtraTouchArea, gestureValidator) ) } } - } class ChildrenTransitionScope( + private val containerSize: IntSize, private val appyxComponent: BaseAppyxComponent, private val gestureExtraTouchArea: Dp, private val gestureValidator: GestureValidator @@ -132,66 +139,95 @@ class ChildrenTransitionScope( uiModels .forEach { elementUiModel -> + val id = elementUiModel.element.id + key(elementUiModel.element.id) { - var transformedBoundingBox by remember(elementUiModel.element.id) { - mutableStateOf(Rect.Zero) - } - var offsetCenter by remember(elementUiModel.element.id) { mutableStateOf(Offset.Zero) } - var size by remember(elementUiModel.element.id) { mutableStateOf(IntSize.Zero) } + var transformedBoundingBox by remember(id) { mutableStateOf(Rect.Zero) } + var elementSize by remember(id) { mutableStateOf(IntSize.Zero) } + var offsetCenter by remember(id) { mutableStateOf(Offset.Zero) } val isVisible by elementUiModel.visibleState.collectAsState() + elementUiModel.persistentContainer() + if (isVisible) { - Box( - modifier = Modifier - .offset { offsetCenter.round() } - .width(with(density) { size.width.toDp() }) - .height(with(density) { size.height.toDp() }) - .offset { offsetCenter.round() } - .pointerInput(appyxComponent) { - detectDragGesturesOrCancellation( - onDragStart = { position -> - appyxComponent.onStartDrag(position) - }, - onDrag = { change, dragAmount -> - if (gestureValidator.isGestureValid( - change.position, - transformedBoundingBox.translate(-offsetCenter) - ) - ) { - change.consume() - appyxComponent.onDrag(dragAmount, density) - true - } else { - appyxComponent.onDragEnd() - false - } - }, - onDragEnd = { - appyxComponent.onDragEnd() - }, - ) - } - ) - Child( - elementUiModel = elementUiModel.copy( - modifier = Modifier - .then(elementUiModel.modifier) - .onPlaced { - size = it.size - val localCenter = Offset( - it.size.width.toFloat(), - it.size.height.toFloat() - ) / 2f - transformedBoundingBox = - it.boundsInParent().inflate(gestureExtraTouchAreaPx) - offsetCenter = transformedBoundingBox.center - localCenter - } - ), - saveableStateHolder = saveableStateHolder, - decorator = block - ) + CompositionLocalProvider( + LocalMotionProperties provides elementUiModel.motionProperties + ) { + val elementOffset = offsetCenter.round() - elementOffset(elementSize, containerSize) + + Child( + elementUiModel = elementUiModel.copy( + modifier = Modifier + .offset { elementOffset } + .pointerInput(appyxComponent) { + detectDragGesturesOrCancellation( + onDragStart = { position -> + appyxComponent.onStartDrag(position) + }, + onDrag = { change, dragAmount -> + if (gestureValidator.isGestureValid( + change.position, + transformedBoundingBox.translate(-offsetCenter) + ) + ) { + change.consume() + appyxComponent.onDrag(dragAmount, density) + true + } else { + appyxComponent.onDragEnd() + false + } + }, + onDragEnd = { + appyxComponent.onDragEnd() + }, + ) + } + .offset { -elementOffset } + .then(elementUiModel.modifier) + .onPlaced { + elementSize = it.size + val localCenter = Offset( + it.size.width.toFloat(), + it.size.height.toFloat() + ) / 2f + transformedBoundingBox = + it + .boundsInParent() + .inflate(gestureExtraTouchAreaPx) + offsetCenter = transformedBoundingBox.center - localCenter + } + ), + saveableStateHolder = saveableStateHolder, + decorator = block + ) + } } } } } + + @Composable + private fun elementOffset( + elementSize: IntSize, + containerSize: IntSize + ): IntOffset { + + val positionInside = motionPropertyRenderValue() + val positionOutside = motionPropertyRenderValue() + val layoutDirection = LocalLayoutDirection.current + + val positionInsideOffset by derivedStateOf { + positionInside?.let { + it.alignment.align(elementSize, containerSize, layoutDirection) + } ?: IntOffset.Zero + } + val positionOutsideOffset by derivedStateOf { + positionOutside?.let { + it.alignment.align(elementSize, containerSize, layoutDirection) + } ?: IntOffset.Zero + } + + return positionInsideOffset - positionOutsideOffset + } } From f79b85f05b20ff38269bd84e2ab7e049ef03ebca Mon Sep 17 00:00:00 2001 From: Zsolt Kocsi Date: Tue, 5 Sep 2023 18:55:02 +0100 Subject: [PATCH 03/10] Fix DatingCards --- .../components/experimental/cards/android/DatingCards.kt | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/appyx-components/experimental/cards/android/src/main/kotlin/com/bumble/appyx/components/experimental/cards/android/DatingCards.kt b/appyx-components/experimental/cards/android/src/main/kotlin/com/bumble/appyx/components/experimental/cards/android/DatingCards.kt index 191495aca..600b25846 100644 --- a/appyx-components/experimental/cards/android/src/main/kotlin/com/bumble/appyx/components/experimental/cards/android/DatingCards.kt +++ b/appyx-components/experimental/cards/android/src/main/kotlin/com/bumble/appyx/components/experimental/cards/android/DatingCards.kt @@ -49,11 +49,10 @@ fun DatingCards(modifier: Modifier = Modifier) { appyxComponent = cards, gestureValidator = permissiveValidator, ) { elementUiModel -> - Box( - modifier = elementUiModel.modifier - ) { - ProfileCard(profile = elementUiModel.element.interactionTarget.profile) - } + ProfileCard( + profile = elementUiModel.element.interactionTarget.profile, + modifier = Modifier.fillMaxSize().then(elementUiModel.modifier) + ) } } From 1c1c060f419e7eb56fb13c6898896f71312f06dd Mon Sep 17 00:00:00 2001 From: Zsolt Kocsi Date: Tue, 5 Sep 2023 18:55:17 +0100 Subject: [PATCH 04/10] Update spotlight sample to have clickable + drag --- .../navigation/node/spotlight/SpotlightNode.kt | 15 ++++++++++++--- 1 file changed, 12 insertions(+), 3 deletions(-) diff --git a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/spotlight/SpotlightNode.kt b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/spotlight/SpotlightNode.kt index 190552d73..63402692b 100644 --- a/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/spotlight/SpotlightNode.kt +++ b/demos/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/node/spotlight/SpotlightNode.kt @@ -3,6 +3,7 @@ package com.bumble.appyx.navigation.node.spotlight import androidx.compose.animation.core.Spring import androidx.compose.animation.core.spring import androidx.compose.foundation.background +import androidx.compose.foundation.clickable import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Box import androidx.compose.foundation.layout.Column @@ -14,7 +15,10 @@ import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.material.Button import androidx.compose.material.Text import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember +import androidx.compose.runtime.getValue +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.saveable.rememberSaveable +import androidx.compose.runtime.setValue import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clip import androidx.compose.ui.graphics.Color @@ -65,16 +69,21 @@ class SpotlightNode( override fun resolve(interactionTarget: InteractionTarget, buildContext: BuildContext): Node = when (interactionTarget) { is InteractionTarget.Child -> node(buildContext) { modifier -> - val backgroundColor = remember { colors.shuffled().random() } + val backgroundColorIdx = rememberSaveable { colors.shuffled().indices.random() } + val backgroundColor = colors[backgroundColorIdx] + var clicked by rememberSaveable { mutableStateOf(false) } + Box( modifier = modifier .fillMaxSize() .clip(RoundedCornerShape(5)) .background(backgroundColor) + .clickable { clicked = true } .padding(24.dp) + ) { Text( - text = interactionTarget.index.toString(), + text = "${interactionTarget.index} – Clicked: $clicked", fontSize = 21.sp, color = Color.Black, fontWeight = FontWeight.Bold From 576fbe3ab7a1c4771c934e15acdc6f3b5a36a09f Mon Sep 17 00:00:00 2001 From: Zsolt Kocsi Date: Tue, 5 Sep 2023 19:07:56 +0100 Subject: [PATCH 05/10] Revert default block invocation change --- .../navigation/composable/AppyxComponent.kt | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt index b8a81b0d7..ac15ab680 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt @@ -58,11 +58,7 @@ fun ParentNode.Ap clipToBounds: Boolean = false, gestureValidator: GestureValidator = GestureValidator.defaultValidator, gestureExtraTouchArea: Dp = defaultExtraTouch, - block: @Composable ChildrenTransitionScope.() -> Unit = { - children { child, elementUiModel -> - child(elementUiModel.modifier) - } - } + block: @Composable (ChildrenTransitionScope.() -> Unit)? = null, ) { val density = LocalDensity.current val screenWidthPx = (LocalScreenSize.current.widthDp * density.density).value.roundToInt() @@ -70,6 +66,11 @@ fun ParentNode.Ap val coroutineScope = rememberCoroutineScope() var containerSize by remember { mutableStateOf(IntSize.Zero) } var uiContext by remember { mutableStateOf(null) } + val childrenBlock = block ?: { + children { child, _ -> + child() + } + } LaunchedEffect(uiContext) { uiContext?.let { appyxComponent.updateContext(it) } @@ -99,8 +100,8 @@ fun ParentNode.Ap } } ) { - CompositionLocalProvider(LocalBoxScope provides this) { - block( + CompositionLocalProvider(LocalBoxScope provides this@Box) { + childrenBlock( ChildrenTransitionScope(containerSize, appyxComponent, gestureExtraTouchArea, gestureValidator) ) } @@ -141,7 +142,7 @@ class ChildrenTransitionScope( .forEach { elementUiModel -> val id = elementUiModel.element.id - key(elementUiModel.element.id) { + key(id) { var transformedBoundingBox by remember(id) { mutableStateOf(Rect.Zero) } var elementSize by remember(id) { mutableStateOf(IntSize.Zero) } var offsetCenter by remember(id) { mutableStateOf(Offset.Zero) } From 8770af966d7318dd3859af515a74f401cc04ab1c Mon Sep 17 00:00:00 2001 From: Zsolt Kocsi Date: Fri, 8 Sep 2023 11:25:04 +0100 Subject: [PATCH 06/10] Fix calculation error --- .../com/bumble/appyx/navigation/composable/AppyxComponent.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt index ac15ab680..8b26d4251 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt @@ -229,6 +229,6 @@ class ChildrenTransitionScope( } ?: IntOffset.Zero } - return positionInsideOffset - positionOutsideOffset + return positionInsideOffset + positionOutsideOffset } } From 214603802a865e7ab4b2385ec94021f0300ce9e4 Mon Sep 17 00:00:00 2001 From: Zsolt Kocsi Date: Fri, 8 Sep 2023 12:10:16 +0100 Subject: [PATCH 07/10] Remove unused imports --- .../com/bumble/appyx/interactions/core/DraggableChildren.kt | 2 -- 1 file changed, 2 deletions(-) diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt index 2864aa3e3..a680f2872 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt @@ -20,10 +20,8 @@ import androidx.compose.runtime.setValue import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.draw.clipToBounds -import androidx.compose.ui.draw.drawBehind import androidx.compose.ui.geometry.Offset import androidx.compose.ui.geometry.Rect -import androidx.compose.ui.graphics.Color import androidx.compose.ui.input.pointer.PointerEventType import androidx.compose.ui.input.pointer.pointerInput import androidx.compose.ui.layout.boundsInParent From a157af2955a15ca99861d393ab53830c24fbd47a Mon Sep 17 00:00:00 2001 From: Zsolt Kocsi Date: Fri, 8 Sep 2023 12:15:02 +0100 Subject: [PATCH 08/10] Remove derivedStateOf --- .../interactions/core/DraggableChildren.kt | 18 +++++++----------- .../navigation/composable/AppyxComponent.kt | 18 +++++++----------- 2 files changed, 14 insertions(+), 22 deletions(-) diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt index a680f2872..f35cb435f 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt @@ -10,7 +10,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf @@ -190,16 +189,13 @@ fun elementOffset( val positionOutside = motionPropertyRenderValue() val layoutDirection = LocalLayoutDirection.current - val positionInsideOffset by derivedStateOf { - positionInside?.let { - it.alignment.align(elementSize, containerSize, layoutDirection) - } ?: IntOffset.Zero - } - val positionOutsideOffset by derivedStateOf { - positionOutside?.let { - it.alignment.align(elementSize, containerSize, layoutDirection) - } ?: IntOffset.Zero - } + val positionInsideOffset = positionInside?.let { + it.alignment.align(elementSize, containerSize, layoutDirection) + } ?: IntOffset.Zero + + val positionOutsideOffset = positionOutside?.let { + it.alignment.align(elementSize, containerSize, layoutDirection) + } ?: IntOffset.Zero return positionInsideOffset - positionOutsideOffset } diff --git a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt index 8b26d4251..6125a06a1 100644 --- a/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt +++ b/appyx-navigation/common/src/commonMain/kotlin/com/bumble/appyx/navigation/composable/AppyxComponent.kt @@ -7,7 +7,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.LaunchedEffect import androidx.compose.runtime.collectAsState -import androidx.compose.runtime.derivedStateOf import androidx.compose.runtime.getValue import androidx.compose.runtime.key import androidx.compose.runtime.mutableStateOf @@ -218,16 +217,13 @@ class ChildrenTransitionScope( val positionOutside = motionPropertyRenderValue() val layoutDirection = LocalLayoutDirection.current - val positionInsideOffset by derivedStateOf { - positionInside?.let { - it.alignment.align(elementSize, containerSize, layoutDirection) - } ?: IntOffset.Zero - } - val positionOutsideOffset by derivedStateOf { - positionOutside?.let { - it.alignment.align(elementSize, containerSize, layoutDirection) - } ?: IntOffset.Zero - } + val positionInsideOffset = positionInside?.let { + it.alignment.align(elementSize, containerSize, layoutDirection) + } ?: IntOffset.Zero + + val positionOutsideOffset = positionOutside?.let { + it.alignment.align(elementSize, containerSize, layoutDirection) + } ?: IntOffset.Zero return positionInsideOffset + positionOutsideOffset } From 456b0348af76764844b91be83026a0585d35b9d5 Mon Sep 17 00:00:00 2001 From: Zsolt Kocsi Date: Fri, 8 Sep 2023 12:17:17 +0100 Subject: [PATCH 09/10] Update CHANGELOG.md --- CHANGELOG.md | 1 + 1 file changed, 1 insertion(+) diff --git a/CHANGELOG.md b/CHANGELOG.md index e3911463d..4b0e9015e 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,7 @@ - [#579](https://github.com/bumble-tech/appyx/pull/579) – Expose `AndroidLifecycle` in `PlatformLifecycleRegistry` for Android - [#584](https://github.com/bumble-tech/appyx/pull/584) – Fix applying offset twice in `AppyxComponent` +- [#585](https://github.com/bumble-tech/appyx/pull/585) – Fix drag vs align ## 2.0.0-alpha04 From 901771a899851c6441cfb2238ea3ad6cfeab153d Mon Sep 17 00:00:00 2001 From: Zsolt Kocsi Date: Fri, 8 Sep 2023 18:28:31 +0100 Subject: [PATCH 10/10] Fix missed calculation error --- .../com/bumble/appyx/interactions/core/DraggableChildren.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt index f35cb435f..72c40b3b3 100644 --- a/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt +++ b/appyx-interactions/common/src/commonMain/kotlin/com/bumble/appyx/interactions/core/DraggableChildren.kt @@ -197,5 +197,5 @@ fun elementOffset( it.alignment.align(elementSize, containerSize, layoutDirection) } ?: IntOffset.Zero - return positionInsideOffset - positionOutsideOffset + return positionInsideOffset + positionOutsideOffset }