@onekeyfe/react-native-native-overlay 6.7.0-alpha.359 → 6.7.0-alpha.363
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -125,7 +125,11 @@ class NativeOverlayView(
|
|
|
125
125
|
|
|
126
126
|
fun removeReactChild(child: View) {
|
|
127
127
|
if (contentChild !== child) return
|
|
128
|
-
(child.parent as? ViewGroup)?.
|
|
128
|
+
(child.parent as? ViewGroup)?.let { previous ->
|
|
129
|
+
previous.removeOnLayoutChangeListener(contentLayoutListener)
|
|
130
|
+
previous.removeView(child)
|
|
131
|
+
previous.endViewTransition(child)
|
|
132
|
+
}
|
|
129
133
|
contentChild = null
|
|
130
134
|
}
|
|
131
135
|
|
|
@@ -417,6 +421,8 @@ class NativeOverlayView(
|
|
|
417
421
|
(child.parent as? ViewGroup)?.let { previous ->
|
|
418
422
|
previous.removeOnLayoutChangeListener(contentLayoutListener)
|
|
419
423
|
previous.removeView(child)
|
|
424
|
+
// Android retains a transitioning child's parent until the transition ends.
|
|
425
|
+
previous.endViewTransition(child)
|
|
420
426
|
}
|
|
421
427
|
container.addView(child)
|
|
422
428
|
container.addOnLayoutChangeListener(contentLayoutListener)
|
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
package com.onekey.nativeoverlay
|
|
2
2
|
|
|
3
|
+
import android.animation.ValueAnimator
|
|
3
4
|
import android.graphics.Color
|
|
4
5
|
import android.os.Looper
|
|
5
6
|
import android.view.View
|
|
7
|
+
import android.view.ViewGroup
|
|
6
8
|
import androidx.activity.ComponentActivity
|
|
7
9
|
import androidx.coordinatorlayout.widget.CoordinatorLayout
|
|
8
10
|
import com.facebook.react.bridge.BridgeReactContext
|
|
@@ -11,6 +13,7 @@ import com.google.android.material.bottomsheet.BottomSheetBehavior
|
|
|
11
13
|
import org.junit.Assert.assertEquals
|
|
12
14
|
import org.junit.Assert.assertFalse
|
|
13
15
|
import org.junit.Assert.assertNotSame
|
|
16
|
+
import org.junit.Assert.assertNull
|
|
14
17
|
import org.junit.Assert.assertSame
|
|
15
18
|
import org.junit.Assert.assertTrue
|
|
16
19
|
import org.junit.Test
|
|
@@ -67,10 +70,11 @@ class NativeOverlayMotionTest {
|
|
|
67
70
|
val reactContext = BridgeReactContext(activity)
|
|
68
71
|
reactContext.onHostResume(activity)
|
|
69
72
|
val context = ThemedReactContext(reactContext, activity, "OverlayMotionTest", 1)
|
|
73
|
+
val child = View(context)
|
|
70
74
|
val overlay = NativeOverlayView(context).apply {
|
|
71
75
|
presentation = "sheet"
|
|
72
76
|
sheetHeight = 200.0
|
|
73
|
-
addReactChild(
|
|
77
|
+
addReactChild(child)
|
|
74
78
|
}
|
|
75
79
|
activity.setContentView(overlay)
|
|
76
80
|
var dismissals = 0
|
|
@@ -80,13 +84,24 @@ class NativeOverlayMotionTest {
|
|
|
80
84
|
|
|
81
85
|
val entryField = NativeOverlayView::class.java.getDeclaredField("entry").apply { isAccessible = true }
|
|
82
86
|
val original = entryField.get(overlay) as NativeOverlayEntryRootView
|
|
87
|
+
Shadows.shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(350))
|
|
83
88
|
overlay.visible = false
|
|
84
89
|
overlay.commitConfiguration()
|
|
85
90
|
assertTrue(original.isClosing)
|
|
91
|
+
val animatorField = NativeOverlayView::class.java.getDeclaredField("contentAnimator").apply { isAccessible = true }
|
|
92
|
+
val exitAnimator = animatorField.get(overlay) as ValueAnimator
|
|
93
|
+
assertTrue(exitAnimator.isStarted)
|
|
94
|
+
assertEquals(292L, exitAnimator.duration)
|
|
95
|
+
exitAnimator.currentPlayTime = exitAnimator.duration / 3
|
|
96
|
+
val alphaBeforeReopen = original.animatedView.alpha
|
|
97
|
+
assertTrue(alphaBeforeReopen > 0f && alphaBeforeReopen < 1f)
|
|
98
|
+
assertTrue(original.isClosing)
|
|
86
99
|
|
|
87
100
|
overlay.visible = true
|
|
88
101
|
overlay.commitConfiguration()
|
|
89
102
|
assertSame(original, entryField.get(overlay))
|
|
103
|
+
assertSame(child, original.contentView.getChildAt(0))
|
|
104
|
+
assertEquals(alphaBeforeReopen, original.animatedView.alpha, 0.01f)
|
|
90
105
|
assertTrue(original.isShownForInput)
|
|
91
106
|
assertFalse(original.isClosing)
|
|
92
107
|
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, original.contentView.importantForAccessibility)
|
|
@@ -98,4 +113,125 @@ class NativeOverlayMotionTest {
|
|
|
98
113
|
Shadows.shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(350))
|
|
99
114
|
assertEquals(1, dismissals)
|
|
100
115
|
}
|
|
116
|
+
|
|
117
|
+
@Test
|
|
118
|
+
fun finishingExitMovesTransitioningContentBackToStagingView() {
|
|
119
|
+
val activity = Robolectric.buildActivity(ComponentActivity::class.java).setup().visible().get()
|
|
120
|
+
val reactContext = BridgeReactContext(activity)
|
|
121
|
+
reactContext.onHostResume(activity)
|
|
122
|
+
val context = ThemedReactContext(reactContext, activity, "OverlayMotionTest", 1)
|
|
123
|
+
val child = View(context)
|
|
124
|
+
val overlay = NativeOverlayView(context).apply {
|
|
125
|
+
addReactChild(child)
|
|
126
|
+
}
|
|
127
|
+
activity.setContentView(overlay)
|
|
128
|
+
var dismissals = 0
|
|
129
|
+
overlay.onDismissed = { dismissals++ }
|
|
130
|
+
|
|
131
|
+
overlay.visible = true
|
|
132
|
+
overlay.commitConfiguration()
|
|
133
|
+
val entryField = NativeOverlayView::class.java.getDeclaredField("entry").apply { isAccessible = true }
|
|
134
|
+
val entry = entryField.get(overlay) as NativeOverlayEntryRootView
|
|
135
|
+
Shadows.shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(350))
|
|
136
|
+
|
|
137
|
+
entry.contentView.startViewTransition(child)
|
|
138
|
+
overlay.visible = false
|
|
139
|
+
overlay.commitConfiguration()
|
|
140
|
+
Shadows.shadowOf(Looper.getMainLooper()).idleFor(Duration.ofMillis(350))
|
|
141
|
+
assertSame(overlay, child.parent)
|
|
142
|
+
assertEquals(0, entry.contentView.childCount)
|
|
143
|
+
assertEquals(1, overlay.childCount)
|
|
144
|
+
assertSame(child, overlay.getChildAt(0))
|
|
145
|
+
entry.contentView.endViewTransition(child)
|
|
146
|
+
assertSame(overlay, child.parent)
|
|
147
|
+
assertEquals(1, dismissals)
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
@Test
|
|
151
|
+
fun presentingMovesTransitioningContentIntoEntry() {
|
|
152
|
+
val activity = Robolectric.buildActivity(ComponentActivity::class.java).setup().visible().get()
|
|
153
|
+
val reactContext = BridgeReactContext(activity)
|
|
154
|
+
reactContext.onHostResume(activity)
|
|
155
|
+
val context = ThemedReactContext(reactContext, activity, "OverlayMotionTest", 1)
|
|
156
|
+
val child = View(context)
|
|
157
|
+
val overlay = NativeOverlayView(context).apply {
|
|
158
|
+
addReactChild(child)
|
|
159
|
+
}
|
|
160
|
+
activity.setContentView(overlay)
|
|
161
|
+
|
|
162
|
+
overlay.startViewTransition(child)
|
|
163
|
+
overlay.visible = true
|
|
164
|
+
overlay.commitConfiguration()
|
|
165
|
+
|
|
166
|
+
val entryField = NativeOverlayView::class.java.getDeclaredField("entry").apply { isAccessible = true }
|
|
167
|
+
val entry = entryField.get(overlay) as NativeOverlayEntryRootView
|
|
168
|
+
assertSame(entry.contentView, child.parent)
|
|
169
|
+
assertEquals(0, overlay.childCount)
|
|
170
|
+
assertEquals(1, entry.contentView.childCount)
|
|
171
|
+
assertSame(child, entry.contentView.getChildAt(0))
|
|
172
|
+
overlay.endViewTransition(child)
|
|
173
|
+
assertSame(entry.contentView, child.parent)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
@Test
|
|
177
|
+
fun removingTransitioningStagedContentReleasesItsParentAndLayoutListener() {
|
|
178
|
+
val activity = Robolectric.buildActivity(ComponentActivity::class.java).setup().visible().get()
|
|
179
|
+
val reactContext = BridgeReactContext(activity)
|
|
180
|
+
reactContext.onHostResume(activity)
|
|
181
|
+
val context = ThemedReactContext(reactContext, activity, "OverlayMotionTest", 1)
|
|
182
|
+
val child = View(context)
|
|
183
|
+
val overlay = NativeOverlayView(context).apply { addReactChild(child) }
|
|
184
|
+
activity.setContentView(overlay)
|
|
185
|
+
|
|
186
|
+
overlay.startViewTransition(child)
|
|
187
|
+
overlay.removeReactChild(child)
|
|
188
|
+
|
|
189
|
+
assertNull(child.parent)
|
|
190
|
+
assertEquals(0, overlay.childCount)
|
|
191
|
+
assertEquals(0, overlay.getReactChildCount())
|
|
192
|
+
assertNoContentLayoutListener(overlay, overlay)
|
|
193
|
+
overlay.endViewTransition(child)
|
|
194
|
+
assertNull(child.parent)
|
|
195
|
+
}
|
|
196
|
+
|
|
197
|
+
@Test
|
|
198
|
+
fun replacingTransitioningPresentedContentReleasesTheRemovedChild() {
|
|
199
|
+
val activity = Robolectric.buildActivity(ComponentActivity::class.java).setup().visible().get()
|
|
200
|
+
val reactContext = BridgeReactContext(activity)
|
|
201
|
+
reactContext.onHostResume(activity)
|
|
202
|
+
val context = ThemedReactContext(reactContext, activity, "OverlayMotionTest", 1)
|
|
203
|
+
val child = View(context)
|
|
204
|
+
val overlay = NativeOverlayView(context).apply { addReactChild(child) }
|
|
205
|
+
activity.setContentView(overlay)
|
|
206
|
+
overlay.visible = true
|
|
207
|
+
overlay.commitConfiguration()
|
|
208
|
+
val container = child.parent as ViewGroup
|
|
209
|
+
container.startViewTransition(child)
|
|
210
|
+
|
|
211
|
+
val replacement = View(context)
|
|
212
|
+
overlay.addReactChild(replacement)
|
|
213
|
+
|
|
214
|
+
assertNull(child.parent)
|
|
215
|
+
assertSame(replacement, overlay.getReactChildAt(0))
|
|
216
|
+
assertSame(container, replacement.parent)
|
|
217
|
+
assertEquals(1, container.childCount)
|
|
218
|
+
container.endViewTransition(child)
|
|
219
|
+
assertSame(container, replacement.parent)
|
|
220
|
+
|
|
221
|
+
overlay.removeReactChild(replacement)
|
|
222
|
+
assertNoContentLayoutListener(container, overlay)
|
|
223
|
+
}
|
|
224
|
+
|
|
225
|
+
private fun assertNoContentLayoutListener(container: View, overlay: NativeOverlayView) {
|
|
226
|
+
val listener = NativeOverlayView::class.java.getDeclaredField("contentLayoutListener").apply {
|
|
227
|
+
isAccessible = true
|
|
228
|
+
}.get(overlay)
|
|
229
|
+
val listenerInfo = View::class.java.getDeclaredField("mListenerInfo").apply {
|
|
230
|
+
isAccessible = true
|
|
231
|
+
}.get(container) ?: return
|
|
232
|
+
val layoutListeners = listenerInfo.javaClass.getDeclaredField("mOnLayoutChangeListeners").apply {
|
|
233
|
+
isAccessible = true
|
|
234
|
+
}.get(listenerInfo) as? Collection<*>
|
|
235
|
+
assertFalse(layoutListeners?.contains(listener) == true)
|
|
236
|
+
}
|
|
101
237
|
}
|
package/docs/SPEC.md
CHANGED
|
@@ -232,6 +232,15 @@ request ─► queued ─► active ─► (enter anim) presented
|
|
|
232
232
|
- Content stays mounted until `finalize`. This replaces today's hardcoded
|
|
233
233
|
timers: the Dialog 300 ms portal destroy, the ColorPicker 300 ms, the
|
|
234
234
|
ActionList and Select 150 ms waits, and the DialogLoading 50 ms delays.
|
|
235
|
+
- On Android, moving the same React child between its hidden staging view and
|
|
236
|
+
the entry host must release an in-progress transition held by the previous
|
|
237
|
+
parent before the new parent adds it. Completing that previous transition
|
|
238
|
+
later must not detach the child from its new parent. Animated closing moves
|
|
239
|
+
the child back after exit completion; reopening during exit keeps the same
|
|
240
|
+
child and entry without reporting a completed dismissal. Fabric removal or
|
|
241
|
+
replacement of the React child must also release the old container's layout
|
|
242
|
+
listener and in-progress transition, leaving the removed child without a
|
|
243
|
+
parent; a later transition completion must not affect replacement content.
|
|
235
244
|
- A queued entry is promoted only after the previous entry in its lane is
|
|
236
245
|
finalized, so exit and enter never overlap. `replace` is the exception: the
|
|
237
246
|
new entry enters while the replaced one exits.
|
|
@@ -407,6 +416,11 @@ Automated (P0):
|
|
|
407
416
|
blockers, input readiness, canceled key-up, global/page precedence,
|
|
408
417
|
suspension and blocking-only accessibility isolation. It also checks live
|
|
409
418
|
host reuse, Activity isolation and rebuilding after a cleared weak value.
|
|
419
|
+
- `NativeOverlayMotionTest` exercises the production Android view's transition
|
|
420
|
+
reparenting in both directions, including the exit-completion staging path,
|
|
421
|
+
old-parent transition completion after the move, and reopen during exit.
|
|
422
|
+
It also covers Fabric removal of staged content and replacement of presented
|
|
423
|
+
content during a transition, including layout-listener cleanup.
|
|
410
424
|
- `src/__tests__/pageIsolation.web.test.ts` mounts a real React portal in a
|
|
411
425
|
DOM host and verifies late suspension, level ordering and restoration after
|
|
412
426
|
the blocking entry is removed.
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@onekeyfe/react-native-native-overlay",
|
|
3
|
-
"version": "6.7.0-alpha.
|
|
3
|
+
"version": "6.7.0-alpha.363",
|
|
4
4
|
"description": "Native and Web overlay hosts with ordered levels, page scope and Fabric measurement",
|
|
5
5
|
"main": "./lib/module/index.js",
|
|
6
6
|
"types": "./lib/typescript/src/index.d.ts",
|