Skip to content

Commit

Permalink
Merge pull request #599 from vladcipariu91/494_gesture_control_flag
Browse files Browse the repository at this point in the history
Issue #494: Add flag to control gesture continuity
  • Loading branch information
vladcipariu91 authored Sep 19, 2023
2 parents fef919e + 8551256 commit 8ccd6a3
Show file tree
Hide file tree
Showing 7 changed files with 35 additions and 17 deletions.
3 changes: 1 addition & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,14 @@

## Pending changes

-
- [#494](https://github.com/bumble-tech/appyx/pull/599) Added isContinuous flag to GestureFactory

## 2.0.0-alpha06

### Changed

- [#594](https://github.com/bumble-tech/appyx/pull/594) Reverted JVM bytecode target to JDK11 instead of 17


<div style="text-align: center"><small>15 Sep 2023</small></div>

---
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ import com.bumble.appyx.interactions.core.ui.state.MatchedTargetUiState
import com.bumble.appyx.transitionmodel.BaseMotionController

class Puzzle15MotionController(
private val uiContext: UiContext,
uiContext: UiContext,
defaultAnimationSpec: SpringSpec<Float> = DefaultAnimationSpec
) : BaseMotionController<Tile, Puzzle15Model.State, MutableUiState, TargetUiState>(
uiContext = uiContext,
Expand Down Expand Up @@ -56,6 +56,8 @@ class Puzzle15MotionController(
bounds: TransitionBounds,
) : GestureFactory<Tile, Puzzle15Model.State> {

override val isContinuous: Boolean = false

private val cellSize: Float = bounds.widthPx / 4f

override fun createGesture(
Expand All @@ -66,22 +68,22 @@ class Puzzle15MotionController(
return when (dragDirection4(delta)) {
Drag.Direction4.UP -> Gesture(
operation = Swap(Swap.Direction.DOWN),
completeAt = Offset(0f, -cellSize)
completeAt = Offset(0f, -cellSize),
)

Drag.Direction4.LEFT -> Gesture(
operation = Swap(Swap.Direction.RIGHT),
completeAt = Offset(-cellSize, 0f)
completeAt = Offset(-cellSize, 0f),
)

Drag.Direction4.RIGHT -> Gesture(
operation = Swap(Swap.Direction.LEFT),
completeAt = Offset(cellSize, 0f)
completeAt = Offset(cellSize, 0f),
)

Drag.Direction4.DOWN -> Gesture(
operation = Swap(Swap.Direction.UP),
completeAt = Offset(0f, cellSize)
completeAt = Offset(0f, cellSize),
)

}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -79,6 +79,8 @@ class BackStackParallax<InteractionTarget : Any>(
private val transitionBounds: TransitionBounds,
) : GestureFactory<InteractionTarget, State<InteractionTarget>> {

override val isContinuous: Boolean = false

override fun createGesture(
state: State<InteractionTarget>,
delta: Offset,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -88,7 +88,11 @@ class BackStack3D<InteractionTarget : Any>(
class Gestures<InteractionTarget : Any>(
transitionBounds: TransitionBounds,
) : GestureFactory<InteractionTarget, State<InteractionTarget>> {

override val isContinuous: Boolean = false

private val height = transitionBounds.screenHeightDp

override fun createGesture(
state: State<InteractionTarget>,
delta: Offset,
Expand All @@ -99,7 +103,7 @@ class BackStack3D<InteractionTarget : Any>(
return if (dragVerticalDirection(delta) == Drag.VerticalDirection.DOWN) {
Gesture(
operation = Pop(),
completeAt = Offset(x = 0f, y = heightInPx)
completeAt = Offset(x = 0f, y = heightInPx),
)
} else {
Gesture.Noop()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -90,22 +90,22 @@ internal class DragProgressController<InteractionTarget : Any, State>(
}

val startProgress = gesture!!.startProgress!!
val isGestureContinuous = gestureFactory().isContinuous

// Case: we go forward, it's cool
if (totalTarget > startProgress) {

// Case: standard forward progress
if (totalTarget < startProgress + 1) {
model.setProgress(totalTarget)
val currentProgress = if (currentState is Keyframes<*>) currentState.progress else 0f
AppyxLogger.d(
TAG,
"delta applied forward, new progress: $currentProgress"
)
val currentProgress =
if (currentState is Keyframes<*>) currentState.progress else 0f
AppyxLogger.d(TAG, "delta applied forward, new progress: $currentProgress")

// Case: target is beyond the current segment, we'll need a new operation
} else {
} else if (isGestureContinuous) {
// TODO without recursion

val remainder =
consumePartial(COMPLETE, dragAmount, totalTarget, deltaProgress, startProgress + 1)
if (remainder.getDistanceSquared() > 0) {
Expand All @@ -115,9 +115,10 @@ internal class DragProgressController<InteractionTarget : Any, State>(

// Case: we went back to or beyond the start,
// now we need to re-evaluate for a new operation
} else {
} else if (isGestureContinuous) {
// TODO without recursion
val remainder = consumePartial(REVERT, dragAmount, totalTarget, deltaProgress, startProgress)
val remainder =
consumePartial(REVERT, dragAmount, totalTarget, deltaProgress, startProgress)
if (dragAmount != remainder) {
consumeDrag(remainder)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,14 @@ import androidx.compose.ui.unit.Density

interface GestureFactory<InteractionTarget, ModelState> {

/**
* isContinuous: Boolean - indicates that if during a drag gesture this operation completes
* but there's still offset to process a new gesture will be created that handles the remaining
* amount.
*/
val isContinuous: Boolean
get() = true

fun onStartDrag(position: Offset) {}

fun createGesture(
Expand Down
4 changes: 3 additions & 1 deletion documentation/interactions/gestures.md
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,10 @@ class Gestures<InteractionTarget> : GestureFactory<InteractionTarget, SomeModel.
}
```

`GestureFactory` implementations are usually nested in a specific UI representation. This makes sense since driving a model with gestures usually results in a natural UX if the gestures are in sync with what happens in the UI. However, it's not a requirement – you could use different gestures than the default one for the UI representation.
`GestureFactory` implementations are usually nested in a specific UI representation. This makes sense since driving a model with gestures usually results in a natural UX if the gestures are in sync with what happens in the UI. However, it's not a requirement – you could use different gestures than the default one for the UI representation.

`GestureFactory` contains a Boolean field called `isContinuous` that indicates if during a drag gesture this operation completes but there's still offset to process a new gesture will be created that handles the remaining amount.
This defaults to `true` however it can be overridden and changed as needed.

## Choosing an operation

Expand Down

0 comments on commit 8ccd6a3

Please sign in to comment.