Skip to content

Commit c4f52b1

Browse files
authored
Merge pull request #1211 from square/ray/dialogamageddon
DialogCollator destroys/rebuild instead of dismiss/show
2 parents 8cb0a82 + fae88ea commit c4f52b1

File tree

2 files changed

+29
-31
lines changed

2 files changed

+29
-31
lines changed

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/navigation/DialogCollator.kt

Lines changed: 25 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -1,9 +1,11 @@
11
package com.squareup.workflow1.ui.navigation
22

3+
import com.squareup.workflow1.ui.Compatible
34
import com.squareup.workflow1.ui.ViewEnvironment
45
import com.squareup.workflow1.ui.ViewEnvironmentKey
56
import com.squareup.workflow1.ui.WorkflowUiExperimentalApi
67
import com.squareup.workflow1.ui.navigation.DialogCollator.IdAndSessions
8+
import com.squareup.workflow1.ui.navigation.DialogSession.KeyAndBundle
79
import java.util.UUID
810

911
/**
@@ -38,7 +40,11 @@ internal class DialogSessionUpdate(
3840
oldSessionFinder: OldSessionFinder,
3941
covered: Boolean
4042
) -> DialogSession
41-
)
43+
) {
44+
override fun toString(): String {
45+
return "DialogSessionUpdate(overlay=${Compatible.keyFor(overlay)})"
46+
}
47+
}
4248

4349
/**
4450
* Init method called at the start of [LayeredDialogSessions.update].
@@ -113,8 +119,7 @@ internal fun ViewEnvironment.establishDialogCollator(
113119
* updates that were enqueued with the shared [DialogCollator] are executed in a single
114120
* pass. Because this [DialogCollator] has complete knowledge of the existing stack
115121
* of `Dialog` windows and all updates, it is able to decide if any existing instances need to be
116-
* [dismissed][android.app.Dialog.dismiss] and [re-shown][android.app.Dialog.show]
117-
* to keep them in the correct order.
122+
* [destroyed][DialogSession.destroyDialog] and rebuilt to keep them in the correct order.
118123
*/
119124
@WorkflowUiExperimentalApi
120125
internal class DialogCollator {
@@ -149,7 +154,11 @@ internal class DialogCollator {
149154
val id: UUID,
150155
val updates: List<DialogSessionUpdate>,
151156
val onSessionsUpdated: (List<DialogSession>) -> Unit
152-
)
157+
) {
158+
override fun toString(): String {
159+
return "IdAndUpdates(id=$id, updates=$updates)"
160+
}
161+
}
153162

154163
/**
155164
* The [IdAndUpdates] instances accumulated by all calls to [scheduleUpdates].
@@ -199,10 +208,6 @@ internal class DialogCollator {
199208
.flatMap { it.sessions.map { session -> Pair(it.id, session) } }
200209
.iterator()
201210

202-
// Collects members of establishedSessions that are dismissed because they
203-
// are out of order, so that we can try to show them again.
204-
val hiddenSessions = mutableListOf<Pair<UUID, DialogSession>>()
205-
206211
// Z index of the uppermost ModalOverlay.
207212
val topModalIndex = allUpdates.asSequence()
208213
.flatMap { it.updates.asSequence().map { update -> update.overlay } }
@@ -212,14 +217,13 @@ internal class DialogCollator {
212217
var updatingSessionIndex = 0
213218

214219
val allNewSessions = mutableListOf<DialogSession>()
220+
val viewStates = mutableMapOf<String, KeyAndBundle>()
215221
allUpdates.forEach { idAndUpdates ->
216222
val updatedSessions = mutableListOf<DialogSession>()
217223

218224
// We're building an object that the next LayeredDialogSessions can use to
219225
// find an existing dialog that matches a given Overlay. Any
220-
// incompatible dialog that we skip on the way to find a match is dismissed.
221-
// If we later find a match for one of the dismissed dialogs, it is re-shown --
222-
// which moves it to the front, ensuring the correct Z order.
226+
// incompatible dialog that we skip on the way to find a match is destroyed.
223227
val oldSessionFinder = OldSessionFinder { overlay ->
224228

225229
// First we iterate through the existing windows to find one that belongs
@@ -228,26 +232,23 @@ internal class DialogCollator {
228232
val (id, session) = establishedSessionsIterator.next()
229233
if (idAndUpdates.id == id && session.canShow(overlay)) return@OldSessionFinder session
230234

231-
// Can't update this session from this Overlay. Dismiss it (via Dialog.dismiss
232-
// under the hood), but hold on to it in case it can except a later Overlay.
233-
session.setVisible(false)
234-
hiddenSessions.add(Pair(id, session))
235+
// Can't update this session from this Overlay, save its view state and destroy it.
236+
// If it was just out of z order, a new one with matching id will be made and restored.
237+
session.save()?.let { viewStates[id.toString() + session.savedStateKey] = it }
238+
session.destroyDialog(saveViewState = true)
235239
continue
236240
}
237241

238-
// There are no established windows left. See if any of the ones that were
239-
// dismissed because they were out of order can be shown again.
240-
return@OldSessionFinder hiddenSessions.indexOfFirst { (hiddenId, dialogSession) ->
241-
idAndUpdates.id == hiddenId && dialogSession.canShow(overlay)
242-
}.takeUnless { it == -1 }?.let { compatibleIndex ->
243-
val restoredSession = hiddenSessions.removeAt(compatibleIndex).second
244-
restoredSession.apply { setVisible(true) }
245-
}
242+
// There are no established windows left.
243+
return@OldSessionFinder null
246244
}
247245

248246
idAndUpdates.updates.forEach { update ->
249247
val covered = updatingSessionIndex < topModalIndex
250-
updatedSessions += update.doUpdate(oldSessionFinder, covered)
248+
updatedSessions += update.doUpdate(oldSessionFinder, covered).also { session ->
249+
viewStates.remove(idAndUpdates.id.toString() + session.savedStateKey)
250+
?.let { session.restore(it) }
251+
}
251252
updatingSessionIndex++
252253
}
253254
idAndUpdates.onSessionsUpdated(updatedSessions)

workflow-ui/core-android/src/main/java/com/squareup/workflow1/ui/navigation/DialogSession.kt

Lines changed: 4 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -5,9 +5,6 @@ import android.os.Parcel
55
import android.os.Parcelable
66
import android.os.Parcelable.Creator
77
import android.view.KeyEvent
8-
import android.view.KeyEvent.ACTION_UP
9-
import android.view.KeyEvent.KEYCODE_BACK
10-
import android.view.KeyEvent.KEYCODE_ESCAPE
118
import android.view.MotionEvent
129
import android.view.Window.Callback
1310
import androidx.activity.OnBackPressedDispatcher
@@ -94,9 +91,6 @@ internal class DialogSession(
9491
*/
9592
val savedStateKey = Compatible.keyFor(initialOverlay)
9693

97-
private val KeyEvent.isBackPress: Boolean
98-
get() = (keyCode == KEYCODE_BACK || keyCode == KEYCODE_ESCAPE) && action == ACTION_UP
99-
10094
/**
10195
* One time call to set up our brand new [OverlayDialogHolder] instance.
10296
* This will be followed by one time calls to [showNewDialog] and [destroyDialog].
@@ -242,10 +236,13 @@ internal class DialogSession(
242236
* We are never going to use this `Dialog` again. Tear down our lifecycle hooks
243237
* and dismiss it.
244238
*/
245-
fun destroyDialog() {
239+
fun destroyDialog(saveViewState: Boolean = false) {
246240
if (!destroyed) {
247241
destroyed = true
248242
with(holder.dialog) {
243+
if (isShowing && saveViewState) {
244+
stateRegistryAggregator.saveAndPruneChildRegistryOwner(savedStateKey)
245+
}
249246
// The dialog's views are about to be detached, and when that happens we want to transition
250247
// the dialog view's lifecycle to a terminal state even though the parent is probably still
251248
// alive.

0 commit comments

Comments
 (0)