diff --git a/workflow-ui/core-android/api/core-android.api b/workflow-ui/core-android/api/core-android.api index 5b988ec97c..85188f9988 100644 --- a/workflow-ui/core-android/api/core-android.api +++ b/workflow-ui/core-android/api/core-android.api @@ -99,10 +99,6 @@ public final class com/squareup/workflow1/ui/LayoutScreenViewFactory : com/squar public fun getType ()Lkotlin/reflect/KClass; } -public final class com/squareup/workflow1/ui/NamedScreenViewFactoryKt { - public static final fun NamedScreenViewFactory ()Lcom/squareup/workflow1/ui/ScreenViewFactory; -} - public final class com/squareup/workflow1/ui/NamedViewFactory : com/squareup/workflow1/ui/ViewFactory { public static final field INSTANCE Lcom/squareup/workflow1/ui/NamedViewFactory; public fun buildView (Lcom/squareup/workflow1/ui/Named;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/View; @@ -157,6 +153,9 @@ public abstract interface class com/squareup/workflow1/ui/ScreenViewFactory : co } public final class com/squareup/workflow1/ui/ScreenViewFactory$Companion { + public final synthetic fun forWrapper (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenViewFactory; + public final synthetic fun forWrapper (Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow1/ui/ScreenViewFactory; + public static synthetic fun forWrapper$default (Lcom/squareup/workflow1/ui/ScreenViewFactory$Companion;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/ScreenViewFactory; public final synthetic fun fromCode (Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow1/ui/ScreenViewFactory; public final synthetic fun fromLayout (ILkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenViewFactory; public final synthetic fun fromStaticLayout (I)Lcom/squareup/workflow1/ui/ScreenViewFactory; @@ -185,7 +184,6 @@ public final class com/squareup/workflow1/ui/ScreenViewFactoryFinder$DefaultImpl public final class com/squareup/workflow1/ui/ScreenViewFactoryKt { public static final fun startShowing (Lcom/squareup/workflow1/ui/ScreenViewFactory;Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;Lcom/squareup/workflow1/ui/ViewStarter;)Lcom/squareup/workflow1/ui/ScreenViewHolder; public static synthetic fun startShowing$default (Lcom/squareup/workflow1/ui/ScreenViewFactory;Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;Lcom/squareup/workflow1/ui/ViewStarter;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/ScreenViewHolder; - public static final synthetic fun toUnwrappingViewFactory (Lcom/squareup/workflow1/ui/ScreenViewFactory;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenViewFactory; public static final synthetic fun toUnwrappingViewFactory (Lcom/squareup/workflow1/ui/ScreenViewFactory;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow1/ui/ScreenViewFactory; public static final fun toViewFactory (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;)Lcom/squareup/workflow1/ui/ScreenViewFactory; public static final fun viewBindingLayoutInflater (Landroid/content/Context;Landroid/view/ViewGroup;)Landroid/view/LayoutInflater; diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt index 2857ca08eb..a683153391 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt @@ -6,7 +6,7 @@ import android.view.ViewGroup import kotlin.reflect.KClass /** - * **This will be deprecated in favor of [ScreenViewFactory.toUnwrappingViewFactory] very soon.** + * **This will be deprecated in favor of [ScreenViewFactory.forWrapper] very soon.** * * A [ViewFactory] for [OuterT] that delegates view construction responsibilities * to the factory registered for [InnerT]. Makes it convenient for [OuterT] to wrap diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/NamedScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/NamedScreenViewFactory.kt deleted file mode 100644 index a5e68016b0..0000000000 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/NamedScreenViewFactory.kt +++ /dev/null @@ -1,15 +0,0 @@ -package com.squareup.workflow1.ui - -import com.squareup.workflow1.ui.ScreenViewFactory.Companion.fromCode - -/** - * [ScreenViewFactory] that allows views to display instances of [NamedScreen]. Delegates - * to the factory for [NamedScreen.wrapped]. - */ -@WorkflowUiExperimentalApi -internal fun NamedScreenViewFactory() = - fromCode> { namedScreen, environment, context, container -> - namedScreen.wrapped.toViewFactory(environment) - .toUnwrappingViewFactory, WrappedT> { it.wrapped } - .buildView(namedScreen, environment, context, container) - } 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 890354194c..aeafe53ad2 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 @@ -13,25 +13,6 @@ import com.squareup.workflow1.ui.ScreenViewFactory.Companion.fromViewBinding @WorkflowUiExperimentalApi public typealias ViewBindingInflater = (LayoutInflater, ViewGroup?, Boolean) -> BindingT -/** - * The function that updates a [View] instance built by a [ScreenViewFactory]. - * Each [ScreenViewRunner] instance is paired with the single [View] instance, - * its neighbor in a [ScreenViewHolder]. - * - * This is the interface you'll implement directly to update Android view code - * from your [Screen] renderings. A [ScreenViewRunner] serves as the strategy - * object of a [ScreenViewHolder] instantiated by a [ScreenViewFactory] -- the - * runner provides the implementation for the holder's [ScreenViewHolder.show] - * method. - */ -@WorkflowUiExperimentalApi -public fun interface ScreenViewRunner { - public fun showRendering( - rendering: ScreenT, - viewEnvironment: ViewEnvironment - ) -} - /** * A [ViewRegistry.Entry] that can build Android [View] instances, along with functions * that can update them to display [Screen] renderings of a particular [type], bundled @@ -165,6 +146,125 @@ 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 ReverseNeutronFlowPolarity : ViewEnvironmentKey(Boolean::class) { + * override val default = false + * } + * + * 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) + * ) + * } + * } + * + * @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. + * + * This fully featured variant of the function is able to initialize the freshly + * created [ScreenViewHolder], and transform the wrapped [ScreenViewHolder.runner]. + * + * To make a wrapper that customizes [View] initialization: + * + * 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 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]. + */ + @WorkflowUiExperimentalApi + public inline fun < + reified WrapperT : Screen, + WrappedT : Screen + > forWrapper( + crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT, + 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) + wrapperFactory.buildView( + initialRendering, initialEnvironment, context, container + ).also { beforeShowing(it) } + } } } @@ -266,140 +366,18 @@ public fun ScreenViewFactory.startShowing( */ @WorkflowUiExperimentalApi public fun interface ViewStarter { - /** Called from [View.start]. [doStart] must be invoked. */ + /** Called from [ScreenViewFactory.startShowing]. [doStart] must be invoked. */ public fun startView( view: View, doStart: () -> Unit ) } -/** - * Transforms a [ScreenViewFactory] of [WrappedT] into one that can handle - * instances of [WrapperT]. Allows [WrapperT] to wrap instances of [WrappedT] - * to add information or behavior, without requiring wasteful wrapping in the view system. - * - * One general note: when creating a wrapper rendering, you're very likely to want it - * to implement [Compatible], to ensure that checks made to update or replace a view - * are based on the wrapped item. Each wrapper example below illustrates this. - * - * This a simpler variant of the like named function that takes two arguments, for - * use when there is no need to customize the [view][ScreenViewHolder.view] or - * the [environment][ScreenViewHolder.environment]. - * - * ## Examples - * - * To make one rendering type an "alias" for another -- that is, to use the - * same [ScreenViewFactory] to display it: - * - * class RealScreen(val data: String): Screen - * object RealScreenViewFactory = ScreenViewFactory.fromLayout(...) - * - * class AliasScreen(val similarData: String) : Screen - * - * object AliasScreenViewFactory = - * RealScreenViewFactory.toUnwrappingViewFactory { aliasScreen -> - * RealScreen(aliasScreen.similarData) - * } - * - * To make one rendering type a wrapper for others: - * - * class Wrapper(val wrapped: W: Screen) : Screen, Compatible { - * override val compatibilityKey = Compatible.keyFor(wrapped) - * } - * - * fun WrapperViewFactory() = - * ScreenViewFactory.forBuiltView> { wrapper, env, context, container -> - * // Get the view factory of the wrapped screen. - * wrapper.wrapped.toViewFactory(env) - * // Transform it to factory that accepts Wrapper - * .toUnwrappingViewFactory, W> { it.wrapped } - * // Delegate to the transformed factory to build the view. - * .buildView(wrapper, env, context, container) - * } - * - * To make a wrapper that adds information to the [ViewEnvironment]: - * - * class NeutronFlowPolarity(val reversed: Boolean) { - * companion object : ViewEnvironmentKey( - * NeutronFlowPolarity::class - * ) { - * override val default: NeutronFlowPolarity = - * NeutronFlowPolarity(reversed = false) - * } - * } - * - * class OverrideNeutronFlow( - * val wrapped: W, - * val polarity: NeutronFlowPolarity - * ) : Screen, Compatible { - * override val compatibilityKey: String = Compatible.keyFor(wrapped) - * } - * - * fun OverrideNeutronFlowViewFactory() = - * ScreenViewFactory.forBuiltView> { wrapper, env, context, container -> - * // Get the view factory of the wrapped screen. - * wrapper.wrapped.toViewFactory(env) - * // Transform it to factory that accepts OverrideNeutronFlow, by - * // replacing the OverrideNeutronFlow with an EnvironmentScreen - * .toUnwrappingViewFactory, EnvironmentScreen> { - * it.wrapped.withEnvironment( - * Environment.EMPTY + (NeutronFlowPolarity to it.polarity) - * ) - * } - * // Delegate to the transformed factory to build the view. - * .buildView(wrapper, env, context, container) - * } - * - * @param unwrap a function to extract [WrappedT] instances from [WrapperT]s. - */ -@WorkflowUiExperimentalApi -public inline fun < - reified WrapperT : Screen, - WrappedT : Screen - > ScreenViewFactory.toUnwrappingViewFactory( - crossinline unwrap: (wrapperScreen: WrapperT) -> WrappedT, -): ScreenViewFactory = - toUnwrappingViewFactory(unwrap) { _, ws, e, su -> su(unwrap(ws), e) } - /** * Transforms a [ScreenViewFactory] of [WrappedT] into one that can handle * instances of [WrapperT]. * - * One general note: when creating a wrapper rendering, you're very likely to want it - * to implement [Compatible], to ensure that checks made to update or replace a view - * are based on the wrapped item. Each wrapper example below illustrates this. - * - * Also see the simpler variant of this function that takes only an [unwrap] argument. - * - * ## Example - * - * To make a wrapper that customizes [View] initialization: - * - * class WithTutorialTips(val wrapped: W) : Screen, Compatible { - * override val compatibilityKey = Compatible.keyFor(wrapped) - * } - * - * fun WithTutorialTipsFactory() = - * ScreenViewFactory.forBuiltView> = { - * initialRendering, initialEnv, context, container -> - * // Get the view factory of the wrapped screen. - * initialRendering.wrapped.toViewFactory(initialEnv) - * // Transform it to factory that accepts WithTutorialTips - * .toUnwrappingViewFactory, W>( - * unwrap = { it.wrapped }, - * showWrapperScreen = { view, withTips, env, showUnwrapped -> - * TutorialTipRunner.run(view) - * showUnwrapped(withTips.wrapped, env) - * } - * // Delegate to the transformed factory to build the view. - * .buildView(initialRendering, initialEnv, context, container) - * } - * - * @param unwrap a function to extract [WrappedT] instances from [WrapperT]s. - * - * @param showWrapperScreen a function 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]. + * It is usually more convenient to call [ScreenViewFactory.forWrapper]. */ @WorkflowUiExperimentalApi public inline fun < 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 a265fd31f0..90e216a2a8 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 @@ -1,5 +1,6 @@ package com.squareup.workflow1.ui +import com.squareup.workflow1.ui.ScreenViewFactory.Companion.forWrapper import com.squareup.workflow1.ui.container.BackStackScreen import com.squareup.workflow1.ui.container.BackStackScreenViewFactory import com.squareup.workflow1.ui.container.BodyAndModalsContainer @@ -70,7 +71,7 @@ public interface ScreenViewFactoryFinder { BodyAndModalsContainer as ScreenViewFactory } ?: (rendering as? NamedScreen<*>)?.let { - NamedScreenViewFactory() as ScreenViewFactory + forWrapper, ScreenT> { it.wrapped } as ScreenViewFactory } ?: (rendering as? EnvironmentScreen<*>)?.let { EnvironmentScreenViewFactory() as ScreenViewFactory diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt index 45136e3ea4..ae919e8dec 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt @@ -12,7 +12,7 @@ import com.squareup.workflow1.ui.ScreenViewHolder.Companion.ShowingNothing * [environment] should always hold a reference to the [Screen] most recently shown * in [view], with the key [Showing]. [ScreenViewHolder.showing] provides easy access * to it. Note that the shown [Screen] may not be of type [ScreenT], if this - * [ScreenViewHolder] is wrapped by another one. (See [ScreenViewFactory.toUnwrappingViewFactory].) + * [ScreenViewHolder] is wrapped by another one. (See [ScreenViewFactory.forWrapper].) * * Do not call [runner] directly. Use [ScreenViewHolder.show] instead. Or most commonly, * allow [WorkflowViewStub.show] to call it for you. @@ -50,6 +50,25 @@ public interface ScreenViewHolder { } } +/** + * The function that updates a [View] instance built by a [ScreenViewFactory]. + * Each [ScreenViewRunner] instance is paired with a single [View] instance, + * its neighbor in a [ScreenViewHolder]. + * + * This is the interface you'll implement directly to update Android view code + * from your [Screen] renderings. A [ScreenViewRunner] serves as the strategy + * object of a [ScreenViewHolder] instantiated by a [ScreenViewFactory] -- the + * runner provides the implementation for the holder's [ScreenViewHolder.show] + * method. + */ +@WorkflowUiExperimentalApi +public fun interface ScreenViewRunner { + public fun showRendering( + rendering: ScreenT, + viewEnvironment: ViewEnvironment + ) +} + /** * Returns true if [screen] is [compatible] with the [Screen] instance that * was last [shown][show] by the [view] managed by the receiver. @@ -62,9 +81,9 @@ public fun ScreenViewHolder<*>.canShow(screen: Screen): Boolean { } /** - * Updates the [view] managed by the receiver to display [screen], and - * updates the receiver's [environment] as well. The new [environment] - * will hold a reference to [screen] with key [Showing]. + * Updates the [view][ScreenViewHolder.view] managed by the receiver to + * display [screen], and updates the receiver's [environment] as well. + * The new [environment] will hold a reference to [screen] with key [Showing]. */ @WorkflowUiExperimentalApi public fun ScreenViewHolder.show( @@ -78,7 +97,7 @@ public fun ScreenViewHolder.show( } /** - * Returns the [Screen] most recently used to update the receiver's [view] + * Returns the [Screen] most recently used to update the receiver's [view][ScreenViewHolder.view] * via a call to [show]. */ @WorkflowUiExperimentalApi 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 2934c88616..13c6e7c864 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 @@ -5,8 +5,6 @@ import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler -import com.squareup.workflow1.ui.toUnwrappingViewFactory -import com.squareup.workflow1.ui.toViewFactory /** * Adds optional back button handling to a [wrapped] rendering, possibly overriding that @@ -31,27 +29,23 @@ public class BackButtonScreen( ) : AndroidScreen> { override val viewFactory: ScreenViewFactory> = - ScreenViewFactory.fromCode { initialRendering, initialEnv, context, container -> - initialRendering.wrapped.toViewFactory(initialEnv) - .toUnwrappingViewFactory, W>( - unwrap = { it.wrapped }, - showWrapperScreen = { view, backButtonScreen, env, showUnwrapped -> - if (!backButtonScreen.shadow) { - // Place our handler before invoking innerShowRendering, so that - // its later calls to view.backPressedHandler will take precedence - // over ours. - view.backPressedHandler = backButtonScreen.onBackPressed - } + ScreenViewFactory.forWrapper( + unwrap = { it.wrapped }, + showWrapperScreen = { view, backButtonScreen, env, showUnwrapped -> + if (!backButtonScreen.shadow) { + // Place our handler before invoking innerShowRendering, so that + // its later calls to view.backPressedHandler will take precedence + // over ours. + view.backPressedHandler = backButtonScreen.onBackPressed + } - // Show the wrapped Screen. - showUnwrapped(backButtonScreen.wrapped, env) + // Show the wrapped Screen. + showUnwrapped(backButtonScreen.wrapped, env) - if (backButtonScreen.shadow) { - // Place our handler after invoking innerShowRendering, so that ours wins. - view.backPressedHandler = backButtonScreen.onBackPressed - } - } - ) - .buildView(initialRendering, initialEnv, context, container) - } + if (backButtonScreen.shadow) { + // Place our handler after invoking innerShowRendering, so that ours wins. + view.backPressedHandler = backButtonScreen.onBackPressed + } + } + ) }