@onekeyfe/react-native-native-overlay 3.0.162-alpha.270 → 6.7.0-11
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.
- package/android/build.gradle +6 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayAccessibilityIsolation.kt +37 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayAnimation.kt +5 -10
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayEntryRootView.kt +23 -2
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayHost.kt +31 -32
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayPageHostView.kt +21 -0
- package/android/src/main/java/com/onekey/nativeoverlay/NativeOverlayView.kt +10 -3
- package/android/src/test/java/com/onekey/nativeoverlay/NativeOverlayAccessibilityIsolationTest.kt +112 -0
- package/android/src/test/java/com/onekey/nativeoverlay/NativeOverlayBackTest.kt +295 -0
- package/android/src/test/java/com/onekey/nativeoverlay/NativeOverlayMotionTest.kt +101 -0
- package/docs/SPEC.md +198 -30
- package/ios/NativeOverlayContainerView.swift +20 -7
- package/ios/NativeOverlaySheetController.swift +37 -1
- package/lib/module/OverlayPageHost.web.js +19 -6
- package/lib/module/OverlayPageHost.web.js.map +1 -1
- package/lib/module/OverlayStore.js +13 -1
- package/lib/module/OverlayStore.js.map +1 -1
- package/lib/module/OverlayView.web.js +107 -25
- package/lib/module/OverlayView.web.js.map +1 -1
- package/lib/module/useOverlayController.js +11 -3
- package/lib/module/useOverlayController.js.map +1 -1
- package/lib/module/web/animateTransition.js +20 -12
- package/lib/module/web/animateTransition.js.map +1 -1
- package/lib/module/web/overlayLayers.js +27 -9
- package/lib/module/web/overlayLayers.js.map +1 -1
- package/lib/module/web/useKeyboardShift.js +1 -1
- package/lib/module/web/useKeyboardShift.js.map +1 -1
- package/lib/module/web/useSheetDrag.js +22 -2
- package/lib/module/web/useSheetDrag.js.map +1 -1
- package/lib/typescript/src/OverlayPageHost.web.d.ts.map +1 -1
- package/lib/typescript/src/OverlayStore.d.ts +2 -0
- package/lib/typescript/src/OverlayStore.d.ts.map +1 -1
- package/lib/typescript/src/OverlayView.web.d.ts.map +1 -1
- package/lib/typescript/src/useOverlayController.d.ts.map +1 -1
- package/lib/typescript/src/web/animateTransition.d.ts +2 -2
- package/lib/typescript/src/web/animateTransition.d.ts.map +1 -1
- package/lib/typescript/src/web/overlayLayers.d.ts +3 -0
- package/lib/typescript/src/web/overlayLayers.d.ts.map +1 -1
- package/lib/typescript/src/web/useSheetDrag.d.ts.map +1 -1
- package/package.json +2 -1
- package/src/OverlayPageHost.web.tsx +24 -9
- package/src/OverlayStore.ts +13 -1
- package/src/OverlayView.web.tsx +197 -56
- package/src/useOverlayController.ts +11 -4
- package/src/web/animateTransition.ts +30 -11
- package/src/web/overlayLayers.ts +48 -10
- package/src/web/useKeyboardShift.ts +1 -1
- package/src/web/useSheetDrag.ts +23 -2
package/android/build.gradle
CHANGED
|
@@ -60,6 +60,10 @@ android {
|
|
|
60
60
|
]
|
|
61
61
|
}
|
|
62
62
|
}
|
|
63
|
+
|
|
64
|
+
testOptions {
|
|
65
|
+
unitTests.includeAndroidResources = true
|
|
66
|
+
}
|
|
63
67
|
}
|
|
64
68
|
|
|
65
69
|
repositories {
|
|
@@ -73,6 +77,8 @@ dependencies {
|
|
|
73
77
|
implementation "com.facebook.react:react-android"
|
|
74
78
|
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
|
|
75
79
|
implementation "com.google.android.material:material:1.14.0-alpha05"
|
|
80
|
+
testImplementation "junit:junit:4.13.2"
|
|
81
|
+
testImplementation "org.robolectric:robolectric:4.17"
|
|
76
82
|
}
|
|
77
83
|
|
|
78
84
|
react {
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
package com.onekey.nativeoverlay
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import android.view.ViewGroup
|
|
5
|
+
|
|
6
|
+
/** Restores the caller's flags when a blocking overlay no longer covers it. */
|
|
7
|
+
internal class NativeOverlayAccessibilityIsolation {
|
|
8
|
+
private val hidden = mutableMapOf<View, Int>()
|
|
9
|
+
|
|
10
|
+
fun update(
|
|
11
|
+
root: ViewGroup,
|
|
12
|
+
activeContainer: View?,
|
|
13
|
+
entriesBelow: List<View>,
|
|
14
|
+
globalHost: View,
|
|
15
|
+
) {
|
|
16
|
+
val below = mutableSetOf<View>()
|
|
17
|
+
if (activeContainer != null) {
|
|
18
|
+
var node: View = activeContainer
|
|
19
|
+
while (node !== root && node.parent is ViewGroup) {
|
|
20
|
+
val parent = node.parent as ViewGroup
|
|
21
|
+
for (index in 0 until parent.childCount) {
|
|
22
|
+
val sibling = parent.getChildAt(index)
|
|
23
|
+
if (sibling !== node && sibling !== globalHost) below.add(sibling)
|
|
24
|
+
}
|
|
25
|
+
node = parent
|
|
26
|
+
}
|
|
27
|
+
if (node === root) below.addAll(entriesBelow) else below.clear()
|
|
28
|
+
}
|
|
29
|
+
hidden.keys.filter { it !in below }.forEach { view ->
|
|
30
|
+
view.importantForAccessibility = hidden.remove(view) ?: View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
31
|
+
}
|
|
32
|
+
below.forEach { view ->
|
|
33
|
+
if (view !in hidden) hidden[view] = view.importantForAccessibility
|
|
34
|
+
view.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
|
35
|
+
}
|
|
36
|
+
}
|
|
37
|
+
}
|
|
@@ -69,8 +69,9 @@ internal sealed class OverlayMotion {
|
|
|
69
69
|
}
|
|
70
70
|
|
|
71
71
|
/**
|
|
72
|
-
* Analytic step response of `m·x'' + c·x' + k·x = 0`,
|
|
73
|
-
*
|
|
72
|
+
* Analytic step response of `m·x'' + c·x' + k·x = 0`, with overdamped
|
|
73
|
+
* responses capped at critical damping to match UIKit's sheet response.
|
|
74
|
+
* Driving one ValueAnimator with it
|
|
74
75
|
* keeps alpha, translation, scale and backdrop on the same clock; androidx
|
|
75
76
|
* SpringAnimation would need one animation per property.
|
|
76
77
|
*/
|
|
@@ -89,19 +90,13 @@ internal class SpringInterpolator(private val spring: OverlayMotion.Spring) : Ti
|
|
|
89
90
|
|
|
90
91
|
fun progressAt(spring: OverlayMotion.Spring, t: Double): Double {
|
|
91
92
|
val w0 = sqrt(spring.stiffness / spring.mass)
|
|
92
|
-
val zeta = spring.damping / (2 * sqrt(spring.stiffness * spring.mass))
|
|
93
|
+
val zeta = minOf(1.0, spring.damping / (2 * sqrt(spring.stiffness * spring.mass)))
|
|
93
94
|
return when {
|
|
94
95
|
zeta < 1 -> {
|
|
95
96
|
val wd = w0 * sqrt(1 - zeta * zeta)
|
|
96
97
|
1 - exp(-zeta * w0 * t) * (cos(wd * t) + (zeta * w0 / wd) * sin(wd * t))
|
|
97
98
|
}
|
|
98
|
-
|
|
99
|
-
else -> {
|
|
100
|
-
val root = sqrt(zeta * zeta - 1)
|
|
101
|
-
val r1 = -w0 * (zeta - root)
|
|
102
|
-
val r2 = -w0 * (zeta + root)
|
|
103
|
-
1 - (r2 * exp(r1 * t) - r1 * exp(r2 * t)) / (r2 - r1)
|
|
104
|
-
}
|
|
99
|
+
else -> 1 - exp(-w0 * t) * (1 + w0 * t)
|
|
105
100
|
}
|
|
106
101
|
}
|
|
107
102
|
|
|
@@ -60,7 +60,12 @@ internal class NativeOverlayEntryRootView(
|
|
|
60
60
|
* below), non-blocking ones let it continue past the empty area.
|
|
61
61
|
*/
|
|
62
62
|
override val pointerEvents: PointerEvents
|
|
63
|
-
get() =
|
|
63
|
+
get() = when {
|
|
64
|
+
blocking && isClosing -> PointerEvents.BOX_ONLY
|
|
65
|
+
isClosing -> PointerEvents.NONE
|
|
66
|
+
blocking && isShownForInput -> PointerEvents.AUTO
|
|
67
|
+
else -> PointerEvents.BOX_NONE
|
|
68
|
+
}
|
|
64
69
|
var stackOrder = 0
|
|
65
70
|
var levelOrder = 0
|
|
66
71
|
/** Page scope: the owning page, used by the page host to hide the entry. */
|
|
@@ -74,6 +79,19 @@ internal class NativeOverlayEntryRootView(
|
|
|
74
79
|
var dismissOnBackPress = true
|
|
75
80
|
var dismissOnBackdropPress = false
|
|
76
81
|
var isShownForInput = false
|
|
82
|
+
private var contentAccessibilityBeforeClose: Int? = null
|
|
83
|
+
var isClosing = false
|
|
84
|
+
set(value) {
|
|
85
|
+
if (field == value) return
|
|
86
|
+
field = value
|
|
87
|
+
if (value) {
|
|
88
|
+
contentAccessibilityBeforeClose = contentView.importantForAccessibility
|
|
89
|
+
contentView.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
|
90
|
+
} else {
|
|
91
|
+
contentAccessibilityBeforeClose?.let { contentView.importantForAccessibility = it }
|
|
92
|
+
contentAccessibilityBeforeClose = null
|
|
93
|
+
}
|
|
94
|
+
}
|
|
77
95
|
var onRequestDismiss: ((String) -> Unit)? = null
|
|
78
96
|
|
|
79
97
|
val backdropView = View(reactContext).apply { alpha = 0f }
|
|
@@ -90,12 +108,13 @@ internal class NativeOverlayEntryRootView(
|
|
|
90
108
|
/** `sheet` presentation: the draggable surface; otherwise null. */
|
|
91
109
|
var sheetView: FrameLayout? = null
|
|
92
110
|
private set
|
|
111
|
+
private var sheetAnimationView: CoordinatorLayout? = null
|
|
93
112
|
private var sheetBehavior: BottomSheetBehavior<FrameLayout>? = null
|
|
94
113
|
private var handleView: View? = null
|
|
95
114
|
var onSheetHiddenByPan: (() -> Unit)? = null
|
|
96
115
|
|
|
97
116
|
/** The view that enter / exit animations move. */
|
|
98
|
-
val animatedView: View get() =
|
|
117
|
+
val animatedView: View get() = sheetAnimationView ?: contentView
|
|
99
118
|
|
|
100
119
|
private val touchDispatcher = JSTouchDispatcher(this)
|
|
101
120
|
private val pointerDispatcher = if (ReactFeatureFlags.dispatchPointerEvents) {
|
|
@@ -220,6 +239,7 @@ internal class NativeOverlayEntryRootView(
|
|
|
220
239
|
},
|
|
221
240
|
)
|
|
222
241
|
sheetView = sheet
|
|
242
|
+
sheetAnimationView = coordinator
|
|
223
243
|
sheetBehavior = behavior
|
|
224
244
|
handleView = handle
|
|
225
245
|
return sheet
|
|
@@ -350,6 +370,7 @@ internal class NativeOverlayEntryRootView(
|
|
|
350
370
|
}
|
|
351
371
|
|
|
352
372
|
override fun dispatchTouchEvent(event: MotionEvent): Boolean {
|
|
373
|
+
if (isClosing) return blocking
|
|
353
374
|
if (!isShownForInput) return false
|
|
354
375
|
if (event.actionMasked == MotionEvent.ACTION_DOWN) {
|
|
355
376
|
// Non-blocking overlays (toasts) only take touches that land on content.
|
|
@@ -2,13 +2,13 @@ package com.onekey.nativeoverlay
|
|
|
2
2
|
|
|
3
3
|
import android.app.Activity
|
|
4
4
|
import android.view.KeyEvent
|
|
5
|
-
import android.view.View
|
|
6
5
|
import android.view.ViewGroup
|
|
7
6
|
import android.view.Window
|
|
8
7
|
import android.widget.FrameLayout
|
|
9
8
|
import androidx.activity.ComponentActivity
|
|
10
9
|
import androidx.activity.OnBackPressedCallback
|
|
11
10
|
import java.util.WeakHashMap
|
|
11
|
+
import java.lang.ref.WeakReference
|
|
12
12
|
|
|
13
13
|
/** Mirrors `OVERLAY_LEVEL_ORDER`. */
|
|
14
14
|
internal enum class NativeOverlayLevel(val order: Int) {
|
|
@@ -49,12 +49,12 @@ internal class NativeOverlayHost private constructor(
|
|
|
49
49
|
)
|
|
50
50
|
private val backCallback = object : OnBackPressedCallback(false) {
|
|
51
51
|
override fun handleOnBackPressed() {
|
|
52
|
-
|
|
53
|
-
if (entry.dismissOnBackPress) entry.onRequestDismiss?.invoke("back")
|
|
52
|
+
topBackEntry()?.let { entry ->
|
|
53
|
+
if (!entry.isClosing && entry.dismissOnBackPress) entry.onRequestDismiss?.invoke("back")
|
|
54
54
|
}
|
|
55
55
|
}
|
|
56
56
|
}
|
|
57
|
-
private val
|
|
57
|
+
private val accessibilityIsolation = NativeOverlayAccessibilityIsolation()
|
|
58
58
|
|
|
59
59
|
init {
|
|
60
60
|
clipChildren = false
|
|
@@ -67,8 +67,8 @@ internal class NativeOverlayHost private constructor(
|
|
|
67
67
|
* predictive back, so the dispatcher alone never runs first.
|
|
68
68
|
*/
|
|
69
69
|
fun handleBackKey(event: KeyEvent): Boolean {
|
|
70
|
-
val entry =
|
|
71
|
-
if (event.action == KeyEvent.ACTION_UP && !event.isCanceled && entry.dismissOnBackPress) {
|
|
70
|
+
val entry = topBackEntry() ?: return false
|
|
71
|
+
if (event.action == KeyEvent.ACTION_UP && !event.isCanceled && !entry.isClosing && entry.dismissOnBackPress) {
|
|
72
72
|
entry.onRequestDismiss?.invoke("back")
|
|
73
73
|
}
|
|
74
74
|
return true
|
|
@@ -109,14 +109,14 @@ internal class NativeOverlayHost private constructor(
|
|
|
109
109
|
}
|
|
110
110
|
|
|
111
111
|
fun onEntriesChanged() {
|
|
112
|
-
val
|
|
113
|
-
if (
|
|
112
|
+
val hasBackEntry = topBackEntry() != null
|
|
113
|
+
if (hasBackEntry && !backCallback.isEnabled) {
|
|
114
114
|
// The most recently added callback wins; re-add so ours runs before
|
|
115
115
|
// ReactActivity's and react-native-screens' callbacks.
|
|
116
116
|
backCallback.remove()
|
|
117
117
|
(activity as? ComponentActivity)?.onBackPressedDispatcher?.addCallback(backCallback)
|
|
118
118
|
}
|
|
119
|
-
backCallback.isEnabled =
|
|
119
|
+
backCallback.isEnabled = hasBackEntry
|
|
120
120
|
updateAccessibility()
|
|
121
121
|
}
|
|
122
122
|
|
|
@@ -134,41 +134,40 @@ internal class NativeOverlayHost private constructor(
|
|
|
134
134
|
}
|
|
135
135
|
|
|
136
136
|
/** Global overlays win; otherwise the topmost visible page overlay. */
|
|
137
|
-
private fun
|
|
138
|
-
orderedEntries().lastOrNull {
|
|
139
|
-
|
|
140
|
-
|
|
137
|
+
private fun topBackEntry(): NativeOverlayEntryRootView? =
|
|
138
|
+
orderedEntries().lastOrNull {
|
|
139
|
+
(it.blocking || it.dismissOnBackPress) && it.isShownForInput && (!it.isClosing || it.blocking)
|
|
140
|
+
}
|
|
141
|
+
?: shownPageHosts()
|
|
141
142
|
.flatMap { it.shownEntries() }
|
|
142
|
-
.lastOrNull { it.blocking }
|
|
143
|
+
.lastOrNull { (it.blocking || it.dismissOnBackPress) && (!it.isClosing || it.blocking) }
|
|
144
|
+
|
|
145
|
+
private fun shownPageHosts(): List<NativeOverlayPageHostView> =
|
|
146
|
+
NativeOverlayPageHostView.allHosts().filter {
|
|
147
|
+
it.isAttachedToWindow && it.isShown && it.rootView === activity.window.decorView
|
|
148
|
+
}
|
|
143
149
|
|
|
144
150
|
/** TalkBack only reads the topmost blocking overlay and what is above it. */
|
|
145
151
|
private fun updateAccessibility() {
|
|
146
152
|
val entries = orderedEntries()
|
|
147
153
|
val topIndex = entries.indexOfLast { it.blocking && it.isShownForInput }
|
|
148
|
-
val content =
|
|
149
|
-
val below = mutableListOf<View>()
|
|
154
|
+
val content = activity.findViewById<ViewGroup>(android.R.id.content) ?: return
|
|
150
155
|
if (topIndex >= 0) {
|
|
151
|
-
content
|
|
152
|
-
|
|
153
|
-
}
|
|
154
|
-
below.addAll(entries.subList(0, topIndex))
|
|
155
|
-
}
|
|
156
|
-
val restore = hiddenForAccessibility.keys.filter { it !in below }
|
|
157
|
-
restore.forEach { view ->
|
|
158
|
-
view.importantForAccessibility = hiddenForAccessibility.remove(view) ?: IMPORTANT_FOR_ACCESSIBILITY_AUTO
|
|
159
|
-
}
|
|
160
|
-
below.forEach { view ->
|
|
161
|
-
if (view !in hiddenForAccessibility) {
|
|
162
|
-
hiddenForAccessibility[view] = view.importantForAccessibility
|
|
163
|
-
view.importantForAccessibility = IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
|
|
164
|
-
}
|
|
156
|
+
accessibilityIsolation.update(content, this, entries.take(topIndex), this)
|
|
157
|
+
return
|
|
165
158
|
}
|
|
159
|
+
val pageEntry = shownPageHosts().flatMap { it.shownEntries() }.lastOrNull { it.blocking }
|
|
160
|
+
val pageHost = pageEntry?.parent as? NativeOverlayPageHostView
|
|
161
|
+
val below = pageHost?.shownEntries()?.takeWhile { it !== pageEntry }.orEmpty()
|
|
162
|
+
accessibilityIsolation.update(content, pageHost, below, this)
|
|
166
163
|
}
|
|
167
164
|
|
|
168
165
|
companion object {
|
|
169
|
-
private val hosts = WeakHashMap<Activity, NativeOverlayHost
|
|
166
|
+
private val hosts = WeakHashMap<Activity, WeakReference<NativeOverlayHost>>()
|
|
170
167
|
|
|
171
168
|
fun of(activity: Activity): NativeOverlayHost =
|
|
172
|
-
hosts
|
|
169
|
+
hosts[activity]?.get() ?: NativeOverlayHost(activity).also {
|
|
170
|
+
hosts[activity] = WeakReference(it)
|
|
171
|
+
}
|
|
173
172
|
}
|
|
174
173
|
}
|
|
@@ -3,6 +3,7 @@ package com.onekey.nativeoverlay
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.content.Context
|
|
5
5
|
import android.view.ViewGroup
|
|
6
|
+
import com.facebook.react.bridge.ReactContext
|
|
6
7
|
import com.facebook.react.uimanager.PointerEvents
|
|
7
8
|
import com.facebook.react.uimanager.ReactPointerEventsView
|
|
8
9
|
import java.lang.ref.WeakReference
|
|
@@ -33,6 +34,7 @@ class NativeOverlayPageHostView(context: Context) : ViewGroup(context), ReactPoi
|
|
|
33
34
|
if (owners == suspendedOwners) return
|
|
34
35
|
suspendedOwners = owners
|
|
35
36
|
entries().forEach(::applySuspension)
|
|
37
|
+
onEntriesChanged()
|
|
36
38
|
}
|
|
37
39
|
|
|
38
40
|
internal fun attach(entry: NativeOverlayEntryRootView) {
|
|
@@ -44,10 +46,12 @@ class NativeOverlayPageHostView(context: Context) : ViewGroup(context), ReactPoi
|
|
|
44
46
|
}
|
|
45
47
|
layoutEntries()
|
|
46
48
|
applySuspension(entry)
|
|
49
|
+
onEntriesChanged()
|
|
47
50
|
}
|
|
48
51
|
|
|
49
52
|
internal fun detach(entry: NativeOverlayEntryRootView) {
|
|
50
53
|
removeView(entry)
|
|
54
|
+
onEntriesChanged()
|
|
51
55
|
}
|
|
52
56
|
|
|
53
57
|
/** Visible, input-ready entries in bottom-to-top order. */
|
|
@@ -56,6 +60,23 @@ class NativeOverlayPageHostView(context: Context) : ViewGroup(context), ReactPoi
|
|
|
56
60
|
|
|
57
61
|
fun invalidateHost() {
|
|
58
62
|
if (hostKey.isNotEmpty() && registry[hostKey]?.get() === this) registry.remove(hostKey)
|
|
63
|
+
onEntriesChanged()
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
override fun onAttachedToWindow() {
|
|
67
|
+
super.onAttachedToWindow()
|
|
68
|
+
onEntriesChanged()
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
override fun onDetachedFromWindow() {
|
|
72
|
+
super.onDetachedFromWindow()
|
|
73
|
+
onEntriesChanged()
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
private fun onEntriesChanged() {
|
|
77
|
+
(context as? ReactContext)?.currentActivity?.let {
|
|
78
|
+
NativeOverlayHost.of(it).onEntriesChanged()
|
|
79
|
+
}
|
|
59
80
|
}
|
|
60
81
|
|
|
61
82
|
private var layoutScheduled = false
|
|
@@ -135,6 +135,7 @@ class NativeOverlayView(
|
|
|
135
135
|
config = OverlayAnimationConfig.parse(animationConfig)
|
|
136
136
|
}
|
|
137
137
|
entry?.let(::applyEntryConfiguration)
|
|
138
|
+
notifyHostEntriesChanged()
|
|
138
139
|
if (visible) {
|
|
139
140
|
cycleOpen = true
|
|
140
141
|
if (phase == Phase.HIDDEN || phase == Phase.EXITING) present()
|
|
@@ -199,8 +200,9 @@ class NativeOverlayView(
|
|
|
199
200
|
}
|
|
200
201
|
cancelAnimators()
|
|
201
202
|
phase = Phase.ENTERING
|
|
203
|
+
target.isClosing = false
|
|
202
204
|
target.isShownForInput = true
|
|
203
|
-
|
|
205
|
+
notifyHostEntriesChanged()
|
|
204
206
|
if (blocking) {
|
|
205
207
|
target.sendAccessibilityEvent(AccessibilityEvent.TYPE_WINDOW_STATE_CHANGED)
|
|
206
208
|
}
|
|
@@ -225,8 +227,8 @@ class NativeOverlayView(
|
|
|
225
227
|
val target = entry ?: return finishDismiss("programmatic")
|
|
226
228
|
cancelAnimators()
|
|
227
229
|
phase = Phase.EXITING
|
|
228
|
-
target.
|
|
229
|
-
|
|
230
|
+
target.isClosing = true
|
|
231
|
+
notifyHostEntriesChanged()
|
|
230
232
|
val transition = config.exit.reduceMotionAdjusted(isReduceMotionEnabled())
|
|
231
233
|
if (transition.kind == "none") return finishDismiss("programmatic")
|
|
232
234
|
animate(target, hiddenState(target, transition), 0f, transition.motion) {
|
|
@@ -240,6 +242,7 @@ class NativeOverlayView(
|
|
|
240
242
|
phase = Phase.HIDDEN
|
|
241
243
|
entry?.let { target ->
|
|
242
244
|
target.isShownForInput = false
|
|
245
|
+
target.isClosing = false
|
|
243
246
|
stageContent()
|
|
244
247
|
host?.detach(target)
|
|
245
248
|
pageHost?.detach(target)
|
|
@@ -389,6 +392,10 @@ class NativeOverlayView(
|
|
|
389
392
|
}
|
|
390
393
|
}
|
|
391
394
|
|
|
395
|
+
private fun notifyHostEntriesChanged() {
|
|
396
|
+
reactContext.currentActivity?.let { NativeOverlayHost.of(it).onEntriesChanged() }
|
|
397
|
+
}
|
|
398
|
+
|
|
392
399
|
/**
|
|
393
400
|
* While hidden, keep the React child inside this invisible view. Fabric only
|
|
394
401
|
* reports onLayout for attached views, and fitted sheets size themselves
|
package/android/src/test/java/com/onekey/nativeoverlay/NativeOverlayAccessibilityIsolationTest.kt
ADDED
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
package com.onekey.nativeoverlay
|
|
2
|
+
|
|
3
|
+
import android.view.View
|
|
4
|
+
import android.widget.FrameLayout
|
|
5
|
+
import org.junit.Assert.assertEquals
|
|
6
|
+
import org.junit.Test
|
|
7
|
+
import org.junit.runner.RunWith
|
|
8
|
+
import org.robolectric.RobolectricTestRunner
|
|
9
|
+
import org.robolectric.RuntimeEnvironment
|
|
10
|
+
import org.robolectric.annotation.Config
|
|
11
|
+
|
|
12
|
+
@RunWith(RobolectricTestRunner::class)
|
|
13
|
+
@Config(sdk = [34])
|
|
14
|
+
class NativeOverlayAccessibilityIsolationTest {
|
|
15
|
+
private val context get() = RuntimeEnvironment.getApplication()
|
|
16
|
+
|
|
17
|
+
private class Tree(context: android.content.Context) {
|
|
18
|
+
val root = FrameLayout(context)
|
|
19
|
+
val reactRoot = FrameLayout(context)
|
|
20
|
+
val card = FrameLayout(context)
|
|
21
|
+
val content = View(context).apply { importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_YES }
|
|
22
|
+
val chrome = View(context)
|
|
23
|
+
val pageHost = FrameLayout(context)
|
|
24
|
+
val lower = View(context)
|
|
25
|
+
val blocker = View(context)
|
|
26
|
+
val above = View(context)
|
|
27
|
+
val globalHost = FrameLayout(context)
|
|
28
|
+
|
|
29
|
+
init {
|
|
30
|
+
root.addView(reactRoot)
|
|
31
|
+
root.addView(globalHost)
|
|
32
|
+
reactRoot.addView(card)
|
|
33
|
+
reactRoot.addView(chrome)
|
|
34
|
+
card.addView(content)
|
|
35
|
+
card.addView(pageHost)
|
|
36
|
+
pageHost.addView(lower)
|
|
37
|
+
pageHost.addView(blocker)
|
|
38
|
+
pageHost.addView(above)
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
@Test
|
|
43
|
+
fun pageBlockerHidesPageAndNavigationWithoutHidingTheHost() {
|
|
44
|
+
val tree = Tree(context)
|
|
45
|
+
val isolation = NativeOverlayAccessibilityIsolation()
|
|
46
|
+
isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
|
|
47
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.content.importantForAccessibility)
|
|
48
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.chrome.importantForAccessibility)
|
|
49
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.pageHost.importantForAccessibility)
|
|
50
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.blocker.importantForAccessibility)
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
@Test
|
|
54
|
+
fun entriesAbovePageBlockerAndGlobalHostStayAccessible() {
|
|
55
|
+
val tree = Tree(context)
|
|
56
|
+
NativeOverlayAccessibilityIsolation().update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
|
|
57
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.lower.importantForAccessibility)
|
|
58
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.above.importantForAccessibility)
|
|
59
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.globalHost.importantForAccessibility)
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
@Test
|
|
63
|
+
fun dismissalOrSuspensionRestoresOriginalFlags() {
|
|
64
|
+
val tree = Tree(context)
|
|
65
|
+
tree.lower.importantForAccessibility = View.IMPORTANT_FOR_ACCESSIBILITY_NO
|
|
66
|
+
val isolation = NativeOverlayAccessibilityIsolation()
|
|
67
|
+
isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
|
|
68
|
+
isolation.update(tree.root, null, emptyList(), tree.globalHost)
|
|
69
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_YES, tree.content.importantForAccessibility)
|
|
70
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO, tree.lower.importantForAccessibility)
|
|
71
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.chrome.importantForAccessibility)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
@Test
|
|
75
|
+
fun globalBlockerHidesReactRootThenPageIsolationReturns() {
|
|
76
|
+
val tree = Tree(context)
|
|
77
|
+
val globalLower = View(context)
|
|
78
|
+
tree.globalHost.addView(globalLower)
|
|
79
|
+
val isolation = NativeOverlayAccessibilityIsolation()
|
|
80
|
+
isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
|
|
81
|
+
isolation.update(tree.root, tree.globalHost, listOf(globalLower), tree.globalHost)
|
|
82
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.reactRoot.importantForAccessibility)
|
|
83
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, globalLower.importantForAccessibility)
|
|
84
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_YES, tree.content.importantForAccessibility)
|
|
85
|
+
isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
|
|
86
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.reactRoot.importantForAccessibility)
|
|
87
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, globalLower.importantForAccessibility)
|
|
88
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.content.importantForAccessibility)
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
@Test
|
|
92
|
+
fun detachedHostRestoresIsolationInsteadOfHidingAnotherTree() {
|
|
93
|
+
val tree = Tree(context)
|
|
94
|
+
val isolation = NativeOverlayAccessibilityIsolation()
|
|
95
|
+
isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
|
|
96
|
+
tree.card.removeView(tree.pageHost)
|
|
97
|
+
isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
|
|
98
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_YES, tree.content.importantForAccessibility)
|
|
99
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.lower.importantForAccessibility)
|
|
100
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.chrome.importantForAccessibility)
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
@Test
|
|
104
|
+
fun changingTheBlockingEntryRestoresEntriesNoLongerBelowIt() {
|
|
105
|
+
val tree = Tree(context)
|
|
106
|
+
val isolation = NativeOverlayAccessibilityIsolation()
|
|
107
|
+
isolation.update(tree.root, tree.pageHost, listOf(tree.lower, tree.blocker), tree.globalHost)
|
|
108
|
+
isolation.update(tree.root, tree.pageHost, listOf(tree.lower), tree.globalHost)
|
|
109
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_AUTO, tree.blocker.importantForAccessibility)
|
|
110
|
+
assertEquals(View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS, tree.lower.importantForAccessibility)
|
|
111
|
+
}
|
|
112
|
+
}
|