Skip to content

Commit f76570a

Browse files
committed
Punches up composite rendering API and demonstrates nav logging.
Introduces a few interfaces to make it easier to navigate composite rendering structures, useful for both production logging and workflow unit testing. Updates some samples (hellobackbutton, ravenapp and poetryapp) to demonstrate their use for navigation logging.
1 parent 0c7060a commit f76570a

File tree

10 files changed

+228
-27
lines changed

10 files changed

+228
-27
lines changed

samples/containers/app-poetry/src/main/java/com/squareup/sample/poetryapp/PoetryActivity.kt

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,11 @@ import com.squareup.workflow1.config.AndroidRuntimeConfigTools
1717
import com.squareup.workflow1.ui.Screen
1818
import com.squareup.workflow1.ui.WorkflowLayout
1919
import com.squareup.workflow1.ui.renderWorkflowIn
20+
import com.squareup.workflow1.ui.unwrap
2021
import com.squareup.workflow1.ui.withRegistry
21-
import kotlinx.coroutines.flow.StateFlow
22+
import kotlinx.coroutines.flow.Flow
2223
import kotlinx.coroutines.flow.map
24+
import kotlinx.coroutines.flow.onEach
2325
import timber.log.Timber
2426

2527
private val viewRegistry = SampleContainers
@@ -44,13 +46,15 @@ class PoetryActivity : AppCompatActivity() {
4446
}
4547

4648
class PoetryModel(savedState: SavedStateHandle) : ViewModel() {
47-
val renderings: StateFlow<Screen> by lazy {
49+
val renderings: Flow<Screen> by lazy {
4850
renderWorkflowIn(
4951
workflow = RealPoemsBrowserWorkflow(RealPoemWorkflow()),
5052
scope = viewModelScope,
5153
prop = 0 to 0 to Poem.allPoems,
5254
savedStateHandle = savedState,
5355
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig()
54-
)
56+
).onEach {
57+
Timber.i("Navigated to %s", it.unwrap())
58+
}
5559
}
5660
}

samples/containers/app-raven/src/main/java/com/squareup/sample/ravenapp/RavenActivity.kt

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,10 +17,12 @@ import com.squareup.workflow1.config.AndroidRuntimeConfigTools
1717
import com.squareup.workflow1.ui.Screen
1818
import com.squareup.workflow1.ui.WorkflowLayout
1919
import com.squareup.workflow1.ui.renderWorkflowIn
20+
import com.squareup.workflow1.ui.unwrap
2021
import com.squareup.workflow1.ui.withRegistry
2122
import kotlinx.coroutines.Job
22-
import kotlinx.coroutines.flow.StateFlow
23+
import kotlinx.coroutines.flow.Flow
2324
import kotlinx.coroutines.flow.map
25+
import kotlinx.coroutines.flow.onEach
2426
import kotlinx.coroutines.launch
2527
import timber.log.Timber
2628

@@ -53,7 +55,7 @@ class RavenActivity : AppCompatActivity() {
5355
class RavenModel(savedState: SavedStateHandle) : ViewModel() {
5456
private val running = Job()
5557

56-
val renderings: StateFlow<Screen> by lazy {
58+
val renderings: Flow<Screen> by lazy {
5759
renderWorkflowIn(
5860
workflow = RealPoemWorkflow(),
5961
scope = viewModelScope,
@@ -62,6 +64,8 @@ class RavenModel(savedState: SavedStateHandle) : ViewModel() {
6264
runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig()
6365
) {
6466
running.complete()
67+
}.onEach {
68+
Timber.i("Navigated to %s", it.unwrap())
6569
}
6670
}
6771

samples/containers/common/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailScreen.kt

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
package com.squareup.sample.container.overviewdetail
22

33
import com.squareup.workflow1.ui.Screen
4+
import com.squareup.workflow1.ui.Unwrappable
45
import com.squareup.workflow1.ui.navigation.BackStackScreen
56
import com.squareup.workflow1.ui.navigation.plus
67

@@ -19,7 +20,7 @@ class OverviewDetailScreen<out T : Screen> private constructor(
1920
val overviewRendering: BackStackScreen<T>,
2021
val detailRendering: BackStackScreen<T>? = null,
2122
val selectDefault: (() -> Unit)? = null
22-
) : Screen {
23+
) : Screen, Unwrappable {
2324
constructor(
2425
overviewRendering: BackStackScreen<T>,
2526
detailRendering: BackStackScreen<T>
@@ -37,6 +38,12 @@ class OverviewDetailScreen<out T : Screen> private constructor(
3738
operator fun component1(): BackStackScreen<T> = overviewRendering
3839
operator fun component2(): BackStackScreen<T>? = detailRendering
3940

41+
/**
42+
* For nicer logging. See the call to [unwrap][com.squareup.workflow1.ui.unwrap]
43+
* in the activity.
44+
*/
45+
override val unwrapped = detailRendering ?: overviewRendering
46+
4047
override fun equals(other: Any?): Boolean {
4148
if (this === other) return true
4249
if (javaClass != other?.javaClass) return false

samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/AreYouSureWorkflow.kt

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ import com.squareup.workflow1.ui.AndroidScreen
1414
import com.squareup.workflow1.ui.Screen
1515
import com.squareup.workflow1.ui.ScreenViewFactory
1616
import com.squareup.workflow1.ui.ScreenViewFactory.Companion.map
17+
import com.squareup.workflow1.ui.Unwrappable
1718
import com.squareup.workflow1.ui.navigation.AlertOverlay
1819
import com.squareup.workflow1.ui.navigation.AlertOverlay.Button.NEGATIVE
1920
import com.squareup.workflow1.ui.navigation.AlertOverlay.Button.POSITIVE
@@ -37,12 +38,19 @@ object AreYouSureWorkflow :
3738
): State = snapshot?.toParcelable() ?: Running
3839

3940
class Rendering(
40-
val base: Screen,
41-
val alert: AlertOverlay? = null
42-
) : AndroidScreen<Rendering> {
41+
private val base: Screen,
42+
private val alert: AlertOverlay? = null
43+
) : AndroidScreen<Rendering>, Unwrappable {
4344
override val viewFactory: ScreenViewFactory<Rendering> = map { newRendering ->
4445
BodyAndOverlaysScreen(newRendering.base, listOfNotNull(newRendering.alert))
4546
}
47+
48+
/**
49+
* For nicer logging. See the call to [unwrap][com.squareup.workflow1.ui.unwrap]
50+
* in [HelloBackButtonActivity].
51+
*/
52+
override val unwrapped: Any
53+
get() = alert ?: base
4654
}
4755

4856
@Parcelize

samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/HelloBackButtonActivity.kt

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,11 +15,14 @@ import com.squareup.workflow1.config.AndroidRuntimeConfigTools
1515
import com.squareup.workflow1.ui.Screen
1616
import com.squareup.workflow1.ui.WorkflowLayout
1717
import com.squareup.workflow1.ui.renderWorkflowIn
18+
import com.squareup.workflow1.ui.unwrap
1819
import com.squareup.workflow1.ui.withRegistry
1920
import kotlinx.coroutines.Job
20-
import kotlinx.coroutines.flow.StateFlow
21+
import kotlinx.coroutines.flow.Flow
2122
import kotlinx.coroutines.flow.map
23+
import kotlinx.coroutines.flow.onEach
2224
import kotlinx.coroutines.launch
25+
import timber.log.Timber
2326

2427
private val viewRegistry = SampleContainers
2528

@@ -39,12 +42,18 @@ class HelloBackButtonActivity : AppCompatActivity() {
3942
finish()
4043
}
4144
}
45+
46+
companion object {
47+
init {
48+
Timber.plant(Timber.DebugTree())
49+
}
50+
}
4251
}
4352

4453
class HelloBackButtonModel(savedState: SavedStateHandle) : ViewModel() {
4554
private val running = Job()
4655

47-
val renderings: StateFlow<Screen> by lazy {
56+
val renderings: Flow<Screen> by lazy {
4857
renderWorkflowIn(
4958
workflow = AreYouSureWorkflow,
5059
scope = viewModelScope,
@@ -54,6 +63,8 @@ class HelloBackButtonModel(savedState: SavedStateHandle) : ViewModel() {
5463
// This workflow handles the back button itself, so the activity can't.
5564
// Instead, the workflow emits an output to signal that it's time to shut things down.
5665
running.complete()
66+
}.onEach {
67+
Timber.i("Navigated to %s", it.unwrap())
5768
}
5869
}
5970

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -226,6 +226,7 @@ public final class com/squareup/workflow1/ui/navigation/BackButtonScreen : com/s
226226
public synthetic fun getContent ()Ljava/lang/Object;
227227
public final fun getOnBackPressed ()Lkotlin/jvm/functions/Function0;
228228
public final fun getShadow ()Z
229+
public fun getUnwrapped ()Ljava/lang/Object;
229230
public fun getViewFactory ()Lcom/squareup/workflow1/ui/ScreenViewFactory;
230231
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
231232
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper;

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

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -12,11 +12,27 @@ public final class com/squareup/workflow1/ui/CompatibleKt {
1212
public static final fun compatible (Ljava/lang/Object;Ljava/lang/Object;)Z
1313
}
1414

15-
public abstract interface class com/squareup/workflow1/ui/Container {
15+
public abstract interface class com/squareup/workflow1/ui/Composite : com/squareup/workflow1/ui/Unwrappable {
1616
public abstract fun asSequence ()Lkotlin/sequences/Sequence;
17+
public abstract fun getUnwrapped ()Ljava/lang/Object;
18+
}
19+
20+
public final class com/squareup/workflow1/ui/Composite$DefaultImpls {
21+
public static fun getUnwrapped (Lcom/squareup/workflow1/ui/Composite;)Ljava/lang/Object;
22+
}
23+
24+
public abstract interface class com/squareup/workflow1/ui/Container : com/squareup/workflow1/ui/Composite {
1725
public abstract fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
1826
}
1927

28+
public final class com/squareup/workflow1/ui/Container$DefaultImpls {
29+
public static fun getUnwrapped (Lcom/squareup/workflow1/ui/Container;)Ljava/lang/Object;
30+
}
31+
32+
public final class com/squareup/workflow1/ui/ContainerKt {
33+
public static final fun unwrap (Ljava/lang/Object;)Ljava/lang/Object;
34+
}
35+
2036
public final class com/squareup/workflow1/ui/EnvironmentScreen : com/squareup/workflow1/ui/Screen, com/squareup/workflow1/ui/Wrapper {
2137
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;)V
2238
public synthetic fun <init> (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
@@ -25,6 +41,7 @@ public final class com/squareup/workflow1/ui/EnvironmentScreen : com/squareup/wo
2541
public fun getContent ()Lcom/squareup/workflow1/ui/Screen;
2642
public synthetic fun getContent ()Ljava/lang/Object;
2743
public final fun getEnvironment ()Lcom/squareup/workflow1/ui/ViewEnvironment;
44+
public fun getUnwrapped ()Ljava/lang/Object;
2845
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
2946
public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/EnvironmentScreen;
3047
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper;
@@ -49,6 +66,7 @@ public final class com/squareup/workflow1/ui/NamedScreen : com/squareup/workflow
4966
public fun getContent ()Lcom/squareup/workflow1/ui/Screen;
5067
public synthetic fun getContent ()Ljava/lang/Object;
5168
public final fun getName ()Ljava/lang/String;
69+
public fun getUnwrapped ()Ljava/lang/Object;
5270
public fun hashCode ()I
5371
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
5472
public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/NamedScreen;
@@ -70,6 +88,10 @@ public final class com/squareup/workflow1/ui/TextControllerKt {
7088
public static synthetic fun TextController$default (Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/TextController;
7189
}
7290

91+
public abstract interface class com/squareup/workflow1/ui/Unwrappable {
92+
public abstract fun getUnwrapped ()Ljava/lang/Object;
93+
}
94+
7395
public final class com/squareup/workflow1/ui/ViewEnvironment {
7496
public static final field Companion Lcom/squareup/workflow1/ui/ViewEnvironment$Companion;
7597
public fun equals (Ljava/lang/Object;)Z
@@ -138,6 +160,7 @@ public abstract interface class com/squareup/workflow1/ui/Wrapper : com/squareup
138160
public final class com/squareup/workflow1/ui/Wrapper$DefaultImpls {
139161
public static fun asSequence (Lcom/squareup/workflow1/ui/Wrapper;)Lkotlin/sequences/Sequence;
140162
public static fun getCompatibilityKey (Lcom/squareup/workflow1/ui/Wrapper;)Ljava/lang/String;
163+
public static fun getUnwrapped (Lcom/squareup/workflow1/ui/Wrapper;)Ljava/lang/Object;
141164
}
142165

143166
public final class com/squareup/workflow1/ui/navigation/AlertOverlay : com/squareup/workflow1/ui/navigation/ModalOverlay {
@@ -218,6 +241,7 @@ public final class com/squareup/workflow1/ui/navigation/BackStackScreen : com/sq
218241
public final fun getFrames ()Ljava/util/List;
219242
public final fun getName ()Ljava/lang/String;
220243
public final fun getTop ()Lcom/squareup/workflow1/ui/Screen;
244+
public fun getUnwrapped ()Ljava/lang/Object;
221245
public fun hashCode ()I
222246
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
223247
public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/navigation/BackStackScreen;
@@ -241,13 +265,15 @@ public final class com/squareup/workflow1/ui/navigation/BackStackScreenKt {
241265
public static synthetic fun toBackStackScreenOrNull$default (Ljava/util/List;Ljava/lang/String;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/navigation/BackStackScreen;
242266
}
243267

244-
public final class com/squareup/workflow1/ui/navigation/BodyAndOverlaysScreen : com/squareup/workflow1/ui/Compatible, com/squareup/workflow1/ui/Screen {
268+
public final class com/squareup/workflow1/ui/navigation/BodyAndOverlaysScreen : com/squareup/workflow1/ui/Compatible, com/squareup/workflow1/ui/Composite, com/squareup/workflow1/ui/Screen {
245269
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;Ljava/lang/String;)V
246270
public synthetic fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
271+
public fun asSequence ()Lkotlin/sequences/Sequence;
247272
public final fun getBody ()Lcom/squareup/workflow1/ui/Screen;
248273
public fun getCompatibilityKey ()Ljava/lang/String;
249274
public final fun getName ()Ljava/lang/String;
250275
public final fun getOverlays ()Ljava/util/List;
276+
public fun getUnwrapped ()Ljava/lang/Object;
251277
public final fun mapBody (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/navigation/BodyAndOverlaysScreen;
252278
public final fun mapOverlays (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/navigation/BodyAndOverlaysScreen;
253279
}
@@ -258,6 +284,7 @@ public final class com/squareup/workflow1/ui/navigation/FullScreenModal : com/sq
258284
public fun getCompatibilityKey ()Ljava/lang/String;
259285
public fun getContent ()Lcom/squareup/workflow1/ui/Screen;
260286
public synthetic fun getContent ()Ljava/lang/Object;
287+
public fun getUnwrapped ()Ljava/lang/Object;
261288
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Container;
262289
public synthetic fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/Wrapper;
263290
public fun map (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/navigation/FullScreenModal;
@@ -278,5 +305,6 @@ public abstract interface class com/squareup/workflow1/ui/navigation/ScreenOverl
278305
public final class com/squareup/workflow1/ui/navigation/ScreenOverlay$DefaultImpls {
279306
public static fun asSequence (Lcom/squareup/workflow1/ui/navigation/ScreenOverlay;)Lkotlin/sequences/Sequence;
280307
public static fun getCompatibilityKey (Lcom/squareup/workflow1/ui/navigation/ScreenOverlay;)Ljava/lang/String;
308+
public static fun getUnwrapped (Lcom/squareup/workflow1/ui/navigation/ScreenOverlay;)Ljava/lang/Object;
281309
}
282310

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

Lines changed: 43 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -3,25 +3,55 @@ package com.squareup.workflow1.ui
33
import com.squareup.workflow1.ui.Compatible.Companion.keyFor
44

55
/**
6-
* A rendering type comprised of a set of other renderings.
6+
* A rendering that wraps another that is actually the interesting
7+
* bit (read: the visible bit), particularly from a logging or testing
8+
* point of view.
79
*
8-
* Why two parameter types? The separate [BaseT] type allows implementations
10+
* This is the easiest way to customize behavior of the [unwrap] function.
11+
*/
12+
public interface Unwrappable {
13+
/** Topmost wrapped content, or `this` if empty. */
14+
public val unwrapped: Any
15+
}
16+
17+
/**
18+
* Handy for logging and testing, extracts the "topmost" bit from a receiving
19+
* workflow rendering, honoring [Unwrappable] if applicable.
20+
*/
21+
public tailrec fun Any.unwrap(): Any {
22+
if (this !is Unwrappable) return this
23+
return unwrapped.unwrap()
24+
}
25+
26+
/**
27+
* A rendering that can be decomposed to a [sequence][asSequence] of others.
28+
*/
29+
public interface Composite<out T> : Unwrappable {
30+
public fun asSequence(): Sequence<T>
31+
32+
public override val unwrapped: Any get() = asSequence().lastOrNull() ?: this
33+
}
34+
35+
/**
36+
* A structured [Composite] rendering comprised of a set of other
37+
* renderings of a [specific type][C] of a particular [category][CategoryT],
38+
* and whose contents can be transformed by [map].
39+
*
40+
* Why two parameter types? The separate [CategoryT] type allows implementations
941
* and sub-interfaces to constrain the types that [map] is allowed to
10-
* transform [C] to. E.g., it allows `FooWrapper<S: Screen>` to declare
42+
* transform [C] to. E.g., it allows `BunchOfScreens<S: Screen>` to declare
1143
* that [map] is only able to transform `S` to other types of `Screen`.
1244
*
13-
* @param BaseT the invariant base type of the contents of such a container,
45+
* @param CategoryT the invariant base type of the contents of such a container,
1446
* usually [Screen] or [Overlay][com.squareup.workflow1.ui.navigation.Overlay].
15-
* It is common for the [Container] itself to implement [BaseT], but that is
47+
* It is common for the [Container] itself to implement [CategoryT], but that is
1648
* not a requirement. E.g., [ScreenOverlay][com.squareup.workflow1.ui.navigation.ScreenOverlay]
1749
* is an [Overlay][com.squareup.workflow1.ui.navigation.Overlay], but it
1850
* wraps a [Screen].
1951
*
20-
* @param C the specific subtype of [BaseT] collected by this [Container].
52+
* @param C the specific subtype of [CategoryT] collected by this [Container].
2153
*/
22-
public interface Container<BaseT, out C : BaseT> {
23-
public fun asSequence(): Sequence<C>
24-
54+
public interface Container<CategoryT, out C : CategoryT> : Composite<C> {
2555
/**
2656
* Returns a [Container] with the [transform]ed contents of the receiver.
2757
* It is expected that an implementation will take advantage of covariance
@@ -41,7 +71,7 @@ public interface Container<BaseT, out C : BaseT> {
4171
* val childBackStackScreen = renderChild(childWorkflow) { ... }
4272
* val loggingBackStackScreen = childBackStackScreen.map { LoggingScreen(it) }
4373
*/
44-
public fun <D : BaseT> map(transform: (C) -> D): Container<BaseT, D>
74+
public fun <D : CategoryT> map(transform: (C) -> D): Container<CategoryT, D>
4575
}
4676

4777
/**
@@ -50,9 +80,9 @@ public interface Container<BaseT, out C : BaseT> {
5080
* [EnvironmentScreen][com.squareup.workflow1.ui.EnvironmentScreen] that allows
5181
* changes to be made to the [ViewEnvironment].
5282
*
53-
* Usually a [Wrapper] is [Compatible] only with others of the same type with
54-
* [Compatible] [content]. In aid of that, this interface extends [Compatible] and
55-
* provides a convenient default implementation of [compatibilityKey].
83+
* Usually a [Wrapper] is [Compatible] only with others that are of the same type
84+
* and which are holding [Compatible] [content]. In aid of that, this interface extends
85+
* [Compatible] and provides a convenient default implementation of [compatibilityKey].
5686
*/
5787
public interface Wrapper<BaseT : Any, out C : BaseT> : Container<BaseT, C>, Compatible {
5888
public val content: C

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

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.squareup.workflow1.ui.navigation
22

33
import com.squareup.workflow1.ui.Compatible
44
import com.squareup.workflow1.ui.Compatible.Companion.keyFor
5+
import com.squareup.workflow1.ui.Composite
56
import com.squareup.workflow1.ui.Screen
67

78
/**
@@ -75,9 +76,11 @@ public class BodyAndOverlaysScreen<B : Screen, O : Overlay>(
7576
public val body: B,
7677
public val overlays: List<O> = emptyList(),
7778
public val name: String = ""
78-
) : Screen, Compatible {
79+
) : Screen, Compatible, Composite<Any> {
7980
override val compatibilityKey: String = keyFor(this, name)
8081

82+
override fun asSequence(): Sequence<Any> = sequenceOf(body) + overlays.asSequence()
83+
8184
public fun <S : Screen> mapBody(transform: (B) -> S): BodyAndOverlaysScreen<S, O> {
8285
return BodyAndOverlaysScreen(transform(body), overlays, name)
8386
}

0 commit comments

Comments
 (0)