Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Operation helpers #161

Merged
merged 11 commits into from
Sep 22, 2022
5 changes: 3 additions & 2 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,14 @@
## Pending changes

- [#151](https://github.com/bumble-tech/appyx/issues/151) - **Breaking change**: Renamed `Routing` to `NavTarget`. All related namings are affected (`RoutingElement`, `RoutingKey`, etc.)
- [#151](https://github.com/bumble-tech/appyx/issues/158) - **Breaking change**: Renamed `TransitionState` to `State` in all NavModel impls. Renamed `STASHED_IN_BACK_STACK` to `STASHED`.
- [#158](https://github.com/bumble-tech/appyx/issues/158) - **Breaking change**: Renamed `TransitionState` to `State` in all NavModel impls. Renamed `STASHED_IN_BACK_STACK` to `STASHED`.
- [#146](https://github.com/bumble-tech/appyx/issues/146) - **Breaking change**: Removed `FragmentIntegrationPoint`. Clients should use `ActivityIntegrationPoint.getIntegrationPoint(context: Context)` to get integration point from Fragment
- [#146](https://github.com/bumble-tech/appyx/issues/160) - **Breaking change**: Renamed `navmodel-addons` to `navmodel-samples` and stopped publishing the binary. If you feel we should add any of the samples to the main codebase, please let us know!
- [#168](https://github.com/bumble-tech/appyx/issues/160) - **Breaking change**: Renamed `navmodel-addons` to `navmodel-samples` and stopped publishing the binary. If you feel we should add any of the samples to the main codebase, please let us know!
- [#138](https://github.com/bumble-tech/appyx/pull/138) - **Fixed**: `androidx.appcompat:appcompat` from is exposed via `api` within `com.bumble.appyx:core`. This prevents potential compilation bugs.
- [#143](https://github.com/bumble-tech/appyx/issues/143) - **Fixed**: Correctly exposed transitive dependencies that are part of the libraries ABI
- [#145](https://github.com/bumble-tech/appyx/issues/145) - **Updated**: `SpotlightSlider` now uses offset modifier with lambda
- [#159](https://github.com/bumble-tech/appyx/pull/159) - **Added**: `NodeHost` now takes modifier parameter to decorate the view of a root node
- [#161](https://github.com/bumble-tech/appyx/pull/151) - **Added**: Operations helpers

---

Expand Down
51 changes: 51 additions & 0 deletions core/src/main/kotlin/com/bumble/appyx/core/navigation/Operation.kt
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,57 @@ interface Operation<NavTarget, State> :

fun isApplicable(elements: NavElements<NavTarget, State>): Boolean

/**
* Transitions all elements to a new state
*/
fun NavElements<NavTarget, State>.transitionTo(
newTargetState: State
) = transitionToIndexed(newTargetState) { _, _ -> true }
zsoltk marked this conversation as resolved.
Show resolved Hide resolved

/**
* Transitions all elements to a new state based on the individual elements
*/
fun NavElements<NavTarget, State>.transitionTo(
newTargetState: (NavElement<NavTarget, State>) -> State
) = map { element ->
element.transitionTo(
newTargetState = newTargetState.invoke(element),
operation = this@Operation
)
}

/**
* Transitions elements to a new state if they pass a condition based on the element
*/
fun NavElements<NavTarget, State>.transitionTo(
newTargetState: State, condition: (element: NavElement<NavTarget, State>) -> Boolean
zsoltk marked this conversation as resolved.
Show resolved Hide resolved
) = map { element ->
if (condition.invoke(element)) {
element.transitionTo(
newTargetState = newTargetState,
operation = this@Operation
)
} else {
element
}
}

/**
* Transitions elements to a new state if they pass a condition based on the element and its position
*/
fun NavElements<NavTarget, State>.transitionToIndexed(
newTargetState: State, condition: (index: Int, element: NavElement<NavTarget, State>) -> Boolean
zsoltk marked this conversation as resolved.
Show resolved Hide resolved
zsoltk marked this conversation as resolved.
Show resolved Hide resolved
) = mapIndexed { index, element ->
if (condition.invoke(index, element)) {
element.transitionTo(
newTargetState = newTargetState,
operation = this@Operation
)
} else {
element
}
}

@Parcelize
class Noop<NavTarget, State> : Operation<NavTarget, State> {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import com.bumble.appyx.navmodel.backstack.BackStackElement
import com.bumble.appyx.navmodel.backstack.BackStackElements
import com.bumble.appyx.navmodel.backstack.BackStack.State.ACTIVE
import com.bumble.appyx.navmodel.backstack.BackStack.State.CREATED
import com.bumble.appyx.navmodel.backstack.BackStack.State.STASHED
import com.bumble.appyx.navmodel.backstack.activeElement
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue
Expand All @@ -23,23 +24,15 @@ data class Push<T : Any>(
override fun isApplicable(elements: BackStackElements<T>): Boolean =
element != elements.activeElement

override fun invoke(elements: BackStackElements<T>): BackStackElements<T> {
return elements.map {
if (it.targetState == BackStack.State.ACTIVE) {
it.transitionTo(
newTargetState = BackStack.State.STASHED,
operation = this
)
} else {
it
}
override fun invoke(elements: BackStackElements<T>): BackStackElements<T> =
elements.transitionTo(STASHED) {
it.targetState == ACTIVE
} + BackStackElement(
key = NavKey(element),
fromState = CREATED,
targetState = ACTIVE,
operation = this
)
}
}

fun <T : Any> BackStack<T>.push(element: T) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,13 @@ package com.bumble.appyx.navmodel.backstack.operation

import com.bumble.appyx.core.navigation.NavKey
import com.bumble.appyx.navmodel.backstack.BackStack
import com.bumble.appyx.navmodel.backstack.BackStackElement
import com.bumble.appyx.navmodel.backstack.BackStackElements
import com.bumble.appyx.navmodel.backstack.activeIndex
import com.bumble.appyx.navmodel.backstack.BackStack.State.ACTIVE
import com.bumble.appyx.navmodel.backstack.BackStack.State.CREATED
import com.bumble.appyx.navmodel.backstack.BackStack.State.DESTROYED
import com.bumble.appyx.navmodel.backstack.BackStackElement
import com.bumble.appyx.navmodel.backstack.BackStackElements
import com.bumble.appyx.navmodel.backstack.activeElement
import com.bumble.appyx.navmodel.backstack.activeIndex
import kotlinx.parcelize.Parcelize
import kotlinx.parcelize.RawValue

Expand All @@ -29,15 +30,8 @@ data class Replace<T : Any>(
): BackStackElements<T> {
require(elements.any { it.targetState == ACTIVE }) { "No element to be replaced, state=$elements" }

return elements.mapIndexed { index, element ->
if (index == elements.activeIndex) {
element.transitionTo(
newTargetState = BackStack.State.DESTROYED,
operation = this
)
} else {
element
}
return elements.transitionToIndexed(DESTROYED) { index, _ ->
index == elements.activeIndex
} + BackStackElement(
key = NavKey(element),
fromState = CREATED,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,17 +35,11 @@ sealed class SingleTop<T : Any> : BackStackOperation<T> {
val current = elements.active
requireNotNull(current)

val newElements = elements.dropLast(elements.size - position - 1)
val newElements = elements
.dropLast(elements.size - position - 1)

return newElements.mapIndexed { index, element ->
if (index == newElements.lastIndex) {
element.transitionTo(
newTargetState = BackStack.State.ACTIVE,
operation = this
)
} else {
element
}
return newElements.transitionToIndexed(ACTIVE) { index, _ ->
index == newElements.lastIndex
} + current.transitionTo(
newTargetState = BackStack.State.DESTROYED,
operation = this
Expand Down
1 change: 1 addition & 0 deletions documentation/navmodel/custom.md
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ class SomeOperation<NavTarget : Any> : FooOperation<NavTarget> {
): NavElements<NavTarget, Foo.State> =
// TODO: Mutate elements however you please. Add, remove, change.
// In this example we're changing all elements to transition to BAR.
// You can also use helper methods elements.transitionTo & elements.transitionToIndexed
elements.map {
it.transitionTo(
newTargetState = BAR,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,12 +14,7 @@ class PromoteAll<T : Any> : PromoterOperation<T> {
override fun invoke(
elements: PromoterElements<T>,
): NavElements<T, Promoter.State> =
elements.map {
it.transitionTo(
newTargetState = it.targetState.next(),
operation = this
)
}
elements.transitionTo { it.targetState.next() }
}

fun <T : Any> Promoter<T>.promoteAll() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.bumble.appyx.navmodel.tiles.operation
import com.bumble.appyx.core.navigation.NavElements
import com.bumble.appyx.core.navigation.NavKey
import com.bumble.appyx.navmodel.tiles.Tiles
import com.bumble.appyx.navmodel.tiles.Tiles.State.SELECTED
import com.bumble.appyx.navmodel.tiles.Tiles.State.STANDARD
import com.bumble.appyx.navmodel.tiles.TilesElements
import kotlinx.parcelize.Parcelize

Expand All @@ -16,15 +18,8 @@ data class Deselect<T : Any>(
override fun invoke(
elements: TilesElements<T>
): NavElements<T, Tiles.State> =
elements.map {
if (it.key == key && it.targetState == Tiles.State.SELECTED) {
it.transitionTo(
newTargetState = Tiles.State.STANDARD,
operation = this
)
} else {
it
}
elements.transitionTo(STANDARD) {
it.key == key && it.targetState == SELECTED
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.bumble.appyx.navmodel.tiles.operation

import com.bumble.appyx.core.navigation.NavElements
import com.bumble.appyx.navmodel.tiles.Tiles
import com.bumble.appyx.navmodel.tiles.Tiles.State.SELECTED
import com.bumble.appyx.navmodel.tiles.Tiles.State.STANDARD
import com.bumble.appyx.navmodel.tiles.TilesElements
import kotlinx.parcelize.Parcelize

Expand All @@ -13,15 +15,8 @@ class DeselectAll<T : Any> : TilesOperation<T> {
override fun invoke(
elements: TilesElements<T>
): NavElements<T, Tiles.State> =
elements.map {
if (it.targetState == Tiles.State.SELECTED) {
it.transitionTo(
newTargetState = Tiles.State.STANDARD,
operation = this
)
} else {
it
}
elements.transitionTo(STANDARD) {
it.targetState == SELECTED
}

override fun equals(other: Any?): Boolean = this.javaClass == other?.javaClass
LachlanMcKee marked this conversation as resolved.
Show resolved Hide resolved
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@ package com.bumble.appyx.navmodel.tiles.operation
import com.bumble.appyx.core.navigation.NavElements
import com.bumble.appyx.core.navigation.NavKey
import com.bumble.appyx.navmodel.tiles.Tiles
import com.bumble.appyx.navmodel.tiles.Tiles.State.DESTROYED
import com.bumble.appyx.navmodel.tiles.TilesElements
import kotlinx.parcelize.Parcelize

Expand All @@ -16,15 +17,8 @@ data class Destroy<T : Any>(
override fun invoke(
elements: TilesElements<T>
): NavElements<T, Tiles.State> =
elements.map {
if (it.key == key) {
it.transitionTo(
newTargetState = Tiles.State.DESTROYED,
operation = this
)
} else {
it
}
elements.transitionTo(DESTROYED) {
it.key == key
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ package com.bumble.appyx.navmodel.tiles.operation

import com.bumble.appyx.core.navigation.NavElements
import com.bumble.appyx.navmodel.tiles.Tiles
import com.bumble.appyx.navmodel.tiles.Tiles.State.DESTROYED
import com.bumble.appyx.navmodel.tiles.Tiles.State.SELECTED
import com.bumble.appyx.navmodel.tiles.TilesElements
import kotlinx.parcelize.Parcelize

Expand All @@ -13,15 +15,8 @@ class RemoveSelected<T : Any> : TilesOperation<T> {
override fun invoke(
elements: TilesElements<T>
): NavElements<T, Tiles.State> =
elements.map {
if (it.targetState == Tiles.State.SELECTED) {
it.transitionTo(
newTargetState = Tiles.State.DESTROYED,
operation = this
)
} else {
it
}
elements.transitionTo(DESTROYED) {
it.targetState == SELECTED
}

override fun equals(other: Any?): Boolean = this.javaClass == other?.javaClass
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,8 @@ package com.bumble.appyx.navmodel.tiles.operation
import com.bumble.appyx.core.navigation.NavElements
import com.bumble.appyx.core.navigation.NavKey
import com.bumble.appyx.navmodel.tiles.Tiles
import com.bumble.appyx.navmodel.tiles.Tiles.State.SELECTED
import com.bumble.appyx.navmodel.tiles.Tiles.State.STANDARD
import com.bumble.appyx.navmodel.tiles.TilesElements
import kotlinx.parcelize.Parcelize

Expand All @@ -16,15 +18,8 @@ data class Select<T : Any>(
override fun invoke(
elements: TilesElements<T>
): NavElements<T, Tiles.State> =
elements.map {
if (it.key == key && it.targetState == Tiles.State.STANDARD) {
it.transitionTo(
newTargetState = Tiles.State.SELECTED,
operation = this
)
} else {
it
}
elements.transitionTo(SELECTED) {
it.key == key && it.targetState == STANDARD
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.bumble.appyx.navmodel.tiles.transitionhandler

import android.annotation.SuppressLint
import androidx.compose.animation.core.Spring
import androidx.compose.animation.core.Transition
import androidx.compose.animation.core.animateFloat
import androidx.compose.animation.core.spring
Expand Down Expand Up @@ -62,7 +63,7 @@ class TilesTransitionHandler<T>(

@Composable
fun <T> rememberTilesTransitionHandler(
transitionSpec: TransitionSpec<Tiles.State, Float> = { spring() }
transitionSpec: TransitionSpec<Tiles.State, Float> = { spring(stiffness = Spring.StiffnessVeryLow) }
zsoltk marked this conversation as resolved.
Show resolved Hide resolved
): ModifierTransitionHandler<T, Tiles.State> = remember {
TilesTransitionHandler(transitionSpec)
}