Skip to content

Commit

Permalink
Extracts ScreenViewFactory.map from ScreenViewFactory.forWrapper
Browse files Browse the repository at this point in the history
Coupling `Wrapper` to `ScreenViewFactory.forWrapper` was a mistake,
made simple transformations that weren't in the strict `Wrapper`
shape more difficult.

Fixes #973
  • Loading branch information
rjrjr committed Mar 21, 2023
1 parent 93f4bac commit db31369
Show file tree
Hide file tree
Showing 9 changed files with 173 additions and 124 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,3 @@ fun MayBeLoadingScreen(
loaders.map { FullScreenOverlay(it) }
)
}

@OptIn(WorkflowUiExperimentalApi::class)
val MayBeLoadingScreen.baseScreen: OverviewDetailScreen
get() = body.content

@OptIn(WorkflowUiExperimentalApi::class)
val MayBeLoadingScreen.loaders: List<LoaderSpinner>
get() = overlays.map { it.content }
Original file line number Diff line number Diff line change
@@ -1,18 +1,17 @@
package com.squareup.sample.container.panel

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

/**
* Show a scrim over some [content], which is invisible if [dimmed] is false,
* visible if it is true.
*/
@OptIn(WorkflowUiExperimentalApi::class)
class ScrimScreen<T : Screen>(
val content: T,
class ScrimScreen<C : Screen>(
override val content: C,
val dimmed: Boolean
) : Screen, Compatible {
override val compatibilityKey = keyFor(content, "ScrimScreen")
) : Wrapper<Screen, C>, Screen {
override fun <D : Screen> map(transform: (C) -> D) = ScrimScreen(transform(content), dimmed)
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,17 @@ package com.squareup.sample.hellobackbutton

import android.os.Parcelable
import com.squareup.sample.hellobackbutton.AreYouSureWorkflow.Finished
import com.squareup.sample.hellobackbutton.AreYouSureWorkflow.Rendering
import com.squareup.sample.hellobackbutton.AreYouSureWorkflow.State
import com.squareup.sample.hellobackbutton.AreYouSureWorkflow.State.Quitting
import com.squareup.sample.hellobackbutton.AreYouSureWorkflow.State.Running
import com.squareup.workflow1.Snapshot
import com.squareup.workflow1.StatefulWorkflow
import com.squareup.workflow1.WorkflowAction.Companion.noAction
import com.squareup.workflow1.action
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.container.AlertOverlay
import com.squareup.workflow1.ui.container.AlertOverlay.Button.NEGATIVE
Expand All @@ -27,12 +31,21 @@ import kotlinx.parcelize.Parcelize
*/
@OptIn(WorkflowUiExperimentalApi::class)
object AreYouSureWorkflow :
StatefulWorkflow<Unit, State, Finished, BodyAndOverlaysScreen<*, AlertOverlay>>() {
StatefulWorkflow<Unit, State, Finished, Rendering>() {
override fun initialState(
props: Unit,
snapshot: Snapshot?
): State = snapshot?.toParcelable() ?: Running

data class Rendering(
val base: Screen,
val alert: AlertOverlay? = null
) : AndroidScreen<Rendering> {
override val viewFactory = ScreenViewFactory.map<Rendering, Screen> {
BodyAndOverlaysScreen(base, listOfNotNull(alert))
}
}

@Parcelize
enum class State : Parcelable {
Running,
Expand All @@ -45,12 +58,12 @@ object AreYouSureWorkflow :
renderProps: Unit,
renderState: State,
context: RenderContext
): BodyAndOverlaysScreen<*, AlertOverlay> {
): Rendering {
val ableBakerCharlie = context.renderChild(HelloBackButtonWorkflow, Unit) { noAction() }

return when (renderState) {
Running -> {
BodyAndOverlaysScreen(
Rendering(
BackButtonScreen(ableBakerCharlie) {
// While we always provide a back button handler, by default the view code
// associated with BackButtonScreen ignores ours if the view created for the
Expand Down Expand Up @@ -80,7 +93,7 @@ object AreYouSureWorkflow :
}
)

BodyAndOverlaysScreen(ableBakerCharlie, alert)
Rendering(ableBakerCharlie, alert)
}
}
}
Expand Down
14 changes: 11 additions & 3 deletions workflow-ui/core-android/api/core-android.api
Original file line number Diff line number Diff line change
Expand Up @@ -129,13 +129,17 @@ public final class com/squareup/workflow1/ui/ScreenViewFactoryKt {
}

public abstract interface class com/squareup/workflow1/ui/ScreenViewHolder {
public static final field Companion Lcom/squareup/workflow1/ui/ScreenViewHolder$Companion;
public abstract fun getEnvironment ()Lcom/squareup/workflow1/ui/ViewEnvironment;
public abstract fun getRunner ()Lcom/squareup/workflow1/ui/ScreenViewRunner;
public abstract fun getView ()Landroid/view/View;
}

public final class com/squareup/workflow1/ui/ScreenViewHolder$Companion {
public final fun invoke (Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/view/View;Lcom/squareup/workflow1/ui/ScreenViewRunner;)Lcom/squareup/workflow1/ui/ScreenViewHolder;
}

public final class com/squareup/workflow1/ui/ScreenViewHolderKt {
public static final fun ScreenViewHolder (Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/view/View;Lcom/squareup/workflow1/ui/ScreenViewRunner;)Lcom/squareup/workflow1/ui/ScreenViewHolder;
public static final fun canShow (Lcom/squareup/workflow1/ui/ScreenViewHolder;Lcom/squareup/workflow1/ui/Screen;)Z
public static final fun getShowing (Lcom/squareup/workflow1/ui/ScreenViewHolder;)Lcom/squareup/workflow1/ui/Screen;
public static final fun show (Lcom/squareup/workflow1/ui/ScreenViewHolder;Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;)V
Expand Down Expand Up @@ -429,16 +433,20 @@ public final class com/squareup/workflow1/ui/container/OverlayDialogFactoryKt {
}

public abstract interface class com/squareup/workflow1/ui/container/OverlayDialogHolder {
public static final field Companion Lcom/squareup/workflow1/ui/container/OverlayDialogHolder$Companion;
public abstract fun getDialog ()Landroid/app/Dialog;
public abstract fun getEnvironment ()Lcom/squareup/workflow1/ui/ViewEnvironment;
public abstract fun getOnBackPressed ()Lkotlin/jvm/functions/Function0;
public abstract fun getOnUpdateBounds ()Lkotlin/jvm/functions/Function1;
public abstract fun getRunner ()Lkotlin/jvm/functions/Function2;
}

public final class com/squareup/workflow1/ui/container/OverlayDialogHolder$Companion {
public final fun invoke (Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/app/Dialog;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/ui/container/OverlayDialogHolder;
public static synthetic fun invoke$default (Lcom/squareup/workflow1/ui/container/OverlayDialogHolder$Companion;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/app/Dialog;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/container/OverlayDialogHolder;
}

public final class com/squareup/workflow1/ui/container/OverlayDialogHolderKt {
public static final fun OverlayDialogHolder (Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/app/Dialog;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/ui/container/OverlayDialogHolder;
public static synthetic fun OverlayDialogHolder$default (Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/app/Dialog;Lkotlin/jvm/functions/Function1;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function2;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/container/OverlayDialogHolder;
public static final fun canShow (Lcom/squareup/workflow1/ui/container/OverlayDialogHolder;Lcom/squareup/workflow1/ui/container/Overlay;)Z
public static final fun getShowing (Lcom/squareup/workflow1/ui/container/OverlayDialogHolder;)Lcom/squareup/workflow1/ui/container/Overlay;
public static final fun show (Lcom/squareup/workflow1/ui/container/OverlayDialogHolder;Lcom/squareup/workflow1/ui/container/Overlay;Lcom/squareup/workflow1/ui/ViewEnvironment;)V
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -139,24 +139,10 @@ public interface ScreenViewFactory<in ScreenT : Screen> : ViewRegistry.Entry<Scr
}

/**
* Creates a [ScreenViewFactory] for type [WrapperT] that finds and delegates to the one for
* [WrappedT]. Allows [WrapperT] to add information or behavior, without requiring wasteful
* parallel wrapping in the view system.
* Convenience treatment of [map] for use with [Wrapper] renderings with
* the least possible boilerplate.
*
* ## Examples
*
* To make one [Screen] type a wrapper for others:
*
* class MyWrapper<W : Screen>(
* override val content: W
* ) : AndroidScreen<Wrapper<W>>, Wrapper<Screen, W> {
* override val viewFactory = forWrapper<MyWrapper<W>, W>()
*
* override fun <U : Screen> map(transform: (W) -> U) =
* MyWrapper(transform(content))
* }
*
* To make a wrapper that customizes [View] initialization:
* For example, to make a [Wrapper] that customizes [View] initialization:
*
* class WithTutorialTips<W : Screen>(
* override val content: W
Expand All @@ -168,46 +154,92 @@ public interface ScreenViewFactory<in ScreenT : Screen> : ViewRegistry.Entry<Scr
* override fun <U : Screen> map(transform: (W) -> U) =
* WithTutorialTips(transform(content))
* }
*/
@WorkflowUiExperimentalApi
public inline fun <reified WrapperT, ContentT : Screen> forWrapper(
crossinline prepEnvironment: (environment: ViewEnvironment) -> ViewEnvironment = { it },
crossinline prepContext: (
environment: ViewEnvironment,
context: Context
) -> Context = { _, c -> c },
crossinline beforeShowing: (viewHolder: ScreenViewHolder<WrapperT>) -> Unit = {},
crossinline showWrapper: (
view: View,
wrapper: WrapperT,
environment: ViewEnvironment,
showContent: (ContentT, ViewEnvironment) -> Unit
) -> Unit = { _, src, e, show -> show(src.content, e) },
): ScreenViewFactory<WrapperT>
where WrapperT : Screen, WrapperT : Wrapper<Screen, ContentT> = map(
prepEnvironment,
prepContext,
beforeShowing,
{ view, source, _, environment, showTransformed ->
showWrapper(view, source, environment, showTransformed)
}
) { it.content }

/**
* Creates a [ScreenViewFactory] for renderings of type [SourceT], which finds and
* delegates to the factory for [TransformedT]. Allows [SourceT] to add information
* or behavior, without requiring wasteful parallel wrapping in the view system.
*
* See [forWrapper] if [SourceT] implements [Wrapper].
*
* For example, to display a [Screen] with a strictly locked down layering policy
* via the built-in type
* [BodyAndOverlaysScreen][com.squareup.workflow1.ui.container.BodyAndOverlaysScreen]:
*
* class Layers(
* val base: Screen,
* val wizard: BackStackScreen<*>? = null,
* val alert: AlertOverlay? = null
* ) : AndroidScreen<Layers> {
* override val viewFactory = ScreenViewFactory.map<Layers, Screen> {
* BodyAndOverlaysScreen(it.base, listOfNotNull(it.wizard, it.alert))
* }
* }
*
* @param prepEnvironment a function to process the initial [ViewEnvironment]
* @param prepEnvironment optional 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.
* applied on updates. Add a [showSource] function if you need that.
*
* @param prepContext a function to process the [Context] used to create each [View].
* @param prepContext optional 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 optional function to be invoked immediately after a new [View] is built.
*
* @param beforeShowing a function to be invoked immediately after a new [View] is built.
* @param showSource function to be invoked when an instance of [SourceT] needs
* to be shown in a [View] built to display instances of [TransformedT]. Allows pre-
* and post-processing of the [View]. Default implementation simply applies [transform].
*
* @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 transform a function to extract [TransformedT] instances from [SourceT]s.
*/
@WorkflowUiExperimentalApi
public inline fun <reified WrapperT, WrappedT : Screen> forWrapper(
public inline fun <reified SourceT : Screen, TransformedT : Screen> map(
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<WrapperT>) -> Unit = {},
crossinline showWrapperScreen: (
crossinline beforeShowing: (viewHolder: ScreenViewHolder<SourceT>) -> Unit = {},
crossinline showSource: (
view: View,
wrapperScreen: WrapperT,
source: SourceT,
transformer: (source: SourceT) -> TransformedT,
environment: ViewEnvironment,
showUnwrappedScreen: (WrappedT, ViewEnvironment) -> Unit
) -> Unit = { _, wrapper, e, showWrapper -> showWrapper(wrapper.content, e) },
): ScreenViewFactory<WrapperT> where WrapperT : Screen, WrapperT : Wrapper<Screen, WrappedT> {
showTransformed: (TransformedT, ViewEnvironment) -> Unit
) -> Unit = { _, src, xform, e, show -> show(xform(src), e) },
noinline transform: (source: SourceT) -> TransformedT,
): ScreenViewFactory<SourceT> {
return fromCode { initialRendering, initialEnvironment, context, container ->
val preppedEnvironment = prepEnvironment(initialEnvironment)
val wrappedFactory = unwrap(initialRendering).toViewFactory(preppedEnvironment)
val wrapperFactory = wrappedFactory.toUnwrappingViewFactory(
val wrappedFactory = transform(initialRendering).toViewFactory(preppedEnvironment)
val wrapperFactory = wrappedFactory.map(
transform,
prepEnvironment,
prepContext,
unwrap,
showWrapperScreen
showSource
)

// Note that we give the factory the original initialEnvironment.
Expand Down Expand Up @@ -324,50 +356,50 @@ public fun interface ViewStarter {
}

/**
* Transforms a [ScreenViewFactory] of [WrappedT] into one that can handle instances of [WrapperT].
* Transforms a [ScreenViewFactory] of [TransformedT] into one that can handle
* instances of [SourceT].
*
* @see [ScreenViewFactory.forWrapper].
* @see [ScreenViewFactory.map].
*/
@WorkflowUiExperimentalApi
public inline fun <reified WrapperT, WrappedT> ScreenViewFactory<WrappedT>.toUnwrappingViewFactory(
public inline
fun <reified SourceT : Screen, TransformedT : Screen> ScreenViewFactory<TransformedT>.map(
noinline transform: (wrapperScreen: SourceT) -> TransformedT,
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: (
crossinline showSource: (
view: View,
wrapperScreen: WrapperT,
source: SourceT,
transform: (wrapperScreen: SourceT) -> TransformedT,
environment: ViewEnvironment,
showUnwrappedScreen: (WrappedT, ViewEnvironment) -> Unit
) -> Unit = { _, wrapperScreen, environment, showUnwrappedScreen ->
showUnwrappedScreen(wrapperScreen.content, environment)
}
): ScreenViewFactory<WrapperT>
where WrapperT : Screen, WrapperT : Wrapper<Screen, WrappedT>, WrappedT : Screen {
showTransformed: (TransformedT, ViewEnvironment) -> Unit
) -> Unit = { _, src, xform, e, showTransformed -> showTransformed(xform(src), e) }
): ScreenViewFactory<SourceT> {
val wrappedFactory = this

return object : ScreenViewFactory<WrapperT> by fromCode(
return object : ScreenViewFactory<SourceT> by fromCode(
buildView = { initialRendering, initialEnvironment, context, container ->
val preppedInitialEnvironment = prepEnvironment(initialEnvironment)
val preppedContext = prepContext(preppedInitialEnvironment, context)

val wrappedHolder = wrappedFactory.buildView(
unwrap(initialRendering),
transform(initialRendering),
preppedInitialEnvironment,
preppedContext,
container
)

object : ScreenViewHolder<WrapperT> {
object : ScreenViewHolder<SourceT> {
override val view = wrappedHolder.view
override val environment: ViewEnvironment get() = wrappedHolder.environment

override val runner: ScreenViewRunner<WrapperT> =
ScreenViewRunner { wrapperScreen, newEnvironment ->
showWrapperScreen(view, wrapperScreen, newEnvironment) { unwrappedScreen, env ->
wrappedHolder.runner.showRendering(unwrappedScreen, env)
override val runner: ScreenViewRunner<SourceT> =
ScreenViewRunner { newSource, newEnvironment ->
showSource(view, newSource, transform, newEnvironment) { transformed, env ->
wrappedHolder.runner.showRendering(transformed, env)
}
}
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,11 +74,10 @@ public interface ScreenViewFactoryFinder {
}
?: (rendering as? EnvironmentScreen<*>)?.let {
forWrapper<EnvironmentScreen<ScreenT>, ScreenT>(
prepEnvironment = { e -> e + rendering.environment },
showWrapperScreen = { _, envScreen, environment, showUnwrapped ->
showUnwrapped(envScreen.content, environment + envScreen.environment)
}
) as ScreenViewFactory<ScreenT>
prepEnvironment = { e -> e + rendering.environment }
) { _, envScreen, environment, showUnwrapped ->
showUnwrapped(envScreen.content, environment + envScreen.environment)
} as ScreenViewFactory<ScreenT>
}
?: throw IllegalArgumentException(
"A ScreenViewFactory should have been registered to display $rendering, " +
Expand Down
Loading

0 comments on commit db31369

Please sign in to comment.