Skip to content

Introduces Overlay, and its first uses: BodyAndModalsScreen, AlertOverlay #588

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 1 commit into from
Nov 22, 2021

Conversation

rjrjr
Copy link
Contributor

@rjrjr rjrjr commented Nov 17, 2021

New platform-neutral rendering types:

  • Overlay is a marker type that identifies a window-like rendering.

  • AlertOverlay is the first use of Overlay, models Android's
    AlertDialog

  • BodyAndModalsScreen has a body Screen covered by zero or more
    Overlays that are given a modal treatment -- its body is shielded
    from events while any Overlay is present.

Android support code that uses them to manage Dialogs:

  • OverlayDialogFactory is to Overlay as
    ScreenViewRunner and ScreenViewFactory are to Screen

  • DialogStack uses OverlayDialogFactory to display List<Overlay>
    as a pile of Dialogs

  • BodyAndModalsContainer is a FrameLayout that uses DialogStack
    to display BodyAndModalScreen

  • AlertOverlayDialogFactory creates AlertDialog instances for
    AlertOverlay

Note that BodyAndModalsContainer and AlertOverlayDialogFactory are
internal, with default binings built into Overlay.toDialogFactory()
and Screen.buildView.

Also note that AndroidViewEnvironment.kt and the bulk of AndroidScreen.kt
have been moved to ScreenViewFactory.kt, which seemed like a less
surprising home for them.

Updates hellobackbutton sample to demonstrate the use of all of this.

Note the commented out deprecations. Turns out its impossible to suppress
deprecation warnings in typealias calls like the ones our sample apps are
still using, and those can't be ported until we have a replacement for
ModalViewContainer. Tracked in #589.

@CLAassistant
Copy link

CLAassistant commented Nov 17, 2021

CLA assistant check
All committers have signed the CLA.

@rjrjr rjrjr force-pushed the ray/overlay branch 4 times, most recently from bfa3a55 to b928193 Compare November 17, 2021 19:58
@rjrjr rjrjr changed the title Ray/overlay Introduces Overlay Nov 17, 2021
@rjrjr rjrjr changed the title Introduces Overlay Introduces Overlay et al to replace ModalContainer Nov 17, 2021
@rjrjr rjrjr force-pushed the ray/overlay branch 3 times, most recently from 232fe8d to 58ca707 Compare November 17, 2021 22:19
@@ -31,3 +37,87 @@ public interface ScreenViewFactory<in RenderingT : Screen> : ViewRegistry.Entry<
container: ViewGroup? = null
): View
}

/**
Copy link
Contributor Author

@rjrjr rjrjr Nov 17, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Outside of making getViewFactoryForRendering non-public, no real changes in this file, just moved code in from another location.

@rjrjr rjrjr marked this pull request as ready for review November 17, 2021 22:26
@rjrjr rjrjr requested review from zach-klippenstein and a team as code owners November 17, 2021 22:26
@rjrjr rjrjr force-pushed the ray/overlay branch 2 times, most recently from fef7cac to 4976aeb Compare November 17, 2021 23:55
@rjrjr rjrjr force-pushed the ray/overlay branch 2 times, most recently from 59eaca3 to 7781b18 Compare November 18, 2021 00:30
?: (rendering as? BackStackScreen<*>)?.let {
BackStackScreenViewFactory as ScreenViewFactory<ScreenT>
}
?: (rendering as? BodyAndModalsScreen<*, *>)?.let {
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Okay, I lied -- this line is new

Copy link
Contributor

@steve-the-edwards steve-the-edwards left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank-you for the final kdoc commit. I will likely need more to understand everything going on here, but hopefully @helios175 has a better understanding?

import com.squareup.workflow1.ui.bindShowRendering

@WorkflowUiExperimentalApi
internal class BodyAndModalsContainer @JvmOverloads constructor(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

At the risk of another bikeshed why isn't this BodyAndOverlaysContainer? And the screen BodyAndOverlaysScreen?

Copy link
Contributor Author

@rjrjr rjrjr Nov 18, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Well spotted, and not an accident. I've realized that modals are a subset of overlays, and this name is meant to respect that distinction -- a modal is an overlay whose presence implies that things beneath it are blocked from receiving any events. The presence of a modal is a visual indicator that we are in a different mode of the app -- channeling ancient Macintosh developer docs here.

Contrast this with tooltips and other pop-ups that are also naturally modeled via Overlay, and which should not have the side effect of modality.

Fell into this because the container itself actually has to participate -- dialogs simply can't do the job all by themselves, as we've re-learned painfully through BugSnag in the past year. (Thinking we should double down on that fact and put the container in charge of showing the scrim, which would fix double-shadow issues we have right now.)

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yes this makes total sense and is a great place to settle. We should document this.

@rjrjr rjrjr changed the title Introduces Overlay et al to replace ModalContainer Introduces Overlay, and its first uses: BodyAndModalsScreen, AlertOverlay Nov 18, 2021
@rjrjr
Copy link
Contributor Author

rjrjr commented Nov 18, 2021

Updated to remove the controversial bits (RectangleProvider and its uses), which are also unused at the moment. Figure we can get this merged and then sort that out. Squashed what's left and updated the commit message.

// Custom behavior from the container to be fired when we show a new dialog.
// The default modal container uses this to flush its body of partially
// processed touch events that should have been blocked by the modal.
beforeShowing()
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Probably will need to tweak this to pass the Dialog itself in the next PR.

}
}

(runners - newRunners.toSet()).forEach { it.dismiss() }
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The IDE prompted me to add the toSet() call as an optimization, and I'm very suggestible.

@rjrjr
Copy link
Contributor Author

rjrjr commented Nov 18, 2021

Damn espresso tests are so flaky, I can't tell if I've made them more so.

@rjrjr
Copy link
Contributor Author

rjrjr commented Nov 18, 2021

Simply cannot get a green build. #590 is awful, and clearly unrelated to this PR, we didn't change that app or anything it uses.

Copy link
Contributor

@steve-the-edwards steve-the-edwards left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Definitely understand now! Still have more naming change requests that I think coudl be helpful.

return compatible(this.rendering, rendering)
}

fun showRendering(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change this to bindRendering as I'm thinking of the RecyclerView ViewHolder analogy. What you are doing is 'binding' the rendering to this dialog - nothing is actually changing with respect to its shown/hidden state.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't know why exactly but bind bugs me. How about canTakeRendering and takeRendering?

import com.squareup.workflow1.ui.compatible

@WorkflowUiExperimentalApi
internal class DialogRunner<T : Overlay>(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I would change this to DialogHolder. I realize we use Runner elsewhere but I also just realized that while I got the 'Runner' metaphor the 'ViewHolder' analogy (as in the RecyclerView sense) is much more powerful to me as an Android person with RecyclerView experience.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Great call. When we use Runner elsewhere it really means something else entirely.

}
}

fun canShowRendering(rendering: Overlay): Boolean {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

canBindRendering?

* Provides a [LifecycleOwner] per managed dialog, and view persistence support.
*/
@WorkflowUiExperimentalApi
public class DialogStack(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

DialogStackContainer we use the Container prefix for these logic classes that deal with interaction between groups of Views bound to Renderings no?

Copy link
Contributor Author

@rjrjr rjrjr Nov 20, 2021

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Container isn't good b/c that's consistently meant "a workflow view that displays other workflow views". This is a support class to create such views.

And as @helios175 pointed out, Stack isn't good because this is so completely different from BackStack.

How about LayeredDialogHolders.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, but DialogHolder is a private type. So just LayeredDialogs.

Comment on lines +6 to +10
/**
* 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.
*/
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
/**
* 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 modal [Overlay]s over a body.
* While modals are present, the body is expected to ignore any
* input events -- touch, keyboard, etc.
* Passing an [Overlay] rendering as part of [modals] here marks that
* Overlay as modal - that is, it should block input events around outside
* of its content area. The logic for that input event blocking is handled
* for users of this screen by the internal [BodyAndModalsContainer].
*/

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

it should block input events around outside of its content area

Oh, no no no no no. It should only block events within the bounds of the body. Blocking events outside of that area is a contract violation. And in fact turning on the "modal" flag for an Android Dialog implementation would be a bug precisely because of this.

I'm too tired to phrase this properly right now. Will come up with something before merging.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actually…let me leave this as is and rephrase with the next PR, where I'll actually be implementing proper custom dialogs. There may be more nuances then.

@rjrjr
Copy link
Contributor Author

rjrjr commented Nov 22, 2021

Update with name changes proposed during review:

  • DialogRunner > DialogHolder
  • DialogStack > LayeredDialogs
  • canShowRendering > canTakeRendering
  • showRendering > takeRendering

Still need to find a home for the essay explaining Overlay v. Modal. Guess I'll put it right on the Overlay interface.

…rlay

New platform-neutral rendering types:

- `Overlay` is a marker type that identifies a window-like rendering.

- `AlertOverlay` is the first use of `Overlay`, models Android's
  `AlertDialog`

- `BodyAndModalsScreen` has a body `Screen` covered by zero or more
  `Overlay`s that are given a modal treatment -- its body is shielded
  from events while any `Overlay` is present.

Android support code that uses them to manage `Dialog`s:

- `OverlayDialogFactory` is to `Overlay` as
  `ScreenViewRunner` and `ScreenViewFactory` are to `Screen`.

- `LayeredDialogs` is a support class that uses `OverlayDialogFactory`
  to display `List<Overlay>` as a pile of `Dialog`s. It's meant to
  simplify the implementation of container views that use `Overlay`.

- `BodyAndModalsContainer` is a `FrameLayout` that uses `DialogStack`
  to display `BodyAndModalScreen`. First use of `LayeredDialogs`.

- `AlertOverlayDialogFactory` creates `AlertDialog` instances for
  `AlertOverlay`.

Note that `BodyAndModalsContainer` and `AlertOverlayDialogFactory` are
`internal`, with default binings built into `Overlay.toDialogFactory()`
and `Screen.buildView`.

Also note that `AndroidViewEnvironment.kt` and the bulk of `AndroidScreen.kt`
have been moved to `ScreenViewFactory.kt`, which seemed like a less
surprising home for them.

Updates `hellobackbutton` sample to demonstrate the use of all of this.

Note the commented out deprecations. Turns out its impossible to suppress
deprecation warnings in `typealias` calls like the ones our sample apps are
still using, and those can't be ported until we have a replacement for
`ModalViewContainer`. Tracked in #589.
@rjrjr
Copy link
Contributor Author

rjrjr commented Nov 22, 2021

Updated the kdoc on Overlay:

/**
 * 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.
 * 
 * Whether or not an Overlay's presence indicates that events are blocked from lower
 * layers is a separate concern.
 */
@WorkflowUiExperimentalApi
public interface Overlay

If no one complains I'll merge this when CI blesses it.

@rjrjr rjrjr merged commit 060019c into ray/ui-update Nov 22, 2021
@rjrjr rjrjr deleted the ray/overlay branch November 22, 2021 22:01
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

4 participants