diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/App.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/App.kt index 9a47062583..15189b91ab 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/App.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/App.kt @@ -30,7 +30,6 @@ private val viewEnvironment = ViewEnvironment.EMPTY.withComposeInteropSupport() ) WorkflowRendering( rendering, - viewEnvironment, Modifier.border( shape = RoundedCornerShape(10.dp), width = 10.dp, diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloComposeScreen.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloComposeScreen.kt index 88e8f8b87b..cd32877240 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloComposeScreen.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocompose/HelloComposeScreen.kt @@ -8,7 +8,6 @@ import androidx.compose.runtime.Composable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.tooling.preview.Preview -import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.compose.ComposeScreen import com.squareup.workflow1.ui.compose.tooling.Preview @@ -18,7 +17,7 @@ data class HelloComposeScreen( val message: String, val onClick: () -> Unit ) : ComposeScreen { - @Composable override fun Content(viewEnvironment: ViewEnvironment) { + @Composable override fun Content() { Text( message, modifier = Modifier diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposebinding/HelloBinding.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposebinding/HelloBinding.kt index aa3f65e67d..8776c87455 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposebinding/HelloBinding.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposebinding/HelloBinding.kt @@ -13,7 +13,7 @@ import com.squareup.workflow1.ui.compose.ScreenComposableFactory import com.squareup.workflow1.ui.compose.tooling.Preview @OptIn(WorkflowUiExperimentalApi::class) -val HelloBinding = ScreenComposableFactory { rendering, _ -> +val HelloBinding = ScreenComposableFactory { rendering -> Text( rendering.message, modifier = Modifier diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposebinding/HelloBindingActivity.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposebinding/HelloBindingActivity.kt index f20954ba3c..499fdc73e4 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposebinding/HelloBindingActivity.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposebinding/HelloBindingActivity.kt @@ -18,7 +18,6 @@ import com.squareup.workflow1.ui.ViewRegistry import com.squareup.workflow1.ui.WorkflowLayout import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.compose.withComposeInteropSupport -import com.squareup.workflow1.ui.compose.withCompositionRoot import com.squareup.workflow1.ui.plus import com.squareup.workflow1.ui.renderWorkflowIn import com.squareup.workflow1.ui.withEnvironment @@ -27,10 +26,9 @@ import kotlinx.coroutines.flow.StateFlow @OptIn(WorkflowUiExperimentalApi::class) private val viewEnvironment = (ViewEnvironment.EMPTY + ViewRegistry(HelloBinding)) - .withCompositionRoot { content -> + .withComposeInteropSupport { content -> MaterialTheme(content = content) } - .withComposeInteropSupport() /** * Demonstrates how to create and display a view factory with diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/ComposeWorkflow.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/ComposeWorkflow.kt index 8ca7ab0eae..2dfa5338d1 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/ComposeWorkflow.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/ComposeWorkflow.kt @@ -7,7 +7,6 @@ import com.squareup.workflow1.Sink import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.Workflow import com.squareup.workflow1.WorkflowIdentifier -import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.compose.ComposeScreen @@ -35,17 +34,15 @@ abstract class ComposeWorkflow : /** * Renders [props] by emitting Compose UI. This function will be called to update the UI whenever - * the [props] or [viewEnvironment] change. + * the [props] change. * * @param props The data to render. * @param outputSink A [Sink] that can be used from UI event handlers to send outputs to this * workflow's parent. - * @param viewEnvironment The [ViewEnvironment] passed down through the `ViewBinding` pipeline. */ @Composable abstract fun RenderingContent( props: PropsT, outputSink: Sink, - viewEnvironment: ViewEnvironment ) override fun asStatefulWorkflow(): StatefulWorkflow = @@ -62,14 +59,12 @@ inline fun Workflow.Companion.composed( crossinline render: @Composable ( props: PropsT, outputSink: Sink, - environment: ViewEnvironment ) -> Unit ): ComposeWorkflow = object : ComposeWorkflow() { @Composable override fun RenderingContent( props: PropsT, outputSink: Sink, - viewEnvironment: ViewEnvironment ) { - render(props, outputSink, viewEnvironment) + render(props, outputSink) } } diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/ComposeWorkflowImpl.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/ComposeWorkflowImpl.kt index 3a649575c9..8f9f3bdc04 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/ComposeWorkflowImpl.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/ComposeWorkflowImpl.kt @@ -10,7 +10,6 @@ import com.squareup.workflow1.Snapshot import com.squareup.workflow1.StatefulWorkflow import com.squareup.workflow1.action import com.squareup.workflow1.contraMap -import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.compose.ComposeScreen @@ -39,12 +38,12 @@ internal class ComposeWorkflowImpl( propsHolder, sinkHolder, object : ComposeScreen { - @Composable override fun Content(viewEnvironment: ViewEnvironment) { + @Composable override fun Content() { // The sink will get set on the first render pass, which must happen before this is first // composed, so it should never be null. val sink = sinkHolder.sink!! // Important: Use the props from the MutableState, _not_ the one passed into render. - workflow.RenderingContent(propsHolder.value, sink, viewEnvironment) + workflow.RenderingContent(propsHolder.value, sink) } } ) diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/HelloComposeWorkflow.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/HelloComposeWorkflow.kt index ac91f5afb2..278dce20f2 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/HelloComposeWorkflow.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/hellocomposeworkflow/HelloComposeWorkflow.kt @@ -16,7 +16,6 @@ import com.squareup.sample.compose.hellocomposeworkflow.HelloComposeWorkflow.Tog import com.squareup.workflow1.Sink import com.squareup.workflow1.WorkflowExperimentalRuntime import com.squareup.workflow1.config.AndroidRuntimeConfigTools -import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.compose.WorkflowRendering import com.squareup.workflow1.ui.compose.renderAsState @@ -34,7 +33,6 @@ object HelloComposeWorkflow : ComposeWorkflow() { @Composable override fun RenderingContent( props: String, outputSink: Sink, - viewEnvironment: ViewEnvironment ) { MaterialTheme { Text( @@ -57,5 +55,5 @@ fun HelloComposeWorkflowPreview() { onOutput = {}, runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig() ) - WorkflowRendering(rendering, ViewEnvironment.EMPTY) + WorkflowRendering(rendering) } diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/inlinerendering/InlineRenderingWorkflow.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/inlinerendering/InlineRenderingWorkflow.kt index 3e77d96fb1..d2c688ba98 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/inlinerendering/InlineRenderingWorkflow.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/inlinerendering/InlineRenderingWorkflow.kt @@ -22,7 +22,6 @@ import com.squareup.workflow1.WorkflowExperimentalRuntime import com.squareup.workflow1.config.AndroidRuntimeConfigTools import com.squareup.workflow1.parse import com.squareup.workflow1.ui.Screen -import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.compose.ComposeScreen import com.squareup.workflow1.ui.compose.WorkflowRendering @@ -60,7 +59,7 @@ fun InlineRenderingWorkflowRendering() { onOutput = {}, runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig() ) - WorkflowRendering(rendering, ViewEnvironment.EMPTY) + WorkflowRendering(rendering) } @Preview(showBackground = true) diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/NestedRenderingsActivity.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/NestedRenderingsActivity.kt index ca8a06b5ac..d7d04cd649 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/NestedRenderingsActivity.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/NestedRenderingsActivity.kt @@ -19,24 +19,22 @@ import com.squareup.workflow1.ui.ViewRegistry import com.squareup.workflow1.ui.WorkflowLayout import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.compose.withComposeInteropSupport -import com.squareup.workflow1.ui.compose.withCompositionRoot import com.squareup.workflow1.ui.plus import com.squareup.workflow1.ui.renderWorkflowIn import com.squareup.workflow1.ui.withEnvironment import kotlinx.coroutines.flow.StateFlow @OptIn(WorkflowUiExperimentalApi::class) -private val viewRegistry = ViewRegistry(RecursiveViewFactory) +private val viewRegistry = ViewRegistry(RecursiveComposableFactory) @OptIn(WorkflowUiExperimentalApi::class) private val viewEnvironment = (ViewEnvironment.EMPTY + viewRegistry) - .withCompositionRoot { content -> + .withComposeInteropSupport { content -> CompositionLocalProvider(LocalBackgroundColor provides Color.Green) { content() } } - .withComposeInteropSupport() @WorkflowUiExperimentalApi class NestedRenderingsActivity : AppCompatActivity() { diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/RecursiveViewFactory.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/RecursiveViewFactory.kt index 0a8025cce1..c290ffa885 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/RecursiveViewFactory.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/nestedrenderings/RecursiveViewFactory.kt @@ -24,14 +24,13 @@ import androidx.compose.ui.tooling.preview.Preview import com.squareup.sample.compose.R import com.squareup.sample.compose.nestedrenderings.RecursiveWorkflow.Rendering import com.squareup.workflow1.ui.Screen -import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.compose.ScreenComposableFactory import com.squareup.workflow1.ui.compose.WorkflowRendering import com.squareup.workflow1.ui.compose.tooling.Preview /** - * Composition local of [Color] to use as the background color for a [RecursiveViewFactory]. + * Composition local of [Color] to use as the background color for a [RecursiveComposableFactory]. */ val LocalBackgroundColor = compositionLocalOf { error("No background color specified") } @@ -39,7 +38,7 @@ val LocalBackgroundColor = compositionLocalOf { error("No background colo * A `ViewFactory` that renders [RecursiveWorkflow.Rendering]s. */ @OptIn(WorkflowUiExperimentalApi::class) -val RecursiveViewFactory = ScreenComposableFactory { rendering, viewEnvironment -> +val RecursiveComposableFactory = ScreenComposableFactory { rendering -> // Every child should be drawn with a slightly-darker background color. val color = LocalBackgroundColor.current val childColor = remember(color) { @@ -57,7 +56,6 @@ val RecursiveViewFactory = ScreenComposableFactory { rendering, viewE CompositionLocalProvider(LocalBackgroundColor provides childColor) { Children( rendering.children, - viewEnvironment, // Pass a weight so that the column fills all the space not occupied by the buttons. modifier = Modifier.weight(1f, fill = true) ) @@ -75,7 +73,7 @@ val RecursiveViewFactory = ScreenComposableFactory { rendering, viewE @Composable fun RecursiveViewFactoryPreview() { CompositionLocalProvider(LocalBackgroundColor provides Color.Green) { - RecursiveViewFactory.Preview( + RecursiveComposableFactory.Preview( Rendering( children = listOf( StringRendering("foo"), @@ -97,7 +95,6 @@ fun RecursiveViewFactoryPreview() { @Composable private fun Children( children: List, - viewEnvironment: ViewEnvironment, modifier: Modifier ) { Column( @@ -110,7 +107,6 @@ private fun Children( childRendering, // Pass a weight so all children are partitioned evenly within the total column space. // Without the weight, each child is the full size of the parent. - viewEnvironment, modifier = Modifier .weight(1f, fill = true) .fillMaxWidth() diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/preview/PreviewActivity.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/preview/PreviewActivity.kt index 1606fc23bc..ba99e7a2e1 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/preview/PreviewActivity.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/preview/PreviewActivity.kt @@ -21,7 +21,6 @@ import androidx.compose.ui.graphics.Color import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.dp import com.squareup.workflow1.ui.Screen -import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.compose.ComposeScreen import com.squareup.workflow1.ui.compose.WorkflowRendering @@ -59,8 +58,8 @@ data class ContactRendering( val name: String, val details: ContactDetailsRendering ) : ComposeScreen { - @Composable override fun Content(viewEnvironment: ViewEnvironment) { - ContactDetails(this, viewEnvironment) + @Composable override fun Content() { + ContactDetails(this) } } @@ -70,10 +69,7 @@ data class ContactDetailsRendering( ) : Screen @Composable -private fun ContactDetails( - rendering: ContactRendering, - environment: ViewEnvironment -) { +private fun ContactDetails(rendering: ContactRendering) { Card( modifier = Modifier .padding(8.dp) @@ -86,7 +82,6 @@ private fun ContactDetails( Text(rendering.name, style = MaterialTheme.typography.body1) WorkflowRendering( rendering = rendering.details, - viewEnvironment = environment, modifier = Modifier .aspectRatio(1f) .border(0.dp, Color.LightGray) diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/textinput/App.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/textinput/App.kt index 5533b12514..d50fce750a 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/textinput/App.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/textinput/App.kt @@ -11,11 +11,11 @@ import com.squareup.workflow1.config.AndroidRuntimeConfigTools import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.ViewRegistry import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.compose.WorkflowRendering +import com.squareup.workflow1.ui.compose.RootScreen import com.squareup.workflow1.ui.compose.renderAsState import com.squareup.workflow1.ui.plus -private val viewEnvironment = ViewEnvironment.EMPTY + ViewRegistry(TextInputViewFactory) +private val viewEnvironment = ViewEnvironment.EMPTY + ViewRegistry(TextInputComposableFactory) @Composable fun TextInputApp() { MaterialTheme { @@ -24,7 +24,7 @@ private val viewEnvironment = ViewEnvironment.EMPTY + ViewRegistry(TextInputView onOutput = {}, runtimeConfig = AndroidRuntimeConfigTools.getAppWorkflowRuntimeConfig() ) - WorkflowRendering(rendering, viewEnvironment) + viewEnvironment.RootScreen(rendering) } } diff --git a/samples/compose-samples/src/main/java/com/squareup/sample/compose/textinput/TextInputViewFactory.kt b/samples/compose-samples/src/main/java/com/squareup/sample/compose/textinput/TextInputViewFactory.kt index 979dedf2ea..795eac2b2d 100644 --- a/samples/compose-samples/src/main/java/com/squareup/sample/compose/textinput/TextInputViewFactory.kt +++ b/samples/compose-samples/src/main/java/com/squareup/sample/compose/textinput/TextInputViewFactory.kt @@ -24,7 +24,7 @@ import com.squareup.workflow1.ui.compose.asMutableState import com.squareup.workflow1.ui.compose.tooling.Preview @OptIn(WorkflowUiExperimentalApi::class) -val TextInputViewFactory = ScreenComposableFactory { rendering, _ -> +val TextInputComposableFactory = ScreenComposableFactory { rendering -> Column( modifier = Modifier .fillMaxSize() @@ -52,7 +52,7 @@ val TextInputViewFactory = ScreenComposableFactory { rendering, _ -> @Preview(showBackground = true) @Composable private fun TextInputViewFactoryPreview() { - TextInputViewFactory.Preview( + TextInputComposableFactory.Preview( Rendering( textController = TextController("Hello world"), onSwapText = {} diff --git a/workflow-ui/compose-tooling/src/androidTest/java/com/squareup/workflow1/ui/compose/tooling/PreviewViewFactoryTest.kt b/workflow-ui/compose-tooling/src/androidTest/java/com/squareup/workflow1/ui/compose/tooling/PreviewViewFactoryTest.kt index a9e261a209..4e2caf585f 100644 --- a/workflow-ui/compose-tooling/src/androidTest/java/com/squareup/workflow1/ui/compose/tooling/PreviewViewFactoryTest.kt +++ b/workflow-ui/compose-tooling/src/androidTest/java/com/squareup/workflow1/ui/compose/tooling/PreviewViewFactoryTest.kt @@ -17,6 +17,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4 import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ViewEnvironmentKey import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.compose.LocalWorkflowEnvironment import com.squareup.workflow1.ui.compose.ScreenComposableFactory import com.squareup.workflow1.ui.compose.WorkflowRendering import com.squareup.workflow1.ui.internal.test.IdleAfterTestRule @@ -96,10 +97,10 @@ internal class PreviewViewFactoryTest { } private val ParentWithOneChild = - ScreenComposableFactory { rendering, environment -> + ScreenComposableFactory { rendering -> Column { BasicText(rendering.first.text) - WorkflowRendering(rendering.second, environment) + WorkflowRendering(rendering.second) } } @@ -109,11 +110,11 @@ internal class PreviewViewFactoryTest { } private val ParentWithTwoChildren = - ScreenComposableFactory { rendering, environment -> + ScreenComposableFactory { rendering -> Column { - WorkflowRendering(rendering.first, environment) + WorkflowRendering(rendering.first) BasicText(rendering.second.text) - WorkflowRendering(rendering.third, environment) + WorkflowRendering(rendering.third) } } @@ -156,11 +157,11 @@ internal class PreviewViewFactoryTest { ) : Screen private val ParentRecursive = - ScreenComposableFactory { rendering, environment -> + ScreenComposableFactory { rendering -> Column { BasicText(rendering.text) rendering.child?.let { child -> - WorkflowRendering(rendering = child, viewEnvironment = environment) + WorkflowRendering(rendering = child) } } } @@ -198,8 +199,8 @@ internal class PreviewViewFactoryTest { override val default: String get() = error("Not specified") } - private val ParentConsumesCustomKey = ScreenComposableFactory { _, environment -> - BasicText(environment[TestEnvironmentKey]) + private val ParentConsumesCustomKey = ScreenComposableFactory { _ -> + BasicText(LocalWorkflowEnvironment.current[TestEnvironmentKey]) } @Preview @Composable diff --git a/workflow-ui/compose-tooling/src/main/java/com/squareup/workflow1/ui/compose/tooling/PlaceholderViewFactory.kt b/workflow-ui/compose-tooling/src/main/java/com/squareup/workflow1/ui/compose/tooling/PlaceholderViewFactory.kt index 80483e3bfb..5f6faac1ae 100644 --- a/workflow-ui/compose-tooling/src/main/java/com/squareup/workflow1/ui/compose/tooling/PlaceholderViewFactory.kt +++ b/workflow-ui/compose-tooling/src/main/java/com/squareup/workflow1/ui/compose/tooling/PlaceholderViewFactory.kt @@ -34,7 +34,7 @@ import com.squareup.workflow1.ui.compose.ScreenComposableFactory internal fun placeholderScreenComposableFactory( modifier: Modifier ): ScreenComposableFactory = - ScreenComposableFactory { rendering, _ -> + ScreenComposableFactory { rendering -> BoxWithConstraints { BasicText( modifier = modifier diff --git a/workflow-ui/compose-tooling/src/main/java/com/squareup/workflow1/ui/compose/tooling/Previews.kt b/workflow-ui/compose-tooling/src/main/java/com/squareup/workflow1/ui/compose/tooling/Previews.kt index fbc8dbd55d..76060b91e7 100644 --- a/workflow-ui/compose-tooling/src/main/java/com/squareup/workflow1/ui/compose/tooling/Previews.kt +++ b/workflow-ui/compose-tooling/src/main/java/com/squareup/workflow1/ui/compose/tooling/Previews.kt @@ -7,9 +7,9 @@ import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ScreenViewFactoryFinder import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.compose.RootScreen import com.squareup.workflow1.ui.compose.ScreenComposableFactory import com.squareup.workflow1.ui.compose.ScreenComposableFactoryFinder -import com.squareup.workflow1.ui.compose.WorkflowRendering import com.squareup.workflow1.ui.compose.asComposableFactory /** @@ -82,7 +82,7 @@ public fun ScreenComposableFactory.Preview( ) { val previewEnvironment = rememberPreviewViewEnvironment(placeholderModifier, viewEnvironmentUpdater, mainFactory = this) - WorkflowRendering(rendering, previewEnvironment, modifier) + previewEnvironment.RootScreen(rendering, modifier) } /** diff --git a/workflow-ui/compose/api/compose.api b/workflow-ui/compose/api/compose.api index c5c72f7110..5b4aef0d07 100644 --- a/workflow-ui/compose/api/compose.api +++ b/workflow-ui/compose/api/compose.api @@ -1,33 +1,36 @@ public final class com/squareup/workflow1/ui/compose/ComposableSingletons$ScreenComposableFactoryFinderKt { public static final field INSTANCE Lcom/squareup/workflow1/ui/compose/ComposableSingletons$ScreenComposableFactoryFinderKt; - public static field lambda-1 Lkotlin/jvm/functions/Function4; - public static field lambda-2 Lkotlin/jvm/functions/Function4; - public static field lambda-3 Lkotlin/jvm/functions/Function4; + public static field lambda-1 Lkotlin/jvm/functions/Function3; + public static field lambda-2 Lkotlin/jvm/functions/Function3; + public static field lambda-3 Lkotlin/jvm/functions/Function3; public fun ()V - public final fun getLambda-1$wf1_compose ()Lkotlin/jvm/functions/Function4; - public final fun getLambda-2$wf1_compose ()Lkotlin/jvm/functions/Function4; - public final fun getLambda-3$wf1_compose ()Lkotlin/jvm/functions/Function4; + public final fun getLambda-1$wf1_compose ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-2$wf1_compose ()Lkotlin/jvm/functions/Function3; + public final fun getLambda-3$wf1_compose ()Lkotlin/jvm/functions/Function3; } public abstract interface class com/squareup/workflow1/ui/compose/ComposeScreen : com/squareup/workflow1/ui/Screen { - public abstract fun Content (Lcom/squareup/workflow1/ui/ViewEnvironment;Landroidx/compose/runtime/Composer;I)V + public abstract fun Content (Landroidx/compose/runtime/Composer;I)V } public final class com/squareup/workflow1/ui/compose/ComposeScreenKt { - public static final fun ComposeScreen (Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/ui/compose/ComposeScreen; + public static final fun ComposeScreen (Lkotlin/jvm/functions/Function2;)Lcom/squareup/workflow1/ui/compose/ComposeScreen; } public final class com/squareup/workflow1/ui/compose/CompositionRootKt { - public static final fun withCompositionRoot (Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/ui/ViewEnvironment; public static final fun withCompositionRoot (Lcom/squareup/workflow1/ui/compose/ScreenComposableFactoryFinder;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/ui/compose/ScreenComposableFactoryFinder; } +public final class com/squareup/workflow1/ui/compose/LocalWorkflowEnvironmentKt { + public static final fun getLocalWorkflowEnvironment ()Landroidx/compose/runtime/ProvidableCompositionLocal; +} + public final class com/squareup/workflow1/ui/compose/RenderAsStateKt { public static final fun renderAsState (Lcom/squareup/workflow1/Workflow;Ljava/lang/Object;Ljava/util/List;Lkotlinx/coroutines/CoroutineScope;Ljava/util/Set;Lkotlin/jvm/functions/Function2;Landroidx/compose/runtime/Composer;II)Landroidx/compose/runtime/State; } public abstract interface class com/squareup/workflow1/ui/compose/ScreenComposableFactory : com/squareup/workflow1/ui/ViewRegistry$Entry { - public abstract fun Content (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroidx/compose/runtime/Composer;I)V + public abstract fun Content (Lcom/squareup/workflow1/ui/Screen;Landroidx/compose/runtime/Composer;I)V public abstract fun getKey ()Lcom/squareup/workflow1/ui/ViewRegistry$Key; public abstract fun getType ()Lkotlin/reflect/KClass; } @@ -55,7 +58,7 @@ public final class com/squareup/workflow1/ui/compose/ScreenComposableFactoryFind } public final class com/squareup/workflow1/ui/compose/ScreenComposableFactoryKt { - public static final fun ScreenComposableFactory (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function4;)Lcom/squareup/workflow1/ui/compose/ScreenComposableFactory; + public static final fun ScreenComposableFactory (Lkotlin/reflect/KClass;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/ui/compose/ScreenComposableFactory; public static final fun asComposableFactory (Lcom/squareup/workflow1/ui/ScreenViewFactory;)Lcom/squareup/workflow1/ui/compose/ScreenComposableFactory; public static final fun asViewFactory (Lcom/squareup/workflow1/ui/compose/ScreenComposableFactory;)Lcom/squareup/workflow1/ui/ScreenViewFactory; public static final fun toComposableFactory (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;)Lcom/squareup/workflow1/ui/compose/ScreenComposableFactory; @@ -66,10 +69,12 @@ public final class com/squareup/workflow1/ui/compose/TextControllerAsMutableStat } public final class com/squareup/workflow1/ui/compose/ViewEnvironmentWithComposeSupportKt { - public static final fun withComposeInteropSupport (Lcom/squareup/workflow1/ui/ViewEnvironment;)Lcom/squareup/workflow1/ui/ViewEnvironment; + public static final fun RootScreen (Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/Screen;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V + public static final fun withComposeInteropSupport (Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function3;)Lcom/squareup/workflow1/ui/ViewEnvironment; + public static synthetic fun withComposeInteropSupport$default (Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function3;ILjava/lang/Object;)Lcom/squareup/workflow1/ui/ViewEnvironment; } public final class com/squareup/workflow1/ui/compose/WorkflowRenderingKt { - public static final fun WorkflowRendering (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V + public static final fun WorkflowRendering (Lcom/squareup/workflow1/ui/Screen;Landroidx/compose/ui/Modifier;Landroidx/compose/runtime/Composer;II)V } diff --git a/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/ScreenComposableFactoryTest.kt b/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/ScreenComposableFactoryTest.kt index 23402346fc..d34545dedc 100644 --- a/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/ScreenComposableFactoryTest.kt +++ b/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/ScreenComposableFactoryTest.kt @@ -44,10 +44,10 @@ internal class ScreenComposableFactoryTest { .around(IdlingDispatcherRule) @Test fun showsComposeContent() { - val viewFactory = ScreenComposableFactory { _, _ -> + val composableFactory = ScreenComposableFactory { _ -> BasicText("Hello, world!") } - val viewEnvironment = (ViewEnvironment.EMPTY + ViewRegistry(viewFactory)) + val viewEnvironment = (ViewEnvironment.EMPTY + ViewRegistry(composableFactory)) .withComposeInteropSupport() composeRule.setContent { @@ -60,10 +60,10 @@ internal class ScreenComposableFactoryTest { } @Test fun getsRenderingUpdates() { - val viewFactory = ScreenComposableFactory { rendering, _ -> + val composableFactory = ScreenComposableFactory { rendering -> BasicText(rendering.text, Modifier.testTag("text")) } - val viewEnvironment = (ViewEnvironment.EMPTY + ViewRegistry(viewFactory)) + val viewEnvironment = (ViewEnvironment.EMPTY + ViewRegistry(composableFactory)) .withComposeInteropSupport() var rendering by mutableStateOf(TestRendering("hello")) @@ -84,11 +84,11 @@ internal class ScreenComposableFactoryTest { override val default: String get() = error("No default") } - val viewFactory = ScreenComposableFactory { _, environment -> - val text = environment[testEnvironmentKey] + val composableFactory = ScreenComposableFactory { _ -> + val text = LocalWorkflowEnvironment.current[testEnvironmentKey] BasicText(text, Modifier.testTag("text")) } - val viewRegistry = ViewRegistry(viewFactory) + val viewRegistry = ViewRegistry(composableFactory) var viewEnvironment by mutableStateOf( (ViewEnvironment.EMPTY + viewRegistry + (testEnvironmentKey to "hello")) .withComposeInteropSupport() @@ -109,7 +109,7 @@ internal class ScreenComposableFactoryTest { @Test fun wrapsFactoryWithRoot() { val wrapperText = mutableStateOf("one") val viewEnvironment = (ViewEnvironment.EMPTY + ViewRegistry(TestFactory)) - .withCompositionRoot { content -> + .withComposeInteropSupport { content -> Column { BasicText(wrapperText.value) content() @@ -141,7 +141,7 @@ internal class ScreenComposableFactoryTest { private data class TestRendering(val text: String = "") : Screen private companion object { - val TestFactory = ScreenComposableFactory { rendering, _ -> + val TestFactory = ScreenComposableFactory { rendering -> BasicText(rendering.text) } } diff --git a/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/WorkflowRenderingTest.kt b/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/WorkflowRenderingTest.kt index bd12f84db2..93ae826e38 100644 --- a/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/WorkflowRenderingTest.kt +++ b/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/WorkflowRenderingTest.kt @@ -97,19 +97,19 @@ internal class WorkflowRenderingTest { ) : Screen val registry1 = ViewRegistry( - ScreenComposableFactory { rendering, _ -> + ScreenComposableFactory { rendering -> BasicText(rendering.text) } ) val registry2 = ViewRegistry( - ScreenComposableFactory { rendering, _ -> + ScreenComposableFactory { rendering -> BasicText(rendering.text.reversed()) } ) val registry = mutableStateOf(registry1) composeRule.setContent { - WorkflowRendering(TestRendering("hello"), ViewEnvironment.EMPTY + registry.value) + (ViewEnvironment.EMPTY + registry.value).RootScreen(TestRendering("hello")) } composeRule.onNodeWithText("hello").assertIsDisplayed() @@ -121,11 +121,11 @@ internal class WorkflowRenderingTest { @Test fun wrapsFactoryWithRoot_whenAlreadyInComposition() { data class TestRendering(val text: String) : Screen - val testFactory = ScreenComposableFactory { rendering, _ -> + val testFactory = ScreenComposableFactory { rendering -> BasicText(rendering.text) } val viewEnvironment = (ViewEnvironment.EMPTY + ViewRegistry(testFactory)) - .withCompositionRoot { content -> + .withComposeInteropSupport { content -> Column { BasicText("one") content() @@ -133,7 +133,7 @@ internal class WorkflowRenderingTest { } composeRule.setContent { - WorkflowRendering(TestRendering("two"), viewEnvironment) + viewEnvironment.RootScreen(TestRendering("two"), Modifier) } composeRule.onNodeWithText("one").assertIsDisplayed() @@ -144,7 +144,7 @@ internal class WorkflowRenderingTest { val wrapperText = mutableStateOf("two") composeRule.setContent { - WorkflowRendering(LegacyViewRendering(wrapperText.value), env) + env.RootScreen(LegacyViewRendering(wrapperText.value)) } onView(withText("two")).check(matches(isDisplayed())) @@ -158,7 +158,7 @@ internal class WorkflowRenderingTest { composeRule.setContent { val rendering = NamedScreen(LegacyViewRendering(wrapperText.value), "fnord") - WorkflowRendering(rendering, env) + env.RootScreen(rendering) } onView(withText("two")).check(matches(isDisplayed())) @@ -170,9 +170,8 @@ internal class WorkflowRenderingTest { composeRule.setContent { val outer = LocalView.current - WorkflowRendering( - viewEnvironment = env, - rendering = NamedScreen( + env.RootScreen( + NamedScreen( name = "fnord", content = ComposeScreen { val inner = LocalView.current @@ -196,13 +195,12 @@ internal class WorkflowRenderingTest { composeRule.setContent { val outer = LocalView.current - WorkflowRendering( - viewEnvironment = env, - rendering = ComposeScreen { environment -> + env.RootScreen( + ComposeScreen { val inner = LocalView.current assertThat(inner).isSameInstanceAs(outer) - BasicText(environment[someKey], Modifier.testTag("tag")) + BasicText(LocalWorkflowEnvironment.current[someKey], Modifier.testTag("tag")) }.withEnvironment((someKey to "fnord")) ) } @@ -215,7 +213,7 @@ internal class WorkflowRenderingTest { val lifecycleEvents = mutableListOf() class LifecycleRecorder : ComposableRendering { - @Composable override fun Content(viewEnvironment: ViewEnvironment) { + @Composable override fun Content() { val lifecycle = LocalLifecycleOwner.current.lifecycle DisposableEffect(lifecycle) { lifecycle.addObserver( @@ -232,12 +230,12 @@ internal class WorkflowRenderingTest { } class EmptyRendering : ComposableRendering { - @Composable override fun Content(viewEnvironment: ViewEnvironment) {} + @Composable override fun Content() {} } var rendering: Screen by mutableStateOf(LifecycleRecorder()) composeRule.setContent { - WorkflowRendering(rendering, env) + env.RootScreen(rendering) } composeRule.runOnIdle { @@ -274,12 +272,12 @@ internal class WorkflowRenderingTest { } class EmptyRendering : ComposableRendering { - @Composable override fun Content(viewEnvironment: ViewEnvironment) {} + @Composable override fun Content() {} } var rendering: Screen by mutableStateOf(LifecycleRecorder()) composeRule.setContent { - WorkflowRendering(rendering, env) + env.RootScreen(rendering) } composeRule.runOnIdle { @@ -304,7 +302,7 @@ internal class WorkflowRenderingTest { composeRule.setContent { CompositionLocalProvider(LocalLifecycleOwner provides parentOwner) { - WorkflowRendering(LifecycleRecorder(states), env) + env.RootScreen(LifecycleRecorder(states)) } } @@ -352,7 +350,7 @@ internal class WorkflowRenderingTest { composeRule.setContent { CompositionLocalProvider(LocalLifecycleOwner provides parentOwner) { - WorkflowRendering(LifecycleRecorder(states), env) + env.RootScreen(LifecycleRecorder(states)) } } @@ -363,7 +361,7 @@ internal class WorkflowRenderingTest { @Test fun appliesModifierToComposableContent() { class Rendering : ComposableRendering { - @Composable override fun Content(viewEnvironment: ViewEnvironment) { + @Composable override fun Content() { Box( Modifier .testTag("box") @@ -373,9 +371,8 @@ internal class WorkflowRenderingTest { } composeRule.setContent { - WorkflowRendering( + env.RootScreen( Rendering(), - env, Modifier.size(width = 42.dp, height = 43.dp) ) } @@ -387,15 +384,14 @@ internal class WorkflowRenderingTest { @Test fun propagatesMinConstraints() { class Rendering : ComposableRendering { - @Composable override fun Content(viewEnvironment: ViewEnvironment) { + @Composable override fun Content() { Box(Modifier.testTag("box")) } } composeRule.setContent { - WorkflowRendering( + env.RootScreen( Rendering(), - env, Modifier.sizeIn(minWidth = 42.dp, minHeight = 43.dp) ) } @@ -412,7 +408,7 @@ internal class WorkflowRenderingTest { override val viewFactory = ScreenViewFactory.fromCode { _, initialEnvironment, context, _ -> val view = View(context) - ScreenViewHolder(initialEnvironment, view) { rendering, _ -> + ScreenViewHolder(initialEnvironment, view) { rendering, _ -> view.id = rendering.viewId } } @@ -420,9 +416,8 @@ internal class WorkflowRenderingTest { composeRule.setContent { with(LocalDensity.current) { - WorkflowRendering( + env.RootScreen( LegacyRendering(viewId), - env, Modifier.size(42.toDp(), 43.toDp()) ) } @@ -437,7 +432,7 @@ internal class WorkflowRenderingTest { class Rendering( override val compatibilityKey: String ) : ComposableRendering, Compatible { - @Composable override fun Content(viewEnvironment: ViewEnvironment) { + @Composable override fun Content() { var counter by rememberSaveable { mutableStateOf(0) } Column { BasicText( @@ -457,7 +452,7 @@ internal class WorkflowRenderingTest { var key by mutableStateOf("one") composeRule.setContent { - WorkflowRendering(Rendering(key), env) + env.RootScreen(Rendering(key)) } composeRule.onNodeWithTag("tag") @@ -487,7 +482,7 @@ internal class WorkflowRenderingTest { var disposeCount = 0 class Rendering(val text: String) : ComposableRendering { - @Composable override fun Content(viewEnvironment: ViewEnvironment) { + @Composable override fun Content() { var counter by rememberSaveable { mutableStateOf(0) } Column { BasicText( @@ -507,7 +502,7 @@ internal class WorkflowRenderingTest { var text by mutableStateOf("one") composeRule.setContent { - WorkflowRendering(Rendering(text), env) + env.RootScreen(Rendering(text)) } composeRule.onNodeWithTag("tag") @@ -543,7 +538,7 @@ internal class WorkflowRenderingTest { // For some reason, if we just capture the states val, it is null in the composable. private val states: MutableList ) : ComposableRendering { - @Composable override fun Content(viewEnvironment: ViewEnvironment) { + @Composable override fun Content() { val lifecycle = LocalLifecycleOwner.current.lifecycle DisposableEffect(lifecycle) { this@LifecycleRecorder.states += lifecycle.currentState @@ -574,10 +569,9 @@ internal class WorkflowRenderingTest { override val type: KClass get() = error("whatever") @Composable override fun Content( - rendering: ScreenT, - environment: ViewEnvironment + rendering: ScreenT ) { - (rendering as ComposableRendering).Content(environment) + (rendering as ComposableRendering).Content() } } } else { @@ -594,7 +588,7 @@ internal class WorkflowRenderingTest { .withComposeInteropSupport() private interface ComposableRendering : Screen { - @Composable fun Content(viewEnvironment: ViewEnvironment) + @Composable fun Content() } private data class LegacyViewRendering(val text: String) : AndroidScreen { diff --git a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ComposeScreen.kt b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ComposeScreen.kt index 06a00f645e..c61b58f7f6 100644 --- a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ComposeScreen.kt +++ b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ComposeScreen.kt @@ -15,10 +15,10 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi * [ComposeScreen], like [AndroidScreen][com.squareup.workflow1.ui.AndroidScreen], * is strictly a possible implementation detail of [Screen]. It is a convenience to * minimize the boilerplate required to set up a [ScreenComposableFactory]. - * That interface is the fundamental unit of Compose tooling for Workflow UI. + * (That interface is the fundamental unit of Compose tooling for Workflow UI. * But in day to day use, most developer will work with [ComposeScreen] and be only * vaguely aware of the existence of [ScreenComposableFactory], - * so the bulk of our description of working with Compose is here. + * so the bulk of our description of working with Compose is here.) * * **NB**: A Workflow app that relies on Compose must call [withComposeInteropSupport] * on its top-level [ViewEnvironment]. See that function for details. @@ -77,11 +77,10 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi public interface ComposeScreen : Screen { /** - * The composable content of this rendering. This method will be called with the current rendering - * instance as the receiver, any time a new rendering is emitted, or the [viewEnvironment] - * changes. + * The composable content of this rendering. This method will be called with the + * current rendering instance as the receiver any time a new rendering is emitted. */ - @Composable public fun Content(viewEnvironment: ViewEnvironment) + @Composable public fun Content() } /** @@ -90,9 +89,9 @@ public interface ComposeScreen : Screen { */ @WorkflowUiExperimentalApi public inline fun ComposeScreen( - crossinline content: @Composable (ViewEnvironment) -> Unit + crossinline content: @Composable () -> Unit ): ComposeScreen = object : ComposeScreen { - @Composable override fun Content(viewEnvironment: ViewEnvironment) { - content(viewEnvironment) + @Composable override fun Content() { + content() } } diff --git a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/CompositionRoot.kt b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/CompositionRoot.kt index 1902c7f9fb..0a209a4a75 100644 --- a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/CompositionRoot.kt +++ b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/CompositionRoot.kt @@ -8,7 +8,6 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.CompositionLocalProvider import androidx.compose.runtime.staticCompositionLocalOf import com.squareup.workflow1.ui.Screen -import com.squareup.workflow1.ui.ScreenViewFactoryFinder import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @@ -23,7 +22,7 @@ private val LocalHasViewFactoryRootBeenApplied = staticCompositionLocalOf { fals * [composition locals][androidx.compose.runtime.CompositionLocal] that all * [ScreenComposableFactory] factories need access to, such as UI themes. * - * This function will called once, to wrap the _highest-level_ [ScreenComposableFactory] + * This function will be called once, to wrap the _highest-level_ [ScreenComposableFactory] * in the tree. However, composition locals are propagated down to child [ScreenComposableFactory] * compositions, so any locals provided here will be available in _all_ [ScreenComposableFactory] * compositions. @@ -31,19 +30,11 @@ private val LocalHasViewFactoryRootBeenApplied = staticCompositionLocalOf { fals public typealias CompositionRoot = @Composable (content: @Composable () -> Unit) -> Unit /** - * Convenience function for applying a [CompositionRoot] to this [ViewEnvironment]'s - * [ScreenComposableFactoryFinder]. See [ScreenComposableFactoryFinder.withCompositionRoot]. - */ -@WorkflowUiExperimentalApi -public fun ViewEnvironment.withCompositionRoot(root: CompositionRoot): ViewEnvironment { - return this + - (ScreenComposableFactoryFinder to this[ScreenComposableFactoryFinder].withCompositionRoot(root)) -} - -/** - * Returns a [ScreenViewFactoryFinder] that ensures that any [ScreenComposableFactory] + * Returns a [ScreenComposableFactoryFinder] that ensures that any [ScreenComposableFactory] * factories registered in this registry will be wrapped exactly once with a [CompositionRoot] * wrapper. See [CompositionRoot] for more information. + * + * You will rarely use this directly, prefer [ViewEnvironment.withComposeInteropSupport] */ @WorkflowUiExperimentalApi public fun ScreenComposableFactoryFinder.withCompositionRoot( @@ -52,8 +43,8 @@ public fun ScreenComposableFactoryFinder.withCompositionRoot( return mapFactories { factory -> @Suppress("UNCHECKED_CAST") (factory as? ScreenComposableFactory)?.let { composeFactory -> - ScreenComposableFactory(composeFactory.type) { rendering, environment -> - WrappedWithRootIfNecessary(root) { composeFactory.Content(rendering, environment) } + ScreenComposableFactory(composeFactory.type) { rendering -> + WrappedWithRootIfNecessary(root) { composeFactory.Content(rendering) } } } ?: factory } diff --git a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/LocalWorkflowEnvironment.kt b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/LocalWorkflowEnvironment.kt new file mode 100644 index 0000000000..7361c920f3 --- /dev/null +++ b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/LocalWorkflowEnvironment.kt @@ -0,0 +1,10 @@ +package com.squareup.workflow1.ui.compose + +import androidx.compose.runtime.ProvidableCompositionLocal +import androidx.compose.runtime.compositionLocalOf +import com.squareup.workflow1.ui.ViewEnvironment +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi + +@WorkflowUiExperimentalApi +public val LocalWorkflowEnvironment: ProvidableCompositionLocal = + compositionLocalOf { ViewEnvironment.EMPTY } diff --git a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactory.kt b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactory.kt index b8a6e6b20b..5b1c28777d 100644 --- a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactory.kt +++ b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactory.kt @@ -6,6 +6,8 @@ import androidx.activity.OnBackPressedDispatcherOwner import androidx.activity.compose.LocalOnBackPressedDispatcherOwner import androidx.activity.setViewTreeOnBackPressedDispatcherOwner import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember import androidx.compose.ui.platform.ComposeView import androidx.compose.ui.platform.LocalLifecycleOwner import androidx.compose.ui.viewinterop.AndroidView @@ -24,28 +26,21 @@ import kotlin.reflect.KClass @WorkflowUiExperimentalApi public inline fun ScreenComposableFactory( - noinline content: @Composable ( - rendering: ScreenT, - environment: ViewEnvironment - ) -> Unit + noinline content: @Composable (rendering: ScreenT) -> Unit ): ScreenComposableFactory = ScreenComposableFactory(ScreenT::class, content) @PublishedApi @WorkflowUiExperimentalApi internal fun ScreenComposableFactory( type: KClass, - content: @Composable ( - rendering: ScreenT, - environment: ViewEnvironment - ) -> Unit + content: @Composable (rendering: ScreenT) -> Unit ): ScreenComposableFactory = object : ScreenComposableFactory { override val type: KClass = type @Composable override fun Content( - rendering: ScreenT, - environment: ViewEnvironment + rendering: ScreenT ) { - content(rendering, environment) + content(rendering) } } @@ -90,13 +85,10 @@ public interface ScreenComposableFactory : ViewRegistry.Ent /** * The composable content of this [ScreenComposableFactory]. This method will be called - * any time [rendering] or [environment] change. It is the Compose-based analogue of + * any time [rendering] changes. It is the Compose-based analogue of * [ScreenViewRunner.showRendering][com.squareup.workflow1.ui.ScreenViewRunner.showRendering]. */ - @Composable public fun Content( - rendering: ScreenT, - environment: ViewEnvironment - ) + @Composable public fun Content(rendering: ScreenT) } /** @@ -134,9 +126,26 @@ public fun ScreenComposableFactory.asViewFactory(): ): ScreenViewHolder { val view = ComposeView(context) return ScreenViewHolder(initialEnvironment, view) { newRendering, environment -> + // Update the state whenever a new rendering is emitted. // This lambda will be executed synchronously before ScreenViewHolder.show returns. - view.setContent { Content(newRendering, environment) } + view.setContent { + val onBackOrNull = remember(environment) { + environment.map[OnBackPressedDispatcherOwnerKey] as? OnBackPressedDispatcherOwner + } + if (onBackOrNull == null) { + CompositionLocalProvider(LocalWorkflowEnvironment provides environment) { + Content(newRendering) + } + } else { + CompositionLocalProvider( + LocalWorkflowEnvironment provides environment, + LocalOnBackPressedDispatcherOwner provides onBackOrNull + ) { + Content(newRendering) + } + } + } } } } @@ -161,8 +170,8 @@ public fun ScreenViewFactory.asComposableFactory(): * This is effectively the logic of `WorkflowViewStub`, but translated into Compose idioms. * This approach has a few advantages: * - * - Avoids extra custom views required to host `WorkflowViewStub` inside a Composition. Its trick - * of replacing itself in its parent doesn't play nicely with Compose. + * - Avoids extra custom views required to host `WorkflowViewStub` inside a Composition. + * Its trick of replacing itself in its parent doesn't play nicely with Compose. * - Allows us to pass the correct parent view for inflation (the root of the composition). * - Avoids `WorkflowViewStub` having to do its own lookup to find the correct * [ScreenViewFactory], since we already have the correct one. @@ -174,25 +183,25 @@ public fun ScreenViewFactory.asComposableFactory(): * and in the [ViewEnvironment] for use by any nested [WorkflowViewStub] * * Like `WorkflowViewStub`, this function uses the [viewFactory] to create and memoize a - * `View` to display the [rendering], keeps it updated with the latest [rendering] and - * [environment], and adds it to the composition. + * `View` to display the [rendering], keeps it updated with the latest [rendering] and adds + * it to the composition. */ @Composable override fun Content( - rendering: ScreenT, - environment: ViewEnvironment + rendering: ScreenT ) { val lifecycleOwner = LocalLifecycleOwner.current + val environment = LocalWorkflowEnvironment.current // Make sure any nested WorkflowViewStub will be able to propagate the // OnBackPressedDispatcherOwner, if we found one. No need to fail fast here. // It's only an issue if someone tries to use it, and the error message // at those call sites should be clear enough. val onBackOrNull = LocalOnBackPressedDispatcherOwner.current - ?: environment.map[OnBackPressedDispatcherOwnerKey] as? OnBackPressedDispatcherOwner - val envWithOnBack = onBackOrNull - ?.let { environment + (OnBackPressedDispatcherOwnerKey to it) } - ?: environment + val envWithOnBack = remember(onBackOrNull, environment) { + onBackOrNull?.let { environment + (OnBackPressedDispatcherOwnerKey to it) } + ?: environment + } AndroidView( factory = { context -> diff --git a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactoryFinder.kt b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactoryFinder.kt index 3466c48bb5..23eaaed685 100644 --- a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactoryFinder.kt +++ b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ScreenComposableFactoryFinder.kt @@ -1,5 +1,7 @@ package com.squareup.workflow1.ui.compose +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.runtime.remember import com.squareup.workflow1.ui.EnvironmentScreen import com.squareup.workflow1.ui.NamedScreen import com.squareup.workflow1.ui.Screen @@ -22,8 +24,8 @@ public interface ScreenComposableFactoryFinder { @Suppress("UNCHECKED_CAST") return factoryOrNull ?: (rendering as? ComposeScreen)?.let { - ScreenComposableFactory { rendering, environment -> - rendering.Content(environment) + ScreenComposableFactory { rendering -> + rendering.Content() } as ScreenComposableFactory } @@ -31,18 +33,25 @@ public interface ScreenComposableFactoryFinder { // if it were planned. See similar blocks in ScreenViewFactoryFinder ?: (rendering as? NamedScreen<*>)?.let { - ScreenComposableFactory> { rendering, environment -> - val innerFactory = rendering.content.toComposableFactory(environment) - innerFactory.Content(rendering.content, environment) - // WorkflowRendering(rendering.content, environment) + ScreenComposableFactory> { rendering -> + val innerFactory = rendering.content + .toComposableFactory(LocalWorkflowEnvironment.current) + innerFactory.Content(rendering.content) } as ScreenComposableFactory } ?: (rendering as? EnvironmentScreen<*>)?.let { - ScreenComposableFactory> { rendering, environment -> - val comboEnv = environment + rendering.environment - val innerFactory = rendering.content.toComposableFactory(comboEnv) - innerFactory.Content(rendering.content, comboEnv) - // WorkflowRendering(rendering.content, comboEnv) + ScreenComposableFactory> { rendering -> + val currentEnv = LocalWorkflowEnvironment.current + val innerFactory = rendering.content.toComposableFactory( + currentEnv + rendering.environment + ) + + val comboEnv = remember(currentEnv, rendering.environment) { + currentEnv + rendering.environment + } + CompositionLocalProvider(LocalWorkflowEnvironment provides comboEnv) { + innerFactory.Content(rendering.content) + } } as ScreenComposableFactory } } diff --git a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ViewEnvironmentWithComposeSupport.kt b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ViewEnvironmentWithComposeSupport.kt index 04c809dbd6..af49eefcda 100644 --- a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ViewEnvironmentWithComposeSupport.kt +++ b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/ViewEnvironmentWithComposeSupport.kt @@ -1,11 +1,34 @@ package com.squareup.workflow1.ui.compose +import androidx.compose.runtime.Composable +import androidx.compose.runtime.CompositionLocalProvider +import androidx.compose.ui.Modifier import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ScreenViewFactoryFinder import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +/** + * Alternative to [WorkflowLayout][com.squareup.workflow1.ui.WorkflowLayout] + * for a pure Compose application. Makes the receiver available via [LocalWorkflowEnvironment] + * and runs the composition bound to [screen]. + * + * Note that any app relying on stock navigation classes like + * [BackStackScreen][com.squareup.workflow1.ui.navigation.BackStackScreen] or + * [BodyAndOverlaysScreen][com.squareup.workflow1.ui.navigation.BodyAndOverlaysScreen] + * are not pure Compose, and must call [ViewEnvironment.withComposeInteropSupport] first. + */ +@WorkflowUiExperimentalApi +@Composable public fun ViewEnvironment.RootScreen( + screen: Screen, + modifier: Modifier = Modifier +) { + CompositionLocalProvider(LocalWorkflowEnvironment provides this) { + WorkflowRendering(screen, modifier) + } +} + /** * Replaces the [ScreenComposableFactoryFinder] and [ScreenViewFactoryFinder] * found in the receiving [ViewEnvironment] with wrappers that are able to @@ -14,18 +37,28 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi * to handle renderings bound to `@Composable` functions, and to allow * [WorkflowRendering] to handle renderings bound to [ScreenViewFactory]. * - * Note that the standard navigation related [Screen] types - * (e.g. [BackStackScreen][com.squareup.workflow1.ui.navigation.BackStackScreen]) - * are mainly bound to [View][android.view.View]-based implementations. + * Note that the standard navigation [Screen] types + * ([BackStackScreen][com.squareup.workflow1.ui.navigation.BackStackScreen] and + * [BodyAndOverlaysScreen][com.squareup.workflow1.ui.navigation.BodyAndOverlaysScreen]) + * are bound to [View][android.view.View]-based implementations. * Until that changes, effectively every Compose-based app must call this method. * * App-specific customizations of [ScreenComposableFactoryFinder] and [ScreenViewFactoryFinder] * must be placed in the [ViewEnvironment] before calling this method. + * + * @param compositionRootOrNull optional [CompositionRoot] to be applied whenever + * we create a Compose context, useful hook for applying + * [composition locals][androidx.compose.runtime.CompositionLocal] that all + * [ScreenComposableFactory] factories need access to, such as UI themes. */ @WorkflowUiExperimentalApi -public fun ViewEnvironment.withComposeInteropSupport(): ViewEnvironment { +public fun ViewEnvironment.withComposeInteropSupport( + compositionRootOrNull: CompositionRoot? = null +): ViewEnvironment { val rawViewFactoryFinder = get(ScreenViewFactoryFinder) - val rawComposableFactoryFinder = get(ScreenComposableFactoryFinder) + val rawComposableFactoryFinder = get(ScreenComposableFactoryFinder).let { finder -> + compositionRootOrNull?.let { finder.withCompositionRoot(it) } ?: finder + } val convertingViewFactoryFinder = object : ScreenViewFactoryFinder { override fun getViewFactoryForRendering( @@ -44,8 +77,8 @@ public fun ViewEnvironment.withComposeInteropSupport(): ViewEnvironment { rendering: ScreenT ): ScreenComposableFactory? { return rawComposableFactoryFinder.getComposableFactoryForRendering(environment, rendering) - ?: rawViewFactoryFinder.getViewFactoryForRendering(environment, rendering) - ?.asComposableFactory() + ?: rawViewFactoryFinder + .getViewFactoryForRendering(environment, rendering)?.asComposableFactory() } } diff --git a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/WorkflowRendering.kt b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/WorkflowRendering.kt index e8a4f9b24e..8d2552e2bb 100644 --- a/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/WorkflowRendering.kt +++ b/workflow-ui/compose/src/main/java/com/squareup/workflow1/ui/compose/WorkflowRendering.kt @@ -16,7 +16,6 @@ import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.LifecycleRegistry import com.squareup.workflow1.ui.Compatible import com.squareup.workflow1.ui.Screen -import com.squareup.workflow1.ui.ScreenViewFactoryFinder import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @@ -24,8 +23,9 @@ import com.squareup.workflow1.ui.WorkflowViewStub import com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner /** - * Renders [rendering] into the composition using this [ViewEnvironment]'s - * [ScreenViewFactoryFinder] to generate the view. + * Renders [rendering] into the composition using the [ViewEnvironment] found in + * [LocalWorkflowEnvironment] to source a [ScreenComposableFactoryFinder] to generate + * the view. * * This function fulfills a similar role as [ScreenViewHolder] and [WorkflowViewStub], * but is much more convenient to use from Composable functions. Note that, @@ -34,19 +34,17 @@ import com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner * * ## Example * - * ``` - * data class FramedRendering( - * val borderColor: Color, - * val child: R - * ) : ComposeRendering { + * data class FramedRendering( + * val borderColor: Color, + * val child: R + * ) : ComposeRendering { * - * @Composable override fun Content(viewEnvironment: ViewEnvironment) { - * Surface(border = Border(borderColor, 8.dp)) { - * WorkflowRendering(child, viewEnvironment) + * @Composable override fun Content() { + * Surface(border = Border(borderColor, 8.dp)) { + * WorkflowRendering(child) + * } + * } * } - * } - * } - * ``` * * @param rendering The workflow rendering to display. * @param modifier A [Modifier] that will be applied to composable used to show [rendering]. @@ -57,13 +55,13 @@ import com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner @Composable public fun WorkflowRendering( rendering: Screen, - viewEnvironment: ViewEnvironment, modifier: Modifier = Modifier ) { // This will fetch a new view factory any time the new rendering is incompatible with the previous // one, as determined by Compatible. This corresponds to WorkflowViewStub's canShowRendering // check. val renderingCompatibilityKey = Compatible.keyFor(rendering) + val viewEnvironment = LocalWorkflowEnvironment.current // By surrounding the below code with this key function, any time the new rendering is not // compatible with the previous rendering we'll tear down the previous subtree of the composition, @@ -91,7 +89,7 @@ public fun WorkflowRendering( // into this function is to directly control the layout of the child view – which means // minimum constraints are likely to be significant. Box(modifier, propagateMinConstraints = true) { - composableFactory.Content(rendering, viewEnvironment) + composableFactory.Content(rendering) } } }