From 6221e495d8d8139534fff5c41316bfcc87ebf285 Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Fri, 10 Jun 2022 16:50:53 -0700 Subject: [PATCH 1/2] More flexible modality. Introduces the `ModalOverlay` interface. Moves as much code as possible from `BodyAndModalsContainer` to `LayeredDialogs`, and allows them to turn on modal event blocking behavior per managed `Overlay` instance, rather than always enforcing it. Very sad that I wasn't able to get rid of `BodyAndOverlaysContainer.onAttachedToWindow`, but the SavedStateRegistry timing is just too fussy to allow it. Git was dumb about diff'ing across renames, so they aren't all done in this commit for ease of review. The rest of the renames follow. --- .../poetry/views/MayBeLoadingScreen.kt | 2 +- .../sample/container/panel/PanelOverlay.kt | 3 +- .../sample/mainworkflow/TicTacToeWorkflow.kt | 6 +- .../mainworkflow/TicTacToeWorkflowTest.kt | 2 +- .../ui/container/AndroidDialogBounds.kt | 3 +- .../ui/container/BodyAndModalsContainer.kt | 89 ++------- .../workflow1/ui/container/CoveredByModal.kt | 2 +- .../workflow1/ui/container/DialogHolder.kt | 7 +- .../workflow1/ui/container/LayeredDialogs.kt | 185 ++++++++++++++---- .../ModalScreenOverlayDialogFactory.kt | 27 ++- .../{ModalArea.kt => OverlayArea.kt} | 13 +- .../workflow1/ui/container/AlertOverlay.kt | 2 +- .../ui/container/BodyAndModalsScreen.kt | 22 ++- .../workflow1/ui/container/ModalOverlay.kt | 10 + .../ui/container/ModalScreenOverlay.kt | 3 +- .../workflow1/ui/container/Overlay.kt | 10 +- 16 files changed, 224 insertions(+), 162 deletions(-) rename workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/{ModalArea.kt => OverlayArea.kt} (56%) create mode 100644 workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ModalOverlay.kt diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt index 81a300e16a..b2479edf56 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt @@ -27,4 +27,4 @@ val MayBeLoadingScreen.baseScreen: OverviewDetailScreen @OptIn(WorkflowUiExperimentalApi::class) val MayBeLoadingScreen.loaders: List - get() = modals.map { it.content } + get() = overlays.map { it.content } diff --git a/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt b/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt index a883a956c6..b9368f559b 100644 --- a/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt +++ b/samples/containers/common/src/main/java/com/squareup/sample/container/panel/PanelOverlay.kt @@ -2,9 +2,10 @@ package com.squareup.sample.container.panel import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.container.ModalOverlay import com.squareup.workflow1.ui.container.ScreenOverlay @OptIn(WorkflowUiExperimentalApi::class) class PanelOverlay( override val content: T -) : ScreenOverlay +) : ScreenOverlay, ModalOverlay diff --git a/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt b/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt index 0262fd752a..41db01aa78 100644 --- a/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt +++ b/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt @@ -58,7 +58,7 @@ class TicTacToeWorkflow( renderState: MainState, context: RenderContext ): BodyAndModalsScreen, *> { - val bodyAndModals: BodyAndModalsScreen<*, *> = when (renderState) { + val bodyAndOverlays: BodyAndModalsScreen<*, *> = when (renderState) { is Authenticating -> { val authBackStack = context.renderChild(authWorkflow) { handleAuthResult(it) } // We always show an empty GameScreen behind the PanelOverlay that @@ -101,8 +101,8 @@ class TicTacToeWorkflow( } // Add the scrim. Dim it only if there is a panel showing. - val dim = bodyAndModals.modals.any { modal -> modal is PanelOverlay<*> } - return bodyAndModals.mapBody { body -> ScrimScreen(body, dimmed = dim) } + val dim = bodyAndOverlays.overlays.any { modal -> modal is PanelOverlay<*> } + return bodyAndOverlays.mapBody { body -> ScrimScreen(body, dimmed = dim) } } override fun snapshotState(state: MainState): Snapshot = state.toSnapshot() diff --git a/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt b/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt index 5c52aa83b9..09f032d339 100644 --- a/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt +++ b/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt @@ -64,7 +64,7 @@ class TicTacToeWorkflowTest { BackStackScreen(S(wrapped)) private val BodyAndModalsScreen, *>.panels: List> - get() = modals.mapNotNull { it as? PanelOverlay<*> } + get() = overlays.mapNotNull { it as? PanelOverlay<*> } private fun authWorkflow( screen: String = DEFAULT_AUTH diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/AndroidDialogBounds.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/AndroidDialogBounds.kt index 6013d72900..78825ce82d 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/AndroidDialogBounds.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/AndroidDialogBounds.kt @@ -7,7 +7,6 @@ import android.view.View import android.view.Window import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.environment import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.cancel @@ -40,7 +39,7 @@ internal fun D.maintainBounds( environment: ViewEnvironment, onBoundsChange: (D, Rect) -> Unit ) { - maintainBounds(environment[ModalArea].bounds, onBoundsChange) + maintainBounds(environment[OverlayArea].bounds, onBoundsChange) } @WorkflowUiExperimentalApi 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 9fdc00c010..1114c56779 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 @@ -1,7 +1,6 @@ package com.squareup.workflow1.ui.container import android.content.Context -import android.graphics.Rect import android.os.Parcel import android.os.Parcelable import android.os.Parcelable.Creator @@ -10,10 +9,8 @@ import android.view.KeyEvent import android.view.MotionEvent 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.Compatible -import com.squareup.workflow1.ui.Compatible.Companion.keyFor import com.squareup.workflow1.ui.R import com.squareup.workflow1.ui.ScreenViewFactory import com.squareup.workflow1.ui.ScreenViewHolder @@ -21,10 +18,9 @@ import com.squareup.workflow1.ui.ScreenViewHolder.Companion.Showing import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub -import com.squareup.workflow1.ui.androidx.WorkflowAndroidXSupport -import kotlinx.coroutines.flow.MutableStateFlow @WorkflowUiExperimentalApi +// TODO Rename this BodyAndOverlaysContainer internal class BodyAndModalsContainer @JvmOverloads constructor( context: Context, attributeSet: AttributeSet? = null, @@ -32,10 +28,6 @@ internal class BodyAndModalsContainer @JvmOverloads constructor( defStyleRes: Int = 0 ) : FrameLayout(context, attributeSet, defStyle, defStyleRes) { /** - * Unique identifier for this view for SavedStateRegistry purposes. Based on the - * [Compatible.keyFor] the current rendering. Taking this approach allows - * feature developers to take control over naming, e.g. by wrapping renderings - * with [NamedScreen][com.squareup.workflow1.ui.NamedScreen]. */ private lateinit var savedStateParentKey: String @@ -43,91 +35,46 @@ internal class BodyAndModalsContainer @JvmOverloads constructor( addView(it, ViewGroup.LayoutParams(MATCH_PARENT, MATCH_PARENT)) } - private val dialogs = LayeredDialogs(view = this, modal = true) - - // The bounds of this view in global (display) coordinates, as reported - // by getGlobalVisibleRect. - // - // Made available to managed ModalScreenOverlayDialogFactory instances - // via the ModalArea key in ViewEnvironment. When this updates their - // updateBounds methods will fire. They should resize themselves to - // avoid covering peers of this view. - private val bounds = MutableStateFlow(Rect()) - private val boundsRect = Rect() - - private val boundsListener = OnGlobalLayoutListener { - if (getGlobalVisibleRect(boundsRect) && boundsRect != bounds.value) { - bounds.value = Rect(boundsRect) - } - // Should we close the dialogs if getGlobalVisibleRect returns false? - // https://github.com/square/workflow-kotlin/issues/599 - } - - // Note similar code in DialogHolder. - private var allowEvents = true - set(value) { - val was = field - field = value - if (value != was) { - // https://stackoverflow.com/questions/2886407/dealing-with-rapid-tapping-on-buttons - // If any motion events were enqueued on the main thread, cancel them. - dispatchCancelEvent { super.dispatchTouchEvent(it) } - // When we cancel, have to warn things like RecyclerView that handle streams - // of motion events and eventually dispatch input events (click, key pressed, etc.) - // based on them. - cancelPendingInputEvents() - } - } + private val dialogs = LayeredDialogs.forView( + view = this, + superDispatchTouchEvent = { super.dispatchTouchEvent(it) } + ) fun update( newScreen: BodyAndModalsScreen<*, *>, viewEnvironment: ViewEnvironment ) { - savedStateParentKey = keyFor(viewEnvironment[Showing]) - - val showingModals = newScreen.modals.isNotEmpty() + savedStateParentKey = Compatible.keyFor(viewEnvironment[Showing]) - // There is a long wait from when we show a dialog until it starts blocking - // events for us. To compensate we ignore all touches while any dialogs exist. - allowEvents = !showingModals - - val baseEnv = if (showingModals) viewEnvironment + (CoveredByModal to true) else viewEnvironment - baseViewStub.show(newScreen.body, baseEnv) - - // Allow modal dialogs to restrict themselves to cover only this view. - val dialogsEnv = if (showingModals) viewEnvironment + ModalArea(bounds) else viewEnvironment - - dialogs.update(newScreen.modals, dialogsEnv) + dialogs.update(newScreen.overlays, viewEnvironment) { env -> + baseViewStub.show(newScreen.body, env) + } } override fun onAttachedToWindow() { - super.onAttachedToWindow() - boundsListener.onGlobalLayout() - viewTreeObserver.addOnGlobalLayoutListener(boundsListener) + // I tried to move this to the attachStateChangeListener in LayeredDialogs.Companion.forView, + // but that fires too late and we crash with the dreaded + // "You can consumeRestoredStateForKey only after super.onCreate of corresponding component". + super.onAttachedToWindow() // Wire up dialogs to our parent SavedStateRegistry. - val parentRegistryOwner = WorkflowAndroidXSupport.stateRegistryOwnerFromViewTreeOrContext(this) - dialogs.attachToParentRegistryOwner(savedStateParentKey, parentRegistryOwner) + dialogs.onAttachedToWindow(savedStateParentKey, this) } override fun onDetachedFromWindow() { // Disconnect dialogs from our parent SavedStateRegistry so that it doesn't get asked // to save state anymore. - dialogs.detachFromParentRegistry() - // Don't leak the dialogs if we're suddenly yanked out of view. - // https://github.com/square/workflow-kotlin/issues/314 - dialogs.update(emptyList(), ViewEnvironment.EMPTY) - viewTreeObserver.removeOnGlobalLayoutListener(boundsListener) - bounds.value = Rect() + dialogs.onDetachedFromWindow() + super.onDetachedFromWindow() } override fun dispatchTouchEvent(event: MotionEvent): Boolean { - return !allowEvents || super.dispatchTouchEvent(event) + return !dialogs.allowEvents || super.dispatchTouchEvent(event) } override fun dispatchKeyEvent(event: KeyEvent): Boolean { - return !allowEvents || super.dispatchKeyEvent(event) + return !dialogs.allowEvents || super.dispatchKeyEvent(event) } override fun onSaveInstanceState(): Parcelable { diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/CoveredByModal.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/CoveredByModal.kt index db771dccc1..84304d5098 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/CoveredByModal.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/CoveredByModal.kt @@ -5,7 +5,7 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi /** * True in views managed by [BodyAndModalsScreen] when their events are being blocked - * by a modal [Overlay]. + * by a [ModalOverlay]. */ @WorkflowUiExperimentalApi internal object CoveredByModal : ViewEnvironmentKey(type = Boolean::class) { diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/DialogHolder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/DialogHolder.kt index cce198f90f..49ea7803eb 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/DialogHolder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/DialogHolder.kt @@ -22,15 +22,12 @@ import com.squareup.workflow1.ui.compatible /** * Used by [LayeredDialogs] to keep a [Dialog] tied to its [rendering] and [environment]. - * - * @param modal if true, ignore keyboard and touch events when [CoveredByModal] is also true */ @WorkflowUiExperimentalApi internal class DialogHolder( initialRendering: T, initialViewEnvironment: ViewEnvironment, index: Int, - private val modal: Boolean, private val context: Context, private val factory: OverlayDialogFactory ) { @@ -42,9 +39,11 @@ internal class DialogHolder( var environment: ViewEnvironment = initialViewEnvironment private set + private val modal = initialRendering is ModalOverlay + private var dialogOrNull: Dialog? = null - // Note similar code in BodyAndModalsContainer + // Note similar code in LayeredDialogs private var allowEvents = true set(value) { val was = field diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/LayeredDialogs.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/LayeredDialogs.kt index 3b885d0c77..d7d4bcc9c2 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/LayeredDialogs.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/LayeredDialogs.kt @@ -1,18 +1,25 @@ package com.squareup.workflow1.ui.container import android.content.Context +import android.graphics.Rect import android.os.Parcel import android.os.Parcelable import android.os.Parcelable.Creator +import android.view.MotionEvent import android.view.View +import android.view.View.OnAttachStateChangeListener +import android.view.ViewTreeObserver.OnGlobalLayoutListener import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.ViewTreeLifecycleOwner -import androidx.savedstate.SavedStateRegistryOwner +import com.squareup.workflow1.ui.Compatible import com.squareup.workflow1.ui.ViewEnvironment import com.squareup.workflow1.ui.WorkflowUiExperimentalApi +import com.squareup.workflow1.ui.androidx.WorkflowAndroidXSupport import com.squareup.workflow1.ui.androidx.WorkflowLifecycleOwner import com.squareup.workflow1.ui.androidx.WorkflowSavedStateRegistryAggregator import com.squareup.workflow1.ui.container.DialogHolder.KeyAndBundle +import kotlinx.coroutines.flow.MutableStateFlow +import kotlinx.coroutines.flow.StateFlow /** * Does the bulk of the work of maintaining a set of [Dialog][android.app.Dialog]s @@ -20,33 +27,25 @@ import com.squareup.workflow1.ui.container.DialogHolder.KeyAndBundle * layouts if [BodyAndModalsScreen] or the default [View] bound to it are too restrictive. * Provides a [LifecycleOwner] per managed dialog, and view persistence support. * - * @param modal When true, only the top-most dialog is allowed to process touch and key events + * @param bounds made available to managed dialogs via the [OverlayArea] + * [ViewEnvironmentKey][com.squareup.workflow1.ui.ViewEnvironmentKey], + * which drives [ModalScreenOverlayDialogFactory.updateBounds]. + * + * @param cancelEvents function to be called when a modal session starts -- that is, + * when [update] is first called with a [ModalOverlay] member, or called again with + * one after calls with none. + * + * @param getParentLifecycleOwner provides the [LifecycleOwner] to serve as + * an ancestor to those created for managed [Dialog][android.app.Dialog]s. + * */ @WorkflowUiExperimentalApi -public class LayeredDialogs( +public class LayeredDialogs private constructor( private val context: Context, - private val modal: Boolean, + private val bounds: StateFlow, + private val cancelEvents: () -> Unit, private val getParentLifecycleOwner: () -> LifecycleOwner ) { - /** - * Builds a [LayeredDialogs] which looks through [view] to find its parent - * [LifecycleOwner][getParentLifecycleOwner]. - * - * @param modal When true, only the top-most dialog is allowed to process touch and key events - */ - public constructor( - view: View, - modal: Boolean - ) : this( - context = view.context, - modal = modal, - getParentLifecycleOwner = { - checkNotNull(ViewTreeLifecycleOwner.get(view)) { - "Expected a ViewTreeLifecycleOwner on $view" - } - } - ) - /** * Provides a new `ViewTreeSavedStateRegistryOwner` for each dialog, * which will save to the `ViewTreeSavedStateRegistryOwner` of this container view. @@ -55,8 +54,12 @@ public class LayeredDialogs( private var holders: List> = emptyList() - /** True when any dialogs are visible, or becoming visible. */ - public val hasDialogs: Boolean = holders.isNotEmpty() + public var allowEvents: Boolean = true + private set(value) { + val was = field + field = value + if (value != was) cancelEvents() + } /** * Updates the managed set of [Dialog][android.app.Dialog] instances to reflect @@ -67,21 +70,40 @@ public class LayeredDialogs( * is shown, and is destroyed when it is dismissed. Views nested in a managed dialog * can use [ViewTreeLifecycleOwner][androidx.lifecycle.ViewTreeLifecycleOwner] as * usual. + * + * @param updateBase function to be called before updating the dialogs. + * The [ViewEnvironment] passed to that function is enhanced with a [CoveredByModal] + * as appropriate, to ensure proper behavior of [allowEvents] of nested containers. */ public fun update( overlays: List, - viewEnvironment: ViewEnvironment + viewEnvironment: ViewEnvironment, + updateBase: (environment: ViewEnvironment) -> Unit ) { + val modalIndex = overlays.indexOfFirst { it is ModalOverlay } + val showingModals = modalIndex > -1 + + allowEvents = !showingModals + + updateBase( + if (showingModals) viewEnvironment + (CoveredByModal to true) else viewEnvironment + ) + + val envPlusBounds = viewEnvironment + OverlayArea(bounds) + // On each update we build a new list of the running dialogs, both the // existing ones and any new ones. We need this so that we can compare // it with the previous list, and see what dialogs close. val newHolders = mutableListOf>() for ((i, overlay) in overlays.withIndex()) { + val dialogEnv = + if (i < modalIndex) envPlusBounds + (CoveredByModal to true) else envPlusBounds + newHolders += if (i < holders.size && holders[i].canTakeRendering(overlay)) { // There is already a dialog at this index, and it is compatible // with the new Overlay at that index. Just update it. - holders[i].also { it.takeRendering(overlay, viewEnvironment) } + holders[i].also { it.takeRendering(overlay, dialogEnv) } } else { // We need a new dialog for this overlay. Time to build it. // We wrap our Dialog instances in DialogHolder to keep them @@ -90,11 +112,9 @@ public class LayeredDialogs( // decor view, more consistent with what ScreenViewFactory does, // but calling Window.getDecorView has side effects, and things // break if we call it to early. Need to store them somewhere else. - overlay.toDialogFactory(viewEnvironment).let { dialogFactory -> - DialogHolder( - overlay, viewEnvironment, i, modal, context, dialogFactory - ).also { newHolder -> - newHolder.takeRendering(overlay, viewEnvironment) + overlay.toDialogFactory(dialogEnv).let { dialogFactory -> + DialogHolder(overlay, dialogEnv, i, context, dialogFactory).also { newHolder -> + newHolder.takeRendering(overlay, dialogEnv) // Show the dialog, creating it if necessary. newHolder.show(getParentLifecycleOwner(), stateRegistryAggregator) @@ -115,20 +135,29 @@ public class LayeredDialogs( /** * Must be called whenever the owning view is [attached to a window][View.onAttachedToWindow]. - * Must eventually be matched with a call to [detachFromParentRegistry]. + * Must eventually be matched with a call to [onDetachedFromWindow]. + * + * @param savedStateParentKey Unique identifier for this view for SavedStateRegistry purposes. + * Typically based on the [Compatible.keyFor] the current rendering. Taking this approach + * allows feature developers to take control over naming, e.g. by wrapping renderings + * with [NamedScreen][com.squareup.workflow1.ui.NamedScreen]. */ - public fun attachToParentRegistryOwner( - key: String, - parentOwner: SavedStateRegistryOwner + public fun onAttachedToWindow( + savedStateParentKey: String, + view: View ) { - stateRegistryAggregator.attachToParentRegistry(key, parentOwner) + stateRegistryAggregator.attachToParentRegistry( + savedStateParentKey, + WorkflowAndroidXSupport.stateRegistryOwnerFromViewTreeOrContext(view) + ) } /** - * Must be called whenever the owning view is [detached from a window][View.onDetachedFromWindow]. - * Must be matched with a call to [attachToParentRegistryOwner]. + * Must be called whenever the owning view is + * [detached from a window][View.onDetachedFromWindow]. + * Must be matched with a call to [onAttachedToWindow]. */ - public fun detachFromParentRegistry() { + public fun onDetachedFromWindow() { stateRegistryAggregator.detachFromParentRegistry() } @@ -173,4 +202,80 @@ public class LayeredDialogs( override fun newArray(size: Int): Array = arrayOfNulls(size) } } + + public companion object { + /** + * Creates a [LayeredDialogs] instance based on the given [view], which will + * serve as the source for a [LifecycleOwner], and whose bounds will be reported + * via [OverlayArea]. + * + * - The [view]'s [dispatchTouchEvent][View.dispatchTouchEvent] and + * [dispatchKeyEvent][View.dispatchKeyEvent] methods should be overridden + * to honor [LayeredDialogs.allowEvents]. + * + * - The [view]'s [onAttachedToWindow][View.onAttachedToWindow] and + * [onDetachedFromWindow][View.onDetachedFromWindow] methods must call + * through to the like named methods of the returned [LayeredDialogs] + * ([onAttachedToWindow], [onDetachedFromWindow]). + * + * - The [view]'s [onSaveInstanceState][View.onSaveInstanceState] and + * [onRestoreInstanceState][View.onRestoreInstanceState] methods must call + * through to the like named methods of the returned [LayeredDialogs] + * ([onSaveInstanceState], [onRestoreInstanceState]). + */ + public fun forView( + view: View, + superDispatchTouchEvent: (MotionEvent) -> Unit + ): LayeredDialogs { + val boundsRect = Rect() + if (view.isAttachedToWindow) view.getGlobalVisibleRect(boundsRect) + val bounds = MutableStateFlow(Rect(boundsRect)) + + return LayeredDialogs( + context = view.context, + bounds = bounds, + cancelEvents = { + // Note similar code in DialogHolder. + + // https://stackoverflow.com/questions/2886407/dealing-with-rapid-tapping-on-buttons + // If any motion events were enqueued on the main thread, cancel them. + dispatchCancelEvent { superDispatchTouchEvent(it) } + // When we cancel, have to warn things like RecyclerView that handle streams + // of motion events and eventually dispatch input events (click, key pressed, etc.) + // based on them. + view.cancelPendingInputEvents() + } + ) { + checkNotNull(ViewTreeLifecycleOwner.get(view)) { + "Expected a ViewTreeLifecycleOwner on $view" + } + }.also { dialogs -> + + val boundsListener = OnGlobalLayoutListener { + if (view.getGlobalVisibleRect(boundsRect) && boundsRect != bounds.value) { + bounds.value = Rect(boundsRect) + } + // Should we close the dialogs if getGlobalVisibleRect returns false? + // https://github.com/square/workflow-kotlin/issues/599 + } + + val attachStateChangeListener = object : OnAttachStateChangeListener { + override fun onViewAttachedToWindow(v: View) { + boundsListener.onGlobalLayout() + v.viewTreeObserver.addOnGlobalLayoutListener(boundsListener) + } + + override fun onViewDetachedFromWindow(v: View) { + // Don't leak the dialogs if we're suddenly yanked out of view. + // https://github.com/square/workflow-kotlin/issues/314 + dialogs.update(emptyList(), ViewEnvironment.EMPTY) {} + v.viewTreeObserver.removeOnGlobalLayoutListener(boundsListener) + bounds.value = Rect() + } + } + + view.addOnAttachStateChangeListener(attachStateChangeListener) + } + } + } } 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 875aeff39e..78ef56d21d 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 @@ -24,14 +24,13 @@ import com.squareup.workflow1.ui.toViewFactory import kotlin.reflect.KClass /** - * Default [OverlayDialogFactory] for [ModalScreenOverlay]. - * - * This class is non-final for ease of customization of [ModalScreenOverlay] handling, - * see [OverlayDialogFactoryFinder] for details. It is also convenient to use as a - * base class for custom [ScreenOverlay] rendering types. + * Extensible base implementation of [OverlayDialogFactory] for [ScreenOverlay] + * types. Also serves as the default factory for [ModalScreenOverlay]. + * (See [OverlayDialogFactoryFinder] for guidance on customizing the presentation + * of [ModalScreenOverlay].) * * Dialogs built by this class are compatible with [View.backPressedHandler], and - * honor the [ModalArea] constraint placed in the [ViewEnvironment] by the + * honor the [OverlayArea] constraint placed in the [ViewEnvironment] by the * standard [BodyAndModalsScreen] container. * * Ironically, [Dialog] instances are created with [FLAG_NOT_TOUCH_MODAL], to ensure @@ -39,6 +38,7 @@ import kotlin.reflect.KClass * lower windows. See that method for details. */ @WorkflowUiExperimentalApi +// TODO: Rename ScreenOverlayDialogFactory public open class ModalScreenOverlayDialogFactory>( override val type: KClass ) : OverlayDialogFactory { @@ -56,17 +56,16 @@ public open class ModalScreenOverlayDialogFactory>( } /** - * If the [ScreenOverlay] displayed by a [dialog] created by this - * factory is contained in a [BodyAndModalsScreen], this method will - * be called to report the bounds of the managing view, as reported by [ModalArea]. - * It is expected that such a dialog will be restricted to those bounds. + * This method will be called to report the bounds of the managing container view, + * as reported by [OverlayArea]. Well behaved [ScreenOverlay] dialogs are expected to + * be restricted to those bounds. * * Honoring this contract makes it easy to define areas of the display * that are outside of the "shadow" of a modal dialog. Imagine an app * with a status bar that should not be covered by modals. * - * The default implementation calls straight through to [Dialog.setBounds]. - * Custom implementations are not required to call `super`. + * The default implementation calls straight through to the provided [Dialog.setBounds] + * function. Custom implementations are not required to call `super`. * * @see Dialog.setBounds */ @@ -109,8 +108,8 @@ public open class ModalScreenOverlayDialogFactory>( event.action == ACTION_UP return when { - isBackPress -> contentViewHolder.environment.get(ModalScreenOverlayOnBackPressed) - .onBackPressed(contentViewHolder.view) == true + isBackPress -> contentViewHolder.environment[ModalScreenOverlayOnBackPressed] + .onBackPressed(contentViewHolder.view) else -> realWindowCallback.dispatchKeyEvent(event) } } diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalArea.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/OverlayArea.kt similarity index 56% rename from workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalArea.kt rename to workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/OverlayArea.kt index fefa29336f..07cac705cf 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalArea.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/OverlayArea.kt @@ -8,18 +8,19 @@ import kotlinx.coroutines.flow.MutableStateFlow import kotlinx.coroutines.flow.StateFlow /** - * Reports the the area of the screen whose events should be blocked by any modal [Overlay]. + * Reports the the area of the screen whose events should be blocked by any modal [Overlay], + * in the style reported by [View.getGlobalVisibleRect][android.view.View.getGlobalVisibleRect]. * Expected to be supplied by containers that support [BodyAndModalsScreen]. */ @WorkflowUiExperimentalApi -internal class ModalArea( +internal class OverlayArea( val bounds: StateFlow ) { - companion object : ViewEnvironmentKey(type = ModalArea::class) { - override val default: ModalArea = ModalArea(MutableStateFlow(Rect())) + companion object : ViewEnvironmentKey(type = OverlayArea::class) { + override val default: OverlayArea = OverlayArea(MutableStateFlow(Rect())) } } @WorkflowUiExperimentalApi -internal operator fun ViewEnvironment.plus(modalArea: ModalArea): ViewEnvironment = - this + (ModalArea to modalArea) +internal operator fun ViewEnvironment.plus(overlayArea: OverlayArea): ViewEnvironment = + this + (OverlayArea to overlayArea) diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/AlertOverlay.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/AlertOverlay.kt index c6576fb43e..d2018ed3d2 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/AlertOverlay.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/AlertOverlay.kt @@ -14,7 +14,7 @@ public data class AlertOverlay( val title: String = "", val cancelable: Boolean = true, val onEvent: (Event) -> Unit -) : Overlay { +) : ModalOverlay { public enum class Button { POSITIVE, NEGATIVE, diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsScreen.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsScreen.kt index 1839abb452..39060e8d2b 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsScreen.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsScreen.kt @@ -4,27 +4,29 @@ import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi /** - * A screen that may stack a number of modal [Overlay]s over a body. - * While modals are present, the body is expected to ignore any - * input events -- touch, keyboard, etc. + * A screen that may stack a number of [Overlay]s over a body. + * If any members of [overlays] are [ModalOverlay], the body and + * lower-indexed members of that list are expected to ignore input + * events -- touch, keyboard, etc. * * UI kits are expected to provide handling for this class by default. */ @WorkflowUiExperimentalApi -public class BodyAndModalsScreen( +// TODO rename this BodyAndOverlaysScreen +public class BodyAndModalsScreen( public val body: B, - public val modals: List = emptyList() + public val overlays: List = emptyList() ) : Screen { public constructor( body: B, - vararg modals: M + vararg modals: O ) : this(body, modals.toList()) - public fun mapBody(transform: (B) -> S): BodyAndModalsScreen { - return BodyAndModalsScreen(transform(body), modals) + public fun mapBody(transform: (B) -> S): BodyAndModalsScreen { + return BodyAndModalsScreen(transform(body), overlays) } - public fun mapModals(transform: (M) -> O): BodyAndModalsScreen { - return BodyAndModalsScreen(body, modals.map(transform)) + public fun mapModals(transform: (O) -> N): BodyAndModalsScreen { + return BodyAndModalsScreen(body, overlays.map(transform)) } } diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ModalOverlay.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ModalOverlay.kt new file mode 100644 index 0000000000..9b0cceeb7e --- /dev/null +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ModalOverlay.kt @@ -0,0 +1,10 @@ +package com.squareup.workflow1.ui.container + +import com.squareup.workflow1.ui.WorkflowUiExperimentalApi + +/** + * Marker interface identifying [Overlay] renderings whose presence + * indicates that events are blocked from lower layers. + */ +@WorkflowUiExperimentalApi +public interface ModalOverlay : Overlay diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlay.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlay.kt index 2591ecca61..e7f65e6e3a 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlay.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlay.kt @@ -4,10 +4,11 @@ import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi /** - * A basic [ScreenOverlay] that fills all available space. + * A basic [ScreenOverlay] that covers its container with the wrapped [content] [Screen]. * * UI kits are expected to provide handling for this class by default. */ +// TODO: rename this FullScreenOverlay @WorkflowUiExperimentalApi public class ModalScreenOverlay( public override val content: ContentT diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/Overlay.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/Overlay.kt index a8311ec637..282635903c 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/Overlay.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/Overlay.kt @@ -6,13 +6,11 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi * Marker interface implemented by window-like renderings that map to a layer above * a base [Screen][com.squareup.workflow1.ui.Screen]. * - * Note that an Overlay is not necessarily a modal window, though that is how - * they are used in [BodyAndModalsScreen]. An Overlay can be any part of the UI - * that visually floats in a layer above the main UI, or above other Overlays. - * Possible examples include alerts, drawers, and tooltips. + * An Overlay can be any part of the UI that visually floats in a layer above the main UI, + * or above other Overlays. Possible examples include alerts, drawers, and tooltips. * - * Whether or not an Overlay's presence indicates that events are blocked from lower - * layers is a separate concern. + * Note in particular that an Overlay is not necessarily a modal window. + * Rendering types can opt into modality by extending [ModalOverlay]. */ @WorkflowUiExperimentalApi public interface Overlay From c5446a93cb74ceec56398a6b92ceb8658b2b0a5d Mon Sep 17 00:00:00 2001 From: Ray Ryan Date: Fri, 10 Jun 2022 17:17:27 -0700 Subject: [PATCH 2/2] Renames in wake of introduction of `ModalOverlay`. --- .../poetry/views/MayBeLoadingScreen.kt | 10 +-- .../panel/PanelOverlayDialogFactory.kt | 4 +- .../hellobackbutton/AreYouSureWorkflow.kt | 10 +-- .../sample/mainworkflow/TicTacToeWorkflow.kt | 14 +-- .../mainworkflow/TicTacToeWorkflowTest.kt | 4 +- .../compose/ComposeViewTreeIntegrationTest.kt | 16 ++-- .../ui/modal/AlertContainerScreen.kt | 2 +- .../squareup/workflow1/ui/modal/HasModals.kt | 2 +- workflow-ui/core-android/api/core-android.api | 88 ++++++++++--------- .../workflow1/ui/ScreenViewFactoryFinder.kt | 8 +- .../ui/container/AndroidDialogBounds.kt | 2 +- ...ntainer.kt => BodyAndOverlaysContainer.kt} | 12 +-- .../workflow1/ui/container/CoveredByModal.kt | 2 +- .../workflow1/ui/container/LayeredDialogs.kt | 4 +- .../ModalScreenOverlayOnBackPressed.kt | 4 +- .../workflow1/ui/container/OverlayArea.kt | 2 +- .../container/OverlayDialogFactoryFinder.kt | 10 +-- ...ctory.kt => ScreenOverlayDialogFactory.kt} | 11 ++- workflow-ui/core-common/api/core-common.api | 15 ++-- ...dalsScreen.kt => BodyAndOverlaysScreen.kt} | 11 ++- ...lScreenOverlay.kt => FullScreenOverlay.kt} | 3 +- 21 files changed, 120 insertions(+), 114 deletions(-) rename workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/{BodyAndModalsContainer.kt => BodyAndOverlaysContainer.kt} (90%) rename workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/{ModalScreenOverlayDialogFactory.kt => ScreenOverlayDialogFactory.kt} (94%) rename workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/{BodyAndModalsScreen.kt => BodyAndOverlaysScreen.kt} (68%) rename workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/{ModalScreenOverlay.kt => FullScreenOverlay.kt} (81%) diff --git a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt index b2479edf56..d16777d99b 100644 --- a/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt +++ b/benchmarks/performance-poetry/complex-poetry/src/main/java/com/squareup/benchmarks/performance/complex/poetry/views/MayBeLoadingScreen.kt @@ -3,21 +3,21 @@ package com.squareup.benchmarks.performance.complex.poetry.views import com.squareup.sample.container.overviewdetail.OverviewDetailScreen import com.squareup.sample.container.panel.ScrimScreen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.container.BodyAndModalsScreen -import com.squareup.workflow1.ui.container.ModalScreenOverlay +import com.squareup.workflow1.ui.container.BodyAndOverlaysScreen +import com.squareup.workflow1.ui.container.FullScreenOverlay @OptIn(WorkflowUiExperimentalApi::class) typealias MayBeLoadingScreen = - BodyAndModalsScreen, ModalScreenOverlay> + BodyAndOverlaysScreen, FullScreenOverlay> @OptIn(WorkflowUiExperimentalApi::class) fun MayBeLoadingScreen( baseScreen: OverviewDetailScreen, loaders: List = emptyList() ): MayBeLoadingScreen { - return BodyAndModalsScreen( + return BodyAndOverlaysScreen( ScrimScreen(baseScreen, dimmed = loaders.isNotEmpty()), - loaders.map { ModalScreenOverlay(it) } + loaders.map { FullScreenOverlay(it) } ) } 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 8541e6706d..5aac15b566 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 @@ -5,14 +5,14 @@ import android.graphics.Rect import android.view.View import com.squareup.sample.container.R import com.squareup.workflow1.ui.WorkflowUiExperimentalApi -import com.squareup.workflow1.ui.container.ModalScreenOverlayDialogFactory +import com.squareup.workflow1.ui.container.ScreenOverlayDialogFactory import com.squareup.workflow1.ui.container.setModalContent /** * Android support for [PanelOverlay]. */ @OptIn(WorkflowUiExperimentalApi::class) -internal object PanelOverlayDialogFactory : ModalScreenOverlayDialogFactory>( +internal object PanelOverlayDialogFactory : ScreenOverlayDialogFactory>( type = PanelOverlay::class ) { /** diff --git a/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/AreYouSureWorkflow.kt b/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/AreYouSureWorkflow.kt index cddd3421ff..0bf418de74 100644 --- a/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/AreYouSureWorkflow.kt +++ b/samples/containers/hello-back-button/src/main/java/com/squareup/sample/hellobackbutton/AreYouSureWorkflow.kt @@ -16,7 +16,7 @@ import com.squareup.workflow1.ui.container.AlertOverlay.Button.POSITIVE import com.squareup.workflow1.ui.container.AlertOverlay.Event.ButtonClicked import com.squareup.workflow1.ui.container.AlertOverlay.Event.Canceled import com.squareup.workflow1.ui.container.BackButtonScreen -import com.squareup.workflow1.ui.container.BodyAndModalsScreen +import com.squareup.workflow1.ui.container.BodyAndOverlaysScreen import com.squareup.workflow1.ui.toParcelable import com.squareup.workflow1.ui.toSnapshot import kotlinx.parcelize.Parcelize @@ -27,7 +27,7 @@ import kotlinx.parcelize.Parcelize */ @OptIn(WorkflowUiExperimentalApi::class) object AreYouSureWorkflow : - StatefulWorkflow>() { + StatefulWorkflow>() { override fun initialState( props: Unit, snapshot: Snapshot? @@ -45,12 +45,12 @@ object AreYouSureWorkflow : renderProps: Unit, renderState: State, context: RenderContext - ): BodyAndModalsScreen<*, AlertOverlay> { + ): BodyAndOverlaysScreen<*, AlertOverlay> { val ableBakerCharlie = context.renderChild(HelloBackButtonWorkflow, Unit) { noAction() } return when (renderState) { Running -> { - BodyAndModalsScreen( + BodyAndOverlaysScreen( BackButtonScreen(ableBakerCharlie) { // While we always provide a back button handler, by default the view code // associated with BackButtonScreen ignores ours if the view created for the @@ -80,7 +80,7 @@ object AreYouSureWorkflow : } ) - BodyAndModalsScreen(ableBakerCharlie, alert) + BodyAndOverlaysScreen(ableBakerCharlie, alert) } } } diff --git a/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt b/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt index 41db01aa78..4db026a4cb 100644 --- a/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt +++ b/samples/tictactoe/common/src/main/java/com/squareup/sample/mainworkflow/TicTacToeWorkflow.kt @@ -21,7 +21,7 @@ import com.squareup.workflow1.action import com.squareup.workflow1.renderChild import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.BackStackScreen -import com.squareup.workflow1.ui.container.BodyAndModalsScreen +import com.squareup.workflow1.ui.container.BodyAndOverlaysScreen /** * Application specific root [Workflow], and demonstration of workflow composition. @@ -45,7 +45,7 @@ import com.squareup.workflow1.ui.container.BodyAndModalsScreen class TicTacToeWorkflow( private val authWorkflow: AuthWorkflow, private val runGameWorkflow: RunGameWorkflow -) : StatefulWorkflow, *>>() { +) : StatefulWorkflow, *>>() { override fun initialState( props: Unit, @@ -57,8 +57,8 @@ class TicTacToeWorkflow( renderProps: Unit, renderState: MainState, context: RenderContext - ): BodyAndModalsScreen, *> { - val bodyAndOverlays: BodyAndModalsScreen<*, *> = when (renderState) { + ): BodyAndOverlaysScreen, *> { + val bodyAndOverlays: BodyAndOverlaysScreen<*, *> = when (renderState) { is Authenticating -> { val authBackStack = context.renderChild(authWorkflow) { handleAuthResult(it) } // We always show an empty GameScreen behind the PanelOverlay that @@ -68,14 +68,14 @@ class TicTacToeWorkflow( // cheat is probably the most realistic thing about this sample. val emptyGameScreen = GamePlayScreen() - BodyAndModalsScreen(emptyGameScreen, PanelOverlay(authBackStack)) + BodyAndOverlaysScreen(emptyGameScreen, PanelOverlay(authBackStack)) } is RunningGame -> { val gameRendering = context.renderChild(runGameWorkflow) { startAuth } if (gameRendering.namePrompt == null) { - BodyAndModalsScreen(gameRendering.gameScreen, gameRendering.alerts) + BodyAndOverlaysScreen(gameRendering.gameScreen, gameRendering.alerts) } else { // To prompt for player names, the child puts up a ScreenOverlay, which // we replace here with a tasteful PanelOverlay. @@ -95,7 +95,7 @@ class TicTacToeWorkflow( BackStackScreen(gameRendering.namePrompt.content) val allModals = listOf(PanelOverlay(fullBackStack)) + gameRendering.alerts - BodyAndModalsScreen(gameRendering.gameScreen, allModals) + BodyAndOverlaysScreen(gameRendering.gameScreen, allModals) } } } diff --git a/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt b/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt index 09f032d339..aa161b8a14 100644 --- a/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt +++ b/samples/tictactoe/common/src/test/java/com/squareup/sample/mainworkflow/TicTacToeWorkflowTest.kt @@ -18,7 +18,7 @@ import com.squareup.workflow1.testing.launchForTestingFromStartWith import com.squareup.workflow1.ui.Screen import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.BackStackScreen -import com.squareup.workflow1.ui.container.BodyAndModalsScreen +import com.squareup.workflow1.ui.container.BodyAndOverlaysScreen import org.junit.Test /** @@ -63,7 +63,7 @@ class TicTacToeWorkflowTest { private fun authScreen(wrapped: String = DEFAULT_AUTH) = BackStackScreen(S(wrapped)) - private val BodyAndModalsScreen, *>.panels: List> + private val BodyAndOverlaysScreen, *>.panels: List> get() = overlays.mapNotNull { it as? PanelOverlay<*> } private fun authWorkflow( diff --git a/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/ComposeViewTreeIntegrationTest.kt b/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/ComposeViewTreeIntegrationTest.kt index 601f11ed25..6a0b8ec105 100644 --- a/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/ComposeViewTreeIntegrationTest.kt +++ b/workflow-ui/compose/src/androidTest/java/com/squareup/workflow1/ui/compose/ComposeViewTreeIntegrationTest.kt @@ -35,9 +35,9 @@ import com.squareup.workflow1.ui.ViewRegistry import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.container.AndroidOverlay import com.squareup.workflow1.ui.container.BackStackScreen -import com.squareup.workflow1.ui.container.BodyAndModalsScreen -import com.squareup.workflow1.ui.container.ModalScreenOverlayDialogFactory +import com.squareup.workflow1.ui.container.BodyAndOverlaysScreen import com.squareup.workflow1.ui.container.ScreenOverlay +import com.squareup.workflow1.ui.container.ScreenOverlayDialogFactory import com.squareup.workflow1.ui.internal.test.DetectLeaksAfterTestSuccess import com.squareup.workflow1.ui.internal.test.IdleAfterTestRule import com.squareup.workflow1.ui.internal.test.IdlingDispatcherRule @@ -371,7 +371,7 @@ internal class ComposeViewTreeIntegrationTest { // Show first screen to initialize state. scenario.onActivity { it.setRendering( - BodyAndModalsScreen( + BodyAndOverlaysScreen( EmptyRendering, TestModal(BackStackScreen(EmptyRendering, firstScreen)) ) ) @@ -426,7 +426,7 @@ internal class ComposeViewTreeIntegrationTest { // Show first screen to initialize state. scenario.onActivity { it.setRendering( - BodyAndModalsScreen( + BodyAndOverlaysScreen( EmptyRendering, TestModal(firstScreen), TestModal(secondScreen), TestModal(thirdScreen) ) ) @@ -485,7 +485,7 @@ internal class ComposeViewTreeIntegrationTest { // Show first screen to initialize state. scenario.onActivity { it.setRendering( - BodyAndModalsScreen( + BodyAndOverlaysScreen( EmptyRendering, TestModal(BackStackScreen(EmptyRendering, layer0Screen0)), TestModal(BackStackScreen(EmptyRendering, layer1Screen0)), @@ -508,7 +508,7 @@ internal class ComposeViewTreeIntegrationTest { // Push some screens onto the backstack. scenario.onActivity { it.setRendering( - BodyAndModalsScreen( + BodyAndOverlaysScreen( EmptyRendering, TestModal(BackStackScreen(EmptyRendering, layer0Screen0, layer0Screen1)), TestModal(BackStackScreen(EmptyRendering, layer1Screen0, layer1Screen1)), @@ -545,7 +545,7 @@ internal class ComposeViewTreeIntegrationTest { // Pop both backstacks and check that screens were restored. scenario.onActivity { it.setRendering( - BodyAndModalsScreen( + BodyAndOverlaysScreen( EmptyRendering, TestModal(BackStackScreen(EmptyRendering, layer0Screen0)), TestModal(BackStackScreen(EmptyRendering, layer1Screen0)), @@ -566,7 +566,7 @@ internal class ComposeViewTreeIntegrationTest { data class TestModal( override val content: Screen ) : ScreenOverlay, AndroidOverlay { - override val dialogFactory = object : ModalScreenOverlayDialogFactory( + override val dialogFactory = object : ScreenOverlayDialogFactory( TestModal::class ) { override fun buildDialogWithContentView(contentView: View): Dialog { diff --git a/workflow-ui/container-common/src/main/java/com/squareup/workflow1/ui/modal/AlertContainerScreen.kt b/workflow-ui/container-common/src/main/java/com/squareup/workflow1/ui/modal/AlertContainerScreen.kt index 4e2f717d9b..ed26c00178 100644 --- a/workflow-ui/container-common/src/main/java/com/squareup/workflow1/ui/modal/AlertContainerScreen.kt +++ b/workflow-ui/container-common/src/main/java/com/squareup/workflow1/ui/modal/AlertContainerScreen.kt @@ -7,7 +7,7 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi /** * **This will be deprecated in favor of * [AlertOverlay][com.squareup.workflow1.ui.container.AlertOverlay] and - * [BodyAndModalsScreen][com.squareup.workflow1.ui.container.BodyAndModalsScreen] + * [BodyAndModalsScreen][com.squareup.workflow1.ui.container.BodyAndOverlaysScreen] * very soon.** * * May show a stack of [AlertScreen] over a [beneathModals]. diff --git a/workflow-ui/container-common/src/main/java/com/squareup/workflow1/ui/modal/HasModals.kt b/workflow-ui/container-common/src/main/java/com/squareup/workflow1/ui/modal/HasModals.kt index 57dcf1590c..4bf4deae83 100644 --- a/workflow-ui/container-common/src/main/java/com/squareup/workflow1/ui/modal/HasModals.kt +++ b/workflow-ui/container-common/src/main/java/com/squareup/workflow1/ui/modal/HasModals.kt @@ -4,7 +4,7 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi /** * **This will be deprecated in favor of - * [BodyAndModalsScreen][com.squareup.workflow1.ui.container.BodyAndModalsScreen] + * [BodyAndModalsScreen][com.squareup.workflow1.ui.container.BodyAndOverlaysScreen] * very soon.** * * Interface implemented by screen classes that represent a stack of diff --git a/workflow-ui/core-android/api/core-android.api b/workflow-ui/core-android/api/core-android.api index 85188f9988..34d5f60f45 100644 --- a/workflow-ui/core-android/api/core-android.api +++ b/workflow-ui/core-android/api/core-android.api @@ -511,8 +511,8 @@ public final class com/squareup/workflow1/ui/container/BackStackScreenViewFactor public fun getType ()Lkotlin/reflect/KClass; } -public final class com/squareup/workflow1/ui/container/BodyAndModalsContainer : android/widget/FrameLayout { - public static final field Companion Lcom/squareup/workflow1/ui/container/BodyAndModalsContainer$Companion; +public final class com/squareup/workflow1/ui/container/BodyAndOverlaysContainer : android/widget/FrameLayout { + public static final field Companion Lcom/squareup/workflow1/ui/container/BodyAndOverlaysContainer$Companion; public fun (Landroid/content/Context;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;)V public fun (Landroid/content/Context;Landroid/util/AttributeSet;I)V @@ -520,12 +520,12 @@ public final class com/squareup/workflow1/ui/container/BodyAndModalsContainer : public synthetic fun (Landroid/content/Context;Landroid/util/AttributeSet;IIILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun dispatchKeyEvent (Landroid/view/KeyEvent;)Z public fun dispatchTouchEvent (Landroid/view/MotionEvent;)Z - public final fun update (Lcom/squareup/workflow1/ui/container/BodyAndModalsScreen;Lcom/squareup/workflow1/ui/ViewEnvironment;)V + public final fun update (Lcom/squareup/workflow1/ui/container/BodyAndOverlaysScreen;Lcom/squareup/workflow1/ui/ViewEnvironment;)V } -public final class com/squareup/workflow1/ui/container/BodyAndModalsContainer$Companion : com/squareup/workflow1/ui/ScreenViewFactory { +public final class com/squareup/workflow1/ui/container/BodyAndOverlaysContainer$Companion : com/squareup/workflow1/ui/ScreenViewFactory { public synthetic fun buildView (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Lcom/squareup/workflow1/ui/ScreenViewHolder; - public fun buildView (Lcom/squareup/workflow1/ui/container/BodyAndModalsScreen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Lcom/squareup/workflow1/ui/ScreenViewHolder; + public fun buildView (Lcom/squareup/workflow1/ui/container/BodyAndOverlaysScreen;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;Landroid/view/ViewGroup;)Lcom/squareup/workflow1/ui/ScreenViewHolder; public fun getType ()Lkotlin/reflect/KClass; } @@ -536,7 +536,7 @@ public final class com/squareup/workflow1/ui/container/CoveredByModal : com/squa } public final class com/squareup/workflow1/ui/container/DialogHolder { - public fun (Lcom/squareup/workflow1/ui/container/Overlay;Lcom/squareup/workflow1/ui/ViewEnvironment;IZLandroid/content/Context;Lcom/squareup/workflow1/ui/container/OverlayDialogFactory;)V + public fun (Lcom/squareup/workflow1/ui/container/Overlay;Lcom/squareup/workflow1/ui/ViewEnvironment;ILandroid/content/Context;Lcom/squareup/workflow1/ui/container/OverlayDialogFactory;)V public final fun canTakeRendering (Lcom/squareup/workflow1/ui/container/Overlay;)Z public final fun dismiss ()V public final fun getEnvironment ()Lcom/squareup/workflow1/ui/ViewEnvironment; @@ -587,14 +587,18 @@ public final class com/squareup/workflow1/ui/container/EnvironmentScreenViewFact } public final class com/squareup/workflow1/ui/container/LayeredDialogs { - public fun (Landroid/content/Context;ZLkotlin/jvm/functions/Function0;)V - public fun (Landroid/view/View;Z)V - public final fun attachToParentRegistryOwner (Ljava/lang/String;Landroidx/savedstate/SavedStateRegistryOwner;)V - public final fun detachFromParentRegistry ()V - public final fun getHasDialogs ()Z + public static final field Companion Lcom/squareup/workflow1/ui/container/LayeredDialogs$Companion; + public synthetic fun (Landroid/content/Context;Lkotlinx/coroutines/flow/StateFlow;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/functions/Function0;Lkotlin/jvm/internal/DefaultConstructorMarker;)V + public final fun getAllowEvents ()Z + public final fun onAttachedToWindow (Ljava/lang/String;Landroid/view/View;)V + public final fun onDetachedFromWindow ()V public final fun onRestoreInstanceState (Lcom/squareup/workflow1/ui/container/LayeredDialogs$SavedState;)V public final fun onSaveInstanceState ()Lcom/squareup/workflow1/ui/container/LayeredDialogs$SavedState; - public final fun update (Ljava/util/List;Lcom/squareup/workflow1/ui/ViewEnvironment;)V + public final fun update (Ljava/util/List;Lcom/squareup/workflow1/ui/ViewEnvironment;Lkotlin/jvm/functions/Function1;)V +} + +public final class com/squareup/workflow1/ui/container/LayeredDialogs$Companion { + public final fun forView (Landroid/view/View;Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/LayeredDialogs; } public final class com/squareup/workflow1/ui/container/LayeredDialogs$SavedState : android/os/Parcelable { @@ -613,36 +617,6 @@ public final class com/squareup/workflow1/ui/container/LayeredDialogs$SavedState public synthetic fun newArray (I)[Ljava/lang/Object; } -public final class com/squareup/workflow1/ui/container/ModalArea { - public static final field Companion Lcom/squareup/workflow1/ui/container/ModalArea$Companion; - public fun (Lkotlinx/coroutines/flow/StateFlow;)V - public final fun getBounds ()Lkotlinx/coroutines/flow/StateFlow; -} - -public final class com/squareup/workflow1/ui/container/ModalArea$Companion : com/squareup/workflow1/ui/ViewEnvironmentKey { - public fun getDefault ()Lcom/squareup/workflow1/ui/container/ModalArea; - public synthetic fun getDefault ()Ljava/lang/Object; -} - -public final class com/squareup/workflow1/ui/container/ModalAreaKt { - public static final fun plus (Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/container/ModalArea;)Lcom/squareup/workflow1/ui/ViewEnvironment; -} - -public class com/squareup/workflow1/ui/container/ModalScreenOverlayDialogFactory : com/squareup/workflow1/ui/container/OverlayDialogFactory { - public fun (Lkotlin/reflect/KClass;)V - public synthetic fun buildDialog (Lcom/squareup/workflow1/ui/container/Overlay;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;)Landroid/app/Dialog; - public final fun buildDialog (Lcom/squareup/workflow1/ui/container/ScreenOverlay;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;)Landroid/app/Dialog; - public fun buildDialogWithContentView (Landroid/view/View;)Landroid/app/Dialog; - public fun getType ()Lkotlin/reflect/KClass; - public fun updateBounds (Landroid/app/Dialog;Landroid/graphics/Rect;)V - public synthetic fun updateDialog (Landroid/app/Dialog;Lcom/squareup/workflow1/ui/container/Overlay;Lcom/squareup/workflow1/ui/ViewEnvironment;)V - public final fun updateDialog (Landroid/app/Dialog;Lcom/squareup/workflow1/ui/container/ScreenOverlay;Lcom/squareup/workflow1/ui/ViewEnvironment;)V -} - -public final class com/squareup/workflow1/ui/container/ModalScreenOverlayDialogFactoryKt { - public static final fun setModalContent (Landroid/app/Dialog;Landroid/view/View;)V -} - public abstract interface class com/squareup/workflow1/ui/container/ModalScreenOverlayOnBackPressed { public static final field Companion Lcom/squareup/workflow1/ui/container/ModalScreenOverlayOnBackPressed$Companion; public abstract fun onBackPressed (Landroid/view/View;)Z @@ -657,6 +631,21 @@ public final class com/squareup/workflow1/ui/container/ModalScreenOverlayOnBackP public static final fun plus (Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/container/ModalScreenOverlayOnBackPressed;)Lcom/squareup/workflow1/ui/ViewEnvironment; } +public final class com/squareup/workflow1/ui/container/OverlayArea { + public static final field Companion Lcom/squareup/workflow1/ui/container/OverlayArea$Companion; + public fun (Lkotlinx/coroutines/flow/StateFlow;)V + public final fun getBounds ()Lkotlinx/coroutines/flow/StateFlow; +} + +public final class com/squareup/workflow1/ui/container/OverlayArea$Companion : com/squareup/workflow1/ui/ViewEnvironmentKey { + public fun getDefault ()Lcom/squareup/workflow1/ui/container/OverlayArea; + public synthetic fun getDefault ()Ljava/lang/Object; +} + +public final class com/squareup/workflow1/ui/container/OverlayAreaKt { + public static final fun plus (Lcom/squareup/workflow1/ui/ViewEnvironment;Lcom/squareup/workflow1/ui/container/OverlayArea;)Lcom/squareup/workflow1/ui/ViewEnvironment; +} + public abstract interface class com/squareup/workflow1/ui/container/OverlayDialogFactory : com/squareup/workflow1/ui/ViewRegistry$Entry { public abstract fun buildDialog (Lcom/squareup/workflow1/ui/container/Overlay;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;)Landroid/app/Dialog; public abstract fun updateDialog (Landroid/app/Dialog;Lcom/squareup/workflow1/ui/container/Overlay;Lcom/squareup/workflow1/ui/ViewEnvironment;)V @@ -680,6 +669,21 @@ public final class com/squareup/workflow1/ui/container/OverlayDialogFactoryKt { public static final fun toDialogFactory (Lcom/squareup/workflow1/ui/container/Overlay;Lcom/squareup/workflow1/ui/ViewEnvironment;)Lcom/squareup/workflow1/ui/container/OverlayDialogFactory; } +public class com/squareup/workflow1/ui/container/ScreenOverlayDialogFactory : com/squareup/workflow1/ui/container/OverlayDialogFactory { + public fun (Lkotlin/reflect/KClass;)V + public synthetic fun buildDialog (Lcom/squareup/workflow1/ui/container/Overlay;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;)Landroid/app/Dialog; + public final fun buildDialog (Lcom/squareup/workflow1/ui/container/ScreenOverlay;Lcom/squareup/workflow1/ui/ViewEnvironment;Landroid/content/Context;)Landroid/app/Dialog; + public fun buildDialogWithContentView (Landroid/view/View;)Landroid/app/Dialog; + public fun getType ()Lkotlin/reflect/KClass; + public fun updateBounds (Landroid/app/Dialog;Landroid/graphics/Rect;)V + public synthetic fun updateDialog (Landroid/app/Dialog;Lcom/squareup/workflow1/ui/container/Overlay;Lcom/squareup/workflow1/ui/ViewEnvironment;)V + public final fun updateDialog (Landroid/app/Dialog;Lcom/squareup/workflow1/ui/container/ScreenOverlay;Lcom/squareup/workflow1/ui/ViewEnvironment;)V +} + +public final class com/squareup/workflow1/ui/container/ScreenOverlayDialogFactoryKt { + public static final fun setModalContent (Landroid/app/Dialog;Landroid/view/View;)V +} + public final class com/squareup/workflow1/ui/container/ViewStateCache { public fun ()V public fun (Ljava/util/Map;)V diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactoryFinder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactoryFinder.kt index 90e216a2a8..3b8e4cf976 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactoryFinder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/ScreenViewFactoryFinder.kt @@ -3,8 +3,8 @@ package com.squareup.workflow1.ui import com.squareup.workflow1.ui.ScreenViewFactory.Companion.forWrapper import com.squareup.workflow1.ui.container.BackStackScreen import com.squareup.workflow1.ui.container.BackStackScreenViewFactory -import com.squareup.workflow1.ui.container.BodyAndModalsContainer -import com.squareup.workflow1.ui.container.BodyAndModalsScreen +import com.squareup.workflow1.ui.container.BodyAndOverlaysContainer +import com.squareup.workflow1.ui.container.BodyAndOverlaysScreen import com.squareup.workflow1.ui.container.EnvironmentScreen import com.squareup.workflow1.ui.container.EnvironmentScreenViewFactory @@ -67,8 +67,8 @@ public interface ScreenViewFactoryFinder { ?: (rendering as? BackStackScreen<*>)?.let { BackStackScreenViewFactory as ScreenViewFactory } - ?: (rendering as? BodyAndModalsScreen<*, *>)?.let { - BodyAndModalsContainer as ScreenViewFactory + ?: (rendering as? BodyAndOverlaysScreen<*, *>)?.let { + BodyAndOverlaysContainer as ScreenViewFactory } ?: (rendering as? NamedScreen<*>)?.let { forWrapper, ScreenT> { it.wrapped } as ScreenViewFactory diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/AndroidDialogBounds.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/AndroidDialogBounds.kt index 78825ce82d..68852d50a4 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/AndroidDialogBounds.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/AndroidDialogBounds.kt @@ -19,7 +19,7 @@ import kotlinx.coroutines.flow.onEach * [bounds] is expected to be in global display coordinates, * e.g. as returned from [View.getGlobalVisibleRect]. * - * @see ModalScreenOverlayDialogFactory.updateBounds + * @see ScreenOverlayDialogFactory.updateBounds */ @WorkflowUiExperimentalApi public fun Dialog.setBounds(bounds: Rect) { 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/BodyAndOverlaysContainer.kt similarity index 90% rename from workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsContainer.kt rename to workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/BodyAndOverlaysContainer.kt index 1114c56779..1e8a94845b 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/BodyAndOverlaysContainer.kt @@ -20,14 +20,16 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi import com.squareup.workflow1.ui.WorkflowViewStub @WorkflowUiExperimentalApi -// TODO Rename this BodyAndOverlaysContainer -internal class BodyAndModalsContainer @JvmOverloads constructor( +internal class BodyAndOverlaysContainer @JvmOverloads constructor( context: Context, attributeSet: AttributeSet? = null, defStyle: Int = 0, defStyleRes: Int = 0 ) : FrameLayout(context, attributeSet, defStyle, defStyleRes) { /** + * The unique `SavedStateRegistry` key passed to [LayeredDialogs.onAttachedToWindow], + * derived from the first rendering passed to [update]. See the doc on + * [LayeredDialogs.onAttachedToWindow] for details. */ private lateinit var savedStateParentKey: String @@ -41,7 +43,7 @@ internal class BodyAndModalsContainer @JvmOverloads constructor( ) fun update( - newScreen: BodyAndModalsScreen<*, *>, + newScreen: BodyAndOverlaysScreen<*, *>, viewEnvironment: ViewEnvironment ) { savedStateParentKey = Compatible.keyFor(viewEnvironment[Showing]) @@ -126,10 +128,10 @@ internal class BodyAndModalsContainer @JvmOverloads constructor( } } - companion object : ScreenViewFactory> + companion object : ScreenViewFactory> by ScreenViewFactory.fromCode( buildView = { _, initialEnvironment, context, _ -> - BodyAndModalsContainer(context) + BodyAndOverlaysContainer(context) .let { view -> view.id = R.id.workflow_body_and_modals_container view.layoutParams = (LayoutParams(MATCH_PARENT, MATCH_PARENT)) diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/CoveredByModal.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/CoveredByModal.kt index 84304d5098..163d5779cc 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/CoveredByModal.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/CoveredByModal.kt @@ -4,7 +4,7 @@ import com.squareup.workflow1.ui.ViewEnvironmentKey import com.squareup.workflow1.ui.WorkflowUiExperimentalApi /** - * True in views managed by [BodyAndModalsScreen] when their events are being blocked + * True in views managed by [BodyAndOverlaysScreen] when their events are being blocked * by a [ModalOverlay]. */ @WorkflowUiExperimentalApi diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/LayeredDialogs.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/LayeredDialogs.kt index d7d4bcc9c2..63ecb77e10 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/LayeredDialogs.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/LayeredDialogs.kt @@ -24,12 +24,12 @@ import kotlinx.coroutines.flow.StateFlow /** * Does the bulk of the work of maintaining a set of [Dialog][android.app.Dialog]s * to reflect lists of [Overlay]. Can be used to create custom [Overlay]-based - * layouts if [BodyAndModalsScreen] or the default [View] bound to it are too restrictive. + * layouts if [BodyAndOverlaysScreen] or the default [View] bound to it are too restrictive. * Provides a [LifecycleOwner] per managed dialog, and view persistence support. * * @param bounds made available to managed dialogs via the [OverlayArea] * [ViewEnvironmentKey][com.squareup.workflow1.ui.ViewEnvironmentKey], - * which drives [ModalScreenOverlayDialogFactory.updateBounds]. + * which drives [ScreenOverlayDialogFactory.updateBounds]. * * @param cancelEvents function to be called when a modal session starts -- that is, * when [update] is first called with a [ModalOverlay] member, or called again with 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 711f5a0e2f..eab3dae24c 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 @@ -8,7 +8,7 @@ import com.squareup.workflow1.ui.onBackPressedDispatcherOwnerOrNull /** * The function called to handle back button events in [Dialog][android.app.Dialog]s built - * by [ModalScreenOverlayDialogFactory]. The default implementation uses the + * by [ScreenOverlayDialogFactory]. The default implementation uses the * [Activity][android.app.Activity]'s * [OnBackPressedDispatcher][androidx.activity.OnBackPressedDispatcher]. * @@ -19,7 +19,7 @@ import com.squareup.workflow1.ui.onBackPressedDispatcherOwnerOrNull public fun interface ModalScreenOverlayOnBackPressed { /** * Called when the device back button is pressed and a dialog built by a - * [ModalScreenOverlayDialogFactory] has window focus. + * [ScreenOverlayDialogFactory] has window focus. * * @return true if the back press event was consumed */ diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/OverlayArea.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/OverlayArea.kt index 07cac705cf..249fbed9a9 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/OverlayArea.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/OverlayArea.kt @@ -10,7 +10,7 @@ import kotlinx.coroutines.flow.StateFlow /** * Reports the the area of the screen whose events should be blocked by any modal [Overlay], * in the style reported by [View.getGlobalVisibleRect][android.view.View.getGlobalVisibleRect]. - * Expected to be supplied by containers that support [BodyAndModalsScreen]. + * Expected to be supplied by containers that support [BodyAndOverlaysScreen]. */ @WorkflowUiExperimentalApi internal class OverlayArea( diff --git a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/OverlayDialogFactoryFinder.kt b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/OverlayDialogFactoryFinder.kt index 7167c0074b..e204e8fca0 100644 --- a/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/OverlayDialogFactoryFinder.kt +++ b/workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/OverlayDialogFactoryFinder.kt @@ -8,9 +8,9 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi /** * [ViewEnvironment] service object used by [Overlay.toDialogFactory] to find the right * [OverlayDialogFactoryScreenViewFactory]. The default implementation makes [AndroidOverlay] - * work, and provides default bindings for [AlertOverlay] and [ModalScreenOverlay]. + * work, and provides default bindings for [AlertOverlay] and [FullScreenOverlay]. * - * Here is how this hook could be used to provide a custom dialog to handle [ModalScreenOverlay]: + * Here is how this hook could be used to provide a custom dialog to handle [FullScreenOverlay]: * * class MyDialogFactory : ModalScreenOverlayDialogFactory>( * ModalScreenOverlay::class @@ -58,9 +58,9 @@ public interface OverlayDialogFactoryFinder { ?: (rendering as? AlertOverlay)?.let { AlertOverlayDialogFactory() as OverlayDialogFactory } - ?: (rendering as? ModalScreenOverlay<*>)?.let { - ModalScreenOverlayDialogFactory>( - ModalScreenOverlay::class + ?: (rendering as? FullScreenOverlay<*>)?.let { + ScreenOverlayDialogFactory>( + FullScreenOverlay::class ) as OverlayDialogFactory } ?: throw IllegalArgumentException( 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/ScreenOverlayDialogFactory.kt similarity index 94% rename from workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlayDialogFactory.kt rename to workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ScreenOverlayDialogFactory.kt index 78ef56d21d..cf79d28c22 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/ScreenOverlayDialogFactory.kt @@ -25,21 +25,20 @@ import kotlin.reflect.KClass /** * Extensible base implementation of [OverlayDialogFactory] for [ScreenOverlay] - * types. Also serves as the default factory for [ModalScreenOverlay]. + * types. Also serves as the default factory for [FullScreenOverlay]. * (See [OverlayDialogFactoryFinder] for guidance on customizing the presentation - * of [ModalScreenOverlay].) + * of [FullScreenOverlay].) * * Dialogs built by this class are compatible with [View.backPressedHandler], and * honor the [OverlayArea] constraint placed in the [ViewEnvironment] by the - * standard [BodyAndModalsScreen] container. + * standard [BodyAndOverlaysScreen] container. * * Ironically, [Dialog] instances are created with [FLAG_NOT_TOUCH_MODAL], to ensure * that events outside of the bounds reported by [updateBounds] reach views in * lower windows. See that method for details. */ @WorkflowUiExperimentalApi -// TODO: Rename ScreenOverlayDialogFactory -public open class ModalScreenOverlayDialogFactory>( +public open class ScreenOverlayDialogFactory>( override val type: KClass ) : OverlayDialogFactory { @@ -140,7 +139,7 @@ public open class ModalScreenOverlayDialogFactory>( } /** - * The default implementation of [ModalScreenOverlayDialogFactory.buildDialogWithContentView]. + * The default implementation of [ScreenOverlayDialogFactory.buildDialogWithContentView]. * * Sets the [background][Window.setBackgroundDrawable] of the receiver's [Window] based * on its theme, if any, or else `null`. (Setting the background to `null` ensures the window diff --git a/workflow-ui/core-common/api/core-common.api b/workflow-ui/core-common/api/core-common.api index fc5a31fe09..91ee62e8fb 100644 --- a/workflow-ui/core-common/api/core-common.api +++ b/workflow-ui/core-common/api/core-common.api @@ -139,7 +139,7 @@ public final class com/squareup/workflow1/ui/ViewRegistryKt { public abstract interface annotation class com/squareup/workflow1/ui/WorkflowUiExperimentalApi : java/lang/annotation/Annotation { } -public final class com/squareup/workflow1/ui/container/AlertOverlay : com/squareup/workflow1/ui/container/Overlay { +public final class com/squareup/workflow1/ui/container/AlertOverlay : com/squareup/workflow1/ui/container/ModalOverlay { public fun (Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;)V public synthetic fun (Ljava/util/Map;Ljava/lang/String;Ljava/lang/String;ZLkotlin/jvm/functions/Function1;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public final fun component1 ()Ljava/util/Map; @@ -205,14 +205,14 @@ public final class com/squareup/workflow1/ui/container/BackStackScreenKt { public static final fun toBackStackScreenOrNull (Ljava/util/List;)Lcom/squareup/workflow1/ui/container/BackStackScreen; } -public final class com/squareup/workflow1/ui/container/BodyAndModalsScreen : com/squareup/workflow1/ui/Screen { +public final class com/squareup/workflow1/ui/container/BodyAndOverlaysScreen : com/squareup/workflow1/ui/Screen { public fun (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;)V public synthetic fun (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V public fun (Lcom/squareup/workflow1/ui/Screen;[Lcom/squareup/workflow1/ui/container/Overlay;)V public final fun getBody ()Lcom/squareup/workflow1/ui/Screen; - public final fun getModals ()Ljava/util/List; - public final fun mapBody (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BodyAndModalsScreen; - public final fun mapModals (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BodyAndModalsScreen; + public final fun getOverlays ()Ljava/util/List; + public final fun mapBody (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BodyAndOverlaysScreen; + public final fun mapModals (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BodyAndOverlaysScreen; } public final class com/squareup/workflow1/ui/container/EnvironmentScreen : com/squareup/workflow1/ui/Compatible, com/squareup/workflow1/ui/Screen { @@ -229,12 +229,15 @@ public final class com/squareup/workflow1/ui/container/EnvironmentScreenKt { public static final fun withRegistry (Lcom/squareup/workflow1/ui/Screen;Lcom/squareup/workflow1/ui/ViewRegistry;)Lcom/squareup/workflow1/ui/container/EnvironmentScreen; } -public final class com/squareup/workflow1/ui/container/ModalScreenOverlay : com/squareup/workflow1/ui/container/ScreenOverlay { +public final class com/squareup/workflow1/ui/container/FullScreenOverlay : com/squareup/workflow1/ui/container/ScreenOverlay { public fun (Lcom/squareup/workflow1/ui/Screen;)V public fun getCompatibilityKey ()Ljava/lang/String; public fun getContent ()Lcom/squareup/workflow1/ui/Screen; } +public abstract interface class com/squareup/workflow1/ui/container/ModalOverlay : com/squareup/workflow1/ui/container/Overlay { +} + public abstract interface class com/squareup/workflow1/ui/container/Overlay { } diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsScreen.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BodyAndOverlaysScreen.kt similarity index 68% rename from workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsScreen.kt rename to workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BodyAndOverlaysScreen.kt index 39060e8d2b..3e0a92a1a3 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BodyAndModalsScreen.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/BodyAndOverlaysScreen.kt @@ -12,8 +12,7 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi * UI kits are expected to provide handling for this class by default. */ @WorkflowUiExperimentalApi -// TODO rename this BodyAndOverlaysScreen -public class BodyAndModalsScreen( +public class BodyAndOverlaysScreen( public val body: B, public val overlays: List = emptyList() ) : Screen { @@ -22,11 +21,11 @@ public class BodyAndModalsScreen( vararg modals: O ) : this(body, modals.toList()) - public fun mapBody(transform: (B) -> S): BodyAndModalsScreen { - return BodyAndModalsScreen(transform(body), overlays) + public fun mapBody(transform: (B) -> S): BodyAndOverlaysScreen { + return BodyAndOverlaysScreen(transform(body), overlays) } - public fun mapModals(transform: (O) -> N): BodyAndModalsScreen { - return BodyAndModalsScreen(body, overlays.map(transform)) + public fun mapModals(transform: (O) -> N): BodyAndOverlaysScreen { + return BodyAndOverlaysScreen(body, overlays.map(transform)) } } diff --git a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlay.kt b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/FullScreenOverlay.kt similarity index 81% rename from workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlay.kt rename to workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/FullScreenOverlay.kt index e7f65e6e3a..e0130762af 100644 --- a/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/ModalScreenOverlay.kt +++ b/workflow-ui/core-common/src/main/java/com/squareup/workflow1/ui/container/FullScreenOverlay.kt @@ -8,8 +8,7 @@ import com.squareup.workflow1.ui.WorkflowUiExperimentalApi * * UI kits are expected to provide handling for this class by default. */ -// TODO: rename this FullScreenOverlay @WorkflowUiExperimentalApi -public class ModalScreenOverlay( +public class FullScreenOverlay( public override val content: ContentT ) : ScreenOverlay