1
1
package com.squareup.workflow1.ui.container
2
2
3
3
import android.content.Context
4
- import android.graphics.Rect
5
4
import android.os.Parcel
6
5
import android.os.Parcelable
7
6
import android.os.Parcelable.Creator
@@ -10,124 +9,72 @@ import android.view.KeyEvent
10
9
import android.view.MotionEvent
11
10
import android.view.ViewGroup
12
11
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
13
- import android.view.ViewTreeObserver.OnGlobalLayoutListener
14
12
import android.widget.FrameLayout
15
13
import com.squareup.workflow1.ui.Compatible
16
- import com.squareup.workflow1.ui.Compatible.Companion.keyFor
17
14
import com.squareup.workflow1.ui.R
18
15
import com.squareup.workflow1.ui.ScreenViewFactory
19
16
import com.squareup.workflow1.ui.ScreenViewHolder
20
17
import com.squareup.workflow1.ui.ScreenViewHolder.Companion.Showing
21
18
import com.squareup.workflow1.ui.ViewEnvironment
22
19
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
23
20
import com.squareup.workflow1.ui.WorkflowViewStub
24
- import com.squareup.workflow1.ui.androidx.WorkflowAndroidXSupport
25
- import kotlinx.coroutines.flow.MutableStateFlow
26
21
27
22
@WorkflowUiExperimentalApi
23
+ // TODO Rename this BodyAndOverlaysContainer
28
24
internal class BodyAndModalsContainer @JvmOverloads constructor(
29
25
context : Context ,
30
26
attributeSet : AttributeSet ? = null ,
31
27
defStyle : Int = 0 ,
32
28
defStyleRes : Int = 0
33
29
) : FrameLayout(context, attributeSet, defStyle, defStyleRes) {
34
30
/* *
35
- * Unique identifier for this view for SavedStateRegistry purposes. Based on the
36
- * [Compatible.keyFor] the current rendering. Taking this approach allows
37
- * feature developers to take control over naming, e.g. by wrapping renderings
38
- * with [NamedScreen][com.squareup.workflow1.ui.NamedScreen].
39
31
*/
40
32
private lateinit var savedStateParentKey: String
41
33
42
34
private val baseViewStub: WorkflowViewStub = WorkflowViewStub (context).also {
43
35
addView(it, ViewGroup .LayoutParams (MATCH_PARENT , MATCH_PARENT ))
44
36
}
45
37
46
- private val dialogs = LayeredDialogs (view = this , modal = true )
47
-
48
- // The bounds of this view in global (display) coordinates, as reported
49
- // by getGlobalVisibleRect.
50
- //
51
- // Made available to managed ModalScreenOverlayDialogFactory instances
52
- // via the ModalArea key in ViewEnvironment. When this updates their
53
- // updateBounds methods will fire. They should resize themselves to
54
- // avoid covering peers of this view.
55
- private val bounds = MutableStateFlow (Rect ())
56
- private val boundsRect = Rect ()
57
-
58
- private val boundsListener = OnGlobalLayoutListener {
59
- if (getGlobalVisibleRect(boundsRect) && boundsRect != bounds.value) {
60
- bounds.value = Rect (boundsRect)
61
- }
62
- // Should we close the dialogs if getGlobalVisibleRect returns false?
63
- // https://github.com/square/workflow-kotlin/issues/599
64
- }
65
-
66
- // Note similar code in DialogHolder.
67
- private var allowEvents = true
68
- set(value) {
69
- val was = field
70
- field = value
71
- if (value != was) {
72
- // https://stackoverflow.com/questions/2886407/dealing-with-rapid-tapping-on-buttons
73
- // If any motion events were enqueued on the main thread, cancel them.
74
- dispatchCancelEvent { super .dispatchTouchEvent(it) }
75
- // When we cancel, have to warn things like RecyclerView that handle streams
76
- // of motion events and eventually dispatch input events (click, key pressed, etc.)
77
- // based on them.
78
- cancelPendingInputEvents()
79
- }
80
- }
38
+ private val dialogs = LayeredDialogs .forView(
39
+ view = this ,
40
+ superDispatchTouchEvent = { super .dispatchTouchEvent(it) }
41
+ )
81
42
82
43
fun update (
83
44
newScreen : BodyAndModalsScreen <* , * >,
84
45
viewEnvironment : ViewEnvironment
85
46
) {
86
- savedStateParentKey = keyFor(viewEnvironment[Showing ])
87
-
88
- val showingModals = newScreen.modals.isNotEmpty()
47
+ savedStateParentKey = Compatible .keyFor(viewEnvironment[Showing ])
89
48
90
- // There is a long wait from when we show a dialog until it starts blocking
91
- // events for us. To compensate we ignore all touches while any dialogs exist.
92
- allowEvents = ! showingModals
93
-
94
- val baseEnv = if (showingModals) viewEnvironment + (CoveredByModal to true ) else viewEnvironment
95
- baseViewStub.show(newScreen.body, baseEnv)
96
-
97
- // Allow modal dialogs to restrict themselves to cover only this view.
98
- val dialogsEnv = if (showingModals) viewEnvironment + ModalArea (bounds) else viewEnvironment
99
-
100
- dialogs.update(newScreen.modals, dialogsEnv)
49
+ dialogs.update(newScreen.overlays, viewEnvironment) { env ->
50
+ baseViewStub.show(newScreen.body, env)
51
+ }
101
52
}
102
53
103
54
override fun onAttachedToWindow () {
104
- super .onAttachedToWindow()
105
- boundsListener.onGlobalLayout()
106
- viewTreeObserver.addOnGlobalLayoutListener(boundsListener)
55
+ // I tried to move this to the attachStateChangeListener in LayeredDialogs.Companion.forView,
56
+ // but that fires too late and we crash with the dreaded
57
+ // "You can consumeRestoredStateForKey only after super.onCreate of corresponding component".
107
58
59
+ super .onAttachedToWindow()
108
60
// Wire up dialogs to our parent SavedStateRegistry.
109
- val parentRegistryOwner = WorkflowAndroidXSupport .stateRegistryOwnerFromViewTreeOrContext(this )
110
- dialogs.attachToParentRegistryOwner(savedStateParentKey, parentRegistryOwner)
61
+ dialogs.onAttachedToWindow(savedStateParentKey, this )
111
62
}
112
63
113
64
override fun onDetachedFromWindow () {
114
65
// Disconnect dialogs from our parent SavedStateRegistry so that it doesn't get asked
115
66
// to save state anymore.
116
- dialogs.detachFromParentRegistry()
117
- // Don't leak the dialogs if we're suddenly yanked out of view.
118
- // https://github.com/square/workflow-kotlin/issues/314
119
- dialogs.update(emptyList(), ViewEnvironment .EMPTY )
120
- viewTreeObserver.removeOnGlobalLayoutListener(boundsListener)
121
- bounds.value = Rect ()
67
+ dialogs.onDetachedFromWindow()
68
+
122
69
super .onDetachedFromWindow()
123
70
}
124
71
125
72
override fun dispatchTouchEvent (event : MotionEvent ): Boolean {
126
- return ! allowEvents || super .dispatchTouchEvent(event)
73
+ return ! dialogs. allowEvents || super .dispatchTouchEvent(event)
127
74
}
128
75
129
76
override fun dispatchKeyEvent (event : KeyEvent ): Boolean {
130
- return ! allowEvents || super .dispatchKeyEvent(event)
77
+ return ! dialogs. allowEvents || super .dispatchKeyEvent(event)
131
78
}
132
79
133
80
override fun onSaveInstanceState (): Parcelable {
0 commit comments