From f5233460d1390f0f116027faccd427a7d7d54e8d Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Tue, 7 Feb 2023 19:02:31 -0800 Subject: [PATCH] `Container` and `Wrapper` interfaces, improved `forWrapper()` Introduces the `Container` and `Wrapper` types, giving Workflow UI its first general notion of structure. Their integration with `Compatibile` reduces the likelihood of making the most common mistake with wrapper types (namely, forgetting to do that). And they standardize the `map()` function that gets implemented by wrappers more often than not. Also updates `forWrapper` and `toUnwrappingViewFactory` to be defined in terms of `Wrapper`, allowing us to simplify their APIs by relying on `Wrapper.content` in default lambda implementations. And while we're in the neighborhood, adds long needed `prepEnvironment` and `prepContext` function arguments that simplify transforming a `ScreenViewFactory` to pre-process its `ViewEnvironment` and `Context`. We use this new capability to simplify the default `ScreenViewFactory` implementation for `EnvironmentScreen`. Closes #916 --- .../sample/container/panel/PanelOverlay.kt | 9 +- workflow-ui/core-android/api/core-android.api | 9 +- .../ui/container/BackStackContainerTest.kt | 2 +- .../ui/container/ViewStateCacheTest.kt | 16 +- .../workflow1/ui/ScreenViewFactory.kt | 170 +++++++----------- .../workflow1/ui/ScreenViewFactoryFinder.kt | 10 +- .../ui/container/BackButtonScreen.kt | 23 ++- .../EnvironmentScreenLegacyViewFactory.kt | 2 +- .../container/EnvironmentScreenViewFactory.kt | 25 --- .../workflow1/ui/ScreenViewFactoryTest.kt | 24 +-- ...EnvironmentScreenAndroidIntegrationTest.kt | 3 +- workflow-ui/core-common/api/core-common.api | 57 +++++- .../com/squareup/workflow1/ui/AsScreen.kt | 17 +- .../com/squareup/workflow1/ui/Container.kt | 72 ++++++++ .../com/squareup/workflow1/ui/NamedScreen.kt | 14 +- .../workflow1/ui/container/BackStackScreen.kt | 12 +- .../ui/container/EnvironmentScreen.kt | 22 +-- .../ui/container/FullScreenOverlay.kt | 9 +- .../workflow1/ui/container/ScreenOverlay.kt | 10 +- .../internal/test/WorkflowUiTestActivity.kt | 2 +- .../ui/radiography/WorkflowViewRenderer.kt | 2 +- 21 files changed, 293 insertions(+), 217 deletions(-) delete mode 100644 workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt create mode 100644 workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Container.kt diff --git a/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt b/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt index b9368f559b..401d550360 100644 --- a/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt +++ b/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt @@ -6,6 +6,9 @@ import com.squareup.workflow1.ui.container.ModalOverlay import com.squareup.workflow1.ui.container.ScreenOverlay @OptIn(WorkflowUiExperimentalApi::class) -class PanelOverlay( - override val content: T -) : ScreenOverlay, ModalOverlay +class PanelOverlay( + override val content: C +) : ScreenOverlay, ModalOverlay { + override fun map(transform: (C) -> D): ScreenOverlay = + PanelOverlay(transform(content)) +} diff --git a/workflow-ui/core-android/api/core-android.api b/workflow-ui/core-android/api/core-android.api index 60fecdaef6..c361f12378 100644 --- a/workflow-ui/core-android/api/core-android.api +++ b/workflow-ui/core-android/api/core-android.api @@ -313,13 +313,20 @@ public abstract interface class com/squareup/workflow1/ui/container/AndroidOverl public abstract fun getDialogFactory ()Lcom/squareup/workflow1/ui/container/OverlayDialogFactory; } -public final class com/squareup/workflow1/ui/container/BackButtonScreen : com/squareup/workflow1/ui/AndroidScreen { +public final class com/squareup/workflow1/ui/container/BackButtonScreen : com/squareup/workflow1/ui/AndroidScreen, com/squareup/workflow1/ui/Wrapper { public fun (Lcom/squareup/workflow1/ui/Screen;ZLkotlin/jvm/functions/Function0;)V public synthetic fun (Lcom/squareup/workflow1/ui/Screen;ZLkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun asSequence ()Lkotlin/sequences/Sequence; + public fun getCompatibilityKey ()Ljava/lang/String; + public fun getContent ()Lcom/squareup/workflow1/ui/Screen; + public synthetic fun getContent ()Ljava/lang/Object; public final fun getOnBackPressed ()Lkotlin/jvm/functions/Function0; public final fun getShadow ()Z public fun getViewFactory ()Lcom/squareup/workflow1/ui/ScreenViewFactory; public final fun getWrapped ()Lcom/squareup/workflow1/ui/Screen; + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container; + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper; + public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BackButtonScreen; } public final class com/squareup/workflow1/ui/container/BackStackConfig : java/lang/Enum { diff --git a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/BackStackContainerTest.kt b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/BackStackContainerTest.kt index ea7d2e914e..cc2696074d 100644 --- a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/BackStackContainerTest.kt +++ b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/BackStackContainerTest.kt @@ -159,7 +159,7 @@ internal class BackStackContainerTest { @Suppress("UNCHECKED_CAST") val visibleRendering: Screen - get() = (getChildAt(0)?.tag as NamedScreen<*>).wrapped + get() = (getChildAt(0)?.tag as NamedScreen<*>).content override fun performTransition( oldHolderMaybe: ScreenViewHolder>?, diff --git a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/ViewStateCacheTest.kt b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/ViewStateCacheTest.kt index 99818f3ed6..314dc037ca 100644 --- a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/ViewStateCacheTest.kt +++ b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/ViewStateCacheTest.kt @@ -33,7 +33,7 @@ internal class ViewStateCacheTest { private object AScreen : Screen @Test fun saves_and_restores_self() { - val rendering = NamedScreen(wrapped = AScreen, name = "rendering") + val rendering = NamedScreen(content = AScreen, name = "rendering") val childState = SparseArray().apply { put(0, TestChildState("hello world")) } @@ -58,8 +58,8 @@ internal class ViewStateCacheTest { @Test fun saves_and_restores_child_states_on_navigation() { val cache = ViewStateCache() - val firstRendering = NamedScreen(wrapped = AScreen, name = "first") - val secondRendering = NamedScreen(wrapped = AScreen, name = "second") + val firstRendering = NamedScreen(content = AScreen, name = "first") + val secondRendering = NamedScreen(content = AScreen, name = "second") // Android requires ID to be set for view hierarchy to be saved or restored. val firstView = createTestView(firstRendering, id = 1) val secondView = createTestView(secondRendering) @@ -90,8 +90,8 @@ internal class ViewStateCacheTest { @Test fun doesnt_restore_state_when_restored_view_id_is_different() { val cache = ViewStateCache() - val firstRendering = NamedScreen(wrapped = AScreen, name = "first") - val secondRendering = NamedScreen(wrapped = AScreen, name = "second") + val firstRendering = NamedScreen(content = AScreen, name = "first") + val secondRendering = NamedScreen(content = AScreen, name = "second") // Android requires ID to be set for view hierarchy to be saved or restored. val firstView = createTestView(firstRendering, id = 1) val secondView = createTestView(secondRendering) @@ -133,8 +133,8 @@ internal class ViewStateCacheTest { @Test fun doesnt_restore_state_when_view_id_not_set() { val cache = ViewStateCache() - val firstRendering = NamedScreen(wrapped = AScreen, name = "first") - val secondRendering = NamedScreen(wrapped = AScreen, name = "second") + val firstRendering = NamedScreen(content = AScreen, name = "first") + val secondRendering = NamedScreen(content = AScreen, name = "second") val firstView = createTestView(firstRendering) val secondView = createTestView(secondRendering) @@ -160,7 +160,7 @@ internal class ViewStateCacheTest { @Test fun throws_on_duplicate_renderings() { val cache = ViewStateCache() - val rendering = NamedScreen(wrapped = AScreen, name = "duplicate") + val rendering = NamedScreen(content = AScreen, name = "duplicate") val view = createTestView(rendering) try { diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt index 06f4a7a98a..a8a2580a8c 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt @@ -139,107 +139,81 @@ public interface ScreenViewFactory : ViewRegistry.Entry { override val viewFactory = - * fromLayout(...) } - * - * class AliasScreen(val similarData: String) : AndroidScreen { - * override val viewFactory = forWrapper { aliasScreen -> - * RealScreen(aliasScreen.similarData) } } - * * To make one [Screen] type a wrapper for others: * - * class Wrapper(val wrapped: W: Screen) : AndroidScreen>, Compatible { - * override val compatibilityKey = Compatible.keyFor(wrapped) override val viewFactory = - * ScreenViewFactory.forWrapper, W> { it.wrapped } } - * - * To make a wrapper that adds information to the [ViewEnvironment]: + * class MyWrapper( + * override val content: W + * ) : AndroidScreen>, Wrapper { + * override val viewFactory = forWrapper, W>() * - * class ReverseNeutronFlowPolarity : ViewEnvironmentKey(Boolean::class) { override val - * default = false } + * override fun map(transform: (W) -> U) = + * MyWrapper(transform(content)) + * } * - * class ReversePolarityScreen( val wrapped: W ) : - * AndroidScreen>, Compatible { override val compatibilityKey: String - * = Compatible.keyFor(wrapped) override val viewFactory = forWrapper, - * Screen> { it.wrapped.withEnvironment( Environment.EMPTY + (ReverseNeutronFlowPolarity to - * true) ) } } + * To make a wrapper that customizes [View] initialization: * - * @param unwrap a function to extract [WrappedT] instances from [WrapperT]s. - */ - @WorkflowUiExperimentalApi - public inline fun < - reified WrapperT : Screen, - WrappedT : Screen - > forWrapper( - crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT, - ): ScreenViewFactory = forWrapper( - unwrap = unwrap, - beforeShowing = {} - ) { _, wrapper, e, showWrapper -> - showWrapper(unwrap(wrapper), e) - } - - /** - * Creates a [ScreenViewFactory] for [WrapperT] that finds and delegates to the one for - * [WrappedT]. Allows [WrapperT] to wrap instances of [WrappedT] to add information or behavior, - * without requiring wasteful wrapping in the view system. + * class WithTutorialTips( + * override val content: W + * ) : AndroidScreen>, Wrapper { + * override val viewFactory = forWrapper, W>( + * beforeShowing = { TutorialTipRunner.initialize(it.view) } + * ) * - * This fully featured variant of the function is able to initialize the freshly created - * [ScreenViewHolder], and transform the wrapped [ScreenViewHolder.runner]. + * override fun map(transform: (W) -> U) = + * WithTutorialTips(transform(content)) + * } * - * To make a wrapper that customizes [View] initialization: + * @param prepEnvironment a function to process the initial [ViewEnvironment] + * before the [ScreenViewFactory] is fetched. Note that this function is not + * applied on updates. Add a [showWrapperScreen] function if you need that. * - * class WithTutorialTips( val wrapped: W ) : AndroidScreen>, - * Compatible { override val compatibilityKey = Compatible.keyFor(wrapped) override - * val viewFactory = forWrapper, W>( unwrap = { it.wrapped }, - * beforeShowing = { TutorialTipRunner.initialize(it.view) }, showWrapperScreen = { _, - * wrapper, environment, showWrapper -> showWrapper(unwrap(wrapper), environment) } ) } + * @param prepContext a function to process the [Context] used to create each [View]. + * it is passed the product of [prepEnvironment] * * @param unwrap a function to extract [WrappedT] instances from [WrapperT]s. + * * @param beforeShowing a function to be invoked immediately after a new [View] is built. - * @param showWrapperScreen a function to be invoked when an instance of [WrapperT] needs to be - * shown in a [View] built to display instances of [WrappedT]. Allows pre- and - * post-processing of the [View]. + * + * @param showWrapperScreen a function to be invoked when an instance of [WrapperT] needs + * to be shown in a [View] built to display instances of [WrappedT]. Allows pre- + * and post-processing of the [View]. */ @WorkflowUiExperimentalApi - public inline fun < - reified WrapperT : Screen, - WrappedT : Screen - > forWrapper( - crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT, + public inline fun forWrapper( + crossinline prepEnvironment: (environment: ViewEnvironment) -> ViewEnvironment = { it }, + crossinline prepContext: ( + environment: ViewEnvironment, + context: Context + ) -> Context = { _, c -> c }, + crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT = { it.content }, crossinline beforeShowing: (viewHolder: ScreenViewHolder) -> Unit = {}, crossinline showWrapperScreen: ( view: View, wrapperScreen: WrapperT, environment: ViewEnvironment, showUnwrappedScreen: (WrappedT, ViewEnvironment) -> Unit - ) -> Unit, - ): ScreenViewFactory = - fromCode { initialRendering, initialEnvironment, context, container -> - val wrappedFactory = unwrap(initialRendering).toViewFactory(initialEnvironment) - val wrapperFactory = wrappedFactory.toUnwrappingViewFactory(unwrap, showWrapperScreen) + ) -> Unit = { _, wrapper, e, showWrapper -> showWrapper(wrapper.content, e) }, + ): ScreenViewFactory where WrapperT : Screen, WrapperT : Wrapper { + return fromCode { initialRendering, initialEnvironment, context, container -> + val preppedEnvironment = prepEnvironment(initialEnvironment) + val wrappedFactory = unwrap(initialRendering).toViewFactory(preppedEnvironment) + val wrapperFactory = wrappedFactory.toUnwrappingViewFactory( + prepEnvironment, prepContext, unwrap, showWrapperScreen + ) + + // Note that we give the factory the original initialEnvironment. + // It applies prepEnvironment itself. wrapperFactory.buildView( - initialRendering, - initialEnvironment, - context, - container + initialRendering, initialEnvironment, context, container ).also { beforeShowing(it) } } + } } } @@ -353,44 +327,32 @@ public fun interface ViewStarter { * @see [ScreenViewFactory.forWrapper]. */ @WorkflowUiExperimentalApi -public inline fun < - reified WrapperT : Screen, - WrappedT : Screen - > ScreenViewFactory.toUnwrappingViewFactory( - crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT -): ScreenViewFactory { - return toUnwrappingViewFactory(unwrap) { _, wrapperScreen, environment, showUnwrappedScreen -> - showUnwrappedScreen(unwrap(wrapperScreen), environment) - } -} - -/** - * Transforms a [ScreenViewFactory] of [WrappedT] into one that can handle instances of [WrapperT]. - * - * @see [ScreenViewFactory.forWrapper]. - */ -@WorkflowUiExperimentalApi -public inline fun < - reified WrapperT : Screen, - WrappedT : Screen - > ScreenViewFactory.toUnwrappingViewFactory( - crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT, +public inline fun ScreenViewFactory.toUnwrappingViewFactory( + crossinline prepEnvironment: (environment: ViewEnvironment) -> ViewEnvironment = { e -> e }, + crossinline prepContext: ( + environment: ViewEnvironment, + context: Context + ) -> Context = { _, c -> c }, + crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT = { it.content }, crossinline showWrapperScreen: ( view: View, wrapperScreen: WrapperT, environment: ViewEnvironment, showUnwrappedScreen: (WrappedT, ViewEnvironment) -> Unit - ) -> Unit -): ScreenViewFactory { + ) -> Unit = { _, wrapperScreen, environment, showUnwrappedScreen -> + showUnwrappedScreen(wrapperScreen.content, environment) + } +): ScreenViewFactory + where WrapperT : Screen, WrapperT : Wrapper, WrappedT : Screen { val wrappedFactory = this return object : ScreenViewFactory by fromCode( buildView = { initialRendering, initialEnvironment, context, container -> + val preppedInitialEnvironment = prepEnvironment(initialEnvironment) + val preppedContext = prepContext(preppedInitialEnvironment, context) + val wrappedHolder = wrappedFactory.buildView( - unwrap(initialRendering), - initialEnvironment, - context, - container + unwrap(initialRendering), preppedInitialEnvironment, preppedContext, container ) object : ScreenViewHolder { diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactoryFinder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactoryFinder.kt index 3b8e4cf976..2e16a92b78 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactoryFinder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactoryFinder.kt @@ -6,7 +6,6 @@ import com.squareup.workflow1.ui.container.BackStackScreenViewFactory import com.squareup.workflow1.ui.container.BodyAndOverlaysContainer import com.squareup.workflow1.ui.container.BodyAndOverlaysScreen import com.squareup.workflow1.ui.container.EnvironmentScreen -import com.squareup.workflow1.ui.container.EnvironmentScreenViewFactory /** * [ViewEnvironment] service object used by [Screen.toViewFactory] to find the right @@ -71,10 +70,15 @@ public interface ScreenViewFactoryFinder { BodyAndOverlaysContainer as ScreenViewFactory } ?: (rendering as? NamedScreen<*>)?.let { - forWrapper, ScreenT> { it.wrapped } as ScreenViewFactory + forWrapper, ScreenT>() as ScreenViewFactory } ?: (rendering as? EnvironmentScreen<*>)?.let { - EnvironmentScreenViewFactory() as ScreenViewFactory + forWrapper, ScreenT>( + prepEnvironment = { e -> e + rendering.environment }, + showWrapperScreen = { _, envScreen, environment, showUnwrapped -> + showUnwrapped(envScreen.content, environment + envScreen.environment) + } + ) as ScreenViewFactory } ?: throw IllegalArgumentException( "A ScreenViewFactory should have been registered to display $rendering, " + diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackButtonScreen.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackButtonScreen.kt index 13c6e7c864..808bf2dd0a 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackButtonScreen.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackButtonScreen.kt @@ -4,15 +4,16 @@ import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.Wrapper import com.squareup.workflow1.ui.backPressedHandler /** - * Adds optional back button handling to a [wrapped] rendering, possibly overriding that + * Adds optional back button handling to a [content] rendering, possibly overriding that * the wrapped rendering's own back button handler. * * @param shadow If `true`, [onBackPressed] is set as the * [backPressedHandler][android.view.View.backPressedHandler] after - * the [wrapped] rendering's view is built / updated, effectively overriding it. + * the [content] rendering's view is built / updated, effectively overriding it. * If false (the default), [onBackPressed] is set afterward, to allow the wrapped rendering to * take precedence if it sets a `backPressedHandler` of its own -- the handler provided * here serves as a default. @@ -22,15 +23,16 @@ import com.squareup.workflow1.ui.backPressedHandler * Defaults to `null`. */ @WorkflowUiExperimentalApi -public class BackButtonScreen( - public val wrapped: W, +public class BackButtonScreen( + public override val content: C, public val shadow: Boolean = false, public val onBackPressed: (() -> Unit)? = null -) : AndroidScreen> { +) : Wrapper, AndroidScreen> { + override fun map(transform: (C) -> D): BackButtonScreen = + BackButtonScreen(transform(content), shadow, onBackPressed) - override val viewFactory: ScreenViewFactory> = + override val viewFactory: ScreenViewFactory> = ScreenViewFactory.forWrapper( - unwrap = { it.wrapped }, showWrapperScreen = { view, backButtonScreen, env, showUnwrapped -> if (!backButtonScreen.shadow) { // Place our handler before invoking innerShowRendering, so that @@ -39,8 +41,8 @@ public class BackButtonScreen( view.backPressedHandler = backButtonScreen.onBackPressed } - // Show the wrapped Screen. - showUnwrapped(backButtonScreen.wrapped, env) + // Show the content Screen. + showUnwrapped(backButtonScreen.content, env) if (backButtonScreen.shadow) { // Place our handler after invoking innerShowRendering, so that ours wins. @@ -48,4 +50,7 @@ public class BackButtonScreen( } } ) + + @Deprecated("Use content", ReplaceWith("content")) + public val wrapped: C = content } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenLegacyViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenLegacyViewFactory.kt index e568c047be..580faca271 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenLegacyViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenLegacyViewFactory.kt @@ -12,6 +12,6 @@ internal object EnvironmentScreenLegacyViewFactory : ViewFactory - Pair(environmentScreen.wrapped, environmentScreen.environment + inheritedEnvironment) + Pair(environmentScreen.content, environmentScreen.environment + inheritedEnvironment) } ) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt deleted file mode 100644 index b139451644..0000000000 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt +++ /dev/null @@ -1,25 +0,0 @@ -package com.squareup.workflow1.ui.container - -import com.squareup.workflow1.ui.Screen -import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewFactory.Companion.fromCode -import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.toUnwrappingViewFactory -import com.squareup.workflow1.ui.toViewFactory - -@WorkflowUiExperimentalApi -internal fun EnvironmentScreenViewFactory(): - ScreenViewFactory> { - return fromCode { initialEnvScreen, initialEnvironment, context, container -> - val mergedInitialEnvironment = initialEnvironment + initialEnvScreen.environment - - initialEnvScreen.wrapped.toViewFactory(mergedInitialEnvironment) - .toUnwrappingViewFactory, WrappedT>( - unwrap = { it.wrapped }, - showWrapperScreen = { _, envScreen, environment, showUnwrapped -> - showUnwrapped(envScreen.wrapped, environment + envScreen.environment) - } - ) - .buildView(initialEnvScreen, mergedInitialEnvironment, context, container) - } -} diff --git a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/ScreenViewFactoryTest.kt b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/ScreenViewFactoryTest.kt index ea6cabb3c8..e368bc8d08 100644 --- a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/ScreenViewFactoryTest.kt +++ b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/ScreenViewFactoryTest.kt @@ -1,20 +1,18 @@ -@file:OptIn(WorkflowUiExperimentalApi::class) - package com.squareup.workflow1.ui import android.content.Context import android.view.ViewGroup import com.google.common.truth.Truth.assertThat -import com.squareup.workflow1.ui.ScreenViewFactory.Companion.fromCode +import com.squareup.workflow1.ui.ScreenViewFactory.Companion.forWrapper import com.squareup.workflow1.ui.ViewRegistry.Entry import org.junit.Test import org.mockito.kotlin.mock import kotlin.reflect.KClass import kotlin.test.assertFailsWith +@OptIn(WorkflowUiExperimentalApi::class) internal class ScreenViewFactoryTest { - @OptIn(WorkflowUiExperimentalApi::class) @Test fun missingBindingMessage_isUseful() { val emptyReg = object : ViewRegistry { @@ -67,20 +65,16 @@ internal class ScreenViewFactoryTest { val screen = MyWrapper(MyAndroidScreen()) screen.toViewFactory(env).startShowing(screen, env, mock()) - assertThat(screen.wrapped.viewFactory.built).isTrue() - assertThat(screen.wrapped.viewFactory.updated).isTrue() + assertThat(screen.content.viewFactory.built).isTrue() + assertThat(screen.content.viewFactory.updated).isTrue() } @OptIn(WorkflowUiExperimentalApi::class) - private class MyWrapper( - val wrapped: MyAndroidScreen - ) : AndroidScreen { - override val viewFactory = - fromCode { initialScreen, initialEnvironment, _, _ -> - wrapped.viewFactory.toUnwrappingViewFactory( - unwrap = { wrapped } - ).startShowing(initialScreen, initialEnvironment, mock()) - } + private class MyWrapper( + override val content: C + ) : Wrapper, AndroidScreen> { + override fun map(transform: (C) -> D) = MyWrapper(transform(content)) + override val viewFactory = forWrapper, C>() } private class TestViewFactory( diff --git a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/container/EnvironmentScreenAndroidIntegrationTest.kt b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/container/EnvironmentScreenAndroidIntegrationTest.kt index fd110a050e..6030e7a445 100644 --- a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/container/EnvironmentScreenAndroidIntegrationTest.kt +++ b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/container/EnvironmentScreenAndroidIntegrationTest.kt @@ -1,5 +1,3 @@ -@file:OptIn(WorkflowUiExperimentalApi::class) - package com.squareup.workflow1.ui.container import com.google.common.truth.Truth.assertThat @@ -13,6 +11,7 @@ import com.squareup.workflow1.ui.toViewFactory import org.junit.Test import org.mockito.kotlin.mock +@OptIn(WorkflowUiExperimentalApi::class) internal class EnvironmentScreenAndroidIntegrationTest { @Test fun mergingWorksForBuild() { val altFactory = WrappedFactory() diff --git a/workflow-ui/core-common/api/core-common.api b/workflow-ui/core-common/api/core-common.api index 9668794d27..dcf71ed3b1 100644 --- a/workflow-ui/core-common/api/core-common.api +++ b/workflow-ui/core-common/api/core-common.api @@ -1,7 +1,12 @@ -public final class com/squareup/workflow1/ui/AsScreen : com/squareup/workflow1/ui/Compatible, com/squareup/workflow1/ui/Screen { +public final class com/squareup/workflow1/ui/AsScreen : com/squareup/workflow1/ui/Screen, com/squareup/workflow1/ui/Wrapper { public fun (Ljava/lang/Object;)V + public fun asSequence ()Lkotlin/sequences/Sequence; public fun getCompatibilityKey ()Ljava/lang/String; + public fun getContent ()Ljava/lang/Object; public final fun getRendering ()Ljava/lang/Object; + public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/AsScreen; + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container; + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper; } public final class com/squareup/workflow1/ui/AsScreenKt { @@ -22,6 +27,11 @@ public final class com/squareup/workflow1/ui/CompatibleKt { public static final fun compatible (Ljava/lang/Object;Ljava/lang/Object;)Z } +public abstract interface class com/squareup/workflow1/ui/Container { + public abstract fun asSequence ()Lkotlin/sequences/Sequence; + public abstract fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container; +} + public final class com/squareup/workflow1/ui/Named : com/squareup/workflow1/ui/Compatible { public fun (Ljava/lang/Object;Ljava/lang/String;)V public final fun component1 ()Ljava/lang/Object; @@ -36,17 +46,23 @@ public final class com/squareup/workflow1/ui/Named : com/squareup/workflow1/ui/C public fun toString ()Ljava/lang/String; } -public final class com/squareup/workflow1/ui/NamedScreen : com/squareup/workflow1/ui/Compatible, com/squareup/workflow1/ui/Screen { +public final class com/squareup/workflow1/ui/NamedScreen : com/squareup/workflow1/ui/Screen, com/squareup/workflow1/ui/Wrapper { public fun (Lcom/squareup/workflow1/ui/Screen;Ljava/lang/String;)V + public fun asSequence ()Lkotlin/sequences/Sequence; public final fun component1 ()Lcom/squareup/workflow1/ui/Screen; public final fun component2 ()Ljava/lang/String; public final fun copy (Lcom/squareup/workflow1/ui/Screen;Ljava/lang/String;)Lcom/squareup/workflow1/ui/NamedScreen; public static synthetic fun copy$default (Lcom/squareup/workflow1/ui/NamedScreen;Lcom/squareup/workflow1/ui/Screen;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/NamedScreen; public fun equals (Ljava/lang/Object;)Z public fun getCompatibilityKey ()Ljava/lang/String; + public fun getContent ()Lcom/squareup/workflow1/ui/Screen; + public synthetic fun getContent ()Ljava/lang/Object; public final fun getName ()Ljava/lang/String; public final fun getWrapped ()Lcom/squareup/workflow1/ui/Screen; public fun hashCode ()I + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container; + public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/NamedScreen; + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper; public fun toString ()Ljava/lang/String; } @@ -120,6 +136,18 @@ public final class com/squareup/workflow1/ui/ViewRegistryKt { public abstract interface annotation class com/squareup/workflow1/ui/WorkflowUiExperimentalApi : java/lang/annotation/Annotation { } +public abstract interface class com/squareup/workflow1/ui/Wrapper : com/squareup/workflow1/ui/Compatible, com/squareup/workflow1/ui/Container { + public abstract fun asSequence ()Lkotlin/sequences/Sequence; + public abstract fun getCompatibilityKey ()Ljava/lang/String; + public abstract fun getContent ()Ljava/lang/Object; + public abstract fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper; +} + +public final class com/squareup/workflow1/ui/Wrapper$DefaultImpls { + public static fun asSequence (Lcom/squareup/workflow1/ui/Wrapper;)Lkotlin/sequences/Sequence; + public static fun getCompatibilityKey (Lcom/squareup/workflow1/ui/Wrapper;)Ljava/lang/String; +} + public final class com/squareup/workflow1/ui/container/AlertOverlay : com/squareup/workflow1/ui/container/ModalOverlay { public fun (Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V public synthetic fun (Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V @@ -166,16 +194,18 @@ public final class com/squareup/workflow1/ui/container/AlertOverlay$Event$Cancel public static final field INSTANCE Lcom/squareup/workflow1/ui/container/AlertOverlay$Event$Canceled; } -public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squareup/workflow1/ui/Screen { +public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squareup/workflow1/ui/Container, com/squareup/workflow1/ui/Screen { public fun (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;)V public fun (Lcom/squareup/workflow1/ui/Screen;[Lcom/squareup/workflow1/ui/Screen;)V + public fun asSequence ()Lkotlin/sequences/Sequence; public fun equals (Ljava/lang/Object;)Z public final fun get (I)Lcom/squareup/workflow1/ui/Screen; public final fun getBackStack ()Ljava/util/List; public final fun getFrames ()Ljava/util/List; public final fun getTop ()Lcom/squareup/workflow1/ui/Screen; public fun hashCode ()I - public final fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BackStackScreen; + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container; + public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BackStackScreen; public final fun mapIndexed (Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/ui/container/BackStackScreen; public final fun plus (Lcom/squareup/workflow1/ui/container/BackStackScreen;)Lcom/squareup/workflow1/ui/container/BackStackScreen; public fun toString ()Ljava/lang/String; @@ -196,12 +226,18 @@ public final class com/squareup/workflow1/ui/container/BodyAndOverlaysScreen : c public final fun mapModals (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BodyAndOverlaysScreen; } -public final class com/squareup/workflow1/ui/container/EnvironmentScreen : com/squareup/workflow1/ui/Compatible, com/squareup/workflow1/ui/Screen { +public final class com/squareup/workflow1/ui/container/EnvironmentScreen : com/squareup/workflow1/ui/Screen, com/squareup/workflow1/ui/Wrapper { public fun (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;)V public synthetic fun (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;ILkotlin/jvm/internal/DefaultConstructorMarker;)V + public fun asSequence ()Lkotlin/sequences/Sequence; public fun getCompatibilityKey ()Ljava/lang/String; + public fun getContent ()Lcom/squareup/workflow1/ui/Screen; + public synthetic fun getContent ()Ljava/lang/Object; public final fun getEnvironment ()Lcom/squareup/workflow1/ui/ViewEnvironment; public final fun getWrapped ()Lcom/squareup/workflow1/ui/Screen; + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container; + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper; + public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/EnvironmentScreen; } public final class com/squareup/workflow1/ui/container/EnvironmentScreenKt { @@ -212,8 +248,14 @@ public final class com/squareup/workflow1/ui/container/EnvironmentScreenKt { public final class com/squareup/workflow1/ui/container/FullScreenOverlay : com/squareup/workflow1/ui/container/ScreenOverlay { public fun (Lcom/squareup/workflow1/ui/Screen;)V + public fun asSequence ()Lkotlin/sequences/Sequence; public fun getCompatibilityKey ()Ljava/lang/String; public fun getContent ()Lcom/squareup/workflow1/ui/Screen; + public synthetic fun getContent ()Ljava/lang/Object; + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container; + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper; + public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/FullScreenOverlay; + public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/ScreenOverlay; } public abstract interface class com/squareup/workflow1/ui/container/ModalOverlay : com/squareup/workflow1/ui/container/Overlay { @@ -222,12 +264,13 @@ public abstract interface class com/squareup/workflow1/ui/container/ModalOverlay public abstract interface class com/squareup/workflow1/ui/container/Overlay { } -public abstract interface class com/squareup/workflow1/ui/container/ScreenOverlay : com/squareup/workflow1/ui/Compatible, com/squareup/workflow1/ui/container/Overlay { - public abstract fun getCompatibilityKey ()Ljava/lang/String; +public abstract interface class com/squareup/workflow1/ui/container/ScreenOverlay : com/squareup/workflow1/ui/Wrapper, com/squareup/workflow1/ui/container/Overlay { public abstract fun getContent ()Lcom/squareup/workflow1/ui/Screen; + public abstract fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/ScreenOverlay; } public final class com/squareup/workflow1/ui/container/ScreenOverlay$DefaultImpls { + public static fun asSequence (Lcom/squareup/workflow1/ui/container/ScreenOverlay;)Lkotlin/sequences/Sequence; public static fun getCompatibilityKey (Lcom/squareup/workflow1/ui/container/ScreenOverlay;)Ljava/lang/String; } diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/AsScreen.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/AsScreen.kt index 6a7f8cc1fa..72926206b7 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/AsScreen.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/AsScreen.kt @@ -6,17 +6,20 @@ package com.squareup.workflow1.ui * `Overlay` interfaces, and will be deprecated and deleted sooner or later. */ @WorkflowUiExperimentalApi -public class AsScreen( - public val rendering: W -) : Screen, Compatible { +public class AsScreen( + override val content: C +) : Screen, Wrapper { init { - check(rendering !is Screen) { - "AsScreen is for converting non-Screen renderings, it should not wrap Screen $rendering." + check(content !is Screen) { + "AsScreen is for converting non-Screen renderings, it should not wrap Screen $content." } } - override val compatibilityKey: String - get() = Compatible.keyFor(rendering, "AsScreen") + @Deprecated("Use content", ReplaceWith("content")) + public val rendering: C = content + + override fun map(transform: (C) -> D): AsScreen = + AsScreen(transform(content)) } /** diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Container.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Container.kt new file mode 100644 index 0000000000..fa18bf3b96 --- /dev/null +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Container.kt @@ -0,0 +1,72 @@ +package com.squareup.workflow1.ui + +import com.squareup.workflow1.ui.Compatible.Companion.keyFor + +/** + * A rendering type comprised of a set of other renderings. + * + * Why two parameter types? The separate [BaseT] type allows implementations + * and sub-interfaces to constrain the types that [map] is allowed to + * transform [ContentT] to. E.g., it allows `FooWrapper` to declare + * that [map] is only able to transform `S` to other types of `Screen`. + * + * @param BaseT the invariant base type of the contents of such a container, + * usually [Screen] or [Overlay][com.squareup.workflow1.ui.container.Overlay]. + * It is common for the [Container] itself to implement [BaseT], but that is + * not a requirement. E.g., [ScreenOverlay][com.squareup.workflow1.ui.container.ScreenOverlay] + * is an [Overlay][com.squareup.workflow1.ui.container.Overlay], but it + * wraps a [Screen]. + * + * @param ContentT the specific subtype of [BaseT] collected by this [Container]. + */ +@WorkflowUiExperimentalApi +public interface Container { + public fun asSequence(): Sequence + + /** + * Returns a [Container] with the [transform]ed contents of the receiver. + * It is expected that an implementation will take advantage of covariance + * to declare its own type as the return type, rather than plain old [Container]. + * This requirement is not enforced because recursive generics are a fussy nuisance. + * + * For example, suppose we want to create `LoggingScreen`, one that wraps any + * other screen to add some logging calls. Its implementation of this method + * would be expected to have a return type of `LoggingScreen` rather than `Container`: + * + * override fun map(transform: (C) -> D): LoggingScreen = + * LoggingScreen(transform(content)) + * + * By requiring all [Container] types to implement [map], we ensure that their + * contents can be repackaged in interesting ways, e.g.: + * + * val childBackStackScreen = renderChild(childWorkflow) { ... } + * val loggingBackStackScreen = childBackStackScreen.map { LoggingScreen(it) } + */ + public fun map(transform: (ContentT) -> ContentU): Container +} + +/** + * A singleton [Container] -- that is, a rendering which wraps a single rendering of type + * [ContentT]. + * + * Usually a [Wrapper] is [Compatible] only with others of the same type with + * [Compatible] [content]. In aid of that, this interface extends [Compatible] and + * provides a convenient default implementation of [compatibilityKey]. + */ +@WorkflowUiExperimentalApi +public interface Wrapper : Container, Compatible { + public val content: ContentT + + /** + * Default implementation makes this [Wrapper] compatible with others of the same type, + * and which wrap compatible [content]. + */ + public override val compatibilityKey: String + get() = keyFor(content, this::class.simpleName ?: "Wrapper") + + public override fun asSequence(): Sequence = sequenceOf(content) + + public override fun map( + transform: (ContentT) -> ContentU + ): Wrapper +} diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/NamedScreen.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/NamedScreen.kt index c351609772..4b7668af76 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/NamedScreen.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/NamedScreen.kt @@ -3,20 +3,24 @@ package com.squareup.workflow1.ui /** * Allows [Screen] renderings that do not implement [Compatible] themselves to be distinguished * by more than just their type. Instances are [compatible] if they have the same name - * and have [compatible] [wrapped] fields. + * and have [compatible] [content] fields. * * UI kits are expected to provide handling for this class by default. */ @WorkflowUiExperimentalApi -public data class NamedScreen( - val wrapped: W, +public data class NamedScreen( + override val content: C, val name: String -) : Screen, Compatible { +) : Screen, Wrapper { init { require(name.isNotBlank()) { "name must not be blank." } } - override val compatibilityKey: String = Compatible.keyFor(wrapped, "NamedScreen($name)") + @Deprecated("Use content", ReplaceWith("content")) + public val wrapped: C = content + + override fun map(transform: (C) -> D): NamedScreen = + NamedScreen(transform(content), name) override fun toString(): String { return "${super.toString()}: $compatibilityKey" diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt index aa236752dd..bb8815eee4 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt @@ -1,5 +1,6 @@ package com.squareup.workflow1.ui.container +import com.squareup.workflow1.ui.Container import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @@ -19,7 +20,7 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi public class BackStackScreen( bottom: StackedT, rest: List -) : Screen { +) : Screen, Container { /** * Creates a screen with elements listed from the [bottom] to the top. */ @@ -28,6 +29,8 @@ public class BackStackScreen( vararg rest: StackedT ) : this(bottom, rest.toList()) + override fun asSequence(): Sequence = frames.asSequence() + public val frames: List = listOf(bottom) + rest /** @@ -50,9 +53,10 @@ public class BackStackScreen( } } - public fun map(transform: (StackedT) -> R): BackStackScreen { - return frames.map(transform) - .toBackStackScreen() + public override fun map( + transform: (StackedT) -> StackedU + ): BackStackScreen { + return frames.map(transform).toBackStackScreen() } public fun mapIndexed(transform: (index: Int, StackedT) -> R): BackStackScreen { diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreen.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreen.kt index 89aef75fa8..6b05343e23 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreen.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreen.kt @@ -1,14 +1,14 @@ package com.squareup.workflow1.ui.container -import com.squareup.workflow1.ui.Compatible import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.ViewRegistry import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.Wrapper import com.squareup.workflow1.ui.plus /** - * Pairs a [wrapped] rendering with a [environment] to support its display. + * Pairs a [content] rendering with a [environment] to support its display. * Typically the rendering type (`RenderingT`) of the root of a UI workflow, * but can be used at any point to modify the [ViewEnvironment] received from * a parent view. @@ -16,15 +16,15 @@ import com.squareup.workflow1.ui.plus * UI kits are expected to provide handling for this class by default. */ @WorkflowUiExperimentalApi -public class EnvironmentScreen( - public val wrapped: V, +public class EnvironmentScreen( + public override val content: C, public val environment: ViewEnvironment = ViewEnvironment.EMPTY -) : Compatible, Screen { - /** - * Ensures that we make the decision to update or replace the root view based on - * the wrapped [wrapped]. - */ - override val compatibilityKey: String = Compatible.keyFor(wrapped, "EnvironmentScreen") +) : Wrapper, Screen { + override fun map(transform: (C) -> D): EnvironmentScreen = + EnvironmentScreen(transform(content), environment) + + @Deprecated("Use content", ReplaceWith("content")) + public val wrapped: C = content } /** @@ -55,7 +55,7 @@ public fun Screen.withEnvironment( if (environment.map.isEmpty()) { this } else { - EnvironmentScreen(wrapped, this.environment + environment) + EnvironmentScreen(content, this.environment + environment) } } else -> EnvironmentScreen(this, environment) diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/FullScreenOverlay.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/FullScreenOverlay.kt index e0130762af..a6b8f41bf7 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/FullScreenOverlay.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/FullScreenOverlay.kt @@ -9,6 +9,9 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi * UI kits are expected to provide handling for this class by default. */ @WorkflowUiExperimentalApi -public class FullScreenOverlay( - public override val content: ContentT -) : ScreenOverlay +public class FullScreenOverlay( + public override val content: C +) : ScreenOverlay { + override fun map(transform: (C) -> D): FullScreenOverlay = + FullScreenOverlay(transform(content)) +} diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ScreenOverlay.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ScreenOverlay.kt index 23a459ae28..7d7814d72c 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ScreenOverlay.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ScreenOverlay.kt @@ -1,17 +1,15 @@ package com.squareup.workflow1.ui.container -import com.squareup.workflow1.ui.Compatible -import com.squareup.workflow1.ui.Compatible.Companion.keyFor import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.Wrapper /** * An [Overlay] built around a root [content] [Screen]. */ @WorkflowUiExperimentalApi -public interface ScreenOverlay : Overlay, Compatible { - public val content: ContentT +public interface ScreenOverlay : Overlay, Wrapper { + public override val content: ContentT - override val compatibilityKey: String - get() = keyFor(content, this::class.simpleName ?: ScreenOverlay::class.simpleName!!) + override fun map(transform: (ContentT) -> ContentU): ScreenOverlay } diff --git a/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/WorkflowUiTestActivity.kt b/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/WorkflowUiTestActivity.kt index acdf72d60a..aacf46fa79 100644 --- a/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/WorkflowUiTestActivity.kt +++ b/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/WorkflowUiTestActivity.kt @@ -101,7 +101,7 @@ public open class WorkflowUiTestActivity : AppCompatActivity() { public fun setRendering(rendering: Screen): View { lastRendering = rendering val named = NamedScreen( - wrapped = rendering, + content = rendering, name = renderingCounter.toString() ) rootStub.show(named, viewEnvironment) diff --git a/workflow-ui/radiography/src/main/java/com/squareup/workflow1/ui/radiography/WorkflowViewRenderer.kt b/workflow-ui/radiography/src/main/java/com/squareup/workflow1/ui/radiography/WorkflowViewRenderer.kt index b3643a301a..43c0e4987b 100644 --- a/workflow-ui/radiography/src/main/java/com/squareup/workflow1/ui/radiography/WorkflowViewRenderer.kt +++ b/workflow-ui/radiography/src/main/java/com/squareup/workflow1/ui/radiography/WorkflowViewRenderer.kt @@ -37,7 +37,7 @@ private object WorkflowViewRendererImpl : ViewStateRenderer { private fun AttributeAppendable.renderRendering(rendering: Any) { val actualRendering = (rendering as? Named<*>)?.wrapped - ?: (rendering as? NamedScreen<*>)?.wrapped + ?: (rendering as? NamedScreen<*>)?.content ?: rendering append("workflow-rendering-type:${actualRendering::class.java.name}")