From b11f98afe86cb1ea6deb0a061b8eb21d18b7f829 Mon Sep 17 00:00:00 2001 From: ganfra Date: Tue, 19 Dec 2023 12:53:54 +0100 Subject: [PATCH] Add Overlay navModel and related classes --- libraries/architecture/build.gradle.kts | 1 + .../overlay/HideOverlayBackPressHandler.kt | 39 ++++++++++++++ .../libraries/architecture/overlay/Overlay.kt | 49 +++++++++++++++++ .../architecture/overlay/operation/Hide.kt | 54 +++++++++++++++++++ .../overlay/operation/OverlayOperation.kt | 22 ++++++++ .../architecture/overlay/operation/Show.kt | 48 +++++++++++++++++ 6 files changed, 213 insertions(+) create mode 100644 libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt create mode 100644 libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt create mode 100644 libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt create mode 100644 libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/OverlayOperation.kt create mode 100644 libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt diff --git a/libraries/architecture/build.gradle.kts b/libraries/architecture/build.gradle.kts index 68a25ead04..aff0e48c69 100644 --- a/libraries/architecture/build.gradle.kts +++ b/libraries/architecture/build.gradle.kts @@ -15,6 +15,7 @@ */ plugins { id("io.element.android-compose-library") + id("kotlin-parcelize") } android { diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt new file mode 100644 index 0000000000..38149f2e92 --- /dev/null +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/HideOverlayBackPressHandler.kt @@ -0,0 +1,39 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.architecture.overlay + +import com.bumble.appyx.core.navigation.backpresshandlerstrategies.BaseBackPressHandlerStrategy +import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.BackStackElements +import io.element.android.libraries.architecture.overlay.operation.Hide +import kotlinx.coroutines.flow.Flow +import kotlinx.coroutines.flow.map + +class HideOverlayBackPressHandler + : BaseBackPressHandlerStrategy() { + + override val canHandleBackPressFlow: Flow by lazy { + navModel.elements.map(::areThereElements) + } + + private fun areThereElements(elements: BackStackElements) = + elements.isNotEmpty() + + override fun onBackPressed() { + navModel.accept(Hide()) + } +} diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt new file mode 100644 index 0000000000..187b74e406 --- /dev/null +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/Overlay.kt @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.architecture.overlay + +import com.bumble.appyx.core.navigation.BaseNavModel +import com.bumble.appyx.core.navigation.NavElements +import com.bumble.appyx.core.navigation.backpresshandlerstrategies.BackPressHandlerStrategy +import com.bumble.appyx.core.navigation.onscreen.OnScreenStateResolver +import com.bumble.appyx.core.navigation.operationstrategies.ExecuteImmediately +import com.bumble.appyx.core.navigation.operationstrategies.OperationStrategy +import com.bumble.appyx.core.state.SavedStateMap +import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.BackStackOnScreenResolver +import com.bumble.appyx.navmodel.backstack.backpresshandler.PopBackPressHandler +import com.bumble.appyx.navmodel.backstack.operation.NewRoot +import com.bumble.appyx.navmodel.backstack.operation.Push + +class Overlay( + savedStateMap: SavedStateMap?, + key: String = requireNotNull(Overlay::class.qualifiedName), + backPressHandler: BackPressHandlerStrategy = HideOverlayBackPressHandler(), + operationStrategy: OperationStrategy = ExecuteImmediately(), + screenResolver: OnScreenStateResolver = BackStackOnScreenResolver, +) : BaseNavModel( + backPressHandler = backPressHandler, + screenResolver = screenResolver, + operationStrategy = operationStrategy, + finalState = BackStack.State.DESTROYED, + savedStateMap = savedStateMap, + key = key, +) { + + override val initialElements: NavElements + get() = emptyList() +} diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt new file mode 100644 index 0000000000..e782d4537f --- /dev/null +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Hide.kt @@ -0,0 +1,54 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.architecture.overlay.operation + +import com.bumble.appyx.navmodel.backstack.BackStack +import com.bumble.appyx.navmodel.backstack.BackStackElements +import com.bumble.appyx.navmodel.backstack.activeIndex +import io.element.android.libraries.architecture.overlay.Overlay +import kotlinx.parcelize.Parcelize + +@Parcelize +class Hide : OverlayOperation { + + override fun isApplicable(elements: BackStackElements): Boolean = + elements.any { it.targetState == BackStack.State.ACTIVE } + + override fun invoke( + elements: BackStackElements + ): BackStackElements { + val hideIndex = elements.activeIndex + require(hideIndex != -1) { "Nothing to hide, state=$elements" } + return elements.mapIndexed { index, element -> + when (index) { + hideIndex -> element.transitionTo( + newTargetState = BackStack.State.DESTROYED, + operation = this + ) + else -> element + } + } + } + + override fun equals(other: Any?): Boolean = this.javaClass == other?.javaClass + + override fun hashCode(): Int = this.javaClass.hashCode() +} + +fun Overlay.hide() { + accept(Hide()) +} diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/OverlayOperation.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/OverlayOperation.kt new file mode 100644 index 0000000000..83bb82ef4f --- /dev/null +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/OverlayOperation.kt @@ -0,0 +1,22 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.architecture.overlay.operation + +import com.bumble.appyx.core.navigation.Operation +import com.bumble.appyx.navmodel.backstack.BackStack + +interface OverlayOperation : Operation diff --git a/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt new file mode 100644 index 0000000000..90561127ef --- /dev/null +++ b/libraries/architecture/src/main/kotlin/io/element/android/libraries/architecture/overlay/operation/Show.kt @@ -0,0 +1,48 @@ +/* + * Copyright (c) 2023 New Vector Ltd + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package io.element.android.libraries.architecture.overlay.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.activeElement +import io.element.android.libraries.architecture.overlay.Overlay +import kotlinx.parcelize.Parcelize +import kotlinx.parcelize.RawValue + +@Parcelize +data class Show( + private val element: @RawValue T +) : OverlayOperation { + + override fun isApplicable(elements: BackStackElements): Boolean = + element != elements.activeElement + + override fun invoke(elements: BackStackElements): BackStackElements = listOf( + BackStackElement( + key = NavKey(element), + fromState = BackStack.State.CREATED, + targetState = BackStack.State.ACTIVE, + operation = this + ) + ) +} + +fun Overlay.show(element: T) { + accept(Show(element)) +}