From 28e9a7e1adfcd1f611d50c1603e544a2adcbd9b9 Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Fri, 7 Jan 2022 10:10:10 -0800 Subject: [PATCH 01/12] Renames ScreenViewRunner to ScreenViewUpdater --- .../overviewdetail/OverviewDetailContainer.kt | 6 ++-- .../sample/poetryapp/PoemListScreen.kt | 8 ++--- ...ner.kt => HelloBackButtonLayoutUpdater.kt} | 8 ++--- .../sample/poetry/StanzaListScreen.kt | 8 ++--- .../squareup/sample/poetry/StanzaScreen.kt | 12 ++++---- ...utRunner.kt => BoardsListLayoutUpdater.kt} | 10 +++---- .../com/squareup/sample/dungeon/Component.kt | 8 ++--- ...meLayoutRunner.kt => GameLayoutUpdater.kt} | 8 ++--- .../squareup/sample/dungeon/LoadingBinding.kt | 10 +++---- ...t => ShakeableTimeMachineLayoutUpdater.kt} | 10 +++---- .../shakeable/ShakeableTimeMachineWorkflow.kt | 2 +- .../helloworkflowfragment/HelloRendering.kt | 4 +-- .../sample/helloworkflow/HelloRendering.kt | 4 +-- .../sample/stubvisibility/OuterRendering.kt | 4 +-- .../authworkflow/AuthorizingViewFactory.kt | 4 +-- .../sample/authworkflow/LoginViewFactory.kt | 4 +-- .../authworkflow/SecondFactorViewFactory.kt | 4 +-- ...youtRunner.kt => GameOverLayoutUpdater.kt} | 10 +++---- .../gameworkflow/GamePlayViewFactory.kt | 4 +-- .../sample/gameworkflow/NewGameViewFactory.kt | 4 +-- .../gameworkflow/TicTacToeViewBindings.kt | 2 +- .../squareup/sample/todo/TodoEditorScreen.kt | 10 +++---- .../squareup/sample/todo/TodoListsScreen.kt | 2 +- workflow-ui/core-android/api/core-android.api | 6 ++-- .../squareup/workflow1/ui/AndroidScreen.kt | 6 ++-- .../com/squareup/workflow1/ui/LayoutRunner.kt | 2 +- .../workflow1/ui/LayoutScreenViewFactory.kt | 6 ++-- .../workflow1/ui/ManualScreenViewFactory.kt | 2 +- .../workflow1/ui/ScreenViewFactory.kt | 2 +- ...reenViewRunner.kt => ScreenViewUpdater.kt} | 30 +++++++++---------- .../ui/ViewBindingScreenViewFactory.kt | 6 ++-- 31 files changed, 102 insertions(+), 104 deletions(-) rename samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/{HelloBackButtonLayoutRunner.kt => HelloBackButtonLayoutUpdater.kt} (80%) rename samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/{BoardsListLayoutRunner.kt => BoardsListLayoutUpdater.kt} (92%) rename samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/{GameLayoutRunner.kt => GameLayoutUpdater.kt} (91%) rename samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/{ShakeableTimeMachineLayoutRunner.kt => ShakeableTimeMachineLayoutUpdater.kt} (93%) rename samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/{GameOverLayoutRunner.kt => GameOverLayoutUpdater.kt} (92%) rename workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/{ScreenViewRunner.kt => ScreenViewUpdater.kt} (77%) diff --git a/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt b/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt index 47b8b2c17c..fe0b099884 100644 --- a/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt +++ b/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt @@ -8,7 +8,7 @@ import com.squareup.sample.container.overviewdetail.OverviewDetailConfig.Detail import com.squareup.sample.container.overviewdetail.OverviewDetailConfig.Overview import com.squareup.sample.container.overviewdetail.OverviewDetailConfig.Single import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub @@ -25,7 +25,7 @@ import com.squareup.workflow1.ui.container.withBackStackStateKeyPrefix * with [OverviewDetailScreen.overviewRendering] as the base of the stack. */ @OptIn(WorkflowUiExperimentalApi::class) -class OverviewDetailContainer(view: View) : ScreenViewRunner { +class OverviewDetailContainer(view: View) : ScreenViewUpdater { private val overviewStub: WorkflowViewStub? = view.findViewById(R.id.overview_stub) private val detailStub: WorkflowViewStub? = view.findViewById(R.id.detail_stub) @@ -84,7 +84,7 @@ class OverviewDetailContainer(view: View) : ScreenViewRunner by ScreenViewRunner.bind( + companion object : ScreenViewFactory by ScreenViewUpdater.bind( layoutId = R.layout.overview_detail, constructor = ::OverviewDetailContainer ) { diff --git a/samples/containers/app-poetry/src/main/java/com/squareup/sample/poetryapp/PoemListScreen.kt b/samples/containers/app-poetry/src/main/java/com/squareup/sample/poetryapp/PoemListScreen.kt index c6cfb50dac..e7bcf39ae3 100644 --- a/samples/containers/app-poetry/src/main/java/com/squareup/sample/poetryapp/PoemListScreen.kt +++ b/samples/containers/app-poetry/src/main/java/com/squareup/sample/poetryapp/PoemListScreen.kt @@ -12,7 +12,7 @@ import com.squareup.sample.container.overviewdetail.OverviewDetailConfig.Overvie import com.squareup.sample.container.poetryapp.R import com.squareup.sample.poetry.model.Poem import com.squareup.workflow1.ui.AndroidScreen -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @@ -22,14 +22,14 @@ data class PoemListScreen( val onPoemSelected: (Int) -> Unit, val selection: Int = -1 ) : AndroidScreen { - override val viewFactory = ScreenViewRunner.bind( + override val viewFactory = ScreenViewUpdater.bind( R.layout.list, - ::PoemListLayoutRunner + ::PoemListLayoutUpdater ) } @OptIn(WorkflowUiExperimentalApi::class) -private class PoemListLayoutRunner(view: View) : ScreenViewRunner { +private class PoemListLayoutUpdater(view: View) : ScreenViewUpdater { init { view.findViewById(R.id.list_toolbar) .apply { diff --git a/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/HelloBackButtonLayoutRunner.kt b/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/HelloBackButtonLayoutUpdater.kt similarity index 80% rename from samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/HelloBackButtonLayoutRunner.kt rename to samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/HelloBackButtonLayoutUpdater.kt index 5250b429f2..679e0bcf0e 100644 --- a/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/HelloBackButtonLayoutRunner.kt +++ b/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/HelloBackButtonLayoutUpdater.kt @@ -4,7 +4,7 @@ import android.view.View import android.widget.TextView import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler @@ -15,13 +15,13 @@ data class HelloBackButtonScreen( val onClick: () -> Unit, val onBackPressed: (() -> Unit)? ) : AndroidScreen { - override val viewFactory: ScreenViewFactory = ScreenViewRunner.bind( - R.layout.hello_back_button_layout, ::HelloBackButtonLayoutRunner + override val viewFactory: ScreenViewFactory = ScreenViewUpdater.bind( + R.layout.hello_back_button_layout, ::HelloBackButtonLayoutUpdater ) } @OptIn(WorkflowUiExperimentalApi::class) -private class HelloBackButtonLayoutRunner(view: View) : ScreenViewRunner { +private class HelloBackButtonLayoutUpdater(view: View) : ScreenViewUpdater { private val messageView: TextView = view.findViewById(R.id.hello_message) override fun showRendering( diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListScreen.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListScreen.kt index ec5ebe70c6..0aaa566f71 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListScreen.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListScreen.kt @@ -12,7 +12,7 @@ import com.squareup.sample.container.overviewdetail.OverviewDetailConfig.Overvie import com.squareup.sample.container.poetry.R import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler @@ -28,14 +28,14 @@ data class StanzaListScreen( val onExit: () -> Unit, val selection: Int = -1 ) : AndroidScreen { - override val viewFactory: ScreenViewFactory = ScreenViewRunner.bind( + override val viewFactory: ScreenViewFactory = ScreenViewUpdater.bind( R.layout.list, - ::StanzaListLayoutRunner + ::StanzaListLayoutUpdater ) } @OptIn(WorkflowUiExperimentalApi::class) -private class StanzaListLayoutRunner(view: View) : ScreenViewRunner { +private class StanzaListLayoutUpdater(view: View) : ScreenViewUpdater { private val toolbar = view.findViewById(R.id.list_toolbar) private val recyclerView = view.findViewById(R.id.list_body) .apply { layoutManager = LinearLayoutManager(context) } diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaScreen.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaScreen.kt index 7f3bdf8a57..6ea53afde8 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaScreen.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaScreen.kt @@ -14,7 +14,7 @@ import com.squareup.sample.container.poetry.R import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.Compatible import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler @@ -32,14 +32,14 @@ data class StanzaScreen( ) : AndroidScreen, Compatible { override val compatibilityKey = "$title: $stanzaNumber" - override val viewFactory: ScreenViewFactory = ScreenViewRunner.bind( + override val viewFactory: ScreenViewFactory = ScreenViewUpdater.bind( R.layout.stanza_layout, - ::StanzaLayoutRunner + ::StanzaLayoutUpdater ) } @OptIn(WorkflowUiExperimentalApi::class) -private class StanzaLayoutRunner(private val view: View) : ScreenViewRunner { +private class StanzaLayoutUpdater(private val view: View) : ScreenViewUpdater { private val tabSize = TypedValue .applyDimension(TypedValue.COMPLEX_UNIT_SP, 24f, view.resources.displayMetrics) .toInt() @@ -114,8 +114,8 @@ private class StanzaLayoutRunner(private val view: View) : ScreenViewRunner by ScreenViewRunner.bind( + companion object : ScreenViewFactory by ScreenViewUpdater.bind( R.layout.stanza_layout, - ::StanzaLayoutRunner + ::StanzaLayoutUpdater ) } diff --git a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardsListLayoutRunner.kt b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardsListLayoutUpdater.kt similarity index 92% rename from samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardsListLayoutRunner.kt rename to samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardsListLayoutUpdater.kt index 24db11abde..d0613b50c0 100644 --- a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardsListLayoutRunner.kt +++ b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardsListLayoutUpdater.kt @@ -11,8 +11,8 @@ import com.squareup.cycler.toDataSource import com.squareup.sample.dungeon.DungeonAppWorkflow.DisplayBoardsListScreen import com.squareup.sample.dungeon.board.Board import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner -import com.squareup.workflow1.ui.ScreenViewRunner.Companion.bind +import com.squareup.workflow1.ui.ScreenViewUpdater +import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub @@ -24,7 +24,7 @@ import com.squareup.workflow1.ui.WorkflowViewStub * a `RecyclerView`. */ @OptIn(WorkflowUiExperimentalApi::class) -class BoardsListLayoutRunner(rootView: View) : ScreenViewRunner { +class BoardsListLayoutUpdater(rootView: View) : ScreenViewUpdater { /** * Used to associate a single [ViewEnvironment] and [DisplayBoardsListScreen.onBoardSelected] @@ -48,7 +48,7 @@ class BoardsListLayoutRunner(rootView: View) : ScreenViewRunner val card: CardView = view.findViewById(R.id.board_card) val boardNameView: TextView = view.findViewById(R.id.board_name) - // The board preview is actually rendered using the same ScreenViewRunner as the actual + // The board preview is actually rendered using the same ScreenViewUpdater as the actual // live game. It's easy to delegate to it by just putting a WorkflowViewStub in our // layout and giving it the Board. val boardPreviewView: WorkflowViewStub = view.findViewById(R.id.board_preview_stub) @@ -102,6 +102,6 @@ class BoardsListLayoutRunner(rootView: View) : ScreenViewRunner by bind( - R.layout.boards_list_layout, ::BoardsListLayoutRunner + R.layout.boards_list_layout, ::BoardsListLayoutUpdater ) } diff --git a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/Component.kt b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/Component.kt index a3d5be2c97..f2df2f6157 100644 --- a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/Component.kt +++ b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/Component.kt @@ -7,7 +7,7 @@ import android.os.Vibrator import androidx.appcompat.app.AppCompatActivity import com.squareup.sample.dungeon.DungeonAppWorkflow.State.LoadingBoardList import com.squareup.sample.dungeon.GameSessionWorkflow.State.Loading -import com.squareup.sample.timemachine.shakeable.ShakeableTimeMachineLayoutRunner +import com.squareup.sample.timemachine.shakeable.ShakeableTimeMachineLayoutUpdater import com.squareup.workflow1.ui.ViewRegistry import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.modal.AlertContainer @@ -24,11 +24,11 @@ class Component(context: AppCompatActivity) { @OptIn(WorkflowUiExperimentalApi::class) val viewRegistry = ViewRegistry( - ShakeableTimeMachineLayoutRunner, + ShakeableTimeMachineLayoutUpdater, LoadingBinding(R.string.loading_boards_list), - BoardsListLayoutRunner, + BoardsListLayoutUpdater, LoadingBinding(R.string.loading_board), - GameLayoutRunner, + GameLayoutUpdater, BoardView, AlertContainer ) diff --git a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/GameLayoutRunner.kt b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/GameLayoutUpdater.kt similarity index 91% rename from samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/GameLayoutRunner.kt rename to samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/GameLayoutUpdater.kt index 7a096283f1..ca5f2d03f2 100644 --- a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/GameLayoutRunner.kt +++ b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/GameLayoutUpdater.kt @@ -11,8 +11,8 @@ import com.squareup.sample.dungeon.Direction.RIGHT import com.squareup.sample.dungeon.Direction.UP import com.squareup.sample.dungeon.GameWorkflow.GameRendering import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner -import com.squareup.workflow1.ui.ScreenViewRunner.Companion.bind +import com.squareup.workflow1.ui.ScreenViewUpdater +import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub @@ -22,7 +22,7 @@ import com.squareup.workflow1.ui.WorkflowViewStub * the player. */ @OptIn(WorkflowUiExperimentalApi::class) -class GameLayoutRunner(view: View) : ScreenViewRunner { +class GameLayoutUpdater(view: View) : ScreenViewUpdater { private val boardView: WorkflowViewStub = view.findViewById(R.id.board_stub) private val moveLeft: View = view.findViewById(R.id.move_left) @@ -67,6 +67,6 @@ class GameLayoutRunner(view: View) : ScreenViewRunner { } companion object : ScreenViewFactory by bind( - R.layout.game_layout, ::GameLayoutRunner + R.layout.game_layout, ::GameLayoutUpdater ) } diff --git a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/LoadingBinding.kt b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/LoadingBinding.kt index 019c97c2dc..4ec99d5991 100644 --- a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/LoadingBinding.kt +++ b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/LoadingBinding.kt @@ -7,8 +7,8 @@ import android.widget.TextView import androidx.annotation.StringRes import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner -import com.squareup.workflow1.ui.ScreenViewRunner.Companion.bind +import com.squareup.workflow1.ui.ScreenViewUpdater +import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @@ -23,13 +23,13 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi inline fun LoadingBinding( @StringRes loadingLabelRes: Int ): ScreenViewFactory = - bind(R.layout.loading_layout) { view -> LoadingLayoutRunner(loadingLabelRes, view) } + bind(R.layout.loading_layout) { view -> LoadingLayoutUpdater(loadingLabelRes, view) } @PublishedApi -internal class LoadingLayoutRunner( +internal class LoadingLayoutUpdater( @StringRes private val labelRes: Int, view: View -) : ScreenViewRunner { +) : ScreenViewUpdater { init { view.findViewById(R.id.loading_label) diff --git a/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineLayoutRunner.kt b/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineLayoutUpdater.kt similarity index 93% rename from samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineLayoutRunner.kt rename to samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineLayoutUpdater.kt index c955a91404..77cf285d3b 100644 --- a/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineLayoutRunner.kt +++ b/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineLayoutUpdater.kt @@ -8,8 +8,8 @@ import androidx.constraintlayout.widget.Group import androidx.transition.TransitionManager import com.squareup.sample.timemachine.shakeable.internal.GlassFrameLayout import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.ScreenViewRunner -import com.squareup.workflow1.ui.ScreenViewRunner.Companion.bind +import com.squareup.workflow1.ui.ScreenViewUpdater +import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.WorkflowViewStub @@ -22,9 +22,9 @@ import kotlin.time.ExperimentalTime * [renderings][ShakeableTimeMachineScreen]. */ @OptIn(ExperimentalTime::class, WorkflowUiExperimentalApi::class) -class ShakeableTimeMachineLayoutRunner( +class ShakeableTimeMachineLayoutUpdater( private val view: View -) : ScreenViewRunner { +) : ScreenViewUpdater { private val glassView: GlassFrameLayout = view.findViewById(R.id.glass_view) private val childStub: WorkflowViewStub = view.findViewById(R.id.child_stub) @@ -88,6 +88,6 @@ class ShakeableTimeMachineLayoutRunner( private fun Duration.toUiString(): String = toString() companion object : ScreenViewFactory by bind( - R.layout.shakeable_time_machine_layout, ::ShakeableTimeMachineLayoutRunner + R.layout.shakeable_time_machine_layout, ::ShakeableTimeMachineLayoutUpdater ) } diff --git a/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineWorkflow.kt b/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineWorkflow.kt index 0a3fea30fe..988a39a9b0 100644 --- a/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineWorkflow.kt +++ b/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineWorkflow.kt @@ -18,7 +18,7 @@ import kotlin.time.Duration import kotlin.time.ExperimentalTime /** - * A wrapper around a [TimeMachineWorkflow] that uses [ShakeableTimeMachineLayoutRunner] to render + * A wrapper around a [TimeMachineWorkflow] that uses [ShakeableTimeMachineLayoutUpdater] to render * the [delegate workflow][TimeMachineWorkflow.delegateWorkflow]'s rendering, but wrap it in a * UI to scrub around the recorded timeline when the device is shaken. * diff --git a/samples/hello-workflow-fragment/src/main/java/com/squareup/sample/helloworkflowfragment/HelloRendering.kt b/samples/hello-workflow-fragment/src/main/java/com/squareup/sample/helloworkflowfragment/HelloRendering.kt index 3757079052..d852aca4d0 100644 --- a/samples/hello-workflow-fragment/src/main/java/com/squareup/sample/helloworkflowfragment/HelloRendering.kt +++ b/samples/hello-workflow-fragment/src/main/java/com/squareup/sample/helloworkflowfragment/HelloRendering.kt @@ -3,7 +3,7 @@ package com.squareup.sample.helloworkflowfragment import com.squareup.sample.helloworkflowfragment.databinding.HelloGoodbyeLayoutBinding import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @OptIn(WorkflowUiExperimentalApi::class) @@ -12,7 +12,7 @@ data class HelloRendering( val onClick: () -> Unit ) : AndroidScreen { override val viewFactory: ScreenViewFactory = - ScreenViewRunner.bind(HelloGoodbyeLayoutBinding::inflate) { r, _ -> + ScreenViewUpdater.bind(HelloGoodbyeLayoutBinding::inflate) { r, _ -> helloMessage.text = "${r.message} Fragment" helloMessage.setOnClickListener { r.onClick() } } diff --git a/samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloRendering.kt b/samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloRendering.kt index ee34b15391..ed32e30053 100644 --- a/samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloRendering.kt +++ b/samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloRendering.kt @@ -3,7 +3,7 @@ package com.squareup.sample.helloworkflow import com.squareup.sample.helloworkflow.databinding.HelloGoodbyeLayoutBinding import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @OptIn(WorkflowUiExperimentalApi::class) @@ -12,7 +12,7 @@ data class HelloRendering( val onClick: () -> Unit ) : AndroidScreen { override val viewFactory: ScreenViewFactory = - ScreenViewRunner.bind(HelloGoodbyeLayoutBinding::inflate) { r, _ -> + ScreenViewUpdater.bind(HelloGoodbyeLayoutBinding::inflate) { r, _ -> helloMessage.text = r.message helloMessage.setOnClickListener { r.onClick() } } diff --git a/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/OuterRendering.kt b/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/OuterRendering.kt index ac22b64f52..ab12418734 100644 --- a/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/OuterRendering.kt +++ b/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/OuterRendering.kt @@ -3,7 +3,7 @@ package com.squareup.sample.stubvisibility import com.squareup.sample.stubvisibility.databinding.StubVisibilityLayoutBinding import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @OptIn(WorkflowUiExperimentalApi::class) @@ -12,7 +12,7 @@ data class OuterRendering( val bottom: ClickyTextRendering ) : AndroidScreen { override val viewFactory: ScreenViewFactory = - ScreenViewRunner.bind(StubVisibilityLayoutBinding::inflate) { rendering, env -> + ScreenViewUpdater.bind(StubVisibilityLayoutBinding::inflate) { rendering, env -> shouldBeFilledStub.show(rendering.top, env) shouldBeWrappedStub.show(rendering.bottom, env) } diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/AuthorizingViewFactory.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/AuthorizingViewFactory.kt index a9f2411280..4094c47823 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/AuthorizingViewFactory.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/AuthorizingViewFactory.kt @@ -2,11 +2,11 @@ package com.squareup.sample.authworkflow import com.squareup.sample.tictactoe.databinding.AuthorizingLayoutBinding import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @OptIn(WorkflowUiExperimentalApi::class) internal val AuthorizingViewFactory: ScreenViewFactory = - ScreenViewRunner.bind(AuthorizingLayoutBinding::inflate) { rendering, _ -> + ScreenViewUpdater.bind(AuthorizingLayoutBinding::inflate) { rendering, _ -> authorizingMessage.text = rendering.message } diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/LoginViewFactory.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/LoginViewFactory.kt index b16254b150..f236542940 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/LoginViewFactory.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/LoginViewFactory.kt @@ -2,13 +2,13 @@ package com.squareup.sample.authworkflow import com.squareup.sample.tictactoe.databinding.LoginLayoutBinding import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler @OptIn(WorkflowUiExperimentalApi::class) internal val LoginViewFactory: ScreenViewFactory = - ScreenViewRunner.bind(LoginLayoutBinding::inflate) { rendering, _ -> + ScreenViewUpdater.bind(LoginLayoutBinding::inflate) { rendering, _ -> loginErrorMessage.text = rendering.errorMessage loginButton.setOnClickListener { diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/SecondFactorViewFactory.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/SecondFactorViewFactory.kt index 411a8db473..e513065f41 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/SecondFactorViewFactory.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/SecondFactorViewFactory.kt @@ -2,13 +2,13 @@ package com.squareup.sample.authworkflow import com.squareup.sample.tictactoe.databinding.SecondFactorLayoutBinding import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler @OptIn(WorkflowUiExperimentalApi::class) internal val SecondFactorViewFactory: ScreenViewFactory = - ScreenViewRunner.bind(SecondFactorLayoutBinding::inflate) { rendering, _ -> + ScreenViewUpdater.bind(SecondFactorLayoutBinding::inflate) { rendering, _ -> root.backPressedHandler = { rendering.onCancel() } secondFactorToolbar.setNavigationOnClickListener { rendering.onCancel() } diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GameOverLayoutRunner.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GameOverLayoutUpdater.kt similarity index 92% rename from samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GameOverLayoutRunner.kt rename to samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GameOverLayoutUpdater.kt index 2a2bf057f1..d9fe7ec206 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GameOverLayoutRunner.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GameOverLayoutUpdater.kt @@ -11,16 +11,16 @@ import com.squareup.sample.gameworkflow.SyncState.SAVING import com.squareup.sample.tictactoe.databinding.BoardBinding import com.squareup.sample.tictactoe.databinding.GamePlayLayoutBinding import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner -import com.squareup.workflow1.ui.ScreenViewRunner.Companion.bind +import com.squareup.workflow1.ui.ScreenViewUpdater +import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler @OptIn(WorkflowUiExperimentalApi::class) -internal class GameOverLayoutRunner( +internal class GameOverLayoutUpdater( private val binding: GamePlayLayoutBinding -) : ScreenViewRunner { +) : ScreenViewUpdater { private val saveItem: MenuItem = binding.gamePlayToolbar.menu.add("") .apply { @@ -103,6 +103,6 @@ internal class GameOverLayoutRunner( /** Note how easily we're sharing this layout with [GamePlayViewFactory]. */ companion object : ScreenViewFactory by bind( - GamePlayLayoutBinding::inflate, ::GameOverLayoutRunner + GamePlayLayoutBinding::inflate, ::GameOverLayoutUpdater ) } diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GamePlayViewFactory.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GamePlayViewFactory.kt index d1eace7626..025588fc37 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GamePlayViewFactory.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GamePlayViewFactory.kt @@ -4,13 +4,13 @@ import android.view.View import android.view.ViewGroup import com.squareup.sample.tictactoe.databinding.GamePlayLayoutBinding import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.backPressedHandler @OptIn(WorkflowUiExperimentalApi::class) internal val GamePlayViewFactory: ScreenViewFactory = - ScreenViewRunner.bind(GamePlayLayoutBinding::inflate) { rendering, _ -> + ScreenViewUpdater.bind(GamePlayLayoutBinding::inflate) { rendering, _ -> renderBanner(rendering.gameState, rendering.playerInfo) rendering.gameState.board.render(gamePlayBoard.root) diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/NewGameViewFactory.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/NewGameViewFactory.kt index 53a4341e72..8ab5a1cc97 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/NewGameViewFactory.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/NewGameViewFactory.kt @@ -2,13 +2,13 @@ package com.squareup.sample.gameworkflow import com.squareup.sample.tictactoe.databinding.NewGameLayoutBinding import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.ScreenViewRunner +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.backPressedHandler @OptIn(WorkflowUiExperimentalApi::class) internal val NewGameViewFactory: ScreenViewFactory = - ScreenViewRunner.bind(NewGameLayoutBinding::inflate) { rendering, _ -> + ScreenViewUpdater.bind(NewGameLayoutBinding::inflate) { rendering, _ -> if (playerX.text.isBlank()) playerX.setText(rendering.defaultNameX) if (playerO.text.isBlank()) playerO.setText(rendering.defaultNameO) diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/TicTacToeViewBindings.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/TicTacToeViewBindings.kt index 5835cc3d00..8c2408a8d4 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/TicTacToeViewBindings.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/TicTacToeViewBindings.kt @@ -7,5 +7,5 @@ import com.squareup.workflow1.ui.ViewRegistry val TicTacToeViewFactories = ViewRegistry( NewGameViewFactory, GamePlayViewFactory, - GameOverLayoutRunner + GameOverLayoutUpdater ) diff --git a/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoEditorScreen.kt b/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoEditorScreen.kt index 72ef00d114..bd754bec29 100644 --- a/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoEditorScreen.kt +++ b/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoEditorScreen.kt @@ -7,8 +7,8 @@ import android.view.inputmethod.InputMethodManager import com.squareup.sample.todo.databinding.TodoEditorLayoutBinding import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.Compatible -import com.squareup.workflow1.ui.ScreenViewRunner -import com.squareup.workflow1.ui.ScreenViewRunner.Companion.bind +import com.squareup.workflow1.ui.ScreenViewUpdater +import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler @@ -25,13 +25,13 @@ data class TodoEditorScreen( ) : AndroidScreen, Compatible { override val compatibilityKey = Compatible.keyFor(this, "${session.id}") - override val viewFactory = bind(TodoEditorLayoutBinding::inflate, ::Runner) + override val viewFactory = bind(TodoEditorLayoutBinding::inflate, ::Updater) } @OptIn(WorkflowUiExperimentalApi::class) -private class Runner( +private class Updater( private val binding: TodoEditorLayoutBinding -) : ScreenViewRunner { +) : ScreenViewUpdater { private val itemListView = ItemListView.fromLinearLayout(binding.itemContainer) diff --git a/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsScreen.kt b/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsScreen.kt index 573dfd40c9..5a2a8be7df 100644 --- a/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsScreen.kt +++ b/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsScreen.kt @@ -7,7 +7,7 @@ import com.squareup.sample.container.overviewdetail.OverviewDetailConfig.Overvie import com.squareup.sample.todo.databinding.TodoListsLayoutBinding import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewRunner.Companion.bind +import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.WorkflowUiExperimentalApi /** diff --git a/workflow-ui/core-android/api/core-android.api b/workflow-ui/core-android/api/core-android.api index 456cd95b6d..6311ec4e9d 100644 --- a/workflow-ui/core-android/api/core-android.api +++ b/workflow-ui/core-android/api/core-android.api @@ -100,12 +100,12 @@ public final class com/squareup/workflow1/ui/ScreenViewFactoryKt { public static synthetic fun buildView$default (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;Lcom/squareup/workflow1/ui/ViewStarter;ILjava/lang/Object;)Landroid/view/View; } -public abstract interface class com/squareup/workflow1/ui/ScreenViewRunner { - public static final field Companion Lcom/squareup/workflow1/ui/ScreenViewRunner$Companion; +public abstract interface class com/squareup/workflow1/ui/ScreenViewUpdater { + public static final field Companion Lcom/squareup/workflow1/ui/ScreenViewUpdater$Companion; public abstract fun showRendering (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;)V } -public final class com/squareup/workflow1/ui/ScreenViewRunner$Companion { +public final class com/squareup/workflow1/ui/ScreenViewUpdater$Companion { } public final class com/squareup/workflow1/ui/SnapshotParcelsKt { diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidScreen.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidScreen.kt index 2d99280ee4..ad6a1c4f44 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidScreen.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidScreen.kt @@ -5,8 +5,8 @@ package com.squareup.workflow1.ui * via an appropriate [ScreenViewFactory] implementation. * * You will rarely, if ever, write a [ScreenViewFactory] yourself. Instead - * use [ScreenViewRunner.bind] to work with XML layout resources, or - * [BuilderViewFactory] to create views from code. See [ScreenViewRunner] for more + * use [ScreenViewUpdater.bind] to work with XML layout resources, or + * [BuilderViewFactory] to create views from code. See [ScreenViewUpdater] for more * details. * * @OptIn(WorkflowUiExperimentalApi::class) @@ -15,7 +15,7 @@ package com.squareup.workflow1.ui * val onClick: () -> Unit * ) : AndroidScreen { * override val viewFactory = - * ScreenViewRunner.bind(HelloGoodbyeLayoutBinding::inflate) { screen, _ -> + * ScreenViewUpdater.bind(HelloGoodbyeLayoutBinding::inflate) { screen, _ -> * helloMessage.text = screen.message * helloMessage.setOnClickListener { screen.onClick() } * } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutRunner.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutRunner.kt index 0300b09b43..e27c1db201 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutRunner.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutRunner.kt @@ -5,7 +5,7 @@ import androidx.annotation.LayoutRes import androidx.viewbinding.ViewBinding @Suppress("DEPRECATION") -@Deprecated("Use ScreenViewRunner") +@Deprecated("Use ScreenViewUpdater") @WorkflowUiExperimentalApi public fun interface LayoutRunner { public fun showRendering( diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt index 49a117406b..d51e47a47b 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt @@ -8,7 +8,7 @@ import kotlin.reflect.KClass /** * A [ScreenViewFactory] that ties a [layout resource][layoutId] to a - * [ViewRunner factory][runnerConstructor] function. See [ScreenViewRunner] for + * [ViewRunner factory][updaterConstructor] function. See [ScreenViewUpdater] for * details. */ @WorkflowUiExperimentalApi @@ -16,7 +16,7 @@ import kotlin.reflect.KClass internal class LayoutScreenViewFactory( override val type: KClass, @LayoutRes private val layoutId: Int, - private val runnerConstructor: (View) -> ScreenViewRunner + private val updaterConstructor: (View) -> ScreenViewUpdater ) : ScreenViewFactory { override fun buildView( initialRendering: RenderingT, @@ -27,7 +27,7 @@ internal class LayoutScreenViewFactory( return contextForNewView.viewBindingLayoutInflater(container) .inflate(layoutId, container, false) .also { view -> - val runner = runnerConstructor(view) + val runner = updaterConstructor(view) view.bindShowRendering(initialRendering, initialViewEnvironment) { rendering, environment -> runner.showRendering(rendering, environment) } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt index 97147b970e..3ad39f6cd8 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt @@ -7,7 +7,7 @@ import kotlin.reflect.KClass /** * A [ScreenViewFactory] that creates [View]s that need to be generated from code. - * (Use [ScreenViewRunner] to work with XML layout resources.) + * (Use [ScreenViewUpdater] to work with XML layout resources.) * * data class MyScreen(): AndroidScreen { * val viewFactory = ManualScreenViewFactory( diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt index a674dfbbf6..e019611858 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt @@ -15,7 +15,7 @@ import com.squareup.workflow1.ui.container.EnvironmentScreenViewFactory * * Two concrete [ScreenViewFactory] implementations are provided: * - * - The various [bind][ScreenViewRunner.bind] methods on [ScreenViewRunner] allow easy use of + * - The various [bind][ScreenViewUpdater.bind] methods on [ScreenViewUpdater] allow easy use of * Android XML layout resources and [AndroidX ViewBinding][androidx.viewbinding.ViewBinding]. * * - [ManualScreenViewFactory] allows views to be built from code. diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewRunner.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewUpdater.kt similarity index 77% rename from workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewRunner.kt rename to workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewUpdater.kt index 52e51238fa..54a124c931 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewRunner.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewUpdater.kt @@ -11,15 +11,13 @@ import androidx.viewbinding.ViewBinding public typealias ViewBindingInflater = (LayoutInflater, ViewGroup?, Boolean) -> BindingT /** - * A delegate that implements a [showRendering] method to be called when a workflow - * rendering of type [RenderingT] : [Screen] is ready to be displayed in a view created - * by a [ScreenViewFactory]. + * Function that updates the UI built by a [ScreenViewFactory]. * * If you're using [AndroidX ViewBinding][ViewBinding] you likely won't need to - * implement this interface at all. For details, see the three overloads of [ScreenViewRunner.bind]. + * implement this interface at all. For details, see the three overloads of [ScreenViewUpdater.bind]. */ @WorkflowUiExperimentalApi -public fun interface ScreenViewRunner { +public fun interface ScreenViewUpdater { public fun showRendering( rendering: RenderingT, viewEnvironment: ViewEnvironment @@ -31,33 +29,33 @@ public fun interface ScreenViewRunner { * to show renderings of type [RenderingT] : [Screen], using [a lambda][showRendering]. * * val HelloViewFactory: ScreenViewFactory = - * ScreenViewRunner.bind(HelloGoodbyeViewBinding::inflate) { rendering, viewEnvironment -> + * ScreenViewUpdater.bind(HelloGoodbyeViewBinding::inflate) { rendering, viewEnvironment -> * helloMessage.text = rendering.message * helloMessage.setOnClickListener { rendering.onClick(Unit) } * } * * If you need to initialize your view before [showRendering] is called, - * implement [ScreenViewRunner] and create a binding using the `bind` variant - * that accepts a `(ViewBinding) -> ScreenViewRunner` function, below. + * implement [ScreenViewUpdater] and create a binding using the `bind` variant + * that accepts a `(ViewBinding) -> ScreenViewUpdater` function, below. */ public inline fun bind( noinline bindingInflater: ViewBindingInflater, crossinline showRendering: BindingT.(RenderingT, ViewEnvironment) -> Unit ): ScreenViewFactory = bind(bindingInflater) { binding -> - ScreenViewRunner { rendering, viewEnvironment -> + ScreenViewUpdater { rendering, viewEnvironment -> binding.showRendering(rendering, viewEnvironment) } } /** * Creates a [ScreenViewFactory] that [inflates][bindingInflater] a [ViewBinding] ([BindingT]) - * to show renderings of type [RenderingT] : [Screen], using a [ScreenViewRunner] + * to show renderings of type [RenderingT] : [Screen], using a [ScreenViewUpdater] * created by [constructor]. Handy if you need to perform some set up before * [showRendering] is called. * * class HelloScreenRunner( * private val binding: HelloGoodbyeViewBinding - * ) : ScreenViewRunner { + * ) : ScreenViewUpdater { * * override fun showRendering(rendering: HelloScreen) { * binding.messageView.text = rendering.message @@ -74,29 +72,29 @@ public fun interface ScreenViewRunner { */ public inline fun bind( noinline bindingInflater: ViewBindingInflater, - noinline constructor: (BindingT) -> ScreenViewRunner + noinline constructor: (BindingT) -> ScreenViewUpdater ): ScreenViewFactory = ViewBindingScreenViewFactory(RenderingT::class, bindingInflater, constructor) /** * Creates a [ScreenViewFactory] that inflates [layoutId] to show renderings of - * type [RenderingT] : [Screen], using a [ScreenViewRunner] created by [constructor]. + * type [RenderingT] : [Screen], using a [ScreenViewUpdater] created by [constructor]. * Avoids any use of [AndroidX ViewBinding][ViewBinding]. */ public inline fun bind( @LayoutRes layoutId: Int, - noinline constructor: (View) -> ScreenViewRunner + noinline constructor: (View) -> ScreenViewUpdater ): ScreenViewFactory = LayoutScreenViewFactory(RenderingT::class, layoutId, constructor) /** * Creates a [ScreenViewFactory] that inflates [layoutId] to "show" renderings of type [RenderingT], - * with a no-op [ScreenViewRunner]. Handy for showing static views, e.g. when prototyping. + * with a no-op [ScreenViewUpdater]. Handy for showing static views, e.g. when prototyping. */ @Suppress("unused") public inline fun bindNoRunner( @LayoutRes layoutId: Int - ): ScreenViewFactory = bind(layoutId) { ScreenViewRunner { _, _ -> } } + ): ScreenViewFactory = bind(layoutId) { ScreenViewUpdater { _, _ -> } } } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt index ab8d0b072a..f179d301ce 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt @@ -11,7 +11,7 @@ import kotlin.reflect.KClass internal class ViewBindingScreenViewFactory( override val type: KClass, private val bindingInflater: ViewBindingInflater, - private val runnerConstructor: (BindingT) -> ScreenViewRunner + private val updaterConstructor: (BindingT) -> ScreenViewUpdater ) : ScreenViewFactory { override fun buildView( initialRendering: RenderingT, @@ -21,12 +21,12 @@ internal class ViewBindingScreenViewFactory - val runner = runnerConstructor(binding) + val updater = updaterConstructor(binding) binding.root.bindShowRendering( initialRendering, initialViewEnvironment ) { rendering, environment -> - runner.showRendering(rendering, environment) + updater.showRendering(rendering, environment) } } .root From e2ddcb1357c600837463eea9bc33d584a4795938 Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Thu, 6 Jan 2022 12:35:48 -0800 Subject: [PATCH 02/12] wip: Here is the ScreenView interface, how to use it? --- .../squareup/workflow1/ui/BaseScreenView.kt | 29 +++++ .../ui/DecorativeScreenViewFactory.kt | 112 ------------------ .../workflow1/ui/DecorativeViewFactory.kt | 105 +++++++++++++++- .../workflow1/ui/LayoutScreenViewFactory.kt | 29 ++--- .../com/squareup/workflow1/ui/ScreenView.kt | 75 ++++++++++++ .../workflow1/ui/ScreenViewFactory.kt | 35 ++---- .../ui/ViewBindingScreenViewFactory.kt | 34 +++--- 7 files changed, 249 insertions(+), 170 deletions(-) create mode 100644 workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenView.kt create mode 100644 workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenView.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenView.kt new file mode 100644 index 0000000000..5895a852c4 --- /dev/null +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenView.kt @@ -0,0 +1,29 @@ +package com.squareup.workflow1.ui + +import android.view.View + +@WorkflowUiExperimentalApi +internal class BaseScreenView( + private val initialRendering: ScreenT, + private val initialViewEnvironment: ViewEnvironment, + override val androidView: View, + private val runner: ScreenViewRunner +) : ScreenView { + lateinit var currentRendering: ScreenT + lateinit var currentEnvironment: ViewEnvironment + + override val rendering: ScreenT + get() = currentRendering + override val environment: ViewEnvironment + get() = currentEnvironment + + override fun start() { + update(initialRendering, initialViewEnvironment) + } + + override fun update(rendering: ScreenT, environment: ViewEnvironment) { + currentRendering = rendering + currentEnvironment = environment + runner.showRendering(rendering, environment) + } +} diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeScreenViewFactory.kt index bc730bd23d..fe11de5c04 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeScreenViewFactory.kt @@ -5,118 +5,6 @@ import android.view.View import android.view.ViewGroup import kotlin.reflect.KClass -/** - * A [ScreenViewFactory] for [OuterT] that delegates view construction responsibilities - * to the factory registered for [InnerT]. Makes it convenient for [OuterT] to wrap - * instances of [InnerT] to add information or behavior, without requiring wasteful wrapping - * in the view system. - * - * One general note: when creating a wrapper rendering, you're very likely to want it - * to implement [Compatible], to ensure that checks made to update or replace a view - * are based on the wrapped item. Each wrapper example below illustrates this. - * - * ## Examples - * - * To make one rendering type an "alias" for another -- that is, to use the same [ScreenViewFactory] - * to display it -- provide nothing but a single-arg mapping function: - * - * class OriginalRendering(val data: String) : AndroidScreen { - * ... - * } - * class AliasRendering(val similarData: String) - * - * object DecorativeScreenViewFactory : ScreenViewFactory - * by DecorativeScreenViewFactory( - * type = AliasRendering::class, map = { alias -> - * OriginalRendering(alias.similarData) - * } - * ) - * - * To make a wrapper that adds information to the [ViewEnvironment]: - * - * class NeutronFlowPolarity(val reversed: Boolean) : Screen { - * companion object : ViewEnvironmentKey( - * NeutronFlowPolarity::class - * ) { - * override val default: NeutronFlowPolarity = - * NeutronFlowPolarity(reversed = false) - * } - * } - * - * class NeutronFlowPolarityOverride( - * val wrapped: W, - * val polarity: NeutronFlowPolarity - * ) : Screen, Compatible { - * override val compatibilityKey: String = Compatible.keyFor(wrapped) - * } - * - * object NeutronFlowPolarityViewFactory : - * ScreenViewFactory> - * by DecorativeScreenViewFactory( - * type = NeutronFlowPolarityOverride::class, - * map = { override, env -> - * Pair(override.wrapped, env + (NeutronFlowPolarity to override.polarity)) - * } - * ) - * - * To make a wrapper that customizes [View] initialization: - * - * class WithTutorialTips(val wrapped: W) : Screen, Compatible { - * override val compatibilityKey: String = Compatible.keyFor(wrapped) - * } - * - * object WithTutorialTipsViewFactory : ScreenViewFactory> - * by DecorativeScreenViewFactory( - * type = WithTutorialTips::class, - * map = { withTips -> withTips.wrapped }, - * viewStarter = { view, doStart -> - * TutorialTipRunner.run(this) - * doStart() - * } - * ) - * - * To make a wrapper that adds pre- or post-processing to [View] updates: - * - * class BackButtonScreen( - * val wrapped: W, - * val override: Boolean = false, - * val onBackPressed: (() -> Unit)? = null - * ) : Screen, Compatible { - * override val compatibilityKey: String = Compatible.keyFor(wrapped) - * } - * - * object BackButtonViewFactory : ScreenViewFactory> - * by DecorativeScreenViewFactory( - * type = BackButtonScreen::class, - * map = { outer -> outer.wrapped }, - * doShowRendering = { view, innerShowRendering, outerRendering, viewEnvironment -> - * if (!outerRendering.override) { - * // Place our handler before invoking innerShowRendering, so that - * // its later calls to view.backPressedHandler will take precedence - * // over ours. - * view.backPressedHandler = outerRendering.onBackPressed - * } - * - * innerShowRendering.invoke(outerRendering.wrapped, viewEnvironment) - * - * if (outerRendering.override) { - * // Place our handler after invoking innerShowRendering, so that ours wins. - * view.backPressedHandler = outerRendering.onBackPressed - * } - * } - * ) - * - * @param map called to convert instances of [OuterT] to [InnerT], and to - * allow [ViewEnvironment] to be transformed. - * - * @param viewStarter An optional wrapper for the function invoked when [View.start] - * is called, allowing for last second initialization of a newly built [View]. - * See [ViewStarter] for details. - * - * @param doShowRendering called to apply the [ViewShowRendering] function for - * [InnerT], allowing pre- and post-processing. Default implementation simply - * uses [map] to extract the [InnerT] instance from [OuterT] and makes the function call. - */ @WorkflowUiExperimentalApi public class DecorativeScreenViewFactory( override val type: KClass, diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt index a165c18155..aee909a446 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt @@ -5,8 +5,111 @@ import android.view.View import android.view.ViewGroup import kotlin.reflect.KClass +/** + * A [ViewFactory] for [OuterT] that delegates view construction responsibilities + * to the factory registered for [InnerT]. Makes it convenient for [OuterT] to wrap + * instances of [InnerT] to add information or behavior, without requiring wasteful wrapping + * in the view system. + * + * One general note: when creating a wrapper rendering, you're very likely to want it + * to implement [Compatible], to ensure that checks made to update or replace a view + * are based on the wrapped item. Each example below illustrates this. + * + * ## Examples + * + * To make one rendering type an "alias" for another -- that is, to use the same [ViewFactory] + * to display it -- provide nothing but a single-arg mapping function: + * + * class OriginalRendering(val data: String) : AndroidViewRendering {...} + * class AliasRendering(val similarData: String) + * + * object DecorativeViewFactory : ViewFactory + * by DecorativeViewFactory( + * type = AliasRendering::class, map = { alias -> OriginalRendering(alias.similarData) } + * ) + * + * To make a decorator type that adds information to the [ViewEnvironment]: + * + * class NeutronFlowPolarity(val reversed: Boolean) { + * companion object : ViewEnvironmentKey(NeutronFlowPolarity::class) { + * override val default: NeutronFlowPolarity = NeutronFlowPolarity(reversed = false) + * } + * } + * + * class NeutronFlowPolarityOverride( + * val wrapped: W, + * val polarity: NeutronFlowPolarity + * ) : Compatible { + * override val compatibilityKey: String = Compatible.keyFor(wrapped) + * } + * + * object NeutronFlowPolarityViewFactory : ViewFactory> + * by DecorativeViewFactory( + * type = NeutronFlowPolarityOverride::class, + * map = { override, env -> + * Pair(override.wrapped, env + (NeutronFlowPolarity to override.polarity)) + * } + * ) + * + * To make a decorator type that customizes [View] initialization: + * + * class WithTutorialTips(val wrapped: W) : Compatible { + * override val compatibilityKey: String = Compatible.keyFor(wrapped) + * } + * + * object WithTutorialTipsViewFactory : ViewFactory> + * by DecorativeViewFactory( + * type = WithTutorialTips::class, + * map = { withTips -> withTips.wrapped }, + * viewStarter = { view, doStart -> + * TutorialTipRunner.run(view) + * doStart() + * } + * ) + * + * To make a decorator type that adds pre- or post-processing to [View] updates: + * + * class BackButtonScreen( + * val wrapped: W, + * val override: Boolean = false, + * val onBackPressed: (() -> Unit)? = null + * ) : Compatible { + * override val compatibilityKey: String = Compatible.keyFor(wrapped) + * } + * + * object BackButtonViewFactory : ViewFactory> + * by DecorativeViewFactory( + * type = BackButtonScreen::class, + * map = { outer -> outer.wrapped }, + * doShowRendering = { view, innerShowRendering, outerRendering, viewEnvironment -> + * if (!outerRendering.override) { + * // Place our handler before invoking innerShowRendering, so that + * // its later calls to view.backPressedHandler will take precedence + * // over ours. + * view.backPressedHandler = outerRendering.onBackPressed + * } + * + * innerShowRendering.invoke(outerRendering.wrapped, viewEnvironment) + * + * if (outerRendering.override) { + * // Place our handler after invoking innerShowRendering, so that ours wins. + * view.backPressedHandler = outerRendering.onBackPressed + * } + * }) + * + * @param map called to convert instances of [OuterT] to [InnerT], and to + * allow [ViewEnvironment] to be transformed. + * + * @param viewStarter An optional wrapper for the function invoked when [View.start] + * is called, allowing for last second initialization of a newly built [View]. + * See [ViewStarter] for details. + * + * @param doShowRendering called to apply the [ViewShowRendering] function for + * [InnerT], allowing pre- and post-processing. Default implementation simply + * uses [map] to extract the [InnerT] instance from [OuterT] and makes the function call. + */ @Suppress("DEPRECATION") -@Deprecated("Use DecorativeScreenViewFactory") +@Deprecated("Use ManualScreenViewFactory") @WorkflowUiExperimentalApi public class DecorativeViewFactory( override val type: KClass, diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt index d51e47a47b..3c48b110c6 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt @@ -13,24 +13,25 @@ import kotlin.reflect.KClass */ @WorkflowUiExperimentalApi @PublishedApi -internal class LayoutScreenViewFactory( - override val type: KClass, +internal class LayoutScreenViewFactory( + override val type: KClass, @LayoutRes private val layoutId: Int, - private val updaterConstructor: (View) -> ScreenViewUpdater -) : ScreenViewFactory { + private val updaterConstructor: (View) -> ScreenViewUpdater +) : ScreenViewFactory { override fun buildView( - initialRendering: RenderingT, + initialRendering: ScreenT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? - ): View { - return contextForNewView.viewBindingLayoutInflater(container) - .inflate(layoutId, container, false) - .also { view -> - val runner = updaterConstructor(view) - view.bindShowRendering(initialRendering, initialViewEnvironment) { rendering, environment -> - runner.showRendering(rendering, environment) - } - } + ): ScreenView { + val view = + contextForNewView.viewBindingLayoutInflater(container).inflate(layoutId, container, false) + + return BaseScreenView( + initialRendering = initialRendering, + initialViewEnvironment = initialViewEnvironment, + androidView = view, + runner = updaterConstructor(view) + ) } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt new file mode 100644 index 0000000000..f54a0ca6f4 --- /dev/null +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt @@ -0,0 +1,75 @@ +package com.squareup.workflow1.ui + +import android.view.View + +@WorkflowUiExperimentalApi +public interface ScreenView { + public val rendering: ScreenT + public val environment: ViewEnvironment + public val androidView: View + + public fun start() + + public fun update( + rendering: ScreenT, + environment: ViewEnvironment + ) + + @WorkflowUiExperimentalApi + public interface Starter { + /** Called from [start]. [doStart] must be invoked. */ + public fun startView( + view: ScreenView, + doStart: () -> Unit + ) + } +} + +@WorkflowUiExperimentalApi +public fun ScreenView.withStarter( + viewStarter: ScreenView.Starter +): ScreenView { + return object : ScreenView by this { + override fun start() { + viewStarter.startView(this@withStarter, this@withStarter::start) + } + } +} + +@WorkflowUiExperimentalApi +public fun ScreenView.mapRenderings( + transform: (ScreenU) -> ScreenT +): ScreenView { + return object: ScreenView { + lateinit var unmapped: ScreenU + + override val rendering: ScreenU + get() = unmapped + + override val environment: ViewEnvironment + get() = this@mapRenderings.environment + + override val androidView: View + get() = this@mapRenderings.androidView + + override fun start() { + this@mapRenderings.start() + } + + override fun update(rendering: ScreenU, environment: ViewEnvironment) { + unmapped = rendering + this@mapRenderings.update(transform(rendering), environment) + } + } +} + +@WorkflowUiExperimentalApi +public fun ScreenView.mapEnvironment( + updater: (ViewEnvironment) -> ViewEnvironment +): ScreenView{ + return object: ScreenView by this { + override fun update(rendering: ScreenT, environment: ViewEnvironment) { + this@mapEnvironment.update(rendering, updater(environment)) + } + } +} diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt index e019611858..7abe494063 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt @@ -11,7 +11,7 @@ import com.squareup.workflow1.ui.container.EnvironmentScreen import com.squareup.workflow1.ui.container.EnvironmentScreenViewFactory /** - * Factory for [View] instances that can show renderings of type [RenderingT] : [Screen]. + * Factory for [View] instances that can show renderings of type [ScreenT] : [Screen]. * * Two concrete [ScreenViewFactory] implementations are provided: * @@ -25,17 +25,17 @@ import com.squareup.workflow1.ui.container.EnvironmentScreenViewFactory * avoid coupling your workflow directly to the Android runtime, see [ViewRegistry]. */ @WorkflowUiExperimentalApi -public interface ScreenViewFactory : ViewRegistry.Entry { +public interface ScreenViewFactory : ViewRegistry.Entry { /** * Returns a View ready to display [initialRendering] (and any succeeding values) * via [View.showRendering]. */ public fun buildView( - initialRendering: RenderingT, + initialRendering: ScreenT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null - ): View + ): ScreenView } /** @@ -47,10 +47,6 @@ public interface ScreenViewFactory : ViewRegistry.Entry< * [View.showRendering] can be used to update it with new renderings that * are [compatible] with this [Screen]. [WorkflowViewStub] takes care of this chore itself. * - * @param viewStarter An optional wrapper for the function invoked when [View.start] - * is called, allowing for last second initialization of a newly built [View]. - * See [ViewStarter] for details. - * * @throws IllegalArgumentException if no builder can be found for type [ScreenT] * * @throws IllegalStateException if the matching [ScreenViewFactory] fails to call @@ -60,23 +56,11 @@ public interface ScreenViewFactory : ViewRegistry.Entry< public fun ScreenT.buildView( viewEnvironment: ViewEnvironment, contextForNewView: Context, - container: ViewGroup? = null, - viewStarter: ViewStarter? = null, -): View { - val viewFactory = viewEnvironment.getViewFactoryForRendering(this) - - return viewFactory.buildView(this, viewEnvironment, contextForNewView, container).also { view -> - checkNotNull(view.workflowViewStateOrNull) { - "View.bindShowRendering should have been called for $view, typically by the " + - "ScreenViewFactory that created it." - } - viewStarter?.let { givenStarter -> - val doStart = view.starter - view.starter = { newView -> - givenStarter.startView(newView) { doStart.invoke(newView) } - } - } - } + container: ViewGroup? = null +): ScreenView { + return viewEnvironment.getViewFactoryForRendering(this).buildView( + this, viewEnvironment, contextForNewView, container + ) } /** @@ -90,6 +74,7 @@ public fun ScreenT.buildView( * renderings can be mapped to other types. */ @WorkflowUiExperimentalApi +@Deprecated("Use WorkflowView.Starter") public fun interface ViewStarter { /** Called from [View.start]. [doStart] must be invoked. */ public fun startView( diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt index f179d301ce..17674b02d5 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt @@ -1,33 +1,31 @@ package com.squareup.workflow1.ui import android.content.Context -import android.view.View import android.view.ViewGroup import androidx.viewbinding.ViewBinding import kotlin.reflect.KClass @WorkflowUiExperimentalApi @PublishedApi -internal class ViewBindingScreenViewFactory( - override val type: KClass, +internal class ViewBindingScreenViewFactory( + override val type: KClass, private val bindingInflater: ViewBindingInflater, - private val updaterConstructor: (BindingT) -> ScreenViewUpdater -) : ScreenViewFactory { + private val updaterConstructor: (BindingT) -> ScreenViewUpdater +) : ScreenViewFactory { override fun buildView( - initialRendering: RenderingT, + initialRendering: ScreenT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? - ): View = - bindingInflater(contextForNewView.viewBindingLayoutInflater(container), container, false) - .also { binding -> - val updater = updaterConstructor(binding) - binding.root.bindShowRendering( - initialRendering, - initialViewEnvironment - ) { rendering, environment -> - updater.showRendering(rendering, environment) - } - } - .root + ): ScreenView { + val binding = bindingInflater( + contextForNewView.viewBindingLayoutInflater(container), container, false + ) + return BaseScreenView( + initialRendering, + initialViewEnvironment, + binding.root, + runner = updaterConstructor(binding) + ) + } } From c3ba36b5a4aca07d59b78471e8faba2d30f51108 Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Thu, 6 Jan 2022 13:01:42 -0800 Subject: [PATCH 03/12] wip: playing with stub --- .../com/squareup/workflow1/ui/ScreenView.kt | 7 ++- .../squareup/workflow1/ui/WorkflowViewStub.kt | 44 ++++++++++--------- 2 files changed, 29 insertions(+), 22 deletions(-) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt index f54a0ca6f4..f215b46a43 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt @@ -16,7 +16,7 @@ public interface ScreenView { ) @WorkflowUiExperimentalApi - public interface Starter { + public fun interface Starter { /** Called from [start]. [doStart] must be invoked. */ public fun startView( view: ScreenView, @@ -25,6 +25,11 @@ public interface ScreenView { } } +@WorkflowUiExperimentalApi +public fun ScreenView<*>.canShowRendering(screen: Screen): Boolean { + return compatible(rendering, screen) +} + @WorkflowUiExperimentalApi public fun ScreenView.withStarter( viewStarter: ScreenView.Starter diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt index 46557974a0..779dfa013a 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt @@ -69,6 +69,8 @@ public class WorkflowViewStub @JvmOverloads constructor( defStyle: Int = 0, defStyleRes: Int = 0 ) : View(context, attributeSet, defStyle, defStyleRes) { + private lateinit var showing: ScreenView + /** * On-demand access to the view created by the last call to [update], * or this [WorkflowViewStub] instance if none has yet been made. @@ -207,11 +209,10 @@ public class WorkflowViewStub @JvmOverloads constructor( rendering: Screen, viewEnvironment: ViewEnvironment ): View { - actual.takeIf { it.canShowRendering(rendering) } - ?.let { - it.showRendering(rendering, viewEnvironment) - return it - } + if (this != actual && showing.canShowRendering(rendering)) { + showing.update(rendering, viewEnvironment) + return showing.androidView + } val parent = actual.parent as? ViewGroup ?: throw IllegalStateException("WorkflowViewStub must have a non-null ViewGroup parent") @@ -230,25 +231,26 @@ public class WorkflowViewStub @JvmOverloads constructor( WorkflowLifecycleOwner.get(actual)?.destroyOnDetach() } - return rendering.buildView( + val newWorkflowView = rendering.buildView( viewEnvironment, parent.context, - parent, - viewStarter = { view, doStart -> - WorkflowLifecycleOwner.installOn(view) - doStart() - } - ) - .also { newView -> - newView.start() + parent + ).withStarter { view, doStart -> + WorkflowLifecycleOwner.installOn(view.androidView) + doStart() + } + newWorkflowView.start() - if (inflatedId != NO_ID) newView.id = inflatedId - if (updatesVisibility) newView.visibility = visibility - background?.let { newView.background = it } - propagateSavedStateRegistryOwner(newView) - replaceOldViewInParent(parent, newView) - actual = newView - } + val newAndroidView = newWorkflowView.androidView + + if (inflatedId != NO_ID) newAndroidView.id = inflatedId + if (updatesVisibility) newAndroidView.visibility = visibility + background?.let { newAndroidView.background = it } + propagateSavedStateRegistryOwner(newAndroidView) + replaceOldViewInParent(parent, newAndroidView) + actual = newAndroidView + + return newWorkflowView.androidView } /** From de2fc68488c4d6a3e057ca849f40decaf39e83ba Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Thu, 6 Jan 2022 15:42:44 -0800 Subject: [PATCH 04/12] wip: Stub looks done. Some call sites of start() need attention. Also remember to delete DecorativeScreenViewFactory and port the tests for it. --- .../overviewdetail/OverviewDetailContainer.kt | 5 +- .../sample/dungeon/BoardsListLayoutUpdater.kt | 2 +- ...eScreenView.kt => BaseScreenViewHolder.kt} | 16 +- .../workflow1/ui/LayoutScreenViewFactory.kt | 6 +- .../com/squareup/workflow1/ui/ScreenView.kt | 80 --------- .../workflow1/ui/ScreenViewFactory.kt | 4 +- .../squareup/workflow1/ui/ScreenViewHolder.kt | 91 ++++++++++ .../ui/ViewBindingScreenViewFactory.kt | 4 +- .../squareup/workflow1/ui/WorkflowLayout.kt | 10 +- .../squareup/workflow1/ui/WorkflowViewStub.kt | 157 +++++++++--------- .../ui/container/BackStackContainer.kt | 17 +- .../ModalScreenOverlayDialogFactory.kt | 4 +- .../java/com/squareup/workflow1/ui/Screen.kt | 2 +- .../internal/test/WorkflowUiTestActivity.kt | 5 +- 14 files changed, 209 insertions(+), 194 deletions(-) rename workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/{BaseScreenView.kt => BaseScreenViewHolder.kt} (58%) delete mode 100644 workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt create mode 100644 workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt diff --git a/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt b/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt index fe0b099884..7dd6d44a02 100644 --- a/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt +++ b/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt @@ -60,14 +60,14 @@ class OverviewDetailContainer(view: View) : ScreenViewUpdater - detailStub!!.actual.visibility = VISIBLE + detailStub!!.delegateHolder.view.visibility = VISIBLE detailStub.show( detail, viewEnvironment + (OverviewDetailConfig to Detail) ) } ?: run { - detailStub!!.actual.visibility = INVISIBLE + detailStub!!.delegateHolder.view.visibility = INVISIBLE } } } @@ -89,6 +89,5 @@ class OverviewDetailContainer(view: View) : ScreenViewUpdater( +internal class BaseScreenViewHolder( private val initialRendering: ScreenT, private val initialViewEnvironment: ViewEnvironment, - override val androidView: View, + override val view: View, private val runner: ScreenViewRunner -) : ScreenView { +) : ScreenViewHolder { lateinit var currentRendering: ScreenT lateinit var currentEnvironment: ViewEnvironment - override val rendering: ScreenT + override val screen: ScreenT get() = currentRendering override val environment: ViewEnvironment get() = currentEnvironment override fun start() { - update(initialRendering, initialViewEnvironment) + showScreen(initialRendering, initialViewEnvironment) } - override fun update(rendering: ScreenT, environment: ViewEnvironment) { - currentRendering = rendering + override fun showScreen(screen: ScreenT, environment: ViewEnvironment) { + currentRendering = screen currentEnvironment = environment - runner.showRendering(rendering, environment) + runner.showRendering(screen, environment) } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt index 3c48b110c6..eeb8818f2b 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt @@ -23,14 +23,14 @@ internal class LayoutScreenViewFactory( initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? - ): ScreenView { + ): ScreenViewHolder { val view = contextForNewView.viewBindingLayoutInflater(container).inflate(layoutId, container, false) - return BaseScreenView( + return BaseScreenViewHolder( initialRendering = initialRendering, initialViewEnvironment = initialViewEnvironment, - androidView = view, + view = view, runner = updaterConstructor(view) ) } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt deleted file mode 100644 index f215b46a43..0000000000 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenView.kt +++ /dev/null @@ -1,80 +0,0 @@ -package com.squareup.workflow1.ui - -import android.view.View - -@WorkflowUiExperimentalApi -public interface ScreenView { - public val rendering: ScreenT - public val environment: ViewEnvironment - public val androidView: View - - public fun start() - - public fun update( - rendering: ScreenT, - environment: ViewEnvironment - ) - - @WorkflowUiExperimentalApi - public fun interface Starter { - /** Called from [start]. [doStart] must be invoked. */ - public fun startView( - view: ScreenView, - doStart: () -> Unit - ) - } -} - -@WorkflowUiExperimentalApi -public fun ScreenView<*>.canShowRendering(screen: Screen): Boolean { - return compatible(rendering, screen) -} - -@WorkflowUiExperimentalApi -public fun ScreenView.withStarter( - viewStarter: ScreenView.Starter -): ScreenView { - return object : ScreenView by this { - override fun start() { - viewStarter.startView(this@withStarter, this@withStarter::start) - } - } -} - -@WorkflowUiExperimentalApi -public fun ScreenView.mapRenderings( - transform: (ScreenU) -> ScreenT -): ScreenView { - return object: ScreenView { - lateinit var unmapped: ScreenU - - override val rendering: ScreenU - get() = unmapped - - override val environment: ViewEnvironment - get() = this@mapRenderings.environment - - override val androidView: View - get() = this@mapRenderings.androidView - - override fun start() { - this@mapRenderings.start() - } - - override fun update(rendering: ScreenU, environment: ViewEnvironment) { - unmapped = rendering - this@mapRenderings.update(transform(rendering), environment) - } - } -} - -@WorkflowUiExperimentalApi -public fun ScreenView.mapEnvironment( - updater: (ViewEnvironment) -> ViewEnvironment -): ScreenView{ - return object: ScreenView by this { - override fun update(rendering: ScreenT, environment: ViewEnvironment) { - this@mapEnvironment.update(rendering, updater(environment)) - } - } -} diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt index 7abe494063..9b49f8bdde 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt @@ -35,7 +35,7 @@ public interface ScreenViewFactory : ViewRegistry.Entry + ): ScreenViewHolder } /** @@ -57,7 +57,7 @@ public fun ScreenT.buildView( viewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? = null -): ScreenView { +): ScreenViewHolder { return viewEnvironment.getViewFactoryForRendering(this).buildView( this, viewEnvironment, contextForNewView, container ) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt new file mode 100644 index 0000000000..a2c8f30154 --- /dev/null +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt @@ -0,0 +1,91 @@ +package com.squareup.workflow1.ui + +import android.view.View + +/** + * Wraps an [Android view][view] with: + * + * - the current [Screen] [screen] it's displaying + * - the [environment] that [screen] was shown with + * - a [showScreen] method to refresh it + * + * Instances are created via [ScreenViewFactory.buildView]. [start] must be called + * exactly once before [showScreen], to initialize the new view. Use [withStarter] to + * customize what the [start] method does. + */ +@WorkflowUiExperimentalApi +public interface ScreenViewHolder { + public val screen: ScreenT + public val environment: ViewEnvironment + public val view: View + + public fun start() + + public fun showScreen( + screen: ScreenT, + environment: ViewEnvironment + ) + + @WorkflowUiExperimentalApi + public fun interface Starter { + /** Called from [start]. [doStart] must be invoked. */ + public fun startView( + viewHolder: ScreenViewHolder, + doStart: () -> Unit + ) + } +} + +@WorkflowUiExperimentalApi +public fun ScreenViewHolder<*>.canShowScreen(screen: Screen): Boolean { + return compatible(this.screen, screen) +} + +@WorkflowUiExperimentalApi +public fun ScreenViewHolder.withStarter( + viewStarter: ScreenViewHolder.Starter +): ScreenViewHolder { + return object : ScreenViewHolder by this { + override fun start() { + viewStarter.startView(this@withStarter, this@withStarter::start) + } + } +} + +@WorkflowUiExperimentalApi +public fun ScreenViewHolder.mapRenderings( + transform: (ScreenU) -> ScreenT +): ScreenViewHolder { + return object: ScreenViewHolder { + lateinit var unmapped: ScreenU + + override val screen: ScreenU + get() = unmapped + + override val environment: ViewEnvironment + get() = this@mapRenderings.environment + + override val view: View + get() = this@mapRenderings.view + + override fun start() { + this@mapRenderings.start() + } + + override fun showScreen(screen: ScreenU, environment: ViewEnvironment) { + unmapped = screen + this@mapRenderings.showScreen(transform(screen), environment) + } + } +} + +@WorkflowUiExperimentalApi +public fun ScreenViewHolder.mapEnvironment( + updater: (ViewEnvironment) -> ViewEnvironment +): ScreenViewHolder{ + return object: ScreenViewHolder by this { + override fun showScreen(screen: ScreenT, environment: ViewEnvironment) { + this@mapEnvironment.showScreen(screen, updater(environment)) + } + } +} diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt index 17674b02d5..8974af0d5d 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt @@ -17,11 +17,11 @@ internal class ViewBindingScreenViewFactory { + ): ScreenViewHolder { val binding = bindingInflater( contextForNewView.viewBindingLayoutInflater(container), container, false ) - return BaseScreenView( + return BaseScreenViewHolder( initialRendering, initialViewEnvironment, binding.root, diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt index 1cb6b2bcc7..05b5f5cf4e 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowLayout.kt @@ -39,7 +39,7 @@ public class WorkflowLayout( if (id == NO_ID) id = R.id.workflow_layout } - private val showing: WorkflowViewStub = WorkflowViewStub(context).also { rootStub -> + private val stub: WorkflowViewStub = WorkflowViewStub(context).also { rootStub -> rootStub.updatesVisibility = false addView(rootStub, ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)) } @@ -103,17 +103,19 @@ public class WorkflowLayout( } private fun show(rootScreen: EnvironmentScreen<*>) { - showing.show(rootScreen.screen, rootScreen.viewEnvironment) + stub.show(rootScreen.screen, rootScreen.viewEnvironment) restoredChildState?.let { restoredState -> restoredChildState = null - showing.actual.restoreHierarchyState(restoredState) + stub.delegateHolder.view.restoreHierarchyState(restoredState) } } override fun onSaveInstanceState(): Parcelable { return SavedState( super.onSaveInstanceState()!!, - SparseArray().also { array -> showing.actual.saveHierarchyState(array) } + SparseArray().also { array -> + stub.delegateHolder.view.saveHierarchyState(array) + } ) } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt index 779dfa013a..35f8daf941 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt @@ -59,8 +59,8 @@ import com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner * Use [updatesVisibility] and [setBackground] for more control of how [update] * effects the visibility and backgrounds of created views. * - * Use [replaceOldViewInParent] to customize replacing [actual] with a new view, e.g. - * for animated transitions. + * Use [replaceOldViewInParent] to customize replacing the current view with a new one during + * [show], e.g. for animated transitions. */ @WorkflowUiExperimentalApi public class WorkflowViewStub @JvmOverloads constructor( @@ -69,19 +69,41 @@ public class WorkflowViewStub @JvmOverloads constructor( defStyle: Int = 0, defStyleRes: Int = 0 ) : View(context, attributeSet, defStyle, defStyleRes) { - private lateinit var showing: ScreenView + /** Returns null if [update] hasn't been called yet. */ + private val delegateOrNull: View? + get() { + // can be null when called from the constructor. + @Suppress("UNNECESSARY_SAFE_CALL") + return delegateHolder?.view?.takeUnless { it === this } + } - /** - * On-demand access to the view created by the last call to [update], - * or this [WorkflowViewStub] instance if none has yet been made. - */ - public var actual: View = this + public var delegateHolder: ScreenViewHolder = object : ScreenViewHolder { + override val screen: Screen = object : Screen {} + override val environment: ViewEnvironment = ViewEnvironment(mapOf()) + + override val view: View = this@WorkflowViewStub + + override fun start() { + throw UnsupportedOperationException() + } + + override fun showScreen( + screen: Screen, + environment: ViewEnvironment + ) { + throw UnsupportedOperationException() + } + } private set + @Deprecated("Use delegateHolder.view", ReplaceWith("delegateHolder.view")) + public val actual: View + get() = delegateHolder.view + /** - * If true, the visibility of views created by [update] will be copied - * from that of [actual]. Bear in mind that the initial value of - * [actual] is this stub. + * If true, the visibility of new delegate views created by [update] will be copied + * from the current one. The first delegate created will copy the visibility of + * this stub. */ public var updatesVisibility: Boolean = true @@ -116,14 +138,15 @@ public class WorkflowViewStub @JvmOverloads constructor( } /** - * Function called from [update] to replace this stub, or the current [actual], + * Function called from [update] to replace this stub, or its current delegate, * with a new view. Can be updated to provide custom transition effects. * * Note that this method is responsible for copying the [layoutParams][getLayoutParams] - * from the stub to the new view. Also note that in a [WorkflowViewStub] that has never - * been updated, [actual] is the stub itself. + * from the stub to the new view. */ public var replaceOldViewInParent: (ViewGroup, View) -> Unit = { parent, newView -> + val actual = delegateHolder.view + val index = parent.indexOfChild(actual) parent.removeView(actual) actual.layoutParams @@ -132,44 +155,32 @@ public class WorkflowViewStub @JvmOverloads constructor( } /** - * Sets the visibility of [actual]. If [updatesVisibility] is true, the visibility of - * new views created by [update] will copied from [actual]. (Bear in mind that the initial - * value of [actual] is this stub.) + * Sets the visibility of the delegate, or of this stub if [show] has not yet been called. + * + * @see updatesVisibility */ override fun setVisibility(visibility: Int) { super.setVisibility(visibility) - // actual can be null when called from the constructor. - @Suppress("SENSELESS_COMPARISON") - if (actual != this && actual != null) { - actual.visibility = visibility + delegateOrNull?.takeUnless { it == this }?.let { + it.visibility = visibility } } /** - * Returns the visibility of [actual]. (Bear in mind that the initial value of - * [actual] is this stub.) + * Returns the visibility of the delegate, or of this stub if [show] has not yet been called. */ override fun getVisibility(): Int { - // actual can be null when called from the constructor. - @Suppress("SENSELESS_NULL_IN_WHEN") - return when (actual) { - this, null -> super.getVisibility() - else -> actual.visibility - } + return delegateOrNull?.visibility ?: super.getVisibility() } /** - * Sets the background of this stub as usual, and also that of [actual] - * if the given [background] is not null. Any new views created by [update] + * Sets the background of this stub as usual, and also that of the delegate view, + * if the given [background] is not null. Any new delegates created by [update] * will be assigned this background, again if it is not null. */ override fun setBackground(background: Drawable?) { super.setBackground(background) - // actual can be null when called from the constructor. - @Suppress("SENSELESS_COMPARISON") - if (actual != this && actual != null && background != null) { - actual.background = background - } + if (background != null) delegateOrNull?.background = background } @Deprecated("Use show()", ReplaceWith("show(rendering, viewEnvironment)")) @@ -178,79 +189,73 @@ public class WorkflowViewStub @JvmOverloads constructor( viewEnvironment: ViewEnvironment ): View { @Suppress("DEPRECATION") - return show(asScreen(rendering), viewEnvironment) + show(asScreen(rendering), viewEnvironment) + return delegateHolder.view } /** - * Replaces this view with one that can display [rendering]. If the receiver - * has already been replaced, updates the replacement if it [canShowRendering]. - * If the current replacement can't handle [rendering], a new view is put in its place. + * Replaces this view with a [delegate][delegateHolder] that can display [screen]. + * If [show] has already been called previously, updates the current delegate if it + * [canShowScreen]. If the current delegate can't handle [screen], a new view + * is put in its place. * - * The [id][View.setId] of any view created by this method will be set to to [inflatedId], + * The [id][View.setId] of any delegate view created by this method will be set to to [inflatedId], * unless that value is [View.NO_ID]. * - * The [background][setBackground] of any view created by this method will be copied + * The [background][setBackground] of any delegate view created by this method will be copied * from [getBackground], if that value is non-null. * - * If [updatesVisibility] is true, the [visibility][setVisibility] of any view created by - * this method will be copied from [actual]. (Bear in mind that the initial value of - * [actual] is this stub.) - * - * @return the view that showed [rendering] + * If [updatesVisibility] is true, the [visibility][setVisibility] of any delegate view created + * by this method will be copied from [getVisibility]. * - * @throws IllegalArgumentException if no binding can be found for the type of [rendering] + * @return the view that showed [screen] * - * @throws IllegalStateException if the matching - * [ViewFactory][com.squareup.workflow1.ui.ViewFactory] fails to call - * [View.bindShowRendering][com.squareup.workflow1.ui.bindShowRendering] - * when constructing the view + * @throws IllegalArgumentException if no binding can be found for the type of [screen] */ public fun show( - rendering: Screen, + screen: Screen, viewEnvironment: ViewEnvironment - ): View { - if (this != actual && showing.canShowRendering(rendering)) { - showing.update(rendering, viewEnvironment) - return showing.androidView - } + ) { + delegateHolder.takeIf { it.canShowScreen(screen) } + ?.let { + it.showScreen(screen, viewEnvironment) + return + } - val parent = actual.parent as? ViewGroup + val parent = delegateHolder.view.parent as? ViewGroup ?: throw IllegalStateException("WorkflowViewStub must have a non-null ViewGroup parent") - // If we have a delegate view (i.e. this !== actual), then the old delegate is going to - // eventually be detached by replaceOldViewInParent. When that happens, it's not just a regular - // detach, it's a navigation event that effectively says that view will never come back. Thus, - // we want its Lifecycle to move to permanently destroyed, even though the parent lifecycle is - // still probably alive. + // If we have a delegate view, then the old delegate is going to eventually be detached by + // replaceOldViewInParent. When that happens, it's not just a regular detach, it's a navigation + // event that effectively says that view will never come back. Thus, we want its Lifecycle to + // move to permanently destroyed, even though the parent lifecycle is still probably alive. // - // If actual === this, then this stub hasn't been initialized with a real delegate view yet. If - // we're a child of another container which set a WorkflowLifecycleOwner on this view, this - // get() call will return the WLO owned by that parent. We noop in that case since destroying - // that lifecycle is our parent's responsibility in that case, not ours. - if (actual !== this) { - WorkflowLifecycleOwner.get(actual)?.destroyOnDetach() + // If there isn't a delegate, we're a child of another container which set a + // WorkflowLifecycleOwner on this view, this get() call will return the WLO owned by that + // parent. We noop in that case since destroying that lifecycle is our parent's responsibility + // in that case, not ours. + delegateOrNull?.let { + WorkflowLifecycleOwner.get(it)?.destroyOnDetach() } - val newWorkflowView = rendering.buildView( + val newWorkflowView = screen.buildView( viewEnvironment, parent.context, parent ).withStarter { view, doStart -> - WorkflowLifecycleOwner.installOn(view.androidView) + WorkflowLifecycleOwner.installOn(view.view) doStart() } newWorkflowView.start() - val newAndroidView = newWorkflowView.androidView + val newAndroidView = newWorkflowView.view if (inflatedId != NO_ID) newAndroidView.id = inflatedId if (updatesVisibility) newAndroidView.visibility = visibility background?.let { newAndroidView.background = it } propagateSavedStateRegistryOwner(newAndroidView) replaceOldViewInParent(parent, newAndroidView) - actual = newAndroidView - - return newWorkflowView.androidView + delegateHolder = newWorkflowView } /** diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackContainer.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackContainer.kt index be81a2590f..39e00d4bc9 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackContainer.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackContainer.kt @@ -27,7 +27,7 @@ import com.squareup.workflow1.ui.container.BackStackConfig.First import com.squareup.workflow1.ui.container.BackStackConfig.Other import com.squareup.workflow1.ui.container.ViewStateCache.SavedState import com.squareup.workflow1.ui.showRendering -import com.squareup.workflow1.ui.start +import com.squareup.workflow1.ui.withStarter /** * A container view that can display a stream of [BackStackScreen] instances. @@ -98,18 +98,17 @@ public open class BackStackContainer @JvmOverloads constructor( val newView = named.top.buildView( viewEnvironment = environment, contextForNewView = this.context, - container = this, - viewStarter = { view, doStart -> - WorkflowLifecycleOwner.installOn(view) - doStart() - } - ) + container = this + ).withStarter { view, doStart -> + WorkflowLifecycleOwner.installOn(view.view) + doStart() + } newView.start() - viewStateCache.update(named.backStack, oldViewMaybe, newView) + viewStateCache.update(named.backStack, oldViewMaybe, newView.view) val popped = currentRendering?.backStack?.any { compatible(it, named.top) } == true - performTransition(oldViewMaybe, newView, popped) + performTransition(oldViewMaybe, newView.view, popped) // Notify the view we're about to replace that it's going away. oldViewMaybe?.let(WorkflowLifecycleOwner::get)?.destroyOnDetach() diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayDialogFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayDialogFactory.kt index 6cdc8045e2..d7bcfd07a5 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayDialogFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayDialogFactory.kt @@ -15,9 +15,7 @@ import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler import com.squareup.workflow1.ui.buildView -import com.squareup.workflow1.ui.environment import com.squareup.workflow1.ui.showRendering -import com.squareup.workflow1.ui.start import java.lang.IllegalStateException import kotlin.reflect.KClass @@ -68,7 +66,7 @@ public abstract class ModalScreenOverlayDialogFactory>( // If the content view has no backPressedHandler, add a no-op one to // ensure that the `onBackPressed` call below will not leak up to handlers // that should be blocked by this modal session. - if (backPressedHandler == null) backPressedHandler = { } + if (view.backPressedHandler == null) view.backPressedHandler = { } } return buildDialogWithContentView(contentView).also { dialog -> diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Screen.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Screen.kt index 37b586e8aa..ffcac814cf 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Screen.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/Screen.kt @@ -1,7 +1,7 @@ package com.squareup.workflow1.ui /** - * Marker interface implemented by renderings that map to a UI system's 2d view class. + * Marker interface implemented by renderings that model a UI system's 2d box / view class. */ @WorkflowUiExperimentalApi public interface Screen diff --git a/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/WorkflowUiTestActivity.kt b/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/WorkflowUiTestActivity.kt index 70676137ae..2a625206d8 100644 --- a/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/WorkflowUiTestActivity.kt +++ b/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/WorkflowUiTestActivity.kt @@ -45,7 +45,7 @@ public open class WorkflowUiTestActivity : AppCompatActivity() { /** * The [View] that was created to display the last rendering passed to [setRendering]. */ - public val rootRenderedView: View get() = rootStub.actual + public val rootRenderedView: View get() = rootStub.delegateHolder.view /** * Key-value store for custom values that should be retained across configuration changes. @@ -104,7 +104,8 @@ public open class WorkflowUiTestActivity : AppCompatActivity() { wrapped = rendering, name = renderingCounter.toString() ) - return rootStub.show(named, viewEnvironment) + rootStub.show(named, viewEnvironment) + return rootStub.delegateHolder.view } private class NonConfigurationData( From 2ad84bb9936c99a3f6a3127330f757a09039d707 Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Fri, 7 Jan 2022 08:49:36 -0800 Subject: [PATCH 05/12] wip: time to burn down decorator --- .../panel/PanelOverlayDialogFactory.kt | 6 +- .../workflow1/ui/modal/ModalViewContainer.kt | 87 ++++++++++--------- .../squareup/workflow1/ui/WorkflowViewStub.kt | 8 +- .../ModalScreenOverlayDialogFactory.kt | 17 ++-- .../ModalScreenOverlayOnBackPressed.kt | 8 +- 5 files changed, 64 insertions(+), 62 deletions(-) diff --git a/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt b/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt index 6a697a7e20..f896eb47e4 100644 --- a/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt +++ b/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt @@ -17,10 +17,10 @@ import com.squareup.workflow1.ui.container.setBounds internal object PanelOverlayDialogFactory : ModalScreenOverlayDialogFactory>( type = PanelOverlay::class ) { - override fun buildDialogWithContentView(contentView: View): Dialog { - val context = contentView.context + override fun buildDialogWithContent(content: View): Dialog { + val context = content.context return Dialog(context, R.style.PanelDialog).also { dialog -> - dialog.setContentView(contentView) + dialog.setContentView(content) // Welcome to Android. Nothing workflow-related here, this is just how one // finds the window background color for the theme. I sure hope it's better in Compose. diff --git a/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt b/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt index e2b9e7986c..4e4076b0d1 100644 --- a/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt +++ b/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt @@ -12,18 +12,17 @@ import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewGroup.LayoutParams.WRAP_CONTENT import androidx.annotation.IdRes -import com.squareup.workflow1.ui.asScreen import com.squareup.workflow1.ui.BuilderViewFactory import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.ViewRegistry import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.asScreen import com.squareup.workflow1.ui.backPressedHandler import com.squareup.workflow1.ui.bindShowRendering import com.squareup.workflow1.ui.buildView import com.squareup.workflow1.ui.modal.ModalViewContainer.Companion.binding import com.squareup.workflow1.ui.onBackPressedDispatcherOwnerOrNull import com.squareup.workflow1.ui.showRendering -import com.squareup.workflow1.ui.start import kotlin.reflect.KClass /** @@ -65,45 +64,47 @@ public open class ModalViewContainer @JvmOverloads constructor( initialModalRendering: Any, initialViewEnvironment: ViewEnvironment ): DialogRef { - val view = asScreen(initialModalRendering).buildView( - viewEnvironment = initialViewEnvironment, - contextForNewView = this.context, - container = this - ) - .apply { - start() - // If the modal's root view has no backPressedHandler, add a no-op one to - // ensure that the `onBackPressed` call below will not leak up to handlers - // that should be blocked by this modal session. - if (backPressedHandler == null) backPressedHandler = { } - } + val view = asScreen(initialModalRendering) + .buildView( + viewEnvironment = initialViewEnvironment, + contextForNewView = this.context, + container = this + ) + .let { holder -> + holder.start() + holder.view + } + // If the modal's root view has no backPressedHandler, add a no-op one to + // ensure that the `onBackPressed` call below will not leak up to handlers + // that should be blocked by this modal session. + if (view.backPressedHandler == null) view.backPressedHandler = { } return buildDialogForView(view) - .apply { - // Dialogs are modal windows and so they block events, including back button presses - // -- that's their job! But we *want* the Activity's onBackPressedDispatcher to fire - // when back is pressed, so long as it doesn't look past this modal window for handlers. - // - // Here, we handle the ACTION_UP portion of a KEYCODE_BACK key event, and below - // we make sure that the root view has a backPressedHandler that will consume the - // onBackPressed call if no child of the root modal view does. + .apply { + // Dialogs are modal windows and so they block events, including back button presses + // -- that's their job! But we *want* the Activity's onBackPressedDispatcher to fire + // when back is pressed, so long as it doesn't look past this modal window for handlers. + // + // Here, we handle the ACTION_UP portion of a KEYCODE_BACK key event, and below + // we make sure that the root view has a backPressedHandler that will consume the + // onBackPressed call if no child of the root modal view does. - setOnKeyListener { _, keyCode, keyEvent -> - if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.action == ACTION_UP) { - view.context.onBackPressedDispatcherOwnerOrNull() - ?.onBackPressedDispatcher - ?.let { - if (it.hasEnabledCallbacks()) it.onBackPressed() - } - true - } else { - false - } + setOnKeyListener { _, keyCode, keyEvent -> + if (keyCode == KeyEvent.KEYCODE_BACK && keyEvent.action == ACTION_UP) { + view.context.onBackPressedDispatcherOwnerOrNull() + ?.onBackPressedDispatcher + ?.let { + if (it.hasEnabledCallbacks()) it.onBackPressed() + } + true + } else { + false } } - .run { - DialogRef(initialModalRendering, initialViewEnvironment, this, view) - } + } + .run { + DialogRef(initialModalRendering, initialViewEnvironment, this, view) + } } override fun updateDialog(dialogRef: DialogRef) { @@ -118,14 +119,14 @@ public open class ModalViewContainer @JvmOverloads constructor( type: KClass ) : com.squareup.workflow1.ui.ViewFactory by BuilderViewFactory( - type = type, - viewConstructor = { initialRendering, initialEnv, context, _ -> - ModalViewContainer(context).apply { - this.id = id - layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) - bindShowRendering(initialRendering, initialEnv, ::update) - } + type = type, + viewConstructor = { initialRendering, initialEnv, context, _ -> + ModalViewContainer(context).apply { + this.id = id + layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) + bindShowRendering(initialRendering, initialEnv, ::update) } + } ) public companion object { diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt index 35f8daf941..daba8f1a3d 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt @@ -238,7 +238,7 @@ public class WorkflowViewStub @JvmOverloads constructor( WorkflowLifecycleOwner.get(it)?.destroyOnDetach() } - val newWorkflowView = screen.buildView( + val newViewHolder = screen.buildView( viewEnvironment, parent.context, parent @@ -246,16 +246,16 @@ public class WorkflowViewStub @JvmOverloads constructor( WorkflowLifecycleOwner.installOn(view.view) doStart() } - newWorkflowView.start() + newViewHolder.start() - val newAndroidView = newWorkflowView.view + val newAndroidView = newViewHolder.view if (inflatedId != NO_ID) newAndroidView.id = inflatedId if (updatesVisibility) newAndroidView.visibility = visibility background?.let { newAndroidView.background = it } propagateSavedStateRegistryOwner(newAndroidView) replaceOldViewInParent(parent, newAndroidView) - delegateHolder = newWorkflowView + delegateHolder = newViewHolder } /** diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayDialogFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayDialogFactory.kt index d7bcfd07a5..a78f9d4d28 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayDialogFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayDialogFactory.kt @@ -11,6 +11,7 @@ import android.view.View import android.view.Window import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL import com.squareup.workflow1.ui.R +import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler @@ -35,9 +36,9 @@ public abstract class ModalScreenOverlayDialogFactory>( /** * Called from [buildDialog]. Builds (but does not show) the [Dialog] to - * display a [contentView] built for a [ScreenOverlay.content]. + * display a [content] view built for a [ScreenOverlay.content]. */ - public abstract fun buildDialogWithContentView(contentView: View): Dialog + public abstract fun buildDialogWithContent(content: ScreenViewHolder<*>): Dialog /** * If the [ScreenOverlay] displayed by a [dialog] created by this @@ -61,7 +62,7 @@ public abstract class ModalScreenOverlayDialogFactory>( initialEnvironment: ViewEnvironment, context: Context ): Dialog { - val contentView = initialRendering.content.buildView(initialEnvironment, context).apply { + val contentHolder = initialRendering.content.buildView(initialEnvironment, context).apply { start() // If the content view has no backPressedHandler, add a no-op one to // ensure that the `onBackPressed` call below will not leak up to handlers @@ -69,13 +70,13 @@ public abstract class ModalScreenOverlayDialogFactory>( if (view.backPressedHandler == null) view.backPressedHandler = { } } - return buildDialogWithContentView(contentView).also { dialog -> + return buildDialogWithContent(contentHolder).also { dialog -> val window = requireNotNull(dialog.window) { "Dialog must be attached to a window." } // There is no Dialog.getContentView method, and no reliable way to reverse // engineer one (no, android.R.id.content doesn't work). So we stick the // contentView in a tag here, where updateDialog can find it later. - window.peekDecorView()?.setTag(R.id.workflow_modal_dialog_content, contentView) + window.peekDecorView()?.setTag(R.id.workflow_modal_dialog_content, contentHolder) ?: throw IllegalStateException("Expected decorView to have been built.") val realWindowCallback = window.callback @@ -85,15 +86,15 @@ public abstract class ModalScreenOverlayDialogFactory>( event.action == ACTION_UP return when { - isBackPress -> contentView.environment?.get(ModalScreenOverlayOnBackPressed) - ?.onBackPressed(contentView) == true + isBackPress -> contentHolder.environment[ModalScreenOverlayOnBackPressed] + .onBackPressed(contentHolder) else -> realWindowCallback.dispatchKeyEvent(event) } } } window.setFlags(FLAG_NOT_TOUCH_MODAL, FLAG_NOT_TOUCH_MODAL) - dialog.maintainBounds(contentView) { d, b -> updateBounds(d, Rect(b)) } + dialog.maintainBounds(contentHolder.view) { d, b -> updateBounds(d, Rect(b)) } } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayOnBackPressed.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayOnBackPressed.kt index 3641e1f44e..7cc0cddc0f 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayOnBackPressed.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayOnBackPressed.kt @@ -1,6 +1,6 @@ package com.squareup.workflow1.ui.container -import android.view.View +import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.ViewEnvironmentKey import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.ModalScreenOverlayOnBackPressed.Handler @@ -26,11 +26,11 @@ public object ModalScreenOverlayOnBackPressed : ViewEnvironmentKey( * * @return true if the back press event was consumed */ - public fun onBackPressed(contentView: View): Boolean + public fun onBackPressed(content: ScreenViewHolder<*>): Boolean } - override val default: Handler = Handler { view -> - view.context.onBackPressedDispatcherOwnerOrNull() + override val default: Handler = Handler { viewHolder -> + viewHolder.view.context.onBackPressedDispatcherOwnerOrNull() ?.onBackPressedDispatcher ?.let { if (it.hasEnabledCallbacks()) it.onBackPressed() From cf3efb06366273c20589ef561ee77247e577409f Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Fri, 7 Jan 2022 11:34:21 -0800 Subject: [PATCH 06/12] wip: moved binding methods to view factory --- .../overviewdetail/OverviewDetailContainer.kt | 5 +- .../sample/poetryapp/PoemListScreen.kt | 8 +- .../HelloBackButtonLayoutUpdater.kt | 6 +- .../sample/poetry/StanzaListScreen.kt | 7 +- .../squareup/sample/poetry/StanzaScreen.kt | 12 +- .../sample/dungeon/BoardsListLayoutUpdater.kt | 6 +- .../sample/dungeon/GameLayoutUpdater.kt | 6 +- .../squareup/sample/dungeon/LoadingBinding.kt | 8 +- .../ShakeableTimeMachineLayoutUpdater.kt | 6 +- .../helloworkflowfragment/HelloRendering.kt | 16 ++- .../sample/helloworkflow/HelloRendering.kt | 16 ++- .../sample/stubvisibility/OuterRendering.kt | 16 ++- .../authworkflow/AuthorizingViewFactory.kt | 14 ++- .../sample/authworkflow/LoginViewFactory.kt | 23 ++-- .../authworkflow/SecondFactorViewFactory.kt | 25 ++-- .../gameworkflow/GameOverLayoutUpdater.kt | 19 ++- .../gameworkflow/GamePlayViewFactory.kt | 21 ++-- .../sample/gameworkflow/NewGameViewFactory.kt | 25 ++-- .../squareup/sample/todo/TodoEditorScreen.kt | 11 +- .../squareup/sample/todo/TodoListsScreen.kt | 31 +++-- .../squareup/workflow1/ui/AndroidScreen.kt | 2 +- .../workflow1/ui/BaseScreenViewHolder.kt | 4 +- .../ui/DecorativeScreenViewFactory.kt | 69 ----------- .../workflow1/ui/LayoutScreenViewFactory.kt | 7 +- .../workflow1/ui/ManualScreenViewFactory.kt | 35 ++---- .../workflow1/ui/ScreenViewFactory.kt | 109 ++++++++++++++++-- .../squareup/workflow1/ui/ScreenViewHolder.kt | 12 ++ .../workflow1/ui/ScreenViewUpdater.kt | 87 +------------- .../ui/ViewBindingScreenViewFactory.kt | 2 +- 29 files changed, 314 insertions(+), 294 deletions(-) delete mode 100644 workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeScreenViewFactory.kt diff --git a/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt b/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt index 7dd6d44a02..0820c37b53 100644 --- a/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt +++ b/samples/containers/android/src/main/java/com/squareup/sample/container/overviewdetail/OverviewDetailContainer.kt @@ -84,9 +84,8 @@ class OverviewDetailContainer(view: View) : ScreenViewUpdater by ScreenViewUpdater.bind( - layoutId = R.layout.overview_detail, - constructor = ::OverviewDetailContainer + companion object : ScreenViewFactory by ScreenViewFactory.ofLayout(R.layout.overview_detail, + { it: View -> OverviewDetailContainer(it) } ) { private const val OverviewBackStackKey = "overview" } diff --git a/samples/containers/app-poetry/src/main/java/com/squareup/sample/poetryapp/PoemListScreen.kt b/samples/containers/app-poetry/src/main/java/com/squareup/sample/poetryapp/PoemListScreen.kt index e7bcf39ae3..02ebcd2617 100644 --- a/samples/containers/app-poetry/src/main/java/com/squareup/sample/poetryapp/PoemListScreen.kt +++ b/samples/containers/app-poetry/src/main/java/com/squareup/sample/poetryapp/PoemListScreen.kt @@ -12,6 +12,7 @@ import com.squareup.sample.container.overviewdetail.OverviewDetailConfig.Overvie import com.squareup.sample.container.poetryapp.R import com.squareup.sample.poetry.model.Poem import com.squareup.workflow1.ui.AndroidScreen +import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @@ -22,10 +23,9 @@ data class PoemListScreen( val onPoemSelected: (Int) -> Unit, val selection: Int = -1 ) : AndroidScreen { - override val viewFactory = ScreenViewUpdater.bind( - R.layout.list, - ::PoemListLayoutUpdater - ) + override val viewFactory = ScreenViewFactory.ofLayout( + R.layout.list + ) { it: View -> PoemListLayoutUpdater(it) } } @OptIn(WorkflowUiExperimentalApi::class) diff --git a/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/HelloBackButtonLayoutUpdater.kt b/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/HelloBackButtonLayoutUpdater.kt index 679e0bcf0e..dbfb3d8152 100644 --- a/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/HelloBackButtonLayoutUpdater.kt +++ b/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/HelloBackButtonLayoutUpdater.kt @@ -15,9 +15,9 @@ data class HelloBackButtonScreen( val onClick: () -> Unit, val onBackPressed: (() -> Unit)? ) : AndroidScreen { - override val viewFactory: ScreenViewFactory = ScreenViewUpdater.bind( - R.layout.hello_back_button_layout, ::HelloBackButtonLayoutUpdater - ) + override val viewFactory: ScreenViewFactory = ScreenViewFactory.ofLayout( + R.layout.hello_back_button_layout + ) { it: View -> HelloBackButtonLayoutUpdater(it) } } @OptIn(WorkflowUiExperimentalApi::class) diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListScreen.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListScreen.kt index 0aaa566f71..9395c16b93 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListScreen.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaListScreen.kt @@ -28,10 +28,9 @@ data class StanzaListScreen( val onExit: () -> Unit, val selection: Int = -1 ) : AndroidScreen { - override val viewFactory: ScreenViewFactory = ScreenViewUpdater.bind( - R.layout.list, - ::StanzaListLayoutUpdater - ) + override val viewFactory: ScreenViewFactory = ScreenViewFactory.ofLayout( + R.layout.list + ) { it: View -> StanzaListLayoutUpdater(it) } } @OptIn(WorkflowUiExperimentalApi::class) diff --git a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaScreen.kt b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaScreen.kt index 6ea53afde8..d907548756 100644 --- a/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaScreen.kt +++ b/samples/containers/poetry/src/main/java/com/squareup/sample/poetry/StanzaScreen.kt @@ -32,10 +32,9 @@ data class StanzaScreen( ) : AndroidScreen, Compatible { override val compatibilityKey = "$title: $stanzaNumber" - override val viewFactory: ScreenViewFactory = ScreenViewUpdater.bind( - R.layout.stanza_layout, - ::StanzaLayoutUpdater - ) + override val viewFactory: ScreenViewFactory = ScreenViewFactory.ofLayout( + R.layout.stanza_layout + ) { it: View -> StanzaLayoutUpdater(it) } } @OptIn(WorkflowUiExperimentalApi::class) @@ -114,8 +113,7 @@ private class StanzaLayoutUpdater(private val view: View) : ScreenViewUpdater by ScreenViewUpdater.bind( - R.layout.stanza_layout, - ::StanzaLayoutUpdater + companion object : ScreenViewFactory by ScreenViewFactory.ofLayout(R.layout.stanza_layout, + { it: View -> StanzaLayoutUpdater(it) } ) } diff --git a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardsListLayoutUpdater.kt b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardsListLayoutUpdater.kt index e473856b3b..d27e7a5b30 100644 --- a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardsListLayoutUpdater.kt +++ b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardsListLayoutUpdater.kt @@ -12,7 +12,6 @@ import com.squareup.sample.dungeon.DungeonAppWorkflow.DisplayBoardsListScreen import com.squareup.sample.dungeon.board.Board import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ScreenViewUpdater -import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub @@ -101,7 +100,8 @@ class BoardsListLayoutUpdater(rootView: View) : ScreenViewUpdater by bind( - R.layout.boards_list_layout, ::BoardsListLayoutUpdater + companion object : ScreenViewFactory by ScreenViewFactory.ofLayout( + R.layout.boards_list_layout, + { it: View -> BoardsListLayoutUpdater(it) } ) } diff --git a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/GameLayoutUpdater.kt b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/GameLayoutUpdater.kt index ca5f2d03f2..67d85f8aa9 100644 --- a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/GameLayoutUpdater.kt +++ b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/GameLayoutUpdater.kt @@ -12,7 +12,6 @@ import com.squareup.sample.dungeon.Direction.UP import com.squareup.sample.dungeon.GameWorkflow.GameRendering import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ScreenViewUpdater -import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub @@ -66,7 +65,8 @@ class GameLayoutUpdater(view: View) : ScreenViewUpdater { } } - companion object : ScreenViewFactory by bind( - R.layout.game_layout, ::GameLayoutUpdater + companion object : ScreenViewFactory by ScreenViewFactory.ofLayout( + R.layout.game_layout, + { it: View -> GameLayoutUpdater(it) } ) } diff --git a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/LoadingBinding.kt b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/LoadingBinding.kt index 4ec99d5991..fa9630cd8a 100644 --- a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/LoadingBinding.kt +++ b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/LoadingBinding.kt @@ -8,7 +8,6 @@ import androidx.annotation.StringRes import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ScreenViewUpdater -import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @@ -23,7 +22,12 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi inline fun LoadingBinding( @StringRes loadingLabelRes: Int ): ScreenViewFactory = - bind(R.layout.loading_layout) { view -> LoadingLayoutUpdater(loadingLabelRes, view) } + ScreenViewFactory.ofLayout(R.layout.loading_layout) { view: View -> + LoadingLayoutUpdater( + loadingLabelRes, + view + ) + } @PublishedApi internal class LoadingLayoutUpdater( diff --git a/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineLayoutUpdater.kt b/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineLayoutUpdater.kt index 77cf285d3b..a5f25ccca0 100644 --- a/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineLayoutUpdater.kt +++ b/samples/dungeon/timemachine-shakeable/src/main/java/com/squareup/sample/timemachine/shakeable/ShakeableTimeMachineLayoutUpdater.kt @@ -9,7 +9,6 @@ import androidx.transition.TransitionManager import com.squareup.sample.timemachine.shakeable.internal.GlassFrameLayout import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.ScreenViewUpdater -import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.WorkflowViewStub @@ -87,7 +86,8 @@ class ShakeableTimeMachineLayoutUpdater( private fun Duration.toUiString(): String = toString() - companion object : ScreenViewFactory by bind( - R.layout.shakeable_time_machine_layout, ::ShakeableTimeMachineLayoutUpdater + companion object : ScreenViewFactory by ScreenViewFactory.ofLayout( + R.layout.shakeable_time_machine_layout, + { it: View -> ShakeableTimeMachineLayoutUpdater(it) } ) } diff --git a/samples/hello-workflow-fragment/src/main/java/com/squareup/sample/helloworkflowfragment/HelloRendering.kt b/samples/hello-workflow-fragment/src/main/java/com/squareup/sample/helloworkflowfragment/HelloRendering.kt index d852aca4d0..8a614fae41 100644 --- a/samples/hello-workflow-fragment/src/main/java/com/squareup/sample/helloworkflowfragment/HelloRendering.kt +++ b/samples/hello-workflow-fragment/src/main/java/com/squareup/sample/helloworkflowfragment/HelloRendering.kt @@ -1,5 +1,7 @@ package com.squareup.sample.helloworkflowfragment +import android.view.LayoutInflater +import android.view.ViewGroup import com.squareup.sample.helloworkflowfragment.databinding.HelloGoodbyeLayoutBinding import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.ScreenViewFactory @@ -12,8 +14,16 @@ data class HelloRendering( val onClick: () -> Unit ) : AndroidScreen { override val viewFactory: ScreenViewFactory = - ScreenViewUpdater.bind(HelloGoodbyeLayoutBinding::inflate) { r, _ -> - helloMessage.text = "${r.message} Fragment" - helloMessage.setOnClickListener { r.onClick() } + ScreenViewFactory.ofViewBinding({ inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean -> + HelloGoodbyeLayoutBinding.inflate( + inflater, + parent, + attachToParent + ) + }) { binding -> + ScreenViewUpdater { rendering, viewEnvironment -> + binding.helloMessage.text = "${rendering.message} Fragment" + binding.helloMessage.setOnClickListener { rendering.onClick() } + } } } diff --git a/samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloRendering.kt b/samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloRendering.kt index ed32e30053..6532e16763 100644 --- a/samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloRendering.kt +++ b/samples/hello-workflow/src/main/java/com/squareup/sample/helloworkflow/HelloRendering.kt @@ -1,5 +1,7 @@ package com.squareup.sample.helloworkflow +import android.view.LayoutInflater +import android.view.ViewGroup import com.squareup.sample.helloworkflow.databinding.HelloGoodbyeLayoutBinding import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.ScreenViewFactory @@ -12,8 +14,16 @@ data class HelloRendering( val onClick: () -> Unit ) : AndroidScreen { override val viewFactory: ScreenViewFactory = - ScreenViewUpdater.bind(HelloGoodbyeLayoutBinding::inflate) { r, _ -> - helloMessage.text = r.message - helloMessage.setOnClickListener { r.onClick() } + ScreenViewFactory.ofViewBinding({ inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean -> + HelloGoodbyeLayoutBinding.inflate( + inflater, + parent, + attachToParent + ) + }) { binding -> + ScreenViewUpdater { rendering, viewEnvironment -> + binding.helloMessage.text = rendering.message + binding.helloMessage.setOnClickListener { rendering.onClick() } + } } } diff --git a/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/OuterRendering.kt b/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/OuterRendering.kt index ab12418734..60ef5489fe 100644 --- a/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/OuterRendering.kt +++ b/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/OuterRendering.kt @@ -1,5 +1,7 @@ package com.squareup.sample.stubvisibility +import android.view.LayoutInflater +import android.view.ViewGroup import com.squareup.sample.stubvisibility.databinding.StubVisibilityLayoutBinding import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.ScreenViewFactory @@ -12,8 +14,16 @@ data class OuterRendering( val bottom: ClickyTextRendering ) : AndroidScreen { override val viewFactory: ScreenViewFactory = - ScreenViewUpdater.bind(StubVisibilityLayoutBinding::inflate) { rendering, env -> - shouldBeFilledStub.show(rendering.top, env) - shouldBeWrappedStub.show(rendering.bottom, env) + ScreenViewFactory.ofViewBinding({ inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean -> + StubVisibilityLayoutBinding.inflate( + inflater, + parent, + attachToParent + ) + }) { binding -> + ScreenViewUpdater { rendering, viewEnvironment -> + binding.shouldBeFilledStub.show(rendering.top, viewEnvironment) + binding.shouldBeWrappedStub.show(rendering.bottom, viewEnvironment) + } } } diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/AuthorizingViewFactory.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/AuthorizingViewFactory.kt index 4094c47823..5a1e8afa21 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/AuthorizingViewFactory.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/AuthorizingViewFactory.kt @@ -1,5 +1,7 @@ package com.squareup.sample.authworkflow +import android.view.LayoutInflater +import android.view.ViewGroup import com.squareup.sample.tictactoe.databinding.AuthorizingLayoutBinding import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ScreenViewUpdater @@ -7,6 +9,14 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi @OptIn(WorkflowUiExperimentalApi::class) internal val AuthorizingViewFactory: ScreenViewFactory = - ScreenViewUpdater.bind(AuthorizingLayoutBinding::inflate) { rendering, _ -> - authorizingMessage.text = rendering.message + ScreenViewFactory.ofViewBinding({ inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean -> + AuthorizingLayoutBinding.inflate( + inflater, + parent, + attachToParent + ) + }) { binding -> + ScreenViewUpdater { rendering, viewEnvironment -> + binding.authorizingMessage.text = rendering.message + } } diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/LoginViewFactory.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/LoginViewFactory.kt index f236542940..5c835f7311 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/LoginViewFactory.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/LoginViewFactory.kt @@ -1,19 +1,26 @@ package com.squareup.sample.authworkflow +import android.view.LayoutInflater +import android.view.ViewGroup import com.squareup.sample.tictactoe.databinding.LoginLayoutBinding import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.backPressedHandler @OptIn(WorkflowUiExperimentalApi::class) internal val LoginViewFactory: ScreenViewFactory = - ScreenViewUpdater.bind(LoginLayoutBinding::inflate) { rendering, _ -> - loginErrorMessage.text = rendering.errorMessage - - loginButton.setOnClickListener { - rendering.onLogin(loginEmail.text.toString(), loginPassword.text.toString()) + ScreenViewFactory.ofViewBinding({ inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean -> + LoginLayoutBinding.inflate( + inflater, + parent, + attachToParent + ) + }) { binding -> + ScreenViewUpdater { rendering, viewEnvironment -> + binding.loginErrorMessage.text = rendering.errorMessage + binding.loginButton.setOnClickListener { + rendering.onLogin(binding.loginEmail.text.toString(), binding.loginPassword.text.toString()) + } + binding.root.backPressedHandler = { rendering.onCancel() } } - - root.backPressedHandler = { rendering.onCancel() } } diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/SecondFactorViewFactory.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/SecondFactorViewFactory.kt index e513065f41..1a0e974535 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/SecondFactorViewFactory.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/authworkflow/SecondFactorViewFactory.kt @@ -1,20 +1,27 @@ package com.squareup.sample.authworkflow +import android.view.LayoutInflater +import android.view.ViewGroup import com.squareup.sample.tictactoe.databinding.SecondFactorLayoutBinding import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.backPressedHandler @OptIn(WorkflowUiExperimentalApi::class) internal val SecondFactorViewFactory: ScreenViewFactory = - ScreenViewUpdater.bind(SecondFactorLayoutBinding::inflate) { rendering, _ -> - root.backPressedHandler = { rendering.onCancel() } - secondFactorToolbar.setNavigationOnClickListener { rendering.onCancel() } - - secondFactorErrorMessage.text = rendering.errorMessage - - secondFactorSubmitButton.setOnClickListener { - rendering.onSubmit(secondFactor.text.toString()) + ScreenViewFactory.ofViewBinding({ inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean -> + SecondFactorLayoutBinding.inflate( + inflater, + parent, + attachToParent + ) + }) { binding -> + ScreenViewUpdater { rendering, viewEnvironment -> + binding.root.backPressedHandler = { rendering.onCancel() } + binding.secondFactorToolbar.setNavigationOnClickListener { rendering.onCancel() } + binding.secondFactorErrorMessage.text = rendering.errorMessage + binding.secondFactorSubmitButton.setOnClickListener { + rendering.onSubmit(binding.secondFactor.text.toString()) + } } } diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GameOverLayoutUpdater.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GameOverLayoutUpdater.kt index d9fe7ec206..20d9ed6bd2 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GameOverLayoutUpdater.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GameOverLayoutUpdater.kt @@ -1,6 +1,8 @@ package com.squareup.sample.gameworkflow +import android.view.LayoutInflater import android.view.MenuItem +import android.view.ViewGroup import androidx.appcompat.widget.Toolbar import com.squareup.sample.gameworkflow.Ending.Draw import com.squareup.sample.gameworkflow.Ending.Quitted @@ -12,7 +14,6 @@ import com.squareup.sample.tictactoe.databinding.BoardBinding import com.squareup.sample.tictactoe.databinding.GamePlayLayoutBinding import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ScreenViewUpdater -import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler @@ -102,7 +103,17 @@ internal class GameOverLayoutUpdater( } /** Note how easily we're sharing this layout with [GamePlayViewFactory]. */ - companion object : ScreenViewFactory by bind( - GamePlayLayoutBinding::inflate, ::GameOverLayoutUpdater - ) + companion object : ScreenViewFactory by ScreenViewFactory.ofViewBinding( + { inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean -> + GamePlayLayoutBinding.inflate( + inflater, + parent, + attachToParent + ) + }) { binding -> + ScreenViewUpdater + { rendering, viewEnvironment -> + GameOverLayoutUpdater(it) + } + } } diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GamePlayViewFactory.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GamePlayViewFactory.kt index 025588fc37..1979870e9b 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GamePlayViewFactory.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/GamePlayViewFactory.kt @@ -1,21 +1,28 @@ package com.squareup.sample.gameworkflow +import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import com.squareup.sample.tictactoe.databinding.GamePlayLayoutBinding import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.backPressedHandler @OptIn(WorkflowUiExperimentalApi::class) internal val GamePlayViewFactory: ScreenViewFactory = - ScreenViewUpdater.bind(GamePlayLayoutBinding::inflate) { rendering, _ -> - renderBanner(rendering.gameState, rendering.playerInfo) - rendering.gameState.board.render(gamePlayBoard.root) - - setCellClickListeners(gamePlayBoard.root, rendering.gameState, rendering.onClick) - root.backPressedHandler = rendering.onQuit + ScreenViewFactory.ofViewBinding({ inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean -> + GamePlayLayoutBinding.inflate( + inflater, + parent, + attachToParent + ) + }) { binding -> + ScreenViewUpdater { rendering, viewEnvironment -> + binding.renderBanner(rendering.gameState, rendering.playerInfo) + rendering.gameState.board.render(binding.gamePlayBoard.root) + setCellClickListeners(binding.gamePlayBoard.root, rendering.gameState, rendering.onClick) + binding.root.backPressedHandler = rendering.onQuit + } } private fun setCellClickListeners( diff --git a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/NewGameViewFactory.kt b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/NewGameViewFactory.kt index 8ab5a1cc97..80e9b798fc 100644 --- a/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/NewGameViewFactory.kt +++ b/samples/tictactoe/app/src/main/java/com/squareup/sample/gameworkflow/NewGameViewFactory.kt @@ -1,20 +1,27 @@ package com.squareup.sample.gameworkflow +import android.view.LayoutInflater +import android.view.ViewGroup import com.squareup.sample.tictactoe.databinding.NewGameLayoutBinding import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.backPressedHandler @OptIn(WorkflowUiExperimentalApi::class) internal val NewGameViewFactory: ScreenViewFactory = - ScreenViewUpdater.bind(NewGameLayoutBinding::inflate) { rendering, _ -> - if (playerX.text.isBlank()) playerX.setText(rendering.defaultNameX) - if (playerO.text.isBlank()) playerO.setText(rendering.defaultNameO) - - startGame.setOnClickListener { - rendering.onStartGame(playerX.text.toString(), playerO.text.toString()) + ScreenViewFactory.ofViewBinding({ inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean -> + NewGameLayoutBinding.inflate( + inflater, + parent, + attachToParent + ) + }) { binding -> + ScreenViewUpdater { rendering, viewEnvironment -> + if (binding.playerX.text.isBlank()) binding.playerX.setText(rendering.defaultNameX) + if (binding.playerO.text.isBlank()) binding.playerO.setText(rendering.defaultNameO) + binding.startGame.setOnClickListener { + rendering.onStartGame(binding.playerX.text.toString(), binding.playerO.text.toString()) + } + binding.root.backPressedHandler = { rendering.onCancel() } } - - root.backPressedHandler = { rendering.onCancel() } } diff --git a/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoEditorScreen.kt b/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoEditorScreen.kt index bd754bec29..0d6d759c66 100644 --- a/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoEditorScreen.kt +++ b/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoEditorScreen.kt @@ -2,13 +2,14 @@ package com.squareup.sample.todo import android.content.Context.INPUT_METHOD_SERVICE +import android.view.LayoutInflater import android.view.View +import android.view.ViewGroup import android.view.inputmethod.InputMethodManager import com.squareup.sample.todo.databinding.TodoEditorLayoutBinding import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.Compatible import com.squareup.workflow1.ui.ScreenViewUpdater -import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.backPressedHandler @@ -25,7 +26,13 @@ data class TodoEditorScreen( ) : AndroidScreen, Compatible { override val compatibilityKey = Compatible.keyFor(this, "${session.id}") - override val viewFactory = bind(TodoEditorLayoutBinding::inflate, ::Updater) + override val viewFactory = ofViewBinding({ inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean -> + TodoEditorLayoutBinding.inflate( + inflater, + parent, + attachToParent + ) + }) { Updater(it) } } @OptIn(WorkflowUiExperimentalApi::class) diff --git a/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsScreen.kt b/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsScreen.kt index 5a2a8be7df..1aad4ebc8f 100644 --- a/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsScreen.kt +++ b/samples/todo-android/app/src/main/java/com/squareup/sample/todo/TodoListsScreen.kt @@ -1,13 +1,14 @@ package com.squareup.sample.todo import android.view.LayoutInflater +import android.view.ViewGroup import android.widget.TextView import com.squareup.sample.container.overviewdetail.OverviewDetailConfig import com.squareup.sample.container.overviewdetail.OverviewDetailConfig.Overview import com.squareup.sample.todo.databinding.TodoListsLayoutBinding import com.squareup.workflow1.ui.AndroidScreen import com.squareup.workflow1.ui.ScreenViewFactory -import com.squareup.workflow1.ui.ScreenViewUpdater.Companion.bind +import com.squareup.workflow1.ui.ScreenViewUpdater import com.squareup.workflow1.ui.WorkflowUiExperimentalApi /** @@ -26,17 +27,25 @@ data class TodoListsScreen( val selection: Int = -1 ) : AndroidScreen { override val viewFactory: ScreenViewFactory = - bind(TodoListsLayoutBinding::inflate) { rendering, viewEnvironment -> - for ((index, list) in rendering.lists.withIndex()) { - addRow( - index, - list, - selectable = viewEnvironment[OverviewDetailConfig] == Overview, - selected = index == rendering.selection && - viewEnvironment[OverviewDetailConfig] == Overview - ) { rendering.onRowClicked(index) } + ScreenViewFactory.ofViewBinding({ inflater: LayoutInflater, parent: ViewGroup?, attachToParent: Boolean -> + TodoListsLayoutBinding.inflate( + inflater, + parent, + attachToParent + ) + }) { binding -> + ScreenViewUpdater { rendering, viewEnvironment -> + for ((index, list) in rendering.lists.withIndex()) { + binding.addRow( + index, + list, + selectable = viewEnvironment[OverviewDetailConfig] == Overview, + selected = index == rendering.selection && + viewEnvironment[OverviewDetailConfig] == Overview + ) { rendering.onRowClicked(index) } + } + binding.pruneDeadRowsFrom(rendering.lists.size) } - pruneDeadRowsFrom(rendering.lists.size) } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidScreen.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidScreen.kt index ad6a1c4f44..7b44ef48db 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidScreen.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidScreen.kt @@ -5,7 +5,7 @@ package com.squareup.workflow1.ui * via an appropriate [ScreenViewFactory] implementation. * * You will rarely, if ever, write a [ScreenViewFactory] yourself. Instead - * use [ScreenViewUpdater.bind] to work with XML layout resources, or + * use [ScreenViewUpdater.ofViewBinding] to work with XML layout resources, or * [BuilderViewFactory] to create views from code. See [ScreenViewUpdater] for more * details. * diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt index 962688dbc0..70d7c78dff 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt @@ -7,7 +7,7 @@ internal class BaseScreenViewHolder( private val initialRendering: ScreenT, private val initialViewEnvironment: ViewEnvironment, override val view: View, - private val runner: ScreenViewRunner + private val updater: ScreenViewUpdater ) : ScreenViewHolder { lateinit var currentRendering: ScreenT lateinit var currentEnvironment: ViewEnvironment @@ -24,6 +24,6 @@ internal class BaseScreenViewHolder( override fun showScreen(screen: ScreenT, environment: ViewEnvironment) { currentRendering = screen currentEnvironment = environment - runner.showRendering(screen, environment) + updater.showRendering(screen, environment) } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeScreenViewFactory.kt deleted file mode 100644 index fe11de5c04..0000000000 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeScreenViewFactory.kt +++ /dev/null @@ -1,69 +0,0 @@ -package com.squareup.workflow1.ui - -import android.content.Context -import android.view.View -import android.view.ViewGroup -import kotlin.reflect.KClass - -@WorkflowUiExperimentalApi -public class DecorativeScreenViewFactory( - override val type: KClass, - private val map: (OuterT, ViewEnvironment) -> Pair, - private val viewStarter: ViewStarter? = null, - private val doShowRendering: ( - view: View, - innerShowRendering: ViewShowRendering, - outerRendering: OuterT, - env: ViewEnvironment - ) -> Unit = { _, innerShowRendering, outerRendering, viewEnvironment -> - val (innerRendering, processedEnv) = map(outerRendering, viewEnvironment) - innerShowRendering(innerRendering, processedEnv) - } -) : ScreenViewFactory { - - /** - * Convenience constructor for cases requiring no changes to the [ViewEnvironment]. - */ - public constructor( - type: KClass, - map: (OuterT) -> InnerT, - viewStarter: ViewStarter? = null, - doShowRendering: ( - view: View, - innerShowRendering: ViewShowRendering, - outerRendering: OuterT, - env: ViewEnvironment - ) -> Unit = { _, innerShowRendering, outerRendering, viewEnvironment -> - innerShowRendering(map(outerRendering), viewEnvironment) - } - ) : this( - type, - map = { outer, viewEnvironment -> Pair(map(outer), viewEnvironment) }, - viewStarter = viewStarter, - doShowRendering = doShowRendering - ) - - override fun buildView( - initialRendering: OuterT, - initialViewEnvironment: ViewEnvironment, - contextForNewView: Context, - container: ViewGroup? - ): View { - val (innerInitialRendering, processedInitialEnv) = map(initialRendering, initialViewEnvironment) - - return innerInitialRendering.buildView( - processedInitialEnv, - contextForNewView, - container, - viewStarter - ) - .also { view -> - val innerShowRendering: ViewShowRendering = view.getShowRendering()!! - - view.bindShowRendering( - initialRendering, - processedInitialEnv - ) { rendering, env -> doShowRendering(view, innerShowRendering, rendering, env) } - } - } -} diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt index eeb8818f2b..077986ea11 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt @@ -6,11 +6,6 @@ import android.view.ViewGroup import androidx.annotation.LayoutRes import kotlin.reflect.KClass -/** - * A [ScreenViewFactory] that ties a [layout resource][layoutId] to a - * [ViewRunner factory][updaterConstructor] function. See [ScreenViewUpdater] for - * details. - */ @WorkflowUiExperimentalApi @PublishedApi internal class LayoutScreenViewFactory( @@ -31,7 +26,7 @@ internal class LayoutScreenViewFactory( initialRendering = initialRendering, initialViewEnvironment = initialViewEnvironment, view = view, - runner = updaterConstructor(view) + updater = updaterConstructor(view) ) } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt index 3ad39f6cd8..010ed73642 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt @@ -5,39 +5,22 @@ import android.view.View import android.view.ViewGroup import kotlin.reflect.KClass -/** - * A [ScreenViewFactory] that creates [View]s that need to be generated from code. - * (Use [ScreenViewUpdater] to work with XML layout resources.) - * - * data class MyScreen(): AndroidScreen { - * val viewFactory = ManualScreenViewFactory( - * type = MyScreen::class, - * viewConstructor = { initialRendering, _, context, _ -> - * MyFrame(context).apply { - * layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) - * bindShowRendering(initialRendering, ::update) - * } - * ) - * } - * - * private class MyFrame(context: Context) : FrameLayout(context, attributeSet) { - * private fun update(rendering: MyScreen) { ... } - * } - */ @WorkflowUiExperimentalApi -public class ManualScreenViewFactory( - override val type: KClass, +@PublishedApi +internal class ManualScreenViewFactory( + override val type: KClass, private val viewConstructor: ( - initialRendering: RenderingT, + initialRendering: ScreenT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? - ) -> View -) : ScreenViewFactory { + ) -> ScreenViewHolder +) : ScreenViewFactory { override fun buildView( - initialRendering: RenderingT, + initialRendering: ScreenT, initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? - ): View = viewConstructor(initialRendering, initialViewEnvironment, contextForNewView, container) + ): ScreenViewHolder = + viewConstructor(initialRendering, initialViewEnvironment, contextForNewView, container) } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt index 9b49f8bdde..15c399e148 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt @@ -3,6 +3,8 @@ package com.squareup.workflow1.ui import android.content.Context import android.view.View import android.view.ViewGroup +import androidx.annotation.LayoutRes +import androidx.viewbinding.ViewBinding import com.squareup.workflow1.ui.container.BackStackScreen import com.squareup.workflow1.ui.container.BackStackScreenViewFactory import com.squareup.workflow1.ui.container.BodyAndModalsContainer @@ -11,24 +13,24 @@ import com.squareup.workflow1.ui.container.EnvironmentScreen import com.squareup.workflow1.ui.container.EnvironmentScreenViewFactory /** - * Factory for [View] instances that can show renderings of type [ScreenT] : [Screen]. - * - * Two concrete [ScreenViewFactory] implementations are provided: - * - * - The various [bind][ScreenViewUpdater.bind] methods on [ScreenViewUpdater] allow easy use of - * Android XML layout resources and [AndroidX ViewBinding][androidx.viewbinding.ViewBinding]. - * - * - [ManualScreenViewFactory] allows views to be built from code. + * Factory for Android [View] instances that can show renderings of type [ScreenT] : [Screen]. * * It's simplest to have your rendering classes implement [AndroidScreen] to associate * them with appropriate an appropriate [ScreenViewFactory]. For more flexibility, and to * avoid coupling your workflow directly to the Android runtime, see [ViewRegistry]. + * + * - Use [ScreenViewFactory.ofViewBinding] to create a factory for an + * [AndroidX ViewBinding][ViewBinding] + * - Use [ScreenViewFactory.ofLayout] or [ScreenViewFactory.ofStaticLayout] to create + * a factory for an XML layout resource + * - Use [ScreenViewFactory.of] to create a factory entirely at runtime. */ @WorkflowUiExperimentalApi public interface ScreenViewFactory : ViewRegistry.Entry { /** - * Returns a View ready to display [initialRendering] (and any succeeding values) - * via [View.showRendering]. + * Returns a [ScreenViewHolder] ready to display [initialRendering] (and any succeeding values). + * Callers of this method must call [ScreenViewHolder.start] exactly once before + * calling [ScreenViewHolder.showScreen]. */ public fun buildView( initialRendering: ScreenT, @@ -36,11 +38,94 @@ public interface ScreenViewFactory : ViewRegistry.Entry + + public companion object { + /** + * Creates a [ScreenViewFactory] that [inflates][bindingInflater] a [ViewBinding] ([BindingT]) + * to show renderings of type [ScreenT] : [Screen], using [a lambda][showRendering]. + * + * val HelloViewFactory: ScreenViewFactory = + * ScreenViewUpdater.bind(HelloGoodbyeViewBinding::inflate) { rendering, viewEnvironment -> + * helloMessage.text = rendering.message + * helloMessage.setOnClickListener { rendering.onClick(Unit) } + * } + * + * If you need to initialize your view before [showRendering] is called, + * implement [ScreenViewUpdater] and create a binding using the `bind` variant + * that accepts a `(ViewBinding) -> ScreenViewUpdater` function, below. + */ + public inline fun ofViewBinding( + noinline bindingInflater: ViewBindingInflater, + crossinline showRendering: BindingT.(ScreenT, ViewEnvironment) -> Unit + ): ScreenViewFactory = ofViewBinding(bindingInflater) { binding -> + ScreenViewUpdater { rendering, viewEnvironment -> + binding.showRendering(rendering, viewEnvironment) + } + } + + /** + * Creates a [ScreenViewFactory] that [inflates][bindingInflater] a [ViewBinding] ([BindingT]) + * to show renderings of type [ScreenT] : [Screen], using a [ScreenViewUpdater] + * created by [constructor]. Handy if you need to perform some set up before + * [showRendering] is called. + * + * class HelloScreenRunner( + * private val binding: HelloGoodbyeViewBinding + * ) : ScreenViewUpdater { + * + * override fun showRendering(rendering: HelloScreen) { + * binding.messageView.text = rendering.message + * binding.messageView.setOnClickListener { rendering.onClick(Unit) } + * } + * + * companion object : ScreenViewFactory by bind( + * HelloGoodbyeViewBinding::inflate, ::HelloScreenRunner + * ) + * } + * + * If the view doesn't need to be initialized before [showRendering] is called, + * use the variant above which just takes a lambda. + */ + public inline fun ofViewBinding( + noinline bindingInflater: ViewBindingInflater, + noinline constructor: (BindingT) -> ScreenViewUpdater + ): ScreenViewFactory = + ViewBindingScreenViewFactory(ScreenT::class, bindingInflater, constructor) + + /** + * Creates a [ScreenViewFactory] that inflates [layoutId] to show renderings of + * type [ScreenT] : [Screen], using a [ScreenViewUpdater] created by [constructor]. + * Avoids any use of [AndroidX ViewBinding][ViewBinding]. + */ + public inline fun ofLayout( + @LayoutRes layoutId: Int, + noinline constructor: (View) -> ScreenViewUpdater + ): ScreenViewFactory = + LayoutScreenViewFactory(ScreenT::class, layoutId, constructor) + + /** + * Creates a [ScreenViewFactory] that inflates [layoutId] to "show" renderings of type [ScreenT], + * with a no-op [ScreenViewUpdater]. Handy for showing static views, e.g. when prototyping. + */ + @Suppress("unused") + public inline fun ofStaticLayout( + @LayoutRes layoutId: Int + ): ScreenViewFactory = ofLayout(layoutId) { ScreenViewUpdater { _, _ -> } } + + /** Creates a [ScreenViewFactory] entirely from code. */ + public inline fun of( + noinline viewConstructor: ( + initialRendering: ScreenT, + initialViewEnvironment: ViewEnvironment, + contextForNewView: Context, + container: ViewGroup? + ) -> ScreenViewHolder + ): ScreenViewFactory = ManualScreenViewFactory(ScreenT::class, viewConstructor) + } } /** - * It is usually more convenient to use [WorkflowViewStub] or [DecorativeScreenViewFactory] - * than to call this method directly. + * It is usually more convenient to use [WorkflowViewStub] than to call this method directly. * * Finds a [ScreenViewFactory] to create a [View] to display the receiving [Screen]. * The caller is responsible for calling [View.start] on the new [View]. After that, diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt index a2c8f30154..1f36aab17f 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt @@ -36,6 +36,18 @@ public interface ScreenViewHolder { } } +/** Wraps [view] in a [ScreenViewHolder], mainly for use from [ManualScreenViewFactory]. */ +@Suppress("FunctionName") +@WorkflowUiExperimentalApi +public fun ScreenViewHolder( + initialRendering: ScreenT, + initialViewEnvironment: ViewEnvironment, + view: View, + updater: ScreenViewUpdater +): ScreenViewHolder { + return BaseScreenViewHolder(initialRendering, initialViewEnvironment, view, updater) +} + @WorkflowUiExperimentalApi public fun ScreenViewHolder<*>.canShowScreen(screen: Screen): Boolean { return compatible(this.screen, screen) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewUpdater.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewUpdater.kt index 54a124c931..c8d96869cf 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewUpdater.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewUpdater.kt @@ -2,100 +2,19 @@ package com.squareup.workflow1.ui import android.content.Context import android.view.LayoutInflater -import android.view.View import android.view.ViewGroup -import androidx.annotation.LayoutRes import androidx.viewbinding.ViewBinding @WorkflowUiExperimentalApi public typealias ViewBindingInflater = (LayoutInflater, ViewGroup?, Boolean) -> BindingT -/** - * Function that updates the UI built by a [ScreenViewFactory]. - * - * If you're using [AndroidX ViewBinding][ViewBinding] you likely won't need to - * implement this interface at all. For details, see the three overloads of [ScreenViewUpdater.bind]. - */ +/** Function that updates the UI built by a [ScreenViewFactory]. */ @WorkflowUiExperimentalApi -public fun interface ScreenViewUpdater { +public fun interface ScreenViewUpdater { public fun showRendering( - rendering: RenderingT, + rendering: ScreenT, viewEnvironment: ViewEnvironment ) - - public companion object { - /** - * Creates a [ScreenViewFactory] that [inflates][bindingInflater] a [ViewBinding] ([BindingT]) - * to show renderings of type [RenderingT] : [Screen], using [a lambda][showRendering]. - * - * val HelloViewFactory: ScreenViewFactory = - * ScreenViewUpdater.bind(HelloGoodbyeViewBinding::inflate) { rendering, viewEnvironment -> - * helloMessage.text = rendering.message - * helloMessage.setOnClickListener { rendering.onClick(Unit) } - * } - * - * If you need to initialize your view before [showRendering] is called, - * implement [ScreenViewUpdater] and create a binding using the `bind` variant - * that accepts a `(ViewBinding) -> ScreenViewUpdater` function, below. - */ - public inline fun bind( - noinline bindingInflater: ViewBindingInflater, - crossinline showRendering: BindingT.(RenderingT, ViewEnvironment) -> Unit - ): ScreenViewFactory = bind(bindingInflater) { binding -> - ScreenViewUpdater { rendering, viewEnvironment -> - binding.showRendering(rendering, viewEnvironment) - } - } - - /** - * Creates a [ScreenViewFactory] that [inflates][bindingInflater] a [ViewBinding] ([BindingT]) - * to show renderings of type [RenderingT] : [Screen], using a [ScreenViewUpdater] - * created by [constructor]. Handy if you need to perform some set up before - * [showRendering] is called. - * - * class HelloScreenRunner( - * private val binding: HelloGoodbyeViewBinding - * ) : ScreenViewUpdater { - * - * override fun showRendering(rendering: HelloScreen) { - * binding.messageView.text = rendering.message - * binding.messageView.setOnClickListener { rendering.onClick(Unit) } - * } - * - * companion object : ScreenViewFactory by bind( - * HelloGoodbyeViewBinding::inflate, ::HelloScreenRunner - * ) - * } - * - * If the view doesn't need to be initialized before [showRendering] is called, - * use the variant above which just takes a lambda. - */ - public inline fun bind( - noinline bindingInflater: ViewBindingInflater, - noinline constructor: (BindingT) -> ScreenViewUpdater - ): ScreenViewFactory = - ViewBindingScreenViewFactory(RenderingT::class, bindingInflater, constructor) - - /** - * Creates a [ScreenViewFactory] that inflates [layoutId] to show renderings of - * type [RenderingT] : [Screen], using a [ScreenViewUpdater] created by [constructor]. - * Avoids any use of [AndroidX ViewBinding][ViewBinding]. - */ - public inline fun bind( - @LayoutRes layoutId: Int, - noinline constructor: (View) -> ScreenViewUpdater - ): ScreenViewFactory = - LayoutScreenViewFactory(RenderingT::class, layoutId, constructor) - - /** - * Creates a [ScreenViewFactory] that inflates [layoutId] to "show" renderings of type [RenderingT], - * with a no-op [ScreenViewUpdater]. Handy for showing static views, e.g. when prototyping. - */ - @Suppress("unused") - public inline fun bindNoRunner( - @LayoutRes layoutId: Int - ): ScreenViewFactory = bind(layoutId) { ScreenViewUpdater { _, _ -> } } - } } internal fun Context.viewBindingLayoutInflater(container: ViewGroup?) = diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt index 8974af0d5d..69d4cbf5b7 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt @@ -25,7 +25,7 @@ internal class ViewBindingScreenViewFactory Date: Fri, 7 Jan 2022 11:38:35 -0800 Subject: [PATCH 07/12] wip: moved ViewStarter back to a more proper home --- .../workflow1/ui/AndroidViewRegistry.kt | 13 +++++++++++- .../workflow1/ui/ScreenViewFactory.kt | 20 ------------------- 2 files changed, 12 insertions(+), 21 deletions(-) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidViewRegistry.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidViewRegistry.kt index 52b765db0a..34d2544c3b 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidViewRegistry.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AndroidViewRegistry.kt @@ -26,8 +26,19 @@ public fun ) } -@Deprecated("Use getEntryFor()") @WorkflowUiExperimentalApi +@Deprecated("Use ScreenViewHolder.Starter") +public fun interface ViewStarter { + /** Called from [View.start]. [doStart] must be invoked. */ + public fun startView( + view: View, + doStart: () -> Unit + ) +} + +@Suppress("DeprecatedCallableAddReplaceWith") +@WorkflowUiExperimentalApi +@Deprecated("Use getEntryFor()") public fun ViewRegistry.getFactoryFor( renderingType: KClass ): ViewFactory? { diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt index 15c399e148..ce1982c5fd 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt @@ -148,26 +148,6 @@ public fun ScreenT.buildView( ) } -/** - * A wrapper for the function invoked when [View.start] is called, allowing for - * last second initialization of a newly built [View]. Provided via [Screen.buildView] - * or [DecorativeScreenViewFactory.viewStarter]. - * - * While [View.getRendering] may be called from [startView], it is not safe to - * assume that the type of the rendering retrieved matches the type the view was - * originally built to display. [ScreenViewFactory] instances can be wrapped, and - * renderings can be mapped to other types. - */ -@WorkflowUiExperimentalApi -@Deprecated("Use WorkflowView.Starter") -public fun interface ViewStarter { - /** Called from [View.start]. [doStart] must be invoked. */ - public fun startView( - view: View, - doStart: () -> Unit - ) -} - @WorkflowUiExperimentalApi internal fun ViewEnvironment.getViewFactoryForRendering(rendering: ScreenT): ScreenViewFactory { From f6ce9c253b795b6db99cd0fcdb1f94cba0d2efa8 Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Fri, 7 Jan 2022 12:41:47 -0800 Subject: [PATCH 08/12] wip: Manual thing is private --- .../sample/container/ScrimContainer.kt | 38 +++--- .../com/squareup/sample/dungeon/BoardView.kt | 14 ++- .../stubvisibility/ClickyTextRendering.kt | 27 ++--- .../ModalViewContainerLifecycleActivity.kt | 36 +++--- .../workflow1/ui/modal/AlertContainer.kt | 14 +-- .../ui/WorkflowViewStubLifecycleActivity.kt | 11 +- .../ui/WorkflowViewStubLifecycleTest.kt | 108 +++++++++--------- .../BackStackContainerLifecycleActivity.kt | 27 +++-- .../NoTransitionBackStackContainer.kt | 16 ++- .../workflow1/ui/AsScreenViewFactory.kt | 10 +- .../workflow1/ui/BuilderViewFactory.kt | 2 +- .../workflow1/ui/DecorativeViewFactory.kt | 2 +- .../squareup/workflow1/ui/ScreenViewHolder.kt | 8 +- .../workflow1/ui/ViewShowRendering.kt | 2 + .../container/BackStackScreenViewFactory.kt | 14 +-- .../ui/container/BodyAndModalsContainer.kt | 14 +-- .../test/AbstractLifecycleTestActivity.kt | 27 ++--- 17 files changed, 184 insertions(+), 186 deletions(-) diff --git a/samples/containers/android/src/main/java/com/squareup/sample/container/ScrimContainer.kt b/samples/containers/android/src/main/java/com/squareup/sample/container/ScrimContainer.kt index e30b9f9837..ce7a49d9de 100644 --- a/samples/containers/android/src/main/java/com/squareup/sample/container/ScrimContainer.kt +++ b/samples/containers/android/src/main/java/com/squareup/sample/container/ScrimContainer.kt @@ -7,11 +7,10 @@ import android.view.View import android.view.ViewGroup import androidx.core.content.ContextCompat import com.squareup.sample.container.panel.ScrimScreen -import com.squareup.workflow1.ui.ManualScreenViewFactory import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub -import com.squareup.workflow1.ui.bindShowRendering /** * A view that renders only its first child, behind a smoke scrim if @@ -33,7 +32,7 @@ internal class ScrimContainer @JvmOverloads constructor( private val child: View get() = getChildAt(0) - ?: error("Child must be set immediately upon creation.") + ?: error("Child must be set immediately upon creation.") var isDimmed: Boolean = false set(value) { @@ -84,30 +83,29 @@ internal class ScrimContainer @JvmOverloads constructor( ValueAnimator.ofFloat(1f, 0f) }.apply { duration = resources.getInteger(android.R.integer.config_shortAnimTime) - .toLong() + .toLong() addUpdateListener { animation -> scrim.alpha = animation.animatedValue as Float } start() } } @OptIn(WorkflowUiExperimentalApi::class) - companion object : ScreenViewFactory> by ManualScreenViewFactory( - type = ScrimScreen::class, - viewConstructor = { initialRendering, initialViewEnvironment, contextForNewView, _ -> - val stub = WorkflowViewStub(contextForNewView) + companion object : ScreenViewFactory> by ScreenViewFactory.of( + viewConstructor = { initialRendering, initialViewEnvironment, contextForNewView, _ -> + val stub = WorkflowViewStub(contextForNewView) - ScrimContainer(contextForNewView) - .also { view -> - view.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) - view.addView(stub) + ScrimContainer(contextForNewView) + .let { view -> + view.layoutParams = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.MATCH_PARENT) + view.addView(stub) - view.bindShowRendering( - initialRendering, initialViewEnvironment - ) { rendering, environment -> - stub.show(rendering.content, environment) - view.isDimmed = rendering.dimmed - } - } - } + ScreenViewHolder( + initialRendering, initialViewEnvironment, view + ) { rendering, environment -> + stub.show(rendering.content, environment) + view.isDimmed = rendering.dimmed + } + } + } ) } diff --git a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardView.kt b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardView.kt index bdb070c080..4bb4919ecb 100644 --- a/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardView.kt +++ b/samples/dungeon/app/src/main/java/com/squareup/sample/dungeon/BoardView.kt @@ -8,10 +8,9 @@ import android.graphics.Rect import android.view.View import androidx.core.content.ContextCompat import com.squareup.sample.dungeon.board.Board -import com.squareup.workflow1.ui.ManualScreenViewFactory import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.bindShowRendering import kotlin.math.abs import kotlin.math.min @@ -83,10 +82,15 @@ class BoardView(context: Context) : View(context) { } @OptIn(WorkflowUiExperimentalApi::class) - companion object : ScreenViewFactory by ManualScreenViewFactory( - type = Board::class, + companion object : ScreenViewFactory by ScreenViewFactory.of( viewConstructor = { initialRendering, initialEnv, contextForNewView, _ -> BoardView(contextForNewView) - .apply { bindShowRendering(initialRendering, initialEnv) { r, _ -> update(r) } } + .let { view -> + ScreenViewHolder( + initialRendering, + initialEnv, + view + ) { r, _ -> view.update(r) } + } }) } diff --git a/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/ClickyTextRendering.kt b/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/ClickyTextRendering.kt index 2a68a552a5..e18d07bb8d 100644 --- a/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/ClickyTextRendering.kt +++ b/samples/stub-visibility/src/main/java/com/squareup/sample/stubvisibility/ClickyTextRendering.kt @@ -9,9 +9,9 @@ import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.widget.TextView import com.squareup.workflow1.ui.AndroidScreen -import com.squareup.workflow1.ui.ManualScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.bindShowRendering @OptIn(WorkflowUiExperimentalApi::class) data class ClickyTextRendering( @@ -19,25 +19,26 @@ data class ClickyTextRendering( val visible: Boolean = true, val onClick: (() -> Unit)? = null ) : AndroidScreen { - override val viewFactory = ManualScreenViewFactory( - type = ClickyTextRendering::class, + override val viewFactory = ScreenViewFactory.of( viewConstructor = { initialRendering, initialEnv, context, _ -> - TextView(context).also { textView -> + TextView(context).let { textView -> textView.layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) textView.gravity = CENTER - - textView.bindShowRendering(initialRendering, initialEnv) { clickyText, _ -> - textView.text = clickyText.message - textView.isVisible = clickyText.visible - textView.setOnClickListener( - clickyText.onClick?.let { oc -> OnClickListener { oc() } } - ) - } + ScreenViewHolder(initialRendering, initialEnv, textView) { r, _ -> textView.update(r) } } } ) } +@OptIn(WorkflowUiExperimentalApi::class) +private fun TextView.update(clickyText: ClickyTextRendering) { + text = clickyText.message + isVisible = clickyText.visible + setOnClickListener( + clickyText.onClick?.let { oc -> OnClickListener { oc() } } + ) +} + private var View.isVisible: Boolean get() = visibility == VISIBLE set(value) { diff --git a/workflow-ui/container-android/src/androidTest/java/com/squareup/workflow1/ui/modal/test/ModalViewContainerLifecycleActivity.kt b/workflow-ui/container-android/src/androidTest/java/com/squareup/workflow1/ui/modal/test/ModalViewContainerLifecycleActivity.kt index 68b2541d6e..c603f5d294 100644 --- a/workflow-ui/container-android/src/androidTest/java/com/squareup/workflow1/ui/modal/test/ModalViewContainerLifecycleActivity.kt +++ b/workflow-ui/container-android/src/androidTest/java/com/squareup/workflow1/ui/modal/test/ModalViewContainerLifecycleActivity.kt @@ -6,16 +6,15 @@ import android.content.Context import android.view.View import android.view.ViewGroup import android.widget.FrameLayout -import com.squareup.workflow1.ui.asScreen import com.squareup.workflow1.ui.Compatible -import com.squareup.workflow1.ui.ManualScreenViewFactory import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.ViewRegistry import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub -import com.squareup.workflow1.ui.bindShowRendering +import com.squareup.workflow1.ui.asScreen import com.squareup.workflow1.ui.internal.test.AbstractLifecycleTestActivity import com.squareup.workflow1.ui.modal.HasModals import com.squareup.workflow1.ui.modal.ModalViewContainer @@ -33,9 +32,11 @@ internal class ModalViewContainerLifecycleActivity : AbstractLifecycleTestActivi initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? - ): View = View(contextForNewView).apply { - bindShowRendering(initialRendering, initialViewEnvironment) { _, _ -> /* Noop */ } - } + ) = ScreenViewHolder( + initialRendering, + initialViewEnvironment, + View(contextForNewView) + ) { _, _ -> /* Noop */ } } data class TestModals( @@ -55,19 +56,22 @@ internal class ModalViewContainerLifecycleActivity : AbstractLifecycleTestActivi override val viewRegistry: ViewRegistry = ViewRegistry( ModalViewContainer.binding(), BaseRendering, - leafViewBinding(LeafRendering::class, lifecycleLoggingViewObserver { it.name }), - ManualScreenViewFactory(RecurseRendering::class) { initialRendering, + leafViewBinding(lifecycleLoggingViewObserver { it.name }), + ScreenViewFactory.of { initialRendering, initialViewEnvironment, - contextForNewView, _ -> - FrameLayout(contextForNewView).also { container -> + contextForNewView, + _ -> + FrameLayout(contextForNewView).let { container -> val stub = WorkflowViewStub(contextForNewView) container.addView(stub) - container.bindShowRendering( - initialRendering, - initialViewEnvironment - ) { rendering, env -> - stub.show(asScreen(TestModals(listOf(rendering.wrapped))), env) - } + ScreenViewHolder( + initialRendering = initialRendering, + initialViewEnvironment = initialViewEnvironment, + view = container, + updater = { rendering, env -> + stub.show(asScreen(TestModals(listOf(rendering.wrapped))), env) + } + ) } }, ) diff --git a/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/AlertContainer.kt b/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/AlertContainer.kt index c534825294..9008621c6b 100644 --- a/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/AlertContainer.kt +++ b/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/AlertContainer.kt @@ -10,11 +10,10 @@ import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import androidx.annotation.StyleRes import androidx.appcompat.app.AlertDialog -import com.squareup.workflow1.ui.ManualScreenViewFactory import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.bindShowRendering import com.squareup.workflow1.ui.container.R import com.squareup.workflow1.ui.modal.AlertScreen.Button import com.squareup.workflow1.ui.modal.AlertScreen.Button.NEGATIVE @@ -83,14 +82,13 @@ public class AlertContainer @JvmOverloads constructor( private class AlertContainerViewFactory( @StyleRes private val dialogThemeResId: Int = 0 - ) : ScreenViewFactory> by ManualScreenViewFactory( - type = AlertContainerScreen::class, + ) : ScreenViewFactory> by ScreenViewFactory.of( viewConstructor = { initialRendering, initialEnv, context, _ -> AlertContainer(context, dialogThemeResId = dialogThemeResId) - .apply { - id = R.id.workflow_alert_container - layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) - bindShowRendering(initialRendering, initialEnv, ::update) + .let { view -> + view.id = R.id.workflow_alert_container + view.layoutParams = ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT) + ScreenViewHolder(initialRendering, initialEnv, view, view::update) } } ) diff --git a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/WorkflowViewStubLifecycleActivity.kt b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/WorkflowViewStubLifecycleActivity.kt index 9a1754bd2e..75d21e2689 100644 --- a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/WorkflowViewStubLifecycleActivity.kt +++ b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/WorkflowViewStubLifecycleActivity.kt @@ -19,16 +19,17 @@ internal class WorkflowViewStubLifecycleActivity : AbstractLifecycleTestActivity } override val viewRegistry: ViewRegistry = ViewRegistry( - leafViewBinding(LeafRendering::class, lifecycleLoggingViewObserver { it.name }), - ManualScreenViewFactory(RecurseRendering::class) { initialRendering, + leafViewBinding(lifecycleLoggingViewObserver { it.name }), + ScreenViewFactory.of { initialRendering, initialViewEnvironment, contextForNewView, _ -> - FrameLayout(contextForNewView).also { container -> + FrameLayout(contextForNewView).let { container -> val stub = WorkflowViewStub(contextForNewView) container.addView(stub) - container.bindShowRendering( + ScreenViewHolder( initialRendering, - initialViewEnvironment + initialViewEnvironment, + container ) { rendering, env -> stub.show(rendering.wrapped, env) } diff --git a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/WorkflowViewStubLifecycleTest.kt b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/WorkflowViewStubLifecycleTest.kt index e480065127..c9a650b991 100644 --- a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/WorkflowViewStubLifecycleTest.kt +++ b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/WorkflowViewStubLifecycleTest.kt @@ -255,20 +255,19 @@ internal class WorkflowViewStubLifecycleTest { } data class RegistrySetter(val wrapped: TestRendering) : ViewRendering() { - override val viewFactory: ScreenViewFactory = ManualScreenViewFactory( - RegistrySetter::class - ) { initialRendering, initialViewEnvironment, context, _ -> - val stub = WorkflowViewStub(context) - ViewTreeSavedStateRegistryOwner.set(stub, expectedRegistryOwner) + override val viewFactory = + ScreenViewFactory.of { initialRendering, initialViewEnvironment, context, _ -> + val stub = WorkflowViewStub(context) + ViewTreeSavedStateRegistryOwner.set(stub, expectedRegistryOwner) - FrameLayout(context).apply { - addView(stub) + FrameLayout(context).let { view -> + view.addView(stub) - bindShowRendering(initialRendering, initialViewEnvironment) { r, e -> - stub.show(r.wrapped, e) + ScreenViewHolder(initialRendering, initialViewEnvironment, view) { r, e -> + stub.show(r.wrapped, e) + } } } - } } var initialRegistryOwner: SavedStateRegistryOwner? = null @@ -298,61 +297,58 @@ internal class WorkflowViewStubLifecycleTest { const val Tag = "counter" } - override val viewFactory: ScreenViewFactory = ManualScreenViewFactory( - CounterRendering::class - ) { initialRendering, initialViewEnvironment, context, _ -> - var counter = 0 - Button(context).apply button@{ - tag = Tag + override val viewFactory = + ScreenViewFactory.of { initialRendering, initialViewEnvironment, context, _ -> + var counter = 0 + Button(context).apply button@{ + tag = Tag - fun updateText() { - text = "Counter: $counter" - } + fun updateText() { + text = "Counter: $counter" + } - addOnAttachStateChangeListener(object : OnAttachStateChangeListener { - lateinit var registryOwner: SavedStateRegistryOwner - lateinit var lifecycleObserver: LifecycleObserver - - override fun onViewAttachedToWindow(v: View) { - onViewAttached(this@button) - registryOwner = ViewTreeSavedStateRegistryOwner.get(this@button)!! - lifecycleObserver = object : LifecycleEventObserver { - override fun onStateChanged( - source: LifecycleOwner, - event: Event - ) { - if (event == ON_CREATE) { - source.lifecycle.removeObserver(this) - registryOwner.savedStateRegistry.consumeRestoredStateForKey("counter") - ?.let { restoredState -> - counter = restoredState.getInt("value") - updateText() - } + addOnAttachStateChangeListener(object : OnAttachStateChangeListener { + lateinit var registryOwner: SavedStateRegistryOwner + lateinit var lifecycleObserver: LifecycleObserver + + override fun onViewAttachedToWindow(v: View) { + onViewAttached(this@button) + registryOwner = ViewTreeSavedStateRegistryOwner.get(this@button)!! + lifecycleObserver = object : LifecycleEventObserver { + override fun onStateChanged( + source: LifecycleOwner, + event: Event + ) { + if (event == ON_CREATE) { + source.lifecycle.removeObserver(this) + registryOwner.savedStateRegistry.consumeRestoredStateForKey("counter") + ?.let { restoredState -> + counter = restoredState.getInt("value") + updateText() + } + } } } + registryOwner.lifecycle.addObserver(lifecycleObserver) + registryOwner.savedStateRegistry.registerSavedStateProvider("counter") { + Bundle().apply { putInt("value", counter) } + } } - registryOwner.lifecycle.addObserver(lifecycleObserver) - registryOwner.savedStateRegistry.registerSavedStateProvider("counter") { - Bundle().apply { putInt("value", counter) } - } - } - override fun onViewDetachedFromWindow(v: View) { - registryOwner.lifecycle.removeObserver(lifecycleObserver) - registryOwner.savedStateRegistry.unregisterSavedStateProvider("counter") - } - }) + override fun onViewDetachedFromWindow(v: View) { + registryOwner.lifecycle.removeObserver(lifecycleObserver) + registryOwner.savedStateRegistry.unregisterSavedStateProvider("counter") + } + }) - updateText() - setOnClickListener { - counter++ updateText() - } - - bindShowRendering(initialRendering, initialViewEnvironment) { _, _ -> - // Noop + setOnClickListener { + counter++ + updateText() + } + }.let { + ScreenViewHolder(initialRendering, initialViewEnvironment, it) { _, _ -> /* no-op*/ } } } - } } } diff --git a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/BackStackContainerLifecycleActivity.kt b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/BackStackContainerLifecycleActivity.kt index 524e138c87..d88cdef812 100644 --- a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/BackStackContainerLifecycleActivity.kt +++ b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/BackStackContainerLifecycleActivity.kt @@ -8,18 +8,17 @@ import androidx.test.core.app.ActivityScenario import androidx.test.espresso.assertion.ViewAssertions.matches import androidx.test.espresso.matcher.ViewMatchers.isCompletelyDisplayed import androidx.test.espresso.matcher.ViewMatchers.withTagValue -import com.squareup.workflow1.ui.ManualScreenViewFactory import com.squareup.workflow1.ui.Compatible import com.squareup.workflow1.ui.Screen -import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewHolder +import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.ViewRegistry import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub +import com.squareup.workflow1.ui.container.BackStackScreen import com.squareup.workflow1.ui.container.fixtures.BackStackContainerLifecycleActivity.TestRendering.LeafRendering import com.squareup.workflow1.ui.container.fixtures.BackStackContainerLifecycleActivity.TestRendering.RecurseRendering -import com.squareup.workflow1.ui.bindShowRendering -import com.squareup.workflow1.ui.container.BackStackScreen import com.squareup.workflow1.ui.internal.test.AbstractLifecycleTestActivity import com.squareup.workflow1.ui.internal.test.inAnyView import org.hamcrest.Matcher @@ -39,8 +38,11 @@ internal class BackStackContainerLifecycleActivity : AbstractLifecycleTestActivi initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? - ): View = View(contextForNewView).apply { - bindShowRendering(initialRendering, initialViewEnvironment) { _, _ -> /* Noop */ } + ) = ScreenViewHolder( + initialRendering, + initialViewEnvironment, + View(contextForNewView) + ) { _, _ -> /* Noop */ } } @@ -108,16 +110,17 @@ internal class BackStackContainerLifecycleActivity : AbstractLifecycleTestActivi override val viewRegistry: ViewRegistry = ViewRegistry( NoTransitionBackStackContainer, BaseRendering, - leafViewBinding(LeafRendering::class, viewObserver, viewConstructor = ::ViewStateTestView), - ManualScreenViewFactory(RecurseRendering::class) { initialRendering, + leafViewBinding(viewObserver, viewConstructor = ::ViewStateTestView), + ScreenViewFactory.of { initialRendering, initialViewEnvironment, contextForNewView, _ -> - FrameLayout(contextForNewView).also { container -> + FrameLayout(contextForNewView).let { container -> val stub = WorkflowViewStub(contextForNewView) container.addView(stub) - container.bindShowRendering( + ScreenViewHolder( initialRendering, - initialViewEnvironment + initialViewEnvironment, + container ) { rendering, env -> stub.show(rendering.wrappedBackstack.toBackstackWithBase(), env) } @@ -153,6 +156,6 @@ internal fun ActivityScenario.viewForScreen @OptIn(WorkflowUiExperimentalApi::class) internal fun waitForScreen(name: String) { - inAnyView(withTagValue(equalTo(name)) as Matcher) + inAnyView(withTagValue(equalTo(name)) as Matcher) .check(matches(isCompletelyDisplayed())) } diff --git a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/NoTransitionBackStackContainer.kt b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/NoTransitionBackStackContainer.kt index 026848fd26..4d6e364ea2 100644 --- a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/NoTransitionBackStackContainer.kt +++ b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/NoTransitionBackStackContainer.kt @@ -3,13 +3,12 @@ package com.squareup.workflow1.ui.container.fixtures import android.content.Context import android.view.View import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import com.squareup.workflow1.ui.ManualScreenViewFactory +import com.squareup.workflow1.ui.R import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.bindShowRendering import com.squareup.workflow1.ui.container.BackStackContainer import com.squareup.workflow1.ui.container.BackStackScreen -import com.squareup.workflow1.ui.R /** * A subclass of [BackStackContainer] that disables transitions to make it simpler to test the @@ -24,14 +23,13 @@ internal class NoTransitionBackStackContainer(context: Context) : BackStackConta } companion object : ScreenViewFactory> - by ManualScreenViewFactory( - type = BackStackScreen::class, + by ScreenViewFactory.of( viewConstructor = { initialRendering, initialEnv, context, _ -> NoTransitionBackStackContainer(context) - .apply { - id = R.id.workflow_back_stack_container - layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) - bindShowRendering(initialRendering, initialEnv, ::update) + .let { view -> + view.id = R.id.workflow_back_stack_container + view.layoutParams = LayoutParams(MATCH_PARENT, MATCH_PARENT) + ScreenViewHolder(initialRendering, initialEnv, view, view::update) } } ) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AsScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AsScreenViewFactory.kt index 3b3e40f8d1..36d08ca1ee 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AsScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/AsScreenViewFactory.kt @@ -3,8 +3,7 @@ package com.squareup.workflow1.ui @WorkflowUiExperimentalApi @Suppress("DEPRECATION") internal object AsScreenViewFactory : ScreenViewFactory> -by ManualScreenViewFactory( - type = AsScreen::class, +by ScreenViewFactory.of( viewConstructor = { initialRendering, initialViewEnvironment, context, container -> initialViewEnvironment[ViewRegistry] .buildView( @@ -12,12 +11,13 @@ by ManualScreenViewFactory( initialViewEnvironment, context, container - ).also { view -> + ).let { view -> val legacyShowRendering = view.getShowRendering()!! - view.bindShowRendering( + ScreenViewHolder( initialRendering, - initialViewEnvironment + initialViewEnvironment, + view ) { rendering, env -> legacyShowRendering(rendering.rendering, env) } } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BuilderViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BuilderViewFactory.kt index e18aa37185..74327aff5d 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BuilderViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BuilderViewFactory.kt @@ -6,7 +6,7 @@ import android.view.ViewGroup import kotlin.reflect.KClass @Suppress("DEPRECATION") -@Deprecated("Use ManualScreenViewFactory") +@Deprecated("Use ScreenViewFactory.of") @WorkflowUiExperimentalApi public class BuilderViewFactory( override val type: KClass, diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt index aee909a446..44dd2f44c9 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt @@ -109,7 +109,7 @@ import kotlin.reflect.KClass * uses [map] to extract the [InnerT] instance from [OuterT] and makes the function call. */ @Suppress("DEPRECATION") -@Deprecated("Use ManualScreenViewFactory") +@Deprecated("Use ScreenViewFactory.of") @WorkflowUiExperimentalApi public class DecorativeViewFactory( override val type: KClass, diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt index 1f36aab17f..2272358bb1 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt @@ -36,7 +36,7 @@ public interface ScreenViewHolder { } } -/** Wraps [view] in a [ScreenViewHolder], mainly for use from [ManualScreenViewFactory]. */ +/** Wraps [view] in a [ScreenViewHolder], mainly for use from [ScreenViewFactory.of]. */ @Suppress("FunctionName") @WorkflowUiExperimentalApi public fun ScreenViewHolder( @@ -68,7 +68,7 @@ public fun ScreenViewHolder.withStarter( public fun ScreenViewHolder.mapRenderings( transform: (ScreenU) -> ScreenT ): ScreenViewHolder { - return object: ScreenViewHolder { + return object : ScreenViewHolder { lateinit var unmapped: ScreenU override val screen: ScreenU @@ -94,8 +94,8 @@ public fun ScreenViewHolder.mapRen @WorkflowUiExperimentalApi public fun ScreenViewHolder.mapEnvironment( updater: (ViewEnvironment) -> ViewEnvironment -): ScreenViewHolder{ - return object: ScreenViewHolder by this { +): ScreenViewHolder { + return object : ScreenViewHolder by this { override fun showScreen(screen: ScreenT, environment: ViewEnvironment) { this@mapEnvironment.showScreen(screen, updater(environment)) } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewShowRendering.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewShowRendering.kt index 9b9a9d295e..45c2eda109 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewShowRendering.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewShowRendering.kt @@ -28,7 +28,9 @@ public typealias ViewShowRendering = * @see ViewFactory * @see DecorativeViewFactory */ +@Suppress("DeprecatedCallableAddReplaceWith") @WorkflowUiExperimentalApi +@Deprecated("Use ScreenViewHolder") public fun View.bindShowRendering( initialRendering: RenderingT, initialViewEnvironment: ViewEnvironment, diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackScreenViewFactory.kt index 6708071272..6887785494 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackScreenViewFactory.kt @@ -2,22 +2,20 @@ package com.squareup.workflow1.ui.container import android.view.ViewGroup.LayoutParams import android.view.ViewGroup.LayoutParams.MATCH_PARENT -import com.squareup.workflow1.ui.ManualScreenViewFactory import com.squareup.workflow1.ui.R import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.bindShowRendering @WorkflowUiExperimentalApi internal object BackStackScreenViewFactory : ScreenViewFactory> -by ManualScreenViewFactory( - type = BackStackScreen::class, +by ScreenViewFactory.of( viewConstructor = { initialRendering, initialEnv, context, _ -> BackStackContainer(context) - .apply { - id = R.id.workflow_back_stack_container - layoutParams = (LayoutParams(MATCH_PARENT, MATCH_PARENT)) - bindShowRendering(initialRendering, initialEnv, ::update) + .let { view -> + view.id = R.id.workflow_back_stack_container + view.layoutParams = (LayoutParams(MATCH_PARENT, MATCH_PARENT)) + ScreenViewHolder(initialRendering, initialEnv, view, view::update) } } ) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsContainer.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsContainer.kt index 8b37b6f6fe..40b69ca16b 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsContainer.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsContainer.kt @@ -12,13 +12,12 @@ import android.view.ViewGroup import android.view.ViewGroup.LayoutParams.MATCH_PARENT import android.view.ViewTreeObserver.OnGlobalLayoutListener import android.widget.FrameLayout -import com.squareup.workflow1.ui.ManualScreenViewFactory +import com.squareup.workflow1.ui.BaseScreenViewHolder import com.squareup.workflow1.ui.R import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub -import com.squareup.workflow1.ui.bindShowRendering import com.squareup.workflow1.ui.environment import com.squareup.workflow1.ui.getRendering import com.squareup.workflow1.ui.showRendering @@ -167,14 +166,13 @@ internal class BodyAndModalsContainer @JvmOverloads constructor( } companion object : ScreenViewFactory> - by ManualScreenViewFactory( - type = BodyAndModalsScreen::class, + by ScreenViewFactory.of( viewConstructor = { initialRendering, initialEnv, context, _ -> BodyAndModalsContainer(context) - .apply { - id = R.id.workflow_body_and_modals_container - layoutParams = (LayoutParams(MATCH_PARENT, MATCH_PARENT)) - bindShowRendering(initialRendering, initialEnv, ::update) + .let { view -> + view.id = R.id.workflow_body_and_modals_container + view.layoutParams = (LayoutParams(MATCH_PARENT, MATCH_PARENT)) + BaseScreenViewHolder(initialRendering, initialEnv, view, view::update) } } ) diff --git a/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/AbstractLifecycleTestActivity.kt b/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/AbstractLifecycleTestActivity.kt index 5e7115d128..9878e189b5 100644 --- a/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/AbstractLifecycleTestActivity.kt +++ b/workflow-ui/internal-testing-android/src/main/java/com/squareup/workflow1/ui/internal/test/AbstractLifecycleTestActivity.kt @@ -11,14 +11,13 @@ import androidx.lifecycle.Lifecycle.Event import androidx.lifecycle.LifecycleEventObserver import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewTreeLifecycleOwner -import com.squareup.workflow1.ui.ManualScreenViewFactory import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ScreenViewFactory +import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.ViewRegistry import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub -import com.squareup.workflow1.ui.bindShowRendering import kotlin.reflect.KClass /** @@ -89,19 +88,18 @@ public abstract class AbstractLifecycleTestActivity : WorkflowUiTestActivity() { lifecycleEvents += message } - protected fun leafViewBinding( - type: KClass, + protected inline fun leafViewBinding( viewObserver: ViewObserver, - viewConstructor: (Context) -> LeafView = ::LeafView + noinline viewConstructor: (Context) -> LeafView = ::LeafView ): ScreenViewFactory = - ManualScreenViewFactory(type) { initialRendering, initialViewEnvironment, context, _ -> - viewConstructor(context).apply { - this.viewObserver = viewObserver - viewObserver.onViewCreated(this, initialRendering) - - bindShowRendering(initialRendering, initialViewEnvironment) { rendering, _ -> - this.rendering = rendering - viewObserver.onShowRendering(this, rendering) + ScreenViewFactory.of { initialRendering, initialViewEnvironment, context, _ -> + viewConstructor(context).let { view -> + view.viewObserver = viewObserver + viewObserver.onViewCreated(view, initialRendering) + + ScreenViewHolder(initialRendering, initialViewEnvironment, view) { rendering, _ -> + view.rendering = rendering + viewObserver.onShowRendering(view, rendering) } } } @@ -179,11 +177,10 @@ public abstract class AbstractLifecycleTestActivity : WorkflowUiTestActivity() { context: Context ) : FrameLayout(context) { - internal var viewObserver: ViewObserver? = null + public var viewObserver: ViewObserver? = null // We can't rely on getRendering() in case it's wrapped with Named. public lateinit var rendering: R - internal set private val lifecycleObserver = LifecycleEventObserver { _, event -> viewObserver?.onViewTreeLifecycleStateChanged(rendering, event) From 5c1645fb984428bd55872f5dc2b4233acbce576d Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Fri, 7 Jan 2022 14:22:30 -0800 Subject: [PATCH 09/12] wip: nearly there, ported all the decorators --- .../sample/container/BackButtonViewFactory.kt | 50 ++++++++++++------- .../workflow1/ui/DecorativeViewFactory.kt | 2 +- .../workflow1/ui/NamedScreenViewFactory.kt | 7 ++- .../workflow1/ui/ScreenViewFactory.kt | 2 +- .../squareup/workflow1/ui/ScreenViewHolder.kt | 47 ++++++++++++----- .../container/EnvironmentScreenViewFactory.kt | 25 ++++++---- 6 files changed, 87 insertions(+), 46 deletions(-) diff --git a/samples/containers/android/src/main/java/com/squareup/sample/container/BackButtonViewFactory.kt b/samples/containers/android/src/main/java/com/squareup/sample/container/BackButtonViewFactory.kt index 7e4ba2b328..0ba16c7e95 100644 --- a/samples/containers/android/src/main/java/com/squareup/sample/container/BackButtonViewFactory.kt +++ b/samples/containers/android/src/main/java/com/squareup/sample/container/BackButtonViewFactory.kt @@ -1,31 +1,43 @@ package com.squareup.sample.container -import com.squareup.workflow1.ui.DecorativeScreenViewFactory +import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.acceptRenderings import com.squareup.workflow1.ui.backPressedHandler +import com.squareup.workflow1.ui.buildView +import com.squareup.workflow1.ui.withShowScreen /** - * [ScreenViewFactory] that performs the work required by [BackButtonScreen]. + * [ScreenViewFactory] that performs the work required by [BackButtonScreen], demonstrating + * some fancy [ScreenViewHolder][com.squareup.workflow1.ui.ScreenViewHolder] tricks in + * the process. */ @WorkflowUiExperimentalApi -object BackButtonViewFactory : ScreenViewFactory> -by DecorativeScreenViewFactory( - type = BackButtonScreen::class, - map = { outer -> outer.wrapped }, - doShowRendering = { view, innerShowRendering, outerRendering, viewEnvironment -> - if (!outerRendering.override) { - // Place our handler before invoking innerShowRendering, so that - // its later calls to view.backPressedHandler will take precedence - // over ours. - view.backPressedHandler = outerRendering.onBackPressed - } +val BackButtonViewFactory: ScreenViewFactory> = + ScreenViewFactory.of { initialRenderingT, initialViewEnvironment, context, container -> + initialRenderingT.wrapped + // Build the view for the wrapped rendering. + .buildView(initialViewEnvironment, context, container) + // Transform it to accept BackButtonScreen directly + .acceptRenderings> { backButtonScreen -> backButtonScreen.wrapped } + // Replace the showScreen method with one that can do a bit of pre- and post-processing + // on the view. + .withShowScreen { backButtonScreen, viewEnvironment -> + if (!backButtonScreen.override) { + // Place our handler before invoking the real showRendering method, so that + // its later calls to view.backPressedHandler will take precedence + // over ours. + view.backPressedHandler = backButtonScreen.onBackPressed + } - innerShowRendering.invoke(outerRendering.wrapped, viewEnvironment) + // The receiver of this lambda is the one that received the withShowScreen call, + // so we're able to call the "real" showScreen method. + showScreen(backButtonScreen, viewEnvironment) - if (outerRendering.override) { - // Place our handler after invoking innerShowRendering, so that ours wins. - view.backPressedHandler = outerRendering.onBackPressed - } + if (backButtonScreen.override) { + // Place our handler after invoking innerShowRendering, so that ours wins. + view.backPressedHandler = backButtonScreen.onBackPressed + } + } } -) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt index 44dd2f44c9..ef8b40d3c5 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/DecorativeViewFactory.kt @@ -109,7 +109,7 @@ import kotlin.reflect.KClass * uses [map] to extract the [InnerT] instance from [OuterT] and makes the function call. */ @Suppress("DEPRECATION") -@Deprecated("Use ScreenViewFactory.of") +@Deprecated("Use ScreenViewFactory.of and the extension methods on ScreenViewHolder") @WorkflowUiExperimentalApi public class DecorativeViewFactory( override val type: KClass, diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/NamedScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/NamedScreenViewFactory.kt index ddff8051a3..479e4f8af0 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/NamedScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/NamedScreenViewFactory.kt @@ -5,5 +5,8 @@ package com.squareup.workflow1.ui * to the factory for [NamedScreen.wrapped]. */ @WorkflowUiExperimentalApi -internal object NamedScreenViewFactory : ScreenViewFactory> -by DecorativeScreenViewFactory(NamedScreen::class, { named -> named.wrapped }) +internal val NamedScreenViewFactory: ScreenViewFactory> = + ScreenViewFactory.of { initialRendering, initialViewEnvironment, context, container -> + initialRendering.wrapped.buildView(initialViewEnvironment, context, container) + .acceptRenderings { named -> named.wrapped } + } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt index ce1982c5fd..cb84a7ae6c 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt @@ -117,7 +117,7 @@ public interface ScreenViewFactory : ViewRegistry.Entry ScreenViewHolder ): ScreenViewFactory = ManualScreenViewFactory(ScreenT::class, viewConstructor) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt index 2272358bb1..fed8994ab0 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt @@ -64,40 +64,61 @@ public fun ScreenViewHolder.withStarter( } } +/** + * @param onShowScreen Function to be called in place of the receiver's + * [ScreenViewHolder.showScreen] method. When invoked, `this` is the + * original [ScreenViewHolder] that received the [withShowScreen] call, + * so [onShowScreen] has access to the original [ScreenViewHolder.showScreen] method. + */ +@WorkflowUiExperimentalApi +public fun ScreenViewHolder.withShowScreen( + onShowScreen: ScreenViewHolder.(ScreenT, ViewEnvironment) -> Unit +): ScreenViewHolder { + return object : ScreenViewHolder by this { + override fun showScreen(screen: ScreenT, environment: ViewEnvironment) { + this@withShowScreen.onShowScreen(screen, environment) + } + } +} + +/** + * Transforms the [ScreenViewHolder.showScreen] method of the receiver to accept [NewS] + * instead of [OriginalS], by applying the given [transform] function. + */ @WorkflowUiExperimentalApi -public fun ScreenViewHolder.mapRenderings( - transform: (ScreenU) -> ScreenT -): ScreenViewHolder { - return object : ScreenViewHolder { - lateinit var unmapped: ScreenU +public fun ScreenViewHolder.acceptRenderings( + transform: (NewS) -> OriginalS +): ScreenViewHolder { + return object : ScreenViewHolder { + lateinit var unmapped: NewS - override val screen: ScreenU + override val screen: NewS get() = unmapped override val environment: ViewEnvironment - get() = this@mapRenderings.environment + get() = this@acceptRenderings.environment override val view: View - get() = this@mapRenderings.view + get() = this@acceptRenderings.view override fun start() { - this@mapRenderings.start() + this@acceptRenderings.start() } - override fun showScreen(screen: ScreenU, environment: ViewEnvironment) { + override fun showScreen(screen: NewS, environment: ViewEnvironment) { unmapped = screen - this@mapRenderings.showScreen(transform(screen), environment) + this@acceptRenderings.showScreen(transform(screen), environment) } } } @WorkflowUiExperimentalApi -public fun ScreenViewHolder.mapEnvironment( +public fun ScreenViewHolder.updateEnvironment( updater: (ViewEnvironment) -> ViewEnvironment ): ScreenViewHolder { return object : ScreenViewHolder by this { override fun showScreen(screen: ScreenT, environment: ViewEnvironment) { - this@mapEnvironment.showScreen(screen, updater(environment)) + this@updateEnvironment.showScreen(screen, updater(environment)) } } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt index c07a482b06..cf33fe1732 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt @@ -1,18 +1,23 @@ package com.squareup.workflow1.ui.container -import com.squareup.workflow1.ui.DecorativeScreenViewFactory +import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.acceptRenderings +import com.squareup.workflow1.ui.buildView import com.squareup.workflow1.ui.updateFrom +import com.squareup.workflow1.ui.withShowScreen @WorkflowUiExperimentalApi -internal object EnvironmentScreenViewFactory : ScreenViewFactory> -by DecorativeScreenViewFactory( - type = EnvironmentScreen::class, - map = { withEnvironment, inheritedEnvironment -> - Pair( - withEnvironment.screen, - inheritedEnvironment.updateFrom(withEnvironment.viewEnvironment) - ) +internal val EnvironmentScreenViewFactory: ScreenViewFactory> = + ScreenViewFactory.of { initialRendering, initialViewEnvironment, context, container -> + initialRendering.screen + // Build the view for the wrapped rendering. + .buildView(initialViewEnvironment, context, container) + // Transform it to accept EnvironmentScreen directly. + .acceptRenderings> { it.screen } + // When showScreen is called, enhance the viewEnvironment with the one in environmentScreen. + .withShowScreen { environmentScreen, viewEnvironment -> + showScreen(environmentScreen, viewEnvironment.updateFrom(environmentScreen.viewEnvironment)) + } } -) From 72d0d123f13c33aabddb622dc8a7bdecf76ea5ae Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Fri, 7 Jan 2022 14:53:18 -0800 Subject: [PATCH 10/12] wip: Holy shit it works. --- .../sample/container/BackButtonViewFactory.kt | 6 +++--- .../container/panel/PanelOverlayDialogFactory.kt | 8 ++++---- .../ui/DecorativeScreenViewFactoryTest.kt | 2 +- .../workflow1/ui/BaseScreenViewHolder.kt | 4 ++-- .../workflow1/ui/NamedScreenViewFactory.kt | 2 +- .../squareup/workflow1/ui/ScreenViewHolder.kt | 10 ++++++---- .../squareup/workflow1/ui/WorkflowViewStub.kt | 16 +++++++--------- .../ui/container/BodyAndModalsContainer.kt | 7 +++---- .../ui/container/EnvironmentScreenViewFactory.kt | 3 +-- 9 files changed, 28 insertions(+), 30 deletions(-) diff --git a/samples/containers/android/src/main/java/com/squareup/sample/container/BackButtonViewFactory.kt b/samples/containers/android/src/main/java/com/squareup/sample/container/BackButtonViewFactory.kt index 0ba16c7e95..635fc963ae 100644 --- a/samples/containers/android/src/main/java/com/squareup/sample/container/BackButtonViewFactory.kt +++ b/samples/containers/android/src/main/java/com/squareup/sample/container/BackButtonViewFactory.kt @@ -15,12 +15,12 @@ import com.squareup.workflow1.ui.withShowScreen */ @WorkflowUiExperimentalApi val BackButtonViewFactory: ScreenViewFactory> = - ScreenViewFactory.of { initialRenderingT, initialViewEnvironment, context, container -> - initialRenderingT.wrapped + ScreenViewFactory.of { initialRendering, initialViewEnvironment, context, container -> + initialRendering.wrapped // Build the view for the wrapped rendering. .buildView(initialViewEnvironment, context, container) // Transform it to accept BackButtonScreen directly - .acceptRenderings> { backButtonScreen -> backButtonScreen.wrapped } + .acceptRenderings(initialRendering) { backButtonScreen -> backButtonScreen.wrapped } // Replace the showScreen method with one that can do a bit of pre- and post-processing // on the view. .withShowScreen { backButtonScreen, viewEnvironment -> diff --git a/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt b/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt index f896eb47e4..34da57d713 100644 --- a/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt +++ b/samples/containers/android/src/main/java/com/squareup/sample/container/panel/PanelOverlayDialogFactory.kt @@ -4,8 +4,8 @@ import android.app.Dialog import android.graphics.Rect import android.graphics.drawable.ColorDrawable import android.util.TypedValue -import android.view.View import com.squareup.sample.container.R +import com.squareup.workflow1.ui.ScreenViewHolder import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.ModalScreenOverlayDialogFactory import com.squareup.workflow1.ui.container.setBounds @@ -17,10 +17,10 @@ import com.squareup.workflow1.ui.container.setBounds internal object PanelOverlayDialogFactory : ModalScreenOverlayDialogFactory>( type = PanelOverlay::class ) { - override fun buildDialogWithContent(content: View): Dialog { - val context = content.context + override fun buildDialogWithContent(content: ScreenViewHolder<*>): Dialog { + val context = content.view.context return Dialog(context, R.style.PanelDialog).also { dialog -> - dialog.setContentView(content) + dialog.setContentView(content.view) // Welcome to Android. Nothing workflow-related here, this is just how one // finds the window background color for the theme. I sure hope it's better in Compose. diff --git a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeScreenViewFactoryTest.kt b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeScreenViewFactoryTest.kt index b14c273762..f280b23c96 100644 --- a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeScreenViewFactoryTest.kt +++ b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeScreenViewFactoryTest.kt @@ -21,7 +21,7 @@ internal class DecorativeScreenViewFactoryTest { initialViewEnvironment: ViewEnvironment, contextForNewView: Context, container: ViewGroup? - ): View = InnerView(contextForNewView).apply { + ): RView = InnerView(contextForNewView).apply { bindShowRendering(initialRendering, initialViewEnvironment) { rendering, _ -> events += "inner showRendering $rendering" } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt index 70d7c78dff..c23e553542 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt @@ -9,8 +9,8 @@ internal class BaseScreenViewHolder( override val view: View, private val updater: ScreenViewUpdater ) : ScreenViewHolder { - lateinit var currentRendering: ScreenT - lateinit var currentEnvironment: ViewEnvironment + private var currentRendering: ScreenT = initialRendering + private var currentEnvironment: ViewEnvironment = initialViewEnvironment override val screen: ScreenT get() = currentRendering diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/NamedScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/NamedScreenViewFactory.kt index 479e4f8af0..4222b04a93 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/NamedScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/NamedScreenViewFactory.kt @@ -8,5 +8,5 @@ package com.squareup.workflow1.ui internal val NamedScreenViewFactory: ScreenViewFactory> = ScreenViewFactory.of { initialRendering, initialViewEnvironment, context, container -> initialRendering.wrapped.buildView(initialViewEnvironment, context, container) - .acceptRenderings { named -> named.wrapped } + .acceptRenderings(initialRendering) { named -> named.wrapped } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt index fed8994ab0..fe0c5ff2a7 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt @@ -83,17 +83,19 @@ public fun ScreenViewHolder.withShowScreen( /** * Transforms the [ScreenViewHolder.showScreen] method of the receiver to accept [NewS] - * instead of [OriginalS], by applying the given [transform] function. + * instead of [OriginalS], by applying the [given function][transform] to convert + * [NewS] to [OriginalS]. */ @WorkflowUiExperimentalApi public fun ScreenViewHolder.acceptRenderings( + initialRendering: NewS, transform: (NewS) -> OriginalS ): ScreenViewHolder { return object : ScreenViewHolder { - lateinit var unmapped: NewS + var untransformed: NewS = initialRendering override val screen: NewS - get() = unmapped + get() = untransformed override val environment: ViewEnvironment get() = this@acceptRenderings.environment @@ -106,7 +108,7 @@ public fun ScreenViewHolder.accep } override fun showScreen(screen: NewS, environment: ViewEnvironment) { - unmapped = screen + untransformed = screen this@acceptRenderings.showScreen(transform(screen), environment) } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt index daba8f1a3d..e9b703ad7f 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/WorkflowViewStub.kt @@ -70,11 +70,11 @@ public class WorkflowViewStub @JvmOverloads constructor( defStyleRes: Int = 0 ) : View(context, attributeSet, defStyle, defStyleRes) { /** Returns null if [update] hasn't been called yet. */ - private val delegateOrNull: View? + public val delegateHolderOrNull: ScreenViewHolder? get() { // can be null when called from the constructor. @Suppress("UNNECESSARY_SAFE_CALL") - return delegateHolder?.view?.takeUnless { it === this } + return delegateHolder?.takeUnless { it.view === this } } public var delegateHolder: ScreenViewHolder = object : ScreenViewHolder { @@ -161,16 +161,14 @@ public class WorkflowViewStub @JvmOverloads constructor( */ override fun setVisibility(visibility: Int) { super.setVisibility(visibility) - delegateOrNull?.takeUnless { it == this }?.let { - it.visibility = visibility - } + delegateHolderOrNull?.let { it.view.visibility = visibility } } /** * Returns the visibility of the delegate, or of this stub if [show] has not yet been called. */ override fun getVisibility(): Int { - return delegateOrNull?.visibility ?: super.getVisibility() + return delegateHolderOrNull?.view?.visibility ?: super.getVisibility() } /** @@ -180,7 +178,7 @@ public class WorkflowViewStub @JvmOverloads constructor( */ override fun setBackground(background: Drawable?) { super.setBackground(background) - if (background != null) delegateOrNull?.background = background + if (background != null) delegateHolderOrNull?.view?.background = background } @Deprecated("Use show()", ReplaceWith("show(rendering, viewEnvironment)")) @@ -234,8 +232,8 @@ public class WorkflowViewStub @JvmOverloads constructor( // WorkflowLifecycleOwner on this view, this get() call will return the WLO owned by that // parent. We noop in that case since destroying that lifecycle is our parent's responsibility // in that case, not ours. - delegateOrNull?.let { - WorkflowLifecycleOwner.get(it)?.destroyOnDetach() + delegateHolderOrNull?.let { + WorkflowLifecycleOwner.get(it.view)?.destroyOnDetach() } val newViewHolder = screen.buildView( diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsContainer.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsContainer.kt index 40b69ca16b..ee72f2bc2e 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsContainer.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsContainer.kt @@ -18,9 +18,6 @@ import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub -import com.squareup.workflow1.ui.environment -import com.squareup.workflow1.ui.getRendering -import com.squareup.workflow1.ui.showRendering import kotlinx.coroutines.flow.MutableStateFlow @WorkflowUiExperimentalApi @@ -96,7 +93,9 @@ internal class BodyAndModalsContainer @JvmOverloads constructor( viewTreeObserver.addOnGlobalLayoutListener(boundsListener) // Ugly, but here in case a strange parent detaches and re-attaches us. // https://github.com/square/workflow-kotlin/issues/314 - showRendering(getRendering()!!, environment!!) + baseViewStub.delegateHolderOrNull?.let { + it.showScreen(it.screen, it.environment) + } } override fun onDetachedFromWindow() { diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt index cf33fe1732..390dfaff9b 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/EnvironmentScreenViewFactory.kt @@ -1,6 +1,5 @@ package com.squareup.workflow1.ui.container -import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.acceptRenderings @@ -15,7 +14,7 @@ internal val EnvironmentScreenViewFactory: ScreenViewFactory> { it.screen } + .acceptRenderings(initialRendering) { it.screen } // When showScreen is called, enhance the viewEnvironment with the one in environmentScreen. .withShowScreen { environmentScreen, viewEnvironment -> showScreen(environmentScreen, viewEnvironment.updateFrom(environmentScreen.viewEnvironment)) From 8b57396f44236b8474af2baf7a9753832a389a8b Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Fri, 7 Jan 2022 16:15:15 -0800 Subject: [PATCH 11/12] wip: ported the test, failing --- .../ModalViewContainerLifecycleActivity.kt | 4 +- .../workflow1/ui/modal/ModalViewContainer.kt | 2 +- ...FactoryTest.kt => ScreenViewHolderTest.kt} | 171 ++++++++---------- .../BackStackContainerLifecycleActivity.kt | 4 +- .../workflow1/ui/LayoutScreenViewFactory.kt | 4 +- .../workflow1/ui/ManualScreenViewFactory.kt | 5 +- .../workflow1/ui/ScreenViewFactory.kt | 6 +- .../squareup/workflow1/ui/ScreenViewHolder.kt | 4 +- .../ui/ViewBindingScreenViewFactory.kt | 4 +- .../ui/container/BackStackContainer.kt | 2 +- .../workflow1/ui/ScreenViewFactoryTest.kt | 2 +- 11 files changed, 93 insertions(+), 115 deletions(-) rename workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/{DecorativeScreenViewFactoryTest.kt => ScreenViewHolderTest.kt} (50%) diff --git a/workflow-ui/container-android/src/androidTest/java/com/squareup/workflow1/ui/modal/test/ModalViewContainerLifecycleActivity.kt b/workflow-ui/container-android/src/androidTest/java/com/squareup/workflow1/ui/modal/test/ModalViewContainerLifecycleActivity.kt index c603f5d294..17bd1514d8 100644 --- a/workflow-ui/container-android/src/androidTest/java/com/squareup/workflow1/ui/modal/test/ModalViewContainerLifecycleActivity.kt +++ b/workflow-ui/container-android/src/androidTest/java/com/squareup/workflow1/ui/modal/test/ModalViewContainerLifecycleActivity.kt @@ -30,12 +30,12 @@ internal class ModalViewContainerLifecycleActivity : AbstractLifecycleTestActivi override fun buildView( initialRendering: BaseRendering, initialViewEnvironment: ViewEnvironment, - contextForNewView: Context, + context: Context, container: ViewGroup? ) = ScreenViewHolder( initialRendering, initialViewEnvironment, - View(contextForNewView) + View(context) ) { _, _ -> /* Noop */ } } diff --git a/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt b/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt index 4e4076b0d1..52ed331c72 100644 --- a/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt +++ b/workflow-ui/container-android/src/main/java/com/squareup/workflow1/ui/modal/ModalViewContainer.kt @@ -67,7 +67,7 @@ public open class ModalViewContainer @JvmOverloads constructor( val view = asScreen(initialModalRendering) .buildView( viewEnvironment = initialViewEnvironment, - contextForNewView = this.context, + context = this.context, container = this ) .let { holder -> diff --git a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeScreenViewFactoryTest.kt b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/ScreenViewHolderTest.kt similarity index 50% rename from workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeScreenViewFactoryTest.kt rename to workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/ScreenViewHolderTest.kt index f280b23c96..c98fced2d3 100644 --- a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/DecorativeScreenViewFactoryTest.kt +++ b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/ScreenViewHolderTest.kt @@ -2,48 +2,39 @@ package com.squareup.workflow1.ui import android.content.Context import android.view.View -import android.view.ViewGroup import androidx.test.platform.app.InstrumentationRegistry import com.google.common.truth.Truth.assertThat import org.junit.Test @OptIn(WorkflowUiExperimentalApi::class) -internal class DecorativeScreenViewFactoryTest { +internal class ScreenViewHolderTest { private val instrumentation = InstrumentationRegistry.getInstrumentation() @Test fun viewStarter_is_only_call_to_showRendering() { val events = mutableListOf() - val innerViewFactory = object : ScreenViewFactory { - override val type = InnerRendering::class - override fun buildView( - initialRendering: InnerRendering, - initialViewEnvironment: ViewEnvironment, - contextForNewView: Context, - container: ViewGroup? - ): RView = InnerView(contextForNewView).apply { - bindShowRendering(initialRendering, initialViewEnvironment) { rendering, _ -> - events += "inner showRendering $rendering" + val innerViewFactory = + ScreenViewFactory.of { initialRendering, initialViewEnvironment, context, _ -> + ScreenViewHolder(initialRendering, initialViewEnvironment, InnerView(context)) { s, _ -> + events += "inner showRendering $s" } } - } val envString = object : ViewEnvironmentKey(String::class) { override val default: String get() = "Not set" } - val outerViewFactory = DecorativeScreenViewFactory( - type = OuterRendering::class, - map = { outer, env -> - val enhancedEnv = env + (envString to "Updated environment") - Pair(outer.wrapped, enhancedEnv) - }, - viewStarter = { view, doStart -> - events += "viewStarter ${view.getRendering()} ${view.environment!![envString]}" - doStart() - events += "exit viewStarter" + val outerViewFactory = ScreenViewFactory + .of { initialRendering, initialViewEnvironment, context, container -> + initialRendering.wrapped.buildView(initialViewEnvironment, context, container) + .acceptRenderings(initialRendering) { initialRendering.wrapped } + .withStarter { viewHolder, doStart -> + events += "viewStarter ${viewHolder.screen} ${viewHolder.environment[envString]}" + doStart() + events += "exit viewStarter" + } } - ) + val viewRegistry = ViewRegistry(innerViewFactory, outerViewFactory) val viewEnvironment = ViewEnvironment(mapOf(ViewRegistry to viewRegistry)) @@ -63,27 +54,23 @@ internal class DecorativeScreenViewFactoryTest { @Test fun initial_doShowRendering_calls_wrapped_showRendering() { val events = mutableListOf() - val innerViewFactory = object : ScreenViewFactory { - override val type = InnerRendering::class - override fun buildView( - initialRendering: InnerRendering, - initialViewEnvironment: ViewEnvironment, - contextForNewView: Context, - container: ViewGroup? - ): View = InnerView(contextForNewView).apply { - bindShowRendering(initialRendering, initialViewEnvironment) { rendering, _ -> - events += "inner showRendering $rendering" + val innerViewFactory = + ScreenViewFactory.of { initialRendering, initialViewEnvironment, context, _ -> + ScreenViewHolder(initialRendering, initialViewEnvironment, InnerView(context)) { s, _ -> + events += "inner showRendering $s" } } - } - val outerViewFactory = DecorativeScreenViewFactory( - type = OuterRendering::class, - map = { outer -> outer.wrapped }, - doShowRendering = { _, innerShowRendering, outerRendering, env -> - events += "doShowRendering $outerRendering" - innerShowRendering(outerRendering.wrapped, env) + + val outerViewFactory = ScreenViewFactory + .of { initialRendering, initialViewEnvironment, context, container -> + initialRendering.wrapped.buildView(initialViewEnvironment, context, container) + .acceptRenderings(initialRendering) { it.wrapped } + .withShowScreen { outerRendering, viewEnvironment -> + events += "doShowRendering $outerRendering" + showScreen(outerRendering, viewEnvironment) + } } - ) + val viewRegistry = ViewRegistry(innerViewFactory, outerViewFactory) val viewEnvironment = ViewEnvironment(mapOf(ViewRegistry to viewRegistry)) @@ -102,50 +89,46 @@ internal class DecorativeScreenViewFactoryTest { @Test fun double_wrapping_only_calls_showRendering_once() { val events = mutableListOf() - val innerViewFactory = object : ScreenViewFactory { - override val type = InnerRendering::class - override fun buildView( - initialRendering: InnerRendering, - initialViewEnvironment: ViewEnvironment, - contextForNewView: Context, - container: ViewGroup? - ): View = InnerView(contextForNewView).apply { - bindShowRendering(initialRendering, initialViewEnvironment) { rendering, _ -> - events += "inner showRendering $rendering" + val innerViewFactory = ScreenViewFactory + .of { initialRendering, initialViewEnvironment, context, _ -> + ScreenViewHolder(initialRendering, initialViewEnvironment, InnerView(context)) { s, _ -> + events += "inner showRendering $s" } } - } val envString = object : ViewEnvironmentKey(String::class) { override val default: String get() = "Not set" } - val outerViewFactory = DecorativeScreenViewFactory( - type = OuterRendering::class, - map = { outer, env -> - val enhancedEnv = env + (envString to "Outer Updated environment" + - " SHOULD NOT SEE THIS! It will be clobbered by WayOutRendering") - Pair(outer.wrapped, enhancedEnv) - }, - viewStarter = { view, doStart -> - events += "outer viewStarter ${view.getRendering()} ${view.environment!![envString]}" - doStart() - events += "exit outer viewStarter" + val outerViewFactory = + ScreenViewFactory.of { initialRendering, initialViewEnvironment, context, container -> + initialRendering.wrapped.buildView(initialViewEnvironment, context, container) + .acceptRenderings(initialRendering) { it.wrapped } + .updateEnvironment { env -> + env + (envString to "Outer Updated environment" + + " SHOULD NOT SEE THIS! It will be clobbered by WayOutRendering") + } + .withStarter { viewHolder, doStart -> + events += "outer viewStarter ${viewHolder.screen} ${viewHolder.environment[envString]}" + doStart() + events += "exit outer viewStarter" + } } - ) - val wayOutViewFactory = DecorativeScreenViewFactory( - type = WayOutRendering::class, - map = { wayOut, env -> - val enhancedEnv = env + (envString to "Way Out Updated environment triumphs over all") - Pair(wayOut.wrapped, enhancedEnv) - }, - viewStarter = { view, doStart -> - events += "way out viewStarter ${view.getRendering()} ${view.environment!![envString]}" - doStart() - events += "exit way out viewStarter" + val wayOutViewFactory = ScreenViewFactory + .of { initialRendering, initialViewEnvironment, context, container -> + initialRendering.wrapped.buildView(initialViewEnvironment, context, container) + .acceptRenderings(initialRendering) { it.wrapped } + .updateEnvironment { env -> + env + (envString to "Way Out Updated environment triumphs over all") + } + .withStarter { viewHolder, doStart -> + events += "way out viewStarter ${viewHolder.screen} ${viewHolder.environment[envString]}" + doStart() + events += "exit way out viewStarter" + } } - ) + val viewRegistry = ViewRegistry(innerViewFactory, outerViewFactory, wayOutViewFactory) val viewEnvironment = ViewEnvironment(mapOf(ViewRegistry to viewRegistry)) @@ -178,37 +161,33 @@ internal class DecorativeScreenViewFactoryTest { @Test fun subsequent_showRendering_calls_wrapped_showRendering() { val events = mutableListOf() - val innerViewFactory = object : ScreenViewFactory { - override val type = InnerRendering::class - override fun buildView( - initialRendering: InnerRendering, - initialViewEnvironment: ViewEnvironment, - contextForNewView: Context, - container: ViewGroup? - ): View = InnerView(contextForNewView).apply { - bindShowRendering(initialRendering, initialViewEnvironment) { rendering, _ -> - events += "inner showRendering $rendering" + val innerViewFactory = + ScreenViewFactory.of { initialRendering, initialViewEnvironment, context, _ -> + ScreenViewHolder(initialRendering, initialViewEnvironment, InnerView(context)) { s, _ -> + events += "inner showRendering $s" } } - } - val outerViewFactory = DecorativeScreenViewFactory( - type = OuterRendering::class, - map = { outer -> outer.wrapped }, - doShowRendering = { _, innerShowRendering, outerRendering, env -> - events += "doShowRendering $outerRendering" - innerShowRendering(outerRendering.wrapped, env) + + val outerViewFactory = ScreenViewFactory + .of { initialRendering, initialViewEnvironment, context, container -> + initialRendering.wrapped.buildView(initialViewEnvironment, context, container) + .acceptRenderings(initialRendering) { it.wrapped } + .withShowScreen { outerRendering, viewEnvironment -> + events += "doShowRendering $outerRendering" + showScreen(outerRendering, viewEnvironment) + } } - ) + val viewRegistry = ViewRegistry(innerViewFactory, outerViewFactory) val viewEnvironment = ViewEnvironment(mapOf(ViewRegistry to viewRegistry)) - val view = OuterRendering("out1", InnerRendering("in1")).buildView( + val viewHolder = OuterRendering("out1", InnerRendering("in1")).buildView( viewEnvironment, instrumentation.context ).apply { start() } events.clear() - view.showRendering(OuterRendering("out2", InnerRendering("in2")), viewEnvironment) + viewHolder.showScreen(OuterRendering("out2", InnerRendering("in2")), viewEnvironment) assertThat(events).containsExactly( "doShowRendering OuterRendering(outerData=out2, wrapped=InnerRendering(innerData=in2))", diff --git a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/BackStackContainerLifecycleActivity.kt b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/BackStackContainerLifecycleActivity.kt index d88cdef812..e350d47059 100644 --- a/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/BackStackContainerLifecycleActivity.kt +++ b/workflow-ui/core-android/src/androidTest/java/com/squareup/workflow1/ui/container/fixtures/BackStackContainerLifecycleActivity.kt @@ -36,12 +36,12 @@ internal class BackStackContainerLifecycleActivity : AbstractLifecycleTestActivi override fun buildView( initialRendering: BaseRendering, initialViewEnvironment: ViewEnvironment, - contextForNewView: Context, + context: Context, container: ViewGroup? ) = ScreenViewHolder( initialRendering, initialViewEnvironment, - View(contextForNewView) + View(context) ) { _, _ -> /* Noop */ } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt index 077986ea11..53907c3dca 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/LayoutScreenViewFactory.kt @@ -16,11 +16,11 @@ internal class LayoutScreenViewFactory( override fun buildView( initialRendering: ScreenT, initialViewEnvironment: ViewEnvironment, - contextForNewView: Context, + context: Context, container: ViewGroup? ): ScreenViewHolder { val view = - contextForNewView.viewBindingLayoutInflater(container).inflate(layoutId, container, false) + context.viewBindingLayoutInflater(container).inflate(layoutId, container, false) return BaseScreenViewHolder( initialRendering = initialRendering, diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt index 010ed73642..1390fddbe9 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ManualScreenViewFactory.kt @@ -1,7 +1,6 @@ package com.squareup.workflow1.ui import android.content.Context -import android.view.View import android.view.ViewGroup import kotlin.reflect.KClass @@ -19,8 +18,8 @@ internal class ManualScreenViewFactory( override fun buildView( initialRendering: ScreenT, initialViewEnvironment: ViewEnvironment, - contextForNewView: Context, + context: Context, container: ViewGroup? ): ScreenViewHolder = - viewConstructor(initialRendering, initialViewEnvironment, contextForNewView, container) + viewConstructor(initialRendering, initialViewEnvironment, context, container) } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt index cb84a7ae6c..827eba1402 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactory.kt @@ -35,7 +35,7 @@ public interface ScreenViewFactory : ViewRegistry.Entry @@ -140,11 +140,11 @@ public interface ScreenViewFactory : ViewRegistry.Entry ScreenT.buildView( viewEnvironment: ViewEnvironment, - contextForNewView: Context, + context: Context, container: ViewGroup? = null ): ScreenViewHolder { return viewEnvironment.getViewFactoryForRendering(this).buildView( - this, viewEnvironment, contextForNewView, container + this, viewEnvironment, context, container ) } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt index fe0c5ff2a7..f4090894c8 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewHolder.kt @@ -55,11 +55,11 @@ public fun ScreenViewHolder<*>.canShowScreen(screen: Screen): Boolean { @WorkflowUiExperimentalApi public fun ScreenViewHolder.withStarter( - viewStarter: ScreenViewHolder.Starter + starter: ScreenViewHolder.Starter ): ScreenViewHolder { return object : ScreenViewHolder by this { override fun start() { - viewStarter.startView(this@withStarter, this@withStarter::start) + starter.startView(this@withStarter, this@withStarter::start) } } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt index 69d4cbf5b7..87800872da 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ViewBindingScreenViewFactory.kt @@ -15,11 +15,11 @@ internal class ViewBindingScreenViewFactory { val binding = bindingInflater( - contextForNewView.viewBindingLayoutInflater(container), container, false + context.viewBindingLayoutInflater(container), container, false ) return BaseScreenViewHolder( initialRendering, diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackContainer.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackContainer.kt index 39e00d4bc9..79cab56c9e 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackContainer.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BackStackContainer.kt @@ -97,7 +97,7 @@ public open class BackStackContainer @JvmOverloads constructor( val newView = named.top.buildView( viewEnvironment = environment, - contextForNewView = this.context, + context = this.context, container = this ).withStarter { view, doStart -> WorkflowLifecycleOwner.installOn(view.view) diff --git a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/ScreenViewFactoryTest.kt b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/ScreenViewFactoryTest.kt index d650b901f9..5a4606d2ed 100644 --- a/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/ScreenViewFactoryTest.kt +++ b/workflow-ui/core-android/src/test/java/com/squareup/workflow1/ui/ScreenViewFactoryTest.kt @@ -67,7 +67,7 @@ internal class ScreenViewFactoryTest { override fun buildView( initialRendering: T, initialViewEnvironment: ViewEnvironment, - contextForNewView: Context, + context: Context, container: ViewGroup? ): View { called = true From 118450c344e09c3dcc65e443c3d3aefe0b5b4136 Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Mon, 10 Jan 2022 09:39:15 -0800 Subject: [PATCH 12/12] wip: tryinig final class instead --- .../workflow1/ui/BaseScreenViewHolder.kt | 8 ++-- .../workflow1/ui/BetterScreenViewHolder.kt | 46 +++++++++++++++++++ 2 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BetterScreenViewHolder.kt diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt index c23e553542..51bcb7645b 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BaseScreenViewHolder.kt @@ -3,9 +3,9 @@ package com.squareup.workflow1.ui import android.view.View @WorkflowUiExperimentalApi -internal class BaseScreenViewHolder( - private val initialRendering: ScreenT, - private val initialViewEnvironment: ViewEnvironment, +internal class BaseScreenViewHolder( + initialRendering: ScreenT, + initialViewEnvironment: ViewEnvironment, override val view: View, private val updater: ScreenViewUpdater ) : ScreenViewHolder { @@ -18,7 +18,7 @@ internal class BaseScreenViewHolder( get() = currentEnvironment override fun start() { - showScreen(initialRendering, initialViewEnvironment) + showScreen(currentRendering, currentEnvironment) } override fun showScreen(screen: ScreenT, environment: ViewEnvironment) { diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BetterScreenViewHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BetterScreenViewHolder.kt new file mode 100644 index 0000000000..a75514e9ba --- /dev/null +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/BetterScreenViewHolder.kt @@ -0,0 +1,46 @@ +package com.squareup.workflow1.ui + +import android.view.View + +@WorkflowUiExperimentalApi +public class BetterScreenViewHolder private constructor( + public val view: View, + private val updater: ScreenViewUpdater, + internal val screenGetter: () -> ScreenT, + internal val envGetter: () -> ViewEnvironment, + internal val startWrapper: BetterScreenViewHolder.(() -> Unit) -> Unit, +) { + public constructor( + view: View, + updater: ScreenViewUpdater, + screen: ScreenT, + env: ViewEnvironment + ) : this( + view = view, + updater = updater, + screenGetter = { screen }, + envGetter = { env }, + startWrapper = { doStart -> doStart() } + ) + + public val screen: ScreenT get() = screenGetter() + public val environment: ViewEnvironment get() = envGetter() + + public fun start() { + startWrapper.invoke(this) { updater.showRendering(screen, environment) } + } + + public fun canShowScreen(screen: Screen): Boolean = compatible(this.screen, screen) + + public fun withStarter( + startWrapper: BetterScreenViewHolder.(() -> Unit) -> Unit + ): BetterScreenViewHolder { + return BetterScreenViewHolder(view, updater, screenGetter, envGetter) { doStart -> + startWrapper(doStart) + } + } + + public fun withShowScreen( + + ) +}