Skip to content

Commit adfea02

Browse files
committed
Introduces fun Screen.deepMap(Screen): Screen
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.
1 parent 87ade41 commit adfea02

File tree

13 files changed

+157
-16
lines changed

13 files changed

+157
-16
lines changed

samples/containers/common/src/main/java/com/squareup/sample/container/panel/ScrimScreen.kt

+2-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.squareup.sample.container.panel
22

33
import com.squareup.workflow1.ui.Screen
4+
import com.squareup.workflow1.ui.ScreenWrapper
45
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
56
import com.squareup.workflow1.ui.Wrapper
67

@@ -12,6 +13,6 @@ import com.squareup.workflow1.ui.Wrapper
1213
class ScrimScreen<C : Screen>(
1314
override val content: C,
1415
val dimmed: Boolean
15-
) : Wrapper<Screen, C>, Screen {
16+
) : ScreenWrapper<C>, Screen {
1617
override fun <D : Screen> map(transform: (C) -> D) = ScrimScreen(transform(content), dimmed)
1718
}

samples/nested-overlays/src/main/java/com/squareup/sample/nestedoverlays/TopAndBottomBarsScreen.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,15 @@ import com.squareup.workflow1.ui.AndroidScreen
77
import com.squareup.workflow1.ui.Screen
88
import com.squareup.workflow1.ui.ScreenViewFactory
99
import com.squareup.workflow1.ui.ScreenViewFactory.Companion.fromViewBinding
10+
import com.squareup.workflow1.ui.ScreenWrapper
1011
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
11-
import com.squareup.workflow1.ui.Wrapper
1212

1313
@OptIn(WorkflowUiExperimentalApi::class)
1414
data class TopAndBottomBarsScreen<T : Screen>(
1515
override val content: T,
1616
val topBar: ButtonBar? = null,
1717
val bottomBar: ButtonBar? = null
18-
) : AndroidScreen<TopAndBottomBarsScreen<T>>, Wrapper<Screen, T> {
18+
) : AndroidScreen<TopAndBottomBarsScreen<T>>, ScreenWrapper<T> {
1919
override fun <ContentU : Screen> map(transform: (T) -> ContentU) =
2020
TopAndBottomBarsScreen(transform(content), topBar, bottomBar)
2121

Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
@file:OptIn(WorkflowUiExperimentalApi::class)
2+
3+
package com.squareup.workflow1.ui
4+
5+
import com.google.common.truth.Truth.assertThat
6+
import com.squareup.workflow1.ui.container.BackStackScreen
7+
import com.squareup.workflow1.ui.container.EnvironmentScreen
8+
import com.squareup.workflow1.ui.container.withEnvironment
9+
import org.junit.Test
10+
11+
internal class ScreenContainerTest {
12+
object MyScreen : Screen
13+
14+
@Test
15+
fun deepMapRecurses() {
16+
val backStack = BackStackScreen(NamedScreen(MyScreen, "name"))
17+
@Suppress("UNCHECKED_CAST")
18+
val mappedBackStack = backStack
19+
.deepMap { it.withEnvironment() } as BackStackScreen<NamedScreen<EnvironmentScreen<MyScreen>>>
20+
21+
assertThat(mappedBackStack.top.content.content).isSameInstanceAs(MyScreen)
22+
}
23+
}

workflow-ui/core-android/api/core-android.api

+17-1
Original file line numberDiff line numberDiff line change
@@ -205,6 +205,20 @@ public abstract interface class com/squareup/workflow1/ui/ViewStarter {
205205
public abstract fun startView (Landroid/view/View;Lkotlin/jvm/functions/Function0;)V
206206
}
207207

208+
public abstract interface class com/squareup/workflow1/ui/VisualFactory {
209+
public abstract fun createOrNull (Ljava/lang/Object;Ljava/lang/Object;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/VisualHolder;
210+
}
211+
212+
public abstract interface class com/squareup/workflow1/ui/VisualHolder {
213+
public static final field Companion Lcom/squareup/workflow1/ui/VisualHolder$Companion;
214+
public abstract fun getVisual ()Ljava/lang/Object;
215+
public abstract fun update (Ljava/lang/Object;)Z
216+
}
217+
218+
public final class com/squareup/workflow1/ui/VisualHolder$Companion {
219+
public final fun invoke (Ljava/lang/Object;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/VisualHolder;
220+
}
221+
208222
public final class com/squareup/workflow1/ui/WorkflowLayout : android/widget/FrameLayout {
209223
public fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;)V
210224
public synthetic fun <init> (Landroid/content/Context;Landroid/util/AttributeSet;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -339,7 +353,7 @@ public final class com/squareup/workflow1/ui/container/AsDialogHolderWithContent
339353
public static final fun fixBackgroundAndDimming (Landroid/app/Dialog;)V
340354
}
341355

342-
public final class com/squareup/workflow1/ui/container/BackButtonScreen : com/squareup/workflow1/ui/AndroidScreen, com/squareup/workflow1/ui/Wrapper {
356+
public final class com/squareup/workflow1/ui/container/BackButtonScreen : com/squareup/workflow1/ui/AndroidScreen, com/squareup/workflow1/ui/ScreenWrapper {
343357
public fun <init> (Lcom/squareup/workflow1/ui/Screen;ZLkotlin/jvm/functions/Function0;)V
344358
public synthetic fun <init> (Lcom/squareup/workflow1/ui/Screen;ZLkotlin/jvm/functions/Function0;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
345359
public fun asSequence ()Lkotlin/sequences/Sequence;
@@ -351,6 +365,8 @@ public final class com/squareup/workflow1/ui/container/BackButtonScreen : com/sq
351365
public fun getViewFactory ()Lcom/squareup/workflow1/ui/ScreenViewFactory;
352366
public final fun getWrapped ()Lcom/squareup/workflow1/ui/Screen;
353367
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
368+
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenContainer;
369+
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
354370
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper;
355371
public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BackButtonScreen;
356372
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.squareup.workflow1.ui
2+
3+
@WorkflowUiExperimentalApi
4+
public fun interface VisualFactory<ContextT, in RenderingT, VisualT> {
5+
/**
6+
* Given a ui model ([rendering]), creates a [VisualHolder] which pairs:
7+
*
8+
* - a native view system object of type [VisualT] -- a [visual][VisualHolder.visual]
9+
* - an [update function][VisualHolder.update] to apply [RenderingT] instances to
10+
* the new [VisualT] instance.
11+
*
12+
* This method must not call [VisualHolder.update], to ensure that callers have
13+
* complete control over the lifecycle of the new [VisualT].
14+
*
15+
* @param getFactory can be used to make recursive calls to build VisualT
16+
* instances for sub-parts of [rendering]
17+
*/
18+
public fun createOrNull(
19+
rendering: RenderingT,
20+
context: ContextT,
21+
environment: ViewEnvironment,
22+
getFactory: (ViewEnvironment) -> VisualFactory<ContextT, Any, VisualT>
23+
): VisualHolder<RenderingT, VisualT>?
24+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,24 @@
1+
package com.squareup.workflow1.ui
2+
3+
@WorkflowUiExperimentalApi
4+
public interface VisualHolder<in RenderingT, out VisualT> {
5+
public val visual: VisualT
6+
7+
public fun update(rendering: RenderingT): Boolean
8+
9+
public companion object {
10+
public operator fun <RenderingT, VisualT> invoke(
11+
visual: VisualT,
12+
onUpdate: (RenderingT) -> Unit
13+
): VisualHolder<RenderingT, VisualT> {
14+
return object : VisualHolder<RenderingT, VisualT> {
15+
override val visual = visual
16+
17+
override fun update(rendering: RenderingT): Boolean {
18+
onUpdate(rendering)
19+
return true
20+
}
21+
}
22+
}
23+
}
24+
}

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackButtonScreen.kt

+2-3
Original file line numberDiff line numberDiff line change
@@ -4,8 +4,8 @@ import com.squareup.workflow1.ui.AndroidScreen
44
import com.squareup.workflow1.ui.Compatible.Companion.keyFor
55
import com.squareup.workflow1.ui.Screen
66
import com.squareup.workflow1.ui.ScreenViewFactory
7+
import com.squareup.workflow1.ui.ScreenWrapper
78
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
8-
import com.squareup.workflow1.ui.Wrapper
99
import com.squareup.workflow1.ui.setBackHandler
1010

1111
/**
@@ -28,10 +28,9 @@ public class BackButtonScreen<C : Screen>(
2828
public override val content: C,
2929
public val shadow: Boolean = false,
3030
public val onBackPressed: (() -> Unit)? = null
31-
) : Wrapper<Screen, C>, AndroidScreen<BackButtonScreen<C>> {
31+
) : ScreenWrapper<C>, AndroidScreen<BackButtonScreen<C>> {
3232
// If they change the shadow value, we need to build a new view to reorder the handlers.
3333
override val compatibilityKey: String = keyFor(content, "BackButtonScreen+shadow:$shadow")
34-
3534
override fun <D : Screen> map(transform: (C) -> D): BackButtonScreen<D> =
3635
BackButtonScreen(transform(content), shadow, onBackPressed)
3736

workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/ScreenViewFactoryTest.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -72,7 +72,7 @@ internal class ScreenViewFactoryTest {
7272
@OptIn(WorkflowUiExperimentalApi::class)
7373
private class MyWrapper<C : Screen>(
7474
override val content: C
75-
) : Wrapper<Screen, C>, AndroidScreen<MyWrapper<C>> {
75+
) : ScreenWrapper<C>, AndroidScreen<MyWrapper<C>> {
7676
override fun <D : Screen> map(transform: (C) -> D) = MyWrapper(transform(content))
7777
override val viewFactory = forWrapper<MyWrapper<C>, C>()
7878
}

workflow-ui/core-common/api/core-common.api

+25-3
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ public final class com/squareup/workflow1/ui/Named : com/squareup/workflow1/ui/C
4646
public fun toString ()Ljava/lang/String;
4747
}
4848

49-
public final class com/squareup/workflow1/ui/NamedScreen : com/squareup/workflow1/ui/Screen, com/squareup/workflow1/ui/Wrapper {
49+
public final class com/squareup/workflow1/ui/NamedScreen : com/squareup/workflow1/ui/ScreenWrapper {
5050
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/lang/String;)V
5151
public fun asSequence ()Lkotlin/sequences/Sequence;
5252
public final fun component1 ()Lcom/squareup/workflow1/ui/Screen;
@@ -62,13 +62,32 @@ public final class com/squareup/workflow1/ui/NamedScreen : com/squareup/workflow
6262
public fun hashCode ()I
6363
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
6464
public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/NamedScreen;
65+
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenContainer;
66+
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
6567
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper;
6668
public fun toString ()Ljava/lang/String;
6769
}
6870

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

74+
public abstract interface class com/squareup/workflow1/ui/ScreenContainer : com/squareup/workflow1/ui/Container, com/squareup/workflow1/ui/Screen {
75+
public abstract fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenContainer;
76+
}
77+
78+
public final class com/squareup/workflow1/ui/ScreenContainerKt {
79+
public static final fun deepMap (Lcom/squareup/workflow1/ui/Screen;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Screen;
80+
}
81+
82+
public abstract interface class com/squareup/workflow1/ui/ScreenWrapper : com/squareup/workflow1/ui/Screen, com/squareup/workflow1/ui/ScreenContainer, com/squareup/workflow1/ui/Wrapper {
83+
public abstract fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
84+
}
85+
86+
public final class com/squareup/workflow1/ui/ScreenWrapper$DefaultImpls {
87+
public static fun asSequence (Lcom/squareup/workflow1/ui/ScreenWrapper;)Lkotlin/sequences/Sequence;
88+
public static fun getCompatibilityKey (Lcom/squareup/workflow1/ui/ScreenWrapper;)Ljava/lang/String;
89+
}
90+
7291
public abstract interface class com/squareup/workflow1/ui/TextController {
7392
public abstract fun getOnTextChanged ()Lkotlinx/coroutines/flow/Flow;
7493
public abstract fun getTextValue ()Ljava/lang/String;
@@ -214,7 +233,7 @@ public final class com/squareup/workflow1/ui/container/BackStackConfigKt {
214233
public static final fun plus (Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/container/BackStackConfig;)Lcom/squareup/workflow1/ui/ViewEnvironment;
215234
}
216235

217-
public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squareup/workflow1/ui/Container, com/squareup/workflow1/ui/Screen {
236+
public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squareup/workflow1/ui/ScreenContainer {
218237
public static final field Companion Lcom/squareup/workflow1/ui/container/BackStackScreen$Companion;
219238
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;)V
220239
public fun <init> (Lcom/squareup/workflow1/ui/Screen;[Lcom/squareup/workflow1/ui/Screen;)V
@@ -226,6 +245,7 @@ public final class com/squareup/workflow1/ui/container/BackStackScreen : com/squ
226245
public final fun getTop ()Lcom/squareup/workflow1/ui/Screen;
227246
public fun hashCode ()I
228247
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
248+
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenContainer;
229249
public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BackStackScreen;
230250
public final fun mapIndexed (Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/ui/container/BackStackScreen;
231251
public fun toString ()Ljava/lang/String;
@@ -255,7 +275,7 @@ public final class com/squareup/workflow1/ui/container/BodyAndOverlaysScreen : c
255275
public final fun mapOverlays (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BodyAndOverlaysScreen;
256276
}
257277

258-
public final class com/squareup/workflow1/ui/container/EnvironmentScreen : com/squareup/workflow1/ui/Screen, com/squareup/workflow1/ui/Wrapper {
278+
public final class com/squareup/workflow1/ui/container/EnvironmentScreen : com/squareup/workflow1/ui/ScreenWrapper {
259279
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;)V
260280
public synthetic fun <init> (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
261281
public fun asSequence ()Lkotlin/sequences/Sequence;
@@ -265,6 +285,8 @@ public final class com/squareup/workflow1/ui/container/EnvironmentScreen : com/s
265285
public final fun getEnvironment ()Lcom/squareup/workflow1/ui/ViewEnvironment;
266286
public final fun getWrapped ()Lcom/squareup/workflow1/ui/Screen;
267287
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
288+
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenContainer;
289+
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/ScreenWrapper;
268290
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper;
269291
public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/EnvironmentScreen;
270292
}

workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/NamedScreen.kt

+1-1
Original file line numberDiff line numberDiff line change
@@ -11,7 +11,7 @@ package com.squareup.workflow1.ui
1111
public data class NamedScreen<out C : Screen>(
1212
override val content: C,
1313
val name: String
14-
) : Screen, Wrapper<Screen, C> {
14+
) : ScreenWrapper<C> {
1515
init {
1616
require(name.isNotBlank()) { "name must not be blank." }
1717
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
package com.squareup.workflow1.ui
2+
3+
@WorkflowUiExperimentalApi
4+
public interface ScreenContainer<out C : Screen> : Container<Screen, C>, Screen {
5+
public override fun <D : Screen> map(transform: (C) -> D): ScreenContainer<D>
6+
}
7+
8+
@WorkflowUiExperimentalApi
9+
public interface ScreenWrapper<out C : Screen> : ScreenContainer<C>, Wrapper<Screen, C>, Screen {
10+
public override fun <D : Screen> map(transform: (C) -> D): ScreenWrapper<D>
11+
}
12+
13+
/**
14+
* Applies [transform] to the receiver unless it is a [ScreenContainer]. In that case,
15+
* makes a recursive call to [ScreenContainer.map] and applies [deepMap] to its
16+
* contents.
17+
*
18+
* For example, consider this snippet:
19+
*
20+
* val backStack = BackStackScreen(SomeWrapper(theRealScreen))
21+
* val loggingBackStack = backStack.deepMap { WithLogging(it) }
22+
*
23+
* `loggingBackStack` will have a structure like so:
24+
*
25+
* BackStackScreen(SomeWrapper(WithLogging(theRealScreen)))
26+
*/
27+
@WorkflowUiExperimentalApi
28+
public fun Screen.deepMap(transform: (Screen) -> Screen): Screen {
29+
return if (this is ScreenContainer<*>) map { it.deepMap(transform) }
30+
else transform(this)
31+
}

workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BackStackScreen.kt

+2-2
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
package com.squareup.workflow1.ui.container
22

3-
import com.squareup.workflow1.ui.Container
43
import com.squareup.workflow1.ui.Screen
4+
import com.squareup.workflow1.ui.ScreenContainer
55
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
66
import com.squareup.workflow1.ui.container.BackStackScreen.Companion
77
import com.squareup.workflow1.ui.container.BackStackScreen.Companion.fromList
@@ -22,7 +22,7 @@ import com.squareup.workflow1.ui.container.BackStackScreen.Companion.fromListOrN
2222
@WorkflowUiExperimentalApi
2323
public class BackStackScreen<out StackedT : Screen> internal constructor(
2424
public val frames: List<StackedT>
25-
) : Screen, Container<Screen, StackedT> {
25+
) : ScreenContainer<StackedT> {
2626
/**
2727
* Creates a screen with elements listed from the [bottom] to the top.
2828
*/

workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreen.kt

+3-2
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
11
package com.squareup.workflow1.ui.container
22

33
import com.squareup.workflow1.ui.Screen
4+
import com.squareup.workflow1.ui.ScreenWrapper
45
import com.squareup.workflow1.ui.ViewEnvironment
56
import com.squareup.workflow1.ui.ViewRegistry
67
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
7-
import com.squareup.workflow1.ui.Wrapper
88
import com.squareup.workflow1.ui.plus
99

1010
/**
@@ -19,7 +19,7 @@ import com.squareup.workflow1.ui.plus
1919
public class EnvironmentScreen<out C : Screen>(
2020
public override val content: C,
2121
public val environment: ViewEnvironment = ViewEnvironment.EMPTY
22-
) : Wrapper<Screen, C>, Screen {
22+
) : ScreenWrapper<C> {
2323
override fun <D : Screen> map(transform: (C) -> D): EnvironmentScreen<D> =
2424
EnvironmentScreen(transform(content), environment)
2525

@@ -58,6 +58,7 @@ public fun Screen.withEnvironment(
5858
EnvironmentScreen(content, this.environment + environment)
5959
}
6060
}
61+
6162
else -> EnvironmentScreen(this, environment)
6263
}
6364
}

0 commit comments

Comments
 (0)