Skip to content

Commit

Permalink
Introduces fun Screen.deepMap(Screen): Screen
Browse files Browse the repository at this point in the history
Introduces and uses `Screen`-specific subtypes of the `Container` and `Wrapper` interfaces. This allows the inroduction of `Screen.deepMap()`, which allows us to apply a transformation to the "real" `Screen`s collected in a `Container`, no matter how deeply wrapped they are.
  • Loading branch information
rjrjr committed May 18, 2023
1 parent b9f7024 commit 587b6be
Show file tree
Hide file tree
Showing 13 changed files with 156 additions and 15 deletions.
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package com.squareup.sample.container.panel

import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.ui.ScreenWrapper
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.Wrapper

Expand All @@ -12,6 +13,6 @@ import com.squareup.workflow1.ui.Wrapper
class ScrimScreen<C : Screen>(
override val content: C,
val dimmed: Boolean
) : Wrapper<Screen, C>, Screen {
) : ScreenWrapper<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 @@ -7,15 +7,15 @@ import com.squareup.workflow1.ui.AndroidScreen
import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.ui.ScreenViewFactory
import com.squareup.workflow1.ui.ScreenViewFactory.Companion.fromViewBinding
import com.squareup.workflow1.ui.ScreenWrapper
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.Wrapper

@OptIn(WorkflowUiExperimentalApi::class)
data class TopAndBottomBarsScreen<T : Screen>(
override val content: T,
val topBar: ButtonBar? = null,
val bottomBar: ButtonBar? = null
) : AndroidScreen<TopAndBottomBarsScreen<T>>, Wrapper<Screen, T> {
) : AndroidScreen<TopAndBottomBarsScreen<T>>, ScreenWrapper<T> {
override fun <ContentU : Screen> map(transform: (T) -> ContentU) =
TopAndBottomBarsScreen(transform(content), topBar, bottomBar)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
@file:OptIn(WorkflowUiExperimentalApi::class)

package com.squareup.workflow1.ui

import com.google.common.truth.Truth.assertThat
import com.squareup.workflow1.ui.container.BackStackScreen
import com.squareup.workflow1.ui.container.EnvironmentScreen
import com.squareup.workflow1.ui.container.withEnvironment
import org.junit.Test

internal class ScreenContainerTest {
object MyScreen : Screen

@Test
fun deepMapRecurses() {
val backStack = BackStackScreen(NamedScreen(MyScreen, "name"))
@Suppress("UNCHECKED_CAST")
val mappedBackStack = backStack
.deepMap { it.withEnvironment() } as BackStackScreen<NamedScreen<EnvironmentScreen<MyScreen>>>

assertThat(mappedBackStack.top.content.content).isSameInstanceAs(MyScreen)
}
}
18 changes: 17 additions & 1 deletion workflow-ui/core-android/api/core-android.api
Original file line number Diff line number Diff line change
Expand Up @@ -193,6 +193,20 @@ public abstract interface class com/squareup/workflow1/ui/ViewStarter {
public abstract fun startView (Landroid/view/View;Lkotlin/jvm/functions/Function0;)V
}

public abstract interface class com/squareup/workflow1/ui/VisualFactory {
public abstract fun createOrNull (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/VisualHolder;
}

public abstract interface class com/squareup/workflow1/ui/VisualHolder {
public static final field Companion Lcom/squareup/workflow1/ui/VisualHolder$Companion;
public abstract fun getVisual ()Ljava/lang/Object;
public abstract fun update (Ljava/lang/Object;)Z
}

public final class com/squareup/workflow1/ui/VisualHolder$Companion {
public final fun invoke (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/VisualHolder;
}

public final class com/squareup/workflow1/ui/WorkflowLayout : android/widget/FrameLayout {
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
public synthetic fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
Expand Down Expand Up @@ -318,7 +332,7 @@ 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, com/squareup/workflow1/ui/Wrapper {
public final class com/squareup/workflow1/ui/container/BackButtonScreen : com/squareup/workflow1/ui/AndroidScreen, com/squareup/workflow1/ui/ScreenWrapper {
public fun <init> (Lcom/squareup/workflow1/ui/Screen;ZLkotlin/jvm/functions/Function0;)V
public synthetic fun <init> (Lcom/squareup/workflow1/ui/Screen;ZLkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun asSequence ()Lkotlin/sequences/Sequence;
Expand All @@ -330,6 +344,8 @@ public final class com/squareup/workflow1/ui/container/BackButtonScreen : com/sq
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/ScreenContainer;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
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;
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.squareup.workflow1.ui

@WorkflowUiExperimentalApi
public fun interface VisualFactory<ContextT, in RenderingT, VisualT> {
/**
* Given a ui model ([rendering]), creates a [VisualHolder] which pairs:
*
* - a native view system object of type [VisualT] -- a [visual][VisualHolder.visual]
* - an [update function][VisualHolder.update] to apply [RenderingT] instances to
* the new [VisualT] instance.
*
* This method must not call [VisualHolder.update], to ensure that callers have
* complete control over the lifecycle of the new [VisualT].
*
* @param getFactory can be used to make recursive calls to build VisualT
* instances for sub-parts of [rendering]
*/
public fun createOrNull(
rendering: RenderingT,
context: ContextT,
environment: ViewEnvironment,
getFactory: (ViewEnvironment) -> VisualFactory<ContextT, Any, VisualT>
): VisualHolder<RenderingT, VisualT>?
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
package com.squareup.workflow1.ui

@WorkflowUiExperimentalApi
public interface VisualHolder<in RenderingT, out VisualT> {
public val visual: VisualT

public fun update(rendering: RenderingT): Boolean

public companion object {
public operator fun <RenderingT, VisualT> invoke(
visual: VisualT,
onUpdate: (RenderingT) -> Unit
): VisualHolder<RenderingT, VisualT> {
return object : VisualHolder<RenderingT, VisualT> {
override val visual = visual

override fun update(rendering: RenderingT): Boolean {
onUpdate(rendering)
return true
}
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -3,8 +3,8 @@ package com.squareup.workflow1.ui.container
import com.squareup.workflow1.ui.AndroidScreen
import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.ui.ScreenViewFactory
import com.squareup.workflow1.ui.ScreenWrapper
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
import com.squareup.workflow1.ui.Wrapper
import com.squareup.workflow1.ui.backPressedHandler

/**
Expand All @@ -27,7 +27,7 @@ public class BackButtonScreen<C : Screen>(
public override val content: C,
public val shadow: Boolean = false,
public val onBackPressed: (() -> Unit)? = null
) : Wrapper<Screen, C>, AndroidScreen<BackButtonScreen<C>> {
) : ScreenWrapper<C>, AndroidScreen<BackButtonScreen<C>> {
override fun <D : Screen> map(transform: (C) -> D): BackButtonScreen<D> =
BackButtonScreen(transform(content), shadow, onBackPressed)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -72,7 +72,7 @@ internal class ScreenViewFactoryTest {
@OptIn(WorkflowUiExperimentalApi::class)
private class MyWrapper<C : Screen>(
override val content: C
) : Wrapper<Screen, C>, AndroidScreen<MyWrapper<C>> {
) : ScreenWrapper<C>, AndroidScreen<MyWrapper<C>> {
override fun <D : Screen> map(transform: (C) -> D) = MyWrapper(transform(content))
override val viewFactory = forWrapper<MyWrapper<C>, C>()
}
Expand Down
28 changes: 25 additions & 3 deletions workflow-ui/core-common/api/core-common.api
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ 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/Screen, com/squareup/workflow1/ui/Wrapper {
public final class com/squareup/workflow1/ui/NamedScreen : com/squareup/workflow1/ui/ScreenWrapper {
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/lang/String;)V
public fun asSequence ()Lkotlin/sequences/Sequence;
public final fun component1 ()Lcom/squareup/workflow1/ui/Screen;
Expand All @@ -62,13 +62,32 @@ public final class com/squareup/workflow1/ui/NamedScreen : com/squareup/workflow
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/ScreenContainer;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper;
public fun toString ()Ljava/lang/String;
}

public abstract interface class com/squareup/workflow1/ui/Screen {
}

public abstract interface class com/squareup/workflow1/ui/ScreenContainer : com/squareup/workflow1/ui/Container, com/squareup/workflow1/ui/Screen {
public abstract fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenContainer;
}

public final class com/squareup/workflow1/ui/ScreenContainerKt {
public static final fun deepMap (Lcom/squareup/workflow1/ui/Screen;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Screen;
}

public abstract interface class com/squareup/workflow1/ui/ScreenWrapper : com/squareup/workflow1/ui/Screen, com/squareup/workflow1/ui/ScreenContainer, com/squareup/workflow1/ui/Wrapper {
public abstract fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
}

public final class com/squareup/workflow1/ui/ScreenWrapper$DefaultImpls {
public static fun asSequence (Lcom/squareup/workflow1/ui/ScreenWrapper;)Lkotlin/sequences/Sequence;
public static fun getCompatibilityKey (Lcom/squareup/workflow1/ui/ScreenWrapper;)Ljava/lang/String;
}

public abstract interface class com/squareup/workflow1/ui/TextController {
public abstract fun getOnTextChanged ()Lkotlinx/coroutines/flow/Flow;
public abstract fun getTextValue ()Ljava/lang/String;
Expand Down Expand Up @@ -212,7 +231,7 @@ public final class com/squareup/workflow1/ui/container/BackStackConfigKt {
public static final fun plus (Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/container/BackStackConfig;)Lcom/squareup/workflow1/ui/ViewEnvironment;
}

public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squareup/workflow1/ui/Container, com/squareup/workflow1/ui/Screen {
public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squareup/workflow1/ui/ScreenContainer {
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;)V
public fun <init> (Lcom/squareup/workflow1/ui/Screen;[Lcom/squareup/workflow1/ui/Screen;)V
public fun asSequence ()Lkotlin/sequences/Sequence;
Expand All @@ -223,6 +242,7 @@ public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squ
public final fun getTop ()Lcom/squareup/workflow1/ui/Screen;
public fun hashCode ()I
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/ScreenContainer;
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;
Expand All @@ -246,7 +266,7 @@ 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/Screen, com/squareup/workflow1/ui/Wrapper {
public final class com/squareup/workflow1/ui/container/EnvironmentScreen : com/squareup/workflow1/ui/ScreenWrapper {
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;)V
public synthetic fun <init> (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
public fun asSequence ()Lkotlin/sequences/Sequence;
Expand All @@ -256,6 +276,8 @@ public final class com/squareup/workflow1/ui/container/EnvironmentScreen : com/s
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/ScreenContainer;
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
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;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@ package com.squareup.workflow1.ui
public data class NamedScreen<C : Screen>(
override val content: C,
val name: String
) : Screen, Wrapper<Screen, C> {
) : ScreenWrapper<C> {
init {
require(name.isNotBlank()) { "name must not be blank." }
}
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package com.squareup.workflow1.ui

@WorkflowUiExperimentalApi
public interface ScreenContainer<C : Screen> : Container<Screen, C>, Screen {
public override fun <D : Screen> map(transform: (C) -> D): ScreenContainer<D>
}

@WorkflowUiExperimentalApi
public interface ScreenWrapper<C : Screen> : ScreenContainer<C>, Wrapper<Screen, C>, Screen {
public override fun <D : Screen> map(transform: (C) -> D): ScreenWrapper<D>
}

/**
* Applies [transform] to the receiver unless it is a [ScreenContainer]. In that case,
* makes a recursive call to [ScreenContainer.map].
*
* For example, consider this snippet:
*
* val backStack = BackStackScreen(SomeWrapper(theRealScreen))
* val loggingBackStack = backStack.deepMap { WithLogging(it) }
*
* `loggingBackStack` will have a structure like so:
*
* BackStackScreen(SomeWrapper(WithLogging(theRealScreen)))
*/
@WorkflowUiExperimentalApi
public fun Screen.deepMap(transform: (Screen) -> Screen): Screen {
return if (this is ScreenContainer<*>) map { it.deepMap(transform) }
else transform(this)
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
package com.squareup.workflow1.ui.container

import com.squareup.workflow1.ui.Container
import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.ui.ScreenContainer
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi

/**
Expand All @@ -20,7 +20,7 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
public class BackStackScreen<StackedT : Screen>(
bottom: StackedT,
rest: List<StackedT>
) : Screen, Container<Screen, StackedT> {
) : ScreenContainer<StackedT> {
/**
* Creates a screen with elements listed from the [bottom] to the top.
*/
Expand Down
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
package com.squareup.workflow1.ui.container

import com.squareup.workflow1.ui.Screen
import com.squareup.workflow1.ui.ScreenWrapper
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

/**
Expand All @@ -19,7 +19,7 @@ import com.squareup.workflow1.ui.plus
public class EnvironmentScreen<C : Screen>(
public override val content: C,
public val environment: ViewEnvironment = ViewEnvironment.EMPTY
) : Wrapper<Screen, C>, Screen {
) : ScreenWrapper<C> {
override fun <D : Screen> map(transform: (C) -> D): EnvironmentScreen<D> =
EnvironmentScreen(transform(content), environment)

Expand Down Expand Up @@ -58,6 +58,7 @@ public fun Screen.withEnvironment(
EnvironmentScreen(content, this.environment + environment)
}
}

else -> EnvironmentScreen(this, environment)
}
}

0 comments on commit 587b6be

Please sign in to comment.