@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)?.removeView(child)
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(View(context))
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-12",
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",