@onekeyfe/react-native-native-overlay 6.7.0-12 → 6.7.0-14
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
|
@@ -50,6 +50,13 @@ through `package-publish` with `npm_dist_tag=latest`, verify the registry
|
|
|
50
50
|
versions, and refresh the App dependency and native locks before acceptance.
|
|
51
51
|
The source PR remains open for review.
|
|
52
52
|
|
|
53
|
+
The `6.7.0-14` batch incorporates the review fixes for framework-link
|
|
54
|
+
self-repair, Android overlay transition cleanup, native list/depth ownership,
|
|
55
|
+
and bounded SNI file uploads, together with main's row accessory press fix.
|
|
56
|
+
It supersedes the source snapshots in the previously published `6.7.0-12`
|
|
57
|
+
and `6.7.0-13` batches. Version and lockfile synchronization do not introduce
|
|
58
|
+
additional public behavior; registry verification remains required after publish.
|
|
59
|
+
|
|
53
60
|
## 5.1 Purpose, scope, and non-goals
|
|
54
61
|
|
|
55
62
|
The package owns **where an overlay is drawn, which overlay is on top, and how
|
|
@@ -231,6 +238,15 @@ request ─► queued ─► active ─► (enter anim) presented
|
|
|
231
238
|
- Content stays mounted until `finalize`. This replaces today's hardcoded
|
|
232
239
|
timers: the Dialog 300 ms portal destroy, the ColorPicker 300 ms, the
|
|
233
240
|
ActionList and Select 150 ms waits, and the DialogLoading 50 ms delays.
|
|
241
|
+
- On Android, moving the same React child between its hidden staging view and
|
|
242
|
+
the entry host must release an in-progress transition held by the previous
|
|
243
|
+
parent before the new parent adds it. Completing that previous transition
|
|
244
|
+
later must not detach the child from its new parent. Animated closing moves
|
|
245
|
+
the child back after exit completion; reopening during exit keeps the same
|
|
246
|
+
child and entry without reporting a completed dismissal. Fabric removal or
|
|
247
|
+
replacement of the React child must also release the old container's layout
|
|
248
|
+
listener and in-progress transition, leaving the removed child without a
|
|
249
|
+
parent; a later transition completion must not affect replacement content.
|
|
234
250
|
- A queued entry is promoted only after the previous entry in its lane is
|
|
235
251
|
finalized, so exit and enter never overlap. `replace` is the exception: the
|
|
236
252
|
new entry enters while the replaced one exits.
|
|
@@ -414,6 +430,11 @@ Automated (P0):
|
|
|
414
430
|
blockers, input readiness, canceled key-up, global/page precedence,
|
|
415
431
|
suspension and blocking-only accessibility isolation. It also checks live
|
|
416
432
|
host reuse, Activity isolation and rebuilding after a cleared weak value.
|
|
433
|
+
- `NativeOverlayMotionTest` exercises the production Android view's transition
|
|
434
|
+
reparenting in both directions, including the exit-completion staging path,
|
|
435
|
+
old-parent transition completion after the move, and reopen during exit.
|
|
436
|
+
It also covers Fabric removal of staged content and replacement of presented
|
|
437
|
+
content during a transition, including layout-listener cleanup.
|
|
417
438
|
- `src/__tests__/pageIsolation.web.test.ts` mounts a real React portal in a
|
|
418
439
|
DOM host and verifies late suspension, level ordering and restoration after
|
|
419
440
|
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-
|
|
3
|
+
"version": "6.7.0-14",
|
|
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",
|