-
-
Notifications
You must be signed in to change notification settings - Fork 15
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Merge pull request #62 from arkivanov/back-handler-2
Added back-handler module, deprecated back-pressed module
- Loading branch information
Showing
21 changed files
with
659 additions
and
26 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1 @@ | ||
/build |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
public final class com/arkivanov/essenty/backhandler/AndroidBackHandlerKt { | ||
public static final fun BackHandler (Landroidx/activity/OnBackPressedDispatcher;)Lcom/arkivanov/essenty/backhandler/BackHandler; | ||
public static final fun backHandler (Landroidx/activity/OnBackPressedDispatcherOwner;)Lcom/arkivanov/essenty/backhandler/BackHandler; | ||
} | ||
|
||
public abstract interface class com/arkivanov/essenty/backhandler/BackDispatcher : com/arkivanov/essenty/backhandler/BackHandler { | ||
public abstract fun back ()Z | ||
public abstract fun isEnabled ()Z | ||
} | ||
|
||
public final class com/arkivanov/essenty/backhandler/BackDispatcherKt { | ||
public static final fun BackDispatcher ()Lcom/arkivanov/essenty/backhandler/BackDispatcher; | ||
} | ||
|
||
public abstract interface class com/arkivanov/essenty/backhandler/BackHandler { | ||
public abstract fun register (Lcom/arkivanov/essenty/backhandler/BackHandler$Callback;)V | ||
public abstract fun unregister (Lcom/arkivanov/essenty/backhandler/BackHandler$Callback;)V | ||
} | ||
|
||
public final class com/arkivanov/essenty/backhandler/BackHandler$Callback { | ||
public fun <init> (ZLkotlin/jvm/functions/Function0;)V | ||
public synthetic fun <init> (ZLkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V | ||
public final fun addEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V | ||
public final fun getCallback ()Lkotlin/jvm/functions/Function0; | ||
public final fun isEnabled ()Z | ||
public final fun removeEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V | ||
public final fun setEnabled (Z)V | ||
} | ||
|
||
public abstract interface class com/arkivanov/essenty/backhandler/BackHandlerOwner { | ||
public abstract fun getBackHandler ()Lcom/arkivanov/essenty/backhandler/BackHandler; | ||
} | ||
|
||
public final class com/arkivanov/essenty/backhandler/BuildConfig { | ||
public static final field BUILD_TYPE Ljava/lang/String; | ||
public static final field DEBUG Z | ||
public static final field LIBRARY_PACKAGE_NAME Ljava/lang/String; | ||
public fun <init> ()V | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
public abstract interface class com/arkivanov/essenty/backhandler/BackDispatcher : com/arkivanov/essenty/backhandler/BackHandler { | ||
public abstract fun back ()Z | ||
public abstract fun isEnabled ()Z | ||
} | ||
|
||
public final class com/arkivanov/essenty/backhandler/BackDispatcherKt { | ||
public static final fun BackDispatcher ()Lcom/arkivanov/essenty/backhandler/BackDispatcher; | ||
} | ||
|
||
public abstract interface class com/arkivanov/essenty/backhandler/BackHandler { | ||
public abstract fun register (Lcom/arkivanov/essenty/backhandler/BackHandler$Callback;)V | ||
public abstract fun unregister (Lcom/arkivanov/essenty/backhandler/BackHandler$Callback;)V | ||
} | ||
|
||
public final class com/arkivanov/essenty/backhandler/BackHandler$Callback { | ||
public fun <init> (ZLkotlin/jvm/functions/Function0;)V | ||
public synthetic fun <init> (ZLkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V | ||
public final fun addEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V | ||
public final fun getCallback ()Lkotlin/jvm/functions/Function0; | ||
public final fun isEnabled ()Z | ||
public final fun removeEnabledChangedListener (Lkotlin/jvm/functions/Function1;)V | ||
public final fun setEnabled (Z)V | ||
} | ||
|
||
public abstract interface class com/arkivanov/essenty/backhandler/BackHandlerOwner { | ||
public abstract fun getBackHandler ()Lcom/arkivanov/essenty/backhandler/BackHandler; | ||
} | ||
|
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,29 @@ | ||
import com.arkivanov.gradle.bundle | ||
import com.arkivanov.gradle.setupBinaryCompatibilityValidator | ||
import com.arkivanov.gradle.setupMultiplatform | ||
import com.arkivanov.gradle.setupPublication | ||
import com.arkivanov.gradle.setupSourceSets | ||
|
||
plugins { | ||
id("kotlin-multiplatform") | ||
id("com.android.library") | ||
id("com.arkivanov.gradle.setup") | ||
} | ||
|
||
setupMultiplatform() | ||
setupPublication() | ||
setupBinaryCompatibilityValidator() | ||
|
||
kotlin { | ||
setupSourceSets { | ||
val android by bundle() | ||
|
||
common.main.dependencies { | ||
implementation(project(":utils-internal")) | ||
} | ||
|
||
android.main.dependencies { | ||
implementation(deps.androidx.activity.activityKtx) | ||
} | ||
} | ||
} |
28 changes: 28 additions & 0 deletions
28
back-handler/src/androidMain/kotlin/com/arkivanov/essenty/backhandler/AndroidBackHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
package com.arkivanov.essenty.backhandler | ||
|
||
import androidx.activity.OnBackPressedDispatcher | ||
import androidx.activity.OnBackPressedDispatcherOwner | ||
import androidx.activity.addCallback | ||
|
||
/** | ||
* Creates a new instance of [BackHandler] and attaches it to the provided AndroidX [OnBackPressedDispatcher]. | ||
*/ | ||
fun BackHandler(onBackPressedDispatcher: OnBackPressedDispatcher): BackHandler = | ||
AndroidBackHandler(onBackPressedDispatcher) | ||
|
||
/** | ||
* Creates a new instance of [BackHandler] and attaches it to the AndroidX [OnBackPressedDispatcher]. | ||
*/ | ||
fun OnBackPressedDispatcherOwner.backHandler(): BackHandler = | ||
AndroidBackHandler(onBackPressedDispatcher) | ||
|
||
internal class AndroidBackHandler( | ||
delegate: OnBackPressedDispatcher, | ||
) : AbstractBackHandler() { | ||
|
||
private val delegateCallback = delegate.addCallback(enabled = false) { callCallbacks() } | ||
|
||
override fun onEnabledChanged(isEnabled: Boolean) { | ||
delegateCallback.isEnabled = isEnabled | ||
} | ||
} |
148 changes: 148 additions & 0 deletions
148
...andler/src/androidTest/kotlin/com/arkivanov/essenty/backhandler/AndroidBackHandlerTest.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,148 @@ | ||
package com.arkivanov.essenty.backhandler | ||
|
||
import androidx.activity.OnBackPressedDispatcher | ||
import com.arkivanov.essenty.backhandler.BackHandler.Callback | ||
import kotlin.test.Test | ||
import kotlin.test.assertContentEquals | ||
import kotlin.test.assertFalse | ||
import kotlin.test.assertTrue | ||
|
||
@Suppress("TestFunctionName") | ||
class AndroidBackHandlerTest { | ||
|
||
private val dispatcher = OnBackPressedDispatcher() | ||
private val handler = AndroidBackHandler(dispatcher) | ||
|
||
@Test | ||
fun WHEN_created_THEN_hasEnabledCallbacks_false() { | ||
assertFalse(dispatcher.hasEnabledCallbacks()) | ||
} | ||
|
||
@Test | ||
fun WHEN_enabled_callback_registered_THEN_hasEnabledCallbacks_true() { | ||
handler.register(callback(isEnabled = true)) | ||
|
||
assertTrue(dispatcher.hasEnabledCallbacks()) | ||
} | ||
|
||
@Test | ||
fun WHEN_disabled_callback_registered_THEN_hasEnabledCallbacks_false() { | ||
handler.register(callback(isEnabled = false)) | ||
|
||
assertFalse(dispatcher.hasEnabledCallbacks()) | ||
} | ||
|
||
@Test | ||
fun WHEN_multiple_callbacks_registered_and_one_enabled_THEN_hasEnabledCallbacks_true() { | ||
handler.register(callback(isEnabled = false)) | ||
handler.register(callback(isEnabled = true)) | ||
handler.register(callback(isEnabled = false)) | ||
|
||
assertTrue(dispatcher.hasEnabledCallbacks()) | ||
} | ||
|
||
@Test | ||
fun GIVEN_multiple_disabled_callbacks_WHEN_one_callback_enabled_THEN_hasEnabledCallbacks_true() { | ||
val callback2 = callback(isEnabled = false) | ||
handler.register(callback(isEnabled = false)) | ||
handler.register(callback2) | ||
handler.register(callback(isEnabled = false)) | ||
|
||
callback2.isEnabled = true | ||
|
||
assertTrue(dispatcher.hasEnabledCallbacks()) | ||
} | ||
|
||
@Test | ||
fun GIVEN_multiple_enabled_callbacks_WHEN_all_callbacks_disabled_except_one_THEN_hasEnabledCallbacks_true() { | ||
val callbacks = listOf(callback(isEnabled = true), callback(isEnabled = true), callback(isEnabled = true)) | ||
callbacks.forEach(handler::register) | ||
|
||
callbacks.drop(1).forEach { it.isEnabled = false } | ||
|
||
assertTrue(dispatcher.hasEnabledCallbacks()) | ||
} | ||
|
||
@Test | ||
fun GIVEN_multiple_enabled_callbacks_WHEN_all_callbacks_disabled_THEN_hasEnabledCallbacks_false() { | ||
val callbacks = listOf(callback(isEnabled = true), callback(isEnabled = true), callback(isEnabled = true)) | ||
callbacks.forEach(handler::register) | ||
|
||
callbacks.forEach { it.isEnabled = false } | ||
|
||
assertFalse(dispatcher.hasEnabledCallbacks()) | ||
} | ||
|
||
@Test | ||
fun GIVEN_multiple_enabled_callbacks_WHEN_all_callbacks_removed_THEN_hasEnabledCallbacks_false() { | ||
val callbacks = listOf(callback(isEnabled = true), callback(isEnabled = true), callback(isEnabled = true)) | ||
callbacks.forEach(handler::register) | ||
|
||
callbacks.forEach(handler::unregister) | ||
|
||
assertFalse(dispatcher.hasEnabledCallbacks()) | ||
} | ||
|
||
@Test | ||
fun GIVEN_multiple_enabled_callbacks_WHEN_all_callbacks_removed_except_one_THEN_hasEnabledCallbacks_true() { | ||
val callbacks = listOf(callback(isEnabled = true), callback(isEnabled = true), callback(isEnabled = true)) | ||
callbacks.forEach(handler::register) | ||
|
||
callbacks.drop(1).forEach(handler::unregister) | ||
|
||
assertTrue(dispatcher.hasEnabledCallbacks()) | ||
} | ||
|
||
@Test | ||
fun GIVEN_all_callbacks_disabled_WHEN_onBackPressed_THEN_callbacks_not_called() { | ||
var isCalled = false | ||
repeat(3) { | ||
handler.register(callback(isEnabled = false) { isCalled = true }) | ||
} | ||
|
||
dispatcher.onBackPressed() | ||
|
||
assertFalse(isCalled) | ||
} | ||
|
||
@Test | ||
fun GIVEN_all_callbacks_enabled_WHEN_onBackPressed_THEN_only_last_callback_called() { | ||
val called = MutableList(3) { false } | ||
|
||
repeat(called.size) { index -> | ||
handler.register(callback(isEnabled = true) { called[index] = true }) | ||
} | ||
|
||
dispatcher.onBackPressed() | ||
|
||
assertContentEquals(listOf(false, false, true), called) | ||
} | ||
|
||
@Test | ||
fun GIVEN_only_one_callback_enabled_WHEN_onBackPressed_THEN_only_enabled_callback_called() { | ||
val called = MutableList(3) { false } | ||
|
||
repeat(called.size) { index -> | ||
handler.register(callback(isEnabled = index == 0) { called[index] = true }) | ||
} | ||
|
||
dispatcher.onBackPressed() | ||
|
||
assertContentEquals(listOf(true, false, false), called) | ||
} | ||
|
||
@Test | ||
fun GIVEN_multiple_enabled_callbacks_registered_and_all_callbacks_removed_except_one_WHEN_onBackPressed_THEN_callback_called() { | ||
val called = MutableList(3) { false } | ||
val callbacks = List(called.size) { index -> callback(isEnabled = true) { called[index] = true } } | ||
callbacks.forEach(handler::register) | ||
callbacks.drop(1).forEach(handler::unregister) | ||
|
||
dispatcher.onBackPressed() | ||
|
||
assertContentEquals(listOf(true, false, false), called) | ||
} | ||
|
||
private fun callback(isEnabled: Boolean = true, callback: () -> Unit = {}): Callback = | ||
Callback(isEnabled = isEnabled, callback = callback) | ||
} |
40 changes: 40 additions & 0 deletions
40
back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/AbstractBackHandler.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,40 @@ | ||
package com.arkivanov.essenty.backhandler | ||
|
||
import com.arkivanov.essenty.backhandler.BackHandler.Callback | ||
|
||
internal abstract class AbstractBackHandler : BackHandler { | ||
|
||
private var set = emptySet<Callback>() | ||
private val enabledChangedListener: (Boolean) -> Unit = { onEnabledChanged() } | ||
|
||
override fun register(callback: Callback) { | ||
check(callback !in set) { "Callback is already registered" } | ||
|
||
this.set += callback | ||
callback.addEnabledChangedListener(enabledChangedListener) | ||
onEnabledChanged() | ||
} | ||
|
||
override fun unregister(callback: Callback) { | ||
check(callback in set) { "Callback is not registered" } | ||
|
||
callback.removeEnabledChangedListener(enabledChangedListener) | ||
this.set -= callback | ||
onEnabledChanged() | ||
} | ||
|
||
private fun onEnabledChanged() { | ||
onEnabledChanged(set.any(Callback::isEnabled)) | ||
} | ||
|
||
protected abstract fun onEnabledChanged(isEnabled: Boolean) | ||
|
||
protected fun callCallbacks(): Boolean { | ||
set.lastOrNull(Callback::isEnabled)?.also { | ||
it.callback() | ||
return true | ||
} | ||
|
||
return false | ||
} | ||
} |
27 changes: 27 additions & 0 deletions
27
back-handler/src/commonMain/kotlin/com/arkivanov/essenty/backhandler/BackDispatcher.kt
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,27 @@ | ||
package com.arkivanov.essenty.backhandler | ||
|
||
import kotlin.js.JsName | ||
|
||
/** | ||
* Provides a way to manually trigger back button handlers. | ||
*/ | ||
interface BackDispatcher : BackHandler { | ||
|
||
/** | ||
* Returns `true` if there is at least one enabled handler, `false` otherwise. | ||
*/ | ||
val isEnabled: Boolean | ||
|
||
/** | ||
* Iterates through all registered callbacks in reverse order and triggers the first one enabled. | ||
* | ||
* @return `true` if any handler was triggered, `false` otherwise. | ||
*/ | ||
fun back(): Boolean | ||
} | ||
|
||
/** | ||
* Creates and returns a default implementation of [BackDispatcher]. | ||
*/ | ||
@JsName("backDispatcher") | ||
fun BackDispatcher(): BackDispatcher = DefaultBackDispatcher() |
Oops, something went wrong.