-
-
Notifications
You must be signed in to change notification settings - Fork 84
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
Showing
17 changed files
with
807 additions
and
199 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
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
11 changes: 11 additions & 0 deletions
11
decompose/src/commonMain/kotlin/com/arkivanov/decompose/Optional.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,11 @@ | ||
package com.arkivanov.decompose | ||
|
||
internal data class Optional<out T : Any>(val value: T?) { | ||
|
||
companion object { | ||
val EMPTY: Optional<Nothing> = Optional(null) | ||
} | ||
} | ||
|
||
internal fun <T : Any> optionalOf(value: T? = null): Optional<T> = | ||
if (value == null) Optional.EMPTY else Optional(value) |
10 changes: 10 additions & 0 deletions
10
decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlay.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,10 @@ | ||
package com.arkivanov.decompose.router.overlay | ||
|
||
import com.arkivanov.decompose.Child | ||
|
||
/** | ||
* A state holder for `Child Overlay`. | ||
*/ | ||
data class ChildOverlay<out C : Any, out T : Any>( | ||
val overlay: Child.Created<C, T>? = null, | ||
) |
125 changes: 125 additions & 0 deletions
125
...mpose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/ChildOverlayFactory.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,125 @@ | ||
package com.arkivanov.decompose.router.overlay | ||
|
||
import com.arkivanov.decompose.Child | ||
import com.arkivanov.decompose.ComponentContext | ||
import com.arkivanov.decompose.Optional | ||
import com.arkivanov.decompose.instancekeeper.attachTo | ||
import com.arkivanov.decompose.optionalOf | ||
import com.arkivanov.decompose.router.stack.StackNavigationSource | ||
import com.arkivanov.decompose.router.stack.childStack | ||
import com.arkivanov.decompose.value.Value | ||
import com.arkivanov.decompose.value.operator.map | ||
import com.arkivanov.essenty.instancekeeper.InstanceKeeperDispatcher | ||
import com.arkivanov.essenty.parcelable.Parcelable | ||
import com.arkivanov.essenty.parcelable.ParcelableContainer | ||
import com.arkivanov.essenty.statekeeper.StateKeeperDispatcher | ||
import kotlin.reflect.KClass | ||
|
||
/** | ||
* Initializes and manages component overlay. An overlay component can be either active or dismissed (destroyed). | ||
* | ||
* @param source a source of navigation events | ||
* @param key a key of the overlay, must be unique within the parent component | ||
* @param configurationClass a [KClass] of the component configurations | ||
* @param initialConfiguration a component configuration that should be shown if there is | ||
* no saved state, return `null` to show nothing. | ||
* @param persistent determines whether the navigation state should pre preserved or not, | ||
* default is `true`. | ||
* @param handleBackButton determines whether the overlay should be automatically dismissed | ||
* on back button press or not, default is `false`. | ||
* @param childFactory a factory function that creates new child instances | ||
* @return an observable [Value] of [ChildOverlay] | ||
*/ | ||
fun <C : Parcelable, T : Any> ComponentContext.childOverlay( | ||
source: OverlayNavigationSource<C>, | ||
key: String, | ||
configurationClass: KClass<out C>, | ||
initialConfiguration: () -> C? = { null }, | ||
persistent: Boolean = true, | ||
handleBackButton: Boolean = false, | ||
childFactory: (configuration: C, ComponentContext) -> T, | ||
): Value<ChildOverlay<C, T>> = | ||
childStack( | ||
lifecycle = lifecycle, | ||
stateKeeper = if (persistent) stateKeeper else StateKeeperDispatcher(), | ||
instanceKeeper = if (persistent) instanceKeeper else InstanceKeeperDispatcher().attachTo(lifecycle), | ||
backHandler = backHandler, | ||
source = StackNavigationSourceImpl(source), | ||
initialStack = { configurationStack(initialConfiguration()) }, | ||
saveConfiguration = { ParcelableContainer(it.value) }, | ||
restoreConfiguration = { optionalOf(it.consume(configurationClass)) }, | ||
key = key, | ||
handleBackButton = handleBackButton, | ||
childFactory = { configuration, componentContext -> | ||
if (configuration.value != null) { | ||
optionalOf(childFactory(configuration.value, componentContext)) | ||
} else { | ||
optionalOf() | ||
} | ||
}, | ||
).map { it.active.toChildOverlay() } | ||
|
||
/** | ||
* A convenience extension function for [ComponentContext.childOverlay]. | ||
*/ | ||
inline fun <reified C : Parcelable, T : Any> ComponentContext.childOverlay( | ||
source: OverlayNavigationSource<C>, | ||
key: String, | ||
noinline initialConfiguration: () -> C? = { null }, | ||
persistent: Boolean = true, | ||
handleBackButton: Boolean = false, | ||
noinline childFactory: (configuration: C, ComponentContext) -> T, | ||
): Value<ChildOverlay<C, T>> = | ||
childOverlay( | ||
source = source, | ||
key = key, | ||
configurationClass = C::class, | ||
initialConfiguration = initialConfiguration, | ||
persistent = persistent, | ||
handleBackButton = handleBackButton, | ||
childFactory = childFactory, | ||
) | ||
|
||
private fun <C : Parcelable, T : Any> Child.Created<Optional<C>, Optional<T>>.toChildOverlay(): ChildOverlay<C, T> = | ||
if ((configuration.value != null) && (instance.value != null)) { | ||
ChildOverlay( | ||
overlay = Child.Created( | ||
configuration = configuration.value, | ||
instance = instance.value, | ||
) | ||
) | ||
} else { | ||
ChildOverlay() | ||
} | ||
|
||
private fun <C : Any> configurationStack(configuration: C?): List<Optional<C>> = | ||
listOfNotNull(optionalOf(), configuration?.let(::optionalOf)) | ||
|
||
private class StackNavigationSourceImpl<C : Parcelable>( | ||
private val delegate: OverlayNavigationSource<C>, | ||
) : StackNavigationSource<Optional<C>> { | ||
|
||
private var map = HashMap<(StackNavigationSource.Event<Optional<C>>) -> Unit, (OverlayNavigationSource.Event<C>) -> Unit>() | ||
|
||
override fun subscribe(observer: (StackNavigationSource.Event<Optional<C>>) -> Unit) { | ||
check(observer !in map) | ||
|
||
val sourceObserver: (OverlayNavigationSource.Event<C>) -> Unit = { observer(it.toStackEvent()) } | ||
map += observer to sourceObserver | ||
delegate.subscribe(sourceObserver) | ||
} | ||
|
||
private fun OverlayNavigationSource.Event<C>.toStackEvent(): StackNavigationSource.Event<Optional<C>> = | ||
StackNavigationSource.Event( | ||
transformer = { stack -> configurationStack(transformer(stack.last().value)) }, | ||
onComplete = { newStack, oldStack -> | ||
onComplete(newStack.lastOrNull()?.value, oldStack.lastOrNull()?.value) | ||
}, | ||
) | ||
|
||
override fun unsubscribe(observer: (StackNavigationSource.Event<Optional<C>>) -> Unit) { | ||
map.remove(observer)?.also { | ||
delegate.unsubscribe(it) | ||
} | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/OverlayNavigation.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,6 @@ | ||
package com.arkivanov.decompose.router.overlay | ||
|
||
/** | ||
* Represents [OverlayNavigator] and [OverlayNavigationSource] at the same time. | ||
*/ | ||
interface OverlayNavigation<C : Any> : OverlayNavigator<C>, OverlayNavigationSource<C> |
30 changes: 30 additions & 0 deletions
30
.../src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/OverlayNavigationFactory.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,30 @@ | ||
package com.arkivanov.decompose.router.overlay | ||
|
||
import com.arkivanov.decompose.Relay | ||
import com.arkivanov.decompose.router.overlay.OverlayNavigationSource.Event | ||
|
||
/** | ||
* Returns a default implementation of [OverlayNavigation]. | ||
* Broadcasts navigation events to all subscribed observers. | ||
*/ | ||
fun <C : Any> OverlayNavigation(): OverlayNavigation<C> = OverlayNavigationImpl() | ||
|
||
private class OverlayNavigationImpl<C : Any> : OverlayNavigation<C> { | ||
|
||
private val relay = Relay<Event<C>>() | ||
|
||
override fun navigate( | ||
transformer: (configuration: C?) -> C?, | ||
onComplete: (newConfiguration: C?, oldConfiguration: C?) -> Unit, | ||
) { | ||
relay.accept(Event(transformer, onComplete)) | ||
} | ||
|
||
override fun subscribe(observer: (Event<C>) -> Unit) { | ||
relay.subscribe(observer) | ||
} | ||
|
||
override fun unsubscribe(observer: (Event<C>) -> Unit) { | ||
relay.unsubscribe(observer) | ||
} | ||
} |
18 changes: 18 additions & 0 deletions
18
...e/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/OverlayNavigationSource.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,18 @@ | ||
package com.arkivanov.decompose.router.overlay | ||
|
||
/** | ||
* Represents a source of navigation events for `Child Overlay`. | ||
* | ||
* @see OverlayNavigator | ||
*/ | ||
interface OverlayNavigationSource<C : Any> { | ||
|
||
fun subscribe(observer: (Event<C>) -> Unit) | ||
|
||
fun unsubscribe(observer: (Event<C>) -> Unit) | ||
|
||
class Event<C : Any>( | ||
val transformer: (configuration: C?) -> C?, | ||
val onComplete: (newConfiguration: C?, oldConfiguration: C?) -> Unit = { _, _ -> }, | ||
) | ||
} |
28 changes: 28 additions & 0 deletions
28
decompose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/OverlayNavigator.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.decompose.router.overlay | ||
|
||
interface OverlayNavigator<C : Any> { | ||
|
||
/** | ||
* Transforms the current configuration into a new one. Configuration `null` means that the | ||
* component is not shown. | ||
* | ||
* During the navigation process, the `Child Overlay` compares the new configuration with | ||
* the previous one. The `Child Overlay` ensures that the current component is resumed, and a | ||
* dismissed component is destroyed. | ||
* | ||
* The `Child Overlay` usually performs the navigation synchronously, which means that by the time | ||
* the `navigate` method returns, the navigation is finished and all component lifecycles are | ||
* moved into required states. However, the navigation is performed asynchronously in case of | ||
* recursive invocations - e.g. `dismiss` is called from `onResume` lifecycle callback of a | ||
* component being shown. All recursive invocations are queued and performed one by one once | ||
* the current navigation is finished. | ||
* | ||
* @param transformer transforms the current configuration to a new one, `null` means that the | ||
* component is not shown. | ||
* @param onComplete called when the navigation is finished (either synchronously or asynchronously). | ||
*/ | ||
fun navigate( | ||
transformer: (configuration: C?) -> C?, | ||
onComplete: (newConfiguration: C?, oldConfiguration: C?) -> Unit, | ||
) | ||
} |
29 changes: 29 additions & 0 deletions
29
...mpose/src/commonMain/kotlin/com/arkivanov/decompose/router/overlay/OverlayNavigatorExt.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,29 @@ | ||
package com.arkivanov.decompose.router.overlay | ||
|
||
/** | ||
* A convenience method for [OverlayNavigator.navigate]. | ||
*/ | ||
fun <C : Any> OverlayNavigator<C>.navigate(transformer: (configuration: C?) -> C?) { | ||
navigate(transformer = transformer, onComplete = { _, _ -> }) | ||
} | ||
|
||
/** | ||
* Activates an overlay component represented by the provided [configuration], | ||
* and dismisses (destroys) any currently active component. Does nothing if the provided [configuration] | ||
* is equal to the currently active one. | ||
* | ||
* @param onComplete called when the navigation is finished (either synchronously or asynchronously). | ||
*/ | ||
fun <C : Any> OverlayNavigator<C>.activate(configuration: C, onComplete: () -> Unit = {}) { | ||
navigate(transformer = { configuration }, onComplete = { _, _ -> onComplete() }) | ||
} | ||
|
||
/** | ||
* Dismisses (destroys) the currently active overlay component, if any. | ||
* | ||
* @param onComplete called when the navigation is finished (either synchronously or asynchronously). | ||
* The `isSuccess` argument is `true` if there was an active overlay, `false` otherwise. | ||
*/ | ||
fun <C : Any> OverlayNavigator<C>.dismiss(onComplete: (isSuccess: Boolean) -> Unit = {}) { | ||
navigate(transformer = { null }, onComplete = { _, oldConfiguration -> onComplete(oldConfiguration != null) }) | ||
} |
Oops, something went wrong.