Skip to content

Commit e8811c6

Browse files
committed
Fixes some Dialog handling problems revealed by Nested Overlays sample
- Replaces `View.getGlobalVisibleRect` calls with new, more robust `View.getScreenRect` extension - `Dialog.setBounds` sets `FLAG_LAYOUT_IN_SCREEN` to ensure `Dialog` is really placed where we want it. Otherwise it may be offset by the height of the toolbar. I think we've missed this up until now because our other samples and prod use all have richer themes than the default codepaths set up. - Default `ScreenOverlayDialogFactory` implementation disables dimming, for least confusing out of box behavior. Note that these tweaks do not address #966, that's a bigger effort.
1 parent fbfda50 commit e8811c6

File tree

9 files changed

+76
-36
lines changed

9 files changed

+76
-36
lines changed

samples/nested-overlays/src/androidTest/java/com/squareup/sample/nestedoverlays/NestedOverlaysAppTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -76,7 +76,7 @@ class NestedOverlaysAppTest {
7676
// .perform(click())
7777
// }
7878
// }
79-
79+
8080
private fun ViewInteraction.assertNotDisplayed() {
8181
check(matches(not(isDisplayed())))
8282
}

samples/nested-overlays/src/main/java/com/squareup/sample/nestedoverlays/ButtonBar.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,12 @@ class ButtonBar(
2222
vararg buttons: Button?,
2323
@ColorRes val color: Int = -1,
2424
) : AndroidScreen<ButtonBar> {
25-
val buttons: List<Button> = buttons.filterNotNull().toList()
25+
private val buttons: List<Button> = buttons.filterNotNull().toList()
2626

2727
override val viewFactory =
2828
ScreenViewFactory.fromCode<ButtonBar> { _, initialEnvironment, context, _ ->
2929
LinearLayout(context).let { view ->
30+
@Suppress("DEPRECATION")
3031
if (color > -1) view.background = ColorDrawable(view.resources.getColor(color))
3132

3233
view.gravity = Gravity.CENTER

samples/nested-overlays/src/main/java/com/squareup/sample/nestedoverlays/NestedOverlaysWorkflow.kt

Lines changed: 27 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -44,28 +44,36 @@ object NestedOverlaysWorkflow : StatefulWorkflow<Unit, State, Nothing, NestedOve
4444
onClick = context.eventHandler { state = state.copy(showBottomBar = !state.showBottomBar) }
4545
)
4646

47-
val outerSheet = if (!renderState.showOuterSheet) null else FullScreenOverlay(
48-
ButtonBar(
49-
Button(
50-
name = R.string.CLOSE,
51-
onClick = context.eventHandler { state = state.copy(showOuterSheet = false) }
52-
),
53-
context.toggleInnerSheetButton(renderState),
54-
color = android.R.color.holo_green_light
47+
val outerSheet = if (!renderState.showOuterSheet) {
48+
null
49+
} else {
50+
FullScreenOverlay(
51+
ButtonBar(
52+
Button(
53+
name = R.string.CLOSE,
54+
onClick = context.eventHandler { state = state.copy(showOuterSheet = false) }
55+
),
56+
context.toggleInnerSheetButton(renderState),
57+
color = android.R.color.holo_green_light
58+
)
5559
)
56-
)
60+
}
5761

58-
val innerSheet = if (!renderState.showInnerSheet) null else FullScreenOverlay(
59-
ButtonBar(
60-
Button(
61-
name = R.string.CLOSE,
62-
onClick = context.eventHandler { state = state.copy(showInnerSheet = false) }
63-
),
64-
toggleTopBarButton,
65-
toggleBottomBarButton,
66-
color = android.R.color.holo_red_light
62+
val innerSheet = if (!renderState.showInnerSheet) {
63+
null
64+
} else {
65+
FullScreenOverlay(
66+
ButtonBar(
67+
Button(
68+
name = R.string.CLOSE,
69+
onClick = context.eventHandler { state = state.copy(showInnerSheet = false) }
70+
),
71+
toggleTopBarButton,
72+
toggleBottomBarButton,
73+
color = android.R.color.holo_red_light
74+
)
6775
)
68-
)
76+
}
6977
val bodyBarButtons = ButtonBar(toggleTopBarButton, toggleBottomBarButton)
7078

7179
return BodyAndOverlaysScreen(

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -306,6 +306,7 @@ public final class com/squareup/workflow1/ui/container/AlertOverlayDialogFactory
306306
}
307307

308308
public final class com/squareup/workflow1/ui/container/AndroidDialogBoundsKt {
309+
public static final fun getScreenRect (Landroid/view/View;Landroid/graphics/Rect;)V
309310
public static final fun setBounds (Landroid/app/Dialog;Landroid/graphics/Rect;)V
310311
}
311312

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/AndroidDialogBounds.kt

Lines changed: 25 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,12 @@
11
package com.squareup.workflow1.ui.container
22

3+
import android.annotation.SuppressLint
34
import android.app.Dialog
45
import android.graphics.Rect
56
import android.view.Gravity
67
import android.view.View
78
import android.view.Window
9+
import android.view.WindowManager.LayoutParams.FLAG_LAYOUT_IN_SCREEN
810
import com.squareup.workflow1.ui.ViewEnvironment
911
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
1012
import kotlinx.coroutines.CoroutineScope
@@ -16,16 +18,19 @@ import kotlinx.coroutines.flow.onEach
1618

1719
/**
1820
* Updates the size of the [Window] of the receiving [Dialog].
19-
* [bounds] is expected to be in global display coordinates,
20-
* e.g. as returned from [View.getGlobalVisibleRect].
21+
* [bounds] is expected to be in global screen coordinates,
22+
* as returned from [View.getScreenRect].
2123
*
2224
* @see OverlayDialogHolder.onUpdateBounds
2325
*/
2426
@WorkflowUiExperimentalApi
2527
public fun Dialog.setBounds(bounds: Rect) {
2628
window?.let {
29+
it.addFlags(FLAG_LAYOUT_IN_SCREEN)
2730
it.attributes = it.attributes.apply {
28-
gravity = Gravity.TOP or Gravity.START
31+
// Our absolute coordinates are independent from Rtl.
32+
@SuppressLint("RtlHardcoded")
33+
gravity = Gravity.LEFT or Gravity.TOP // we use absolute coordinates.
2934
width = bounds.width()
3035
height = bounds.height()
3136
x = bounds.left
@@ -34,6 +39,23 @@ public fun Dialog.setBounds(bounds: Rect) {
3439
}
3540
}
3641

42+
/**
43+
* Returns the bounds of this [View] in the coordinate space of the device
44+
* screen, based on [View.getLocationOnScreen] and its reported [width][View.getWidth]
45+
* and [height][View.getHeight].
46+
*/
47+
@WorkflowUiExperimentalApi
48+
public fun View.getScreenRect(rect: Rect) {
49+
val coordinates = IntArray(2)
50+
getLocationOnScreen(coordinates)
51+
rect.apply {
52+
left = coordinates[0]
53+
top = coordinates[1]
54+
right = left + width
55+
bottom = top + height
56+
}
57+
}
58+
3759
@WorkflowUiExperimentalApi
3860
internal fun <D : Dialog> D.maintainBounds(
3961
environment: ViewEnvironment,

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/LayeredDialogSessions.kt

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -279,7 +279,7 @@ public class LayeredDialogSessions private constructor(
279279
superDispatchTouchEvent: (MotionEvent) -> Unit
280280
): LayeredDialogSessions {
281281
val boundsRect = Rect()
282-
if (view.isAttachedToWindow) view.getGlobalVisibleRect(boundsRect)
282+
if (view.isAttachedToWindow) view.getScreenRect(boundsRect)
283283
val boundsStateFlow = MutableStateFlow(Rect(boundsRect))
284284

285285
return LayeredDialogSessions(
@@ -326,9 +326,8 @@ public class LayeredDialogSessions private constructor(
326326
// accordingly.
327327
val attachStateChangeListener = object : OnAttachStateChangeListener {
328328
val boundsListener = OnGlobalLayoutListener {
329-
if (view.getGlobalVisibleRect(boundsRect) && boundsRect != boundsStateFlow.value) {
330-
boundsStateFlow.value = Rect(boundsRect)
331-
}
329+
view.getScreenRect(boundsRect)
330+
if (boundsRect != boundsStateFlow.value) boundsStateFlow.value = Rect(boundsRect)
332331
}
333332

334333
override fun onViewAttachedToWindow(v: View) {

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/container/ScreenOverlayDialogFactory.kt

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ import android.content.Context
55
import android.graphics.drawable.ColorDrawable
66
import android.util.TypedValue
77
import android.view.Window
8+
import android.view.WindowManager.LayoutParams.FLAG_DIM_BEHIND
89
import android.view.WindowManager.LayoutParams.FLAG_NOT_TOUCH_MODAL
910
import com.squareup.workflow1.ui.Screen
1011
import com.squareup.workflow1.ui.ScreenViewHolder
@@ -135,19 +136,22 @@ public open class ScreenOverlayDialogFactory<S : Screen, O : ScreenOverlay<S>>(
135136
// Note that we always tell Android to make the window non-modal, regardless of our own
136137
// notion of its modality. Even a modal dialog should only block events within
137138
// the appropriate bounds, but Android makes them block everywhere.
138-
window.setFlags(FLAG_NOT_TOUCH_MODAL, FLAG_NOT_TOUCH_MODAL)
139+
window.addFlags(FLAG_NOT_TOUCH_MODAL)
139140
}
140141
}
141142
}
142143

143144
/**
144-
* The default implementation of [ScreenOverlayDialogFactory.buildDialogWithContent].
145+
* Used by the default implementation of [ScreenOverlayDialogFactory.buildDialogWithContent]
146+
* to show [contentHolder].
145147
*
146148
* - Makes the receiver [non-cancelable][Dialog.setCancelable]
147149
*
148150
* - Sets the [background][Window.setBackgroundDrawable] of the receiver's [Window] based
149151
* on its theme, if any, or else `null`. (Setting the background to `null` ensures the window
150152
* can go full bleed.)
153+
*
154+
* - Disables dimming.
151155
*/
152156
@OptIn(WorkflowUiExperimentalApi::class)
153157
public fun Dialog.setContent(contentHolder: ScreenViewHolder<*>) {
@@ -163,7 +167,11 @@ public fun Dialog.setContent(contentHolder: ScreenViewHolder<*>) {
163167
if (maybeWindowColor.type in TypedValue.TYPE_FIRST_COLOR_INT..TypedValue.TYPE_LAST_COLOR_INT) {
164168
ColorDrawable(maybeWindowColor.data)
165169
} else {
166-
ColorDrawable(contentHolder.view.resources.getColor(android.R.color.white))
170+
// If we don't at least set it to null, the window cannot go full bleed.
171+
null
167172
}
168-
window!!.setBackgroundDrawable(background)
173+
with(window!!) {
174+
setBackgroundDrawable(background)
175+
clearFlags(FLAG_DIM_BEHIND)
176+
}
169177
}

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

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -216,11 +216,13 @@ public final class com/squareup/workflow1/ui/container/BackStackScreenKt {
216216
public static final fun toBackStackScreenOrNull (Ljava/util/List;)Lcom/squareup/workflow1/ui/container/BackStackScreen;
217217
}
218218

219-
public final class com/squareup/workflow1/ui/container/BodyAndOverlaysScreen : com/squareup/workflow1/ui/Screen {
220-
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;)V
221-
public synthetic fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
219+
public final class com/squareup/workflow1/ui/container/BodyAndOverlaysScreen : com/squareup/workflow1/ui/Compatible, com/squareup/workflow1/ui/Screen {
220+
public fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;Ljava/lang/String;)V
221+
public synthetic fun <init> (Lcom/squareup/workflow1/ui/Screen;Ljava/util/List;Ljava/lang/String;ILkotlin/jvm/internal/DefaultConstructorMarker;)V
222222
public fun <init> (Lcom/squareup/workflow1/ui/Screen;[Lcom/squareup/workflow1/ui/container/Overlay;)V
223223
public final fun getBody ()Lcom/squareup/workflow1/ui/Screen;
224+
public fun getCompatibilityKey ()Ljava/lang/String;
225+
public final fun getName ()Ljava/lang/String;
224226
public final fun getOverlays ()Ljava/util/List;
225227
public final fun mapBody (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BodyAndOverlaysScreen;
226228
public final fun mapModals (Lkotlin/jvm/functions/Function1;)Lcom/squareup/workflow1/ui/container/BodyAndOverlaysScreen;

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

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@ import com.squareup.workflow1.ui.Compatible
44
import com.squareup.workflow1.ui.Compatible.Companion.keyFor
55
import com.squareup.workflow1.ui.Screen
66
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
7-
import com.squareup.workflow1.ui.Wrapper
87

98
/**
109
* A screen that may stack a number of [Overlay]s over a body.

0 commit comments

Comments
 (0)