Skip to content

Commit

Permalink
Replaced PredictiveBackDispatcher with normal methods
Browse files Browse the repository at this point in the history
  • Loading branch information
arkivanov committed Feb 7, 2024
1 parent 9412d93 commit e8adf5a
Show file tree
Hide file tree
Showing 10 changed files with 166 additions and 91 deletions.
23 changes: 12 additions & 11 deletions back-handler/api/android/back-handler.api
Original file line number Diff line number Diff line change
Expand Up @@ -41,19 +41,11 @@ public final class com/arkivanov/essenty/backhandler/BackCallbackKt {
public abstract interface class com/arkivanov/essenty/backhandler/BackDispatcher : com/arkivanov/essenty/backhandler/BackHandler {
public abstract fun addEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public abstract fun back ()Z
public abstract fun cancelPredictiveBack ()V
public abstract fun isEnabled ()Z
public abstract fun progressPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackEvent;)V
public abstract fun removeEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public abstract fun startPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackEvent;)Lcom/arkivanov/essenty/backhandler/BackDispatcher$PredictiveBackDispatcher;
}

public final class com/arkivanov/essenty/backhandler/BackDispatcher$DefaultImpls {
public static fun startPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackDispatcher;Lcom/arkivanov/essenty/backhandler/BackEvent;)Lcom/arkivanov/essenty/backhandler/BackDispatcher$PredictiveBackDispatcher;
}

public abstract interface class com/arkivanov/essenty/backhandler/BackDispatcher$PredictiveBackDispatcher {
public abstract fun cancel ()V
public abstract fun finish ()V
public abstract fun progress (Lcom/arkivanov/essenty/backhandler/BackEvent;)V
public abstract fun startPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackEvent;)Z
}

public final class com/arkivanov/essenty/backhandler/BackDispatcherKt {
Expand All @@ -64,10 +56,19 @@ public final class com/arkivanov/essenty/backhandler/BackEvent {
public fun <init> ()V
public fun <init> (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FF)V
public synthetic fun <init> (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()F
public final fun component2 ()Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public final fun component3 ()F
public final fun component4 ()F
public final fun copy (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FF)Lcom/arkivanov/essenty/backhandler/BackEvent;
public static synthetic fun copy$default (Lcom/arkivanov/essenty/backhandler/BackEvent;FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FFILjava/lang/Object;)Lcom/arkivanov/essenty/backhandler/BackEvent;
public fun equals (Ljava/lang/Object;)Z
public final fun getProgress ()F
public final fun getSwipeEdge ()Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public final fun getTouchX ()F
public final fun getTouchY ()F
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class com/arkivanov/essenty/backhandler/BackEvent$SwipeEdge : java/lang/Enum {
Expand Down
23 changes: 12 additions & 11 deletions back-handler/api/jvm/back-handler.api
Original file line number Diff line number Diff line change
Expand Up @@ -35,19 +35,11 @@ public final class com/arkivanov/essenty/backhandler/BackCallbackKt {
public abstract interface class com/arkivanov/essenty/backhandler/BackDispatcher : com/arkivanov/essenty/backhandler/BackHandler {
public abstract fun addEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public abstract fun back ()Z
public abstract fun cancelPredictiveBack ()V
public abstract fun isEnabled ()Z
public abstract fun progressPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackEvent;)V
public abstract fun removeEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V
public abstract fun startPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackEvent;)Lcom/arkivanov/essenty/backhandler/BackDispatcher$PredictiveBackDispatcher;
}

public final class com/arkivanov/essenty/backhandler/BackDispatcher$DefaultImpls {
public static fun startPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackDispatcher;Lcom/arkivanov/essenty/backhandler/BackEvent;)Lcom/arkivanov/essenty/backhandler/BackDispatcher$PredictiveBackDispatcher;
}

public abstract interface class com/arkivanov/essenty/backhandler/BackDispatcher$PredictiveBackDispatcher {
public abstract fun cancel ()V
public abstract fun finish ()V
public abstract fun progress (Lcom/arkivanov/essenty/backhandler/BackEvent;)V
public abstract fun startPredictiveBack (Lcom/arkivanov/essenty/backhandler/BackEvent;)Z
}

public final class com/arkivanov/essenty/backhandler/BackDispatcherKt {
Expand All @@ -58,10 +50,19 @@ public final class com/arkivanov/essenty/backhandler/BackEvent {
public fun <init> ()V
public fun <init> (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FF)V
public synthetic fun <init> (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FFILkotlin/jvm/internal/DefaultConstructorMarker;)V
public final fun component1 ()F
public final fun component2 ()Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public final fun component3 ()F
public final fun component4 ()F
public final fun copy (FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FF)Lcom/arkivanov/essenty/backhandler/BackEvent;
public static synthetic fun copy$default (Lcom/arkivanov/essenty/backhandler/BackEvent;FLcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;FFILjava/lang/Object;)Lcom/arkivanov/essenty/backhandler/BackEvent;
public fun equals (Ljava/lang/Object;)Z
public final fun getProgress ()F
public final fun getSwipeEdge ()Lcom/arkivanov/essenty/backhandler/BackEvent$SwipeEdge;
public final fun getTouchX ()F
public final fun getTouchY ()F
public fun hashCode ()I
public fun toString ()Ljava/lang/String;
}

public final class com/arkivanov/essenty/backhandler/BackEvent$SwipeEdge : java/lang/Enum {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,24 +38,20 @@ private class AndroidBackHandler(

val onBackPressedCallback: OnBackPressedCallback =
object : OnBackPressedCallback(enabled = dispatcher.isEnabled) {
private var predictiveBackDispatcher: BackDispatcher.PredictiveBackDispatcher? = null

override fun handleOnBackPressed() {
dispatcher.back()
predictiveBackDispatcher = null
}

override fun handleOnBackStarted(backEvent: BackEventCompat) {
predictiveBackDispatcher = dispatcher.startPredictiveBack(backEvent.toEssentyBackEvent())
dispatcher.startPredictiveBack(backEvent.toEssentyBackEvent())
}

override fun handleOnBackProgressed(backEvent: BackEventCompat) {
predictiveBackDispatcher?.progress(backEvent.toEssentyBackEvent())
dispatcher.progressPredictiveBack(backEvent.toEssentyBackEvent())
}

override fun handleOnBackCancelled() {
predictiveBackDispatcher?.cancel()
predictiveBackDispatcher = null
dispatcher.cancelPredictiveBack()
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package com.arkivanov.essenty.backhandler

import androidx.activity.BackEventCompat
import androidx.activity.BackEventCompat.Companion.EDGE_LEFT
import androidx.activity.BackEventCompat.Companion.EDGE_RIGHT
import androidx.activity.OnBackPressedDispatcher
import kotlin.test.Test
import kotlin.test.assertContentEquals
Expand Down Expand Up @@ -173,6 +176,64 @@ class AndroidBackHandlerTest {
assertContentEquals(listOf(2), list)
}

@Test
fun WHEN_progress_with_back_THEN_callbacks_called() {
val receivedEvents = ArrayList<Any?>()

handler.register(
BackCallback(
onBackStarted = { receivedEvents += it },
onBackProgressed = { receivedEvents += it },
onBackCancelled = { receivedEvents += "Cancel" },
onBack = { receivedEvents += "Back" },
)
)

dispatcher.dispatchOnBackStarted(BackEventCompat(progress = 0.1F, swipeEdge = EDGE_LEFT, touchX = 1F, touchY = 2F))
dispatcher.dispatchOnBackProgressed(BackEventCompat(progress = 0.2F, swipeEdge = EDGE_RIGHT, touchX = 2F, touchY = 3F))
dispatcher.dispatchOnBackProgressed(BackEventCompat(progress = 0.3F, swipeEdge = EDGE_LEFT, touchX = 3F, touchY = 4F))
dispatcher.onBackPressed()

assertContentEquals(
listOf(
BackEvent(progress = 0.1F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 1F, touchY = 2F),
BackEvent(progress = 0.2F, swipeEdge = BackEvent.SwipeEdge.RIGHT, touchX = 2F, touchY = 3F),
BackEvent(progress = 0.3F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 3F, touchY = 4F),
"Back",
),
receivedEvents,
)
}

@Test
fun WHEN_progress_with_cancel_THEN_callbacks_called() {
val receivedEvents = ArrayList<Any?>()

handler.register(
BackCallback(
onBackStarted = { receivedEvents += it },
onBackProgressed = { receivedEvents += it },
onBackCancelled = { receivedEvents += "Cancel" },
onBack = { receivedEvents += "Back" },
)
)

dispatcher.dispatchOnBackStarted(BackEventCompat(progress = 0.1F, swipeEdge = EDGE_LEFT, touchX = 1F, touchY = 2F))
dispatcher.dispatchOnBackProgressed(BackEventCompat(progress = 0.2F, swipeEdge = EDGE_RIGHT, touchX = 2F, touchY = 3F))
dispatcher.dispatchOnBackProgressed(BackEventCompat(progress = 0.3F, swipeEdge = EDGE_LEFT, touchX = 3F, touchY = 4F))
dispatcher.dispatchOnBackCancelled()

assertContentEquals(
listOf(
BackEvent(progress = 0.1F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 1F, touchY = 2F),
BackEvent(progress = 0.2F, swipeEdge = BackEvent.SwipeEdge.RIGHT, touchX = 2F, touchY = 3F),
BackEvent(progress = 0.3F, swipeEdge = BackEvent.SwipeEdge.LEFT, touchX = 3F, touchY = 4F),
"Cancel",
),
receivedEvents,
)
}

private fun callback(
isEnabled: Boolean = true,
priority: Int = BackCallback.PRIORITY_DEFAULT,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,43 +12,45 @@ interface BackDispatcher : BackHandler {
*/
val isEnabled: Boolean

/**
* Adds the provided [listener], which will be called every time the enabled state of
* this [BackDispatcher] changes.
*/
fun addEnabledChangedListener(listener: (isEnabled: Boolean) -> Unit)

/**
* Removes the provided enabled state changed [listener].
*/
fun removeEnabledChangedListener(listener: (isEnabled: Boolean) -> Unit)

/**
* Iterates through all registered callbacks in reverse order and triggers the first one enabled.
* If no predictive back gesture is currently in progress, finds the last enabled
* callback with the highest priority and calls [BackCallback.onBack].
*
* If the predictive back gesture is currently in progress, calls [BackCallback.onBack] on
* the previously selected callback.
*
* @return `true` if any handler was triggered, `false` otherwise.
* @return `true` if any callback was triggered, `false` otherwise.
*/
fun back(): Boolean

/**
* Starts handling the predictive back gesture. Picks one of the enabled callback (if any)
* that will be handling the gesture and calls [BackCallback.onBackStarted].
*
* @return `true` if any callback was triggered, `false` otherwise.
*/
fun startPredictiveBack(backEvent: BackEvent): Boolean

/**
* Calls [BackCallback.onBackProgressed] on the previously selected callback.
*/
fun startPredictiveBack(backEvent: BackEvent): PredictiveBackDispatcher? = null
fun progressPredictiveBack(backEvent: BackEvent)

/**
* Dispatches predictive back gesture events to the callback selected by [startPredictiveBack].
* Calls [BackCallback.onBackCancelled] on the previously selected callback.
*/
interface PredictiveBackDispatcher {

/**
* Calls [BackCallback.onBackProgressed] on the selected callback.
*/
fun progress(backEvent: BackEvent)

/**
* Calls [BackCallback.onBack] on the selected callback.
*/
fun finish()

/**
* Calls [BackCallback.onBackCancelled] on the selected callback.
*/
fun cancel()
}
fun cancelPredictiveBack()
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ package com.arkivanov.essenty.backhandler
* @param touchX absolute X location of the touch point of this event.
* @param touchY absolute Y location of the touch point of this event.
*/
class BackEvent(
data class BackEvent(
val progress: Float = 0F,
val swipeEdge: SwipeEdge = SwipeEdge.UNKNOWN,
val touchX: Float = 0F,
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,9 @@
package com.arkivanov.essenty.backhandler

import com.arkivanov.essenty.backhandler.BackDispatcher.PredictiveBackDispatcher

internal class DefaultBackDispatcher : BackDispatcher {

private var set = emptySet<BackCallback>()
private var progressCallback: BackCallback? = null
override val isEnabled: Boolean get() = set.any(BackCallback::isEnabled)
private var enabledChangedListeners = emptySet<(Boolean) -> Unit>()
private var hasEnabledCallback: Boolean = false
Expand All @@ -31,6 +30,11 @@ internal class DefaultBackDispatcher : BackDispatcher {

this.set -= callback
callback.removeEnabledChangedListener(onCallbackEnabledChanged)

if (callback == progressCallback) {
progressCallback = null
}

onCallbackEnabledChanged()
}

Expand All @@ -42,25 +46,28 @@ internal class DefaultBackDispatcher : BackDispatcher {
enabledChangedListeners -= listener
}

override fun back(): Boolean = set.call()
override fun back(): Boolean {
val callback = progressCallback ?: set.findMostImportant()
progressCallback = null
callback?.onBack()

override fun startPredictiveBack(backEvent: BackEvent): PredictiveBackDispatcher? {
val callback = set.findMostImportant() ?: return null
return callback != null
}

override fun startPredictiveBack(backEvent: BackEvent): Boolean {
val callback = set.findMostImportant() ?: return false
progressCallback = callback
callback.onBackStarted(backEvent)

return object : PredictiveBackDispatcher {
override fun progress(backEvent: BackEvent) {
callback.onBackProgressed(backEvent)
}
return true
}

override fun finish() {
callback.onBack()
}
override fun progressPredictiveBack(backEvent: BackEvent) {
progressCallback?.onBackProgressed(backEvent)
}

override fun cancel() {
callback.onBackCancelled()
}
}
override fun cancelPredictiveBack() {
progressCallback?.onBackCancelled()
progressCallback = null
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,3 @@ package com.arkivanov.essenty.backhandler

internal fun Iterable<BackCallback>.findMostImportant(): BackCallback? =
sortedBy(BackCallback::priority).lastOrNull(BackCallback::isEnabled)

internal fun Iterable<BackCallback>.call(): Boolean {
findMostImportant()?.also {
it.onBack()
return true
}

return false
}
Loading

0 comments on commit e8adf5a

Please sign in to comment.