react-native-screens 3.9.0 → 3.10.0
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/README.md +38 -0
- package/android/src/main/java/com/swmansion/rnscreens/CustomSearchView.kt +71 -0
- package/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +7 -0
- package/android/src/main/java/com/swmansion/rnscreens/FragmentBackPressOverrider.kt +29 -0
- package/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt +2 -1
- package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +7 -41
- package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +19 -1
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +29 -2
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +76 -12
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +13 -4
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +8 -0
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt +7 -1
- package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt +1 -0
- package/android/src/main/java/com/swmansion/rnscreens/SearchBarManager.kt +90 -0
- package/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt +150 -0
- package/android/src/main/java/com/swmansion/rnscreens/SearchViewFormatter.kt +40 -0
- package/createNativeStackNavigator/README.md +33 -9
- package/ios/RNSScreen.m +4 -0
- package/ios/RNSScreenStack.m +7 -6
- package/lib/commonjs/index.js +17 -1
- package/lib/commonjs/index.js.map +1 -1
- package/lib/commonjs/index.native.js +66 -18
- package/lib/commonjs/index.native.js.map +1 -1
- package/lib/commonjs/native-stack/utils/useBackPressSubscription.js +67 -0
- package/lib/commonjs/native-stack/utils/useBackPressSubscription.js.map +1 -0
- package/lib/commonjs/native-stack/views/HeaderConfig.js +46 -4
- package/lib/commonjs/native-stack/views/HeaderConfig.js.map +1 -1
- package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js +60 -0
- package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
- package/lib/commonjs/reanimated/ReanimatedScreen.js +7 -79
- package/lib/commonjs/reanimated/ReanimatedScreen.js.map +1 -1
- package/lib/commonjs/reanimated/ReanimatedScreenProvider.js +61 -0
- package/lib/commonjs/reanimated/ReanimatedScreenProvider.js.map +1 -0
- package/lib/commonjs/reanimated/index.js +2 -2
- package/lib/commonjs/reanimated/index.js.map +1 -1
- package/lib/commonjs/utils.js +20 -0
- package/lib/commonjs/utils.js.map +1 -0
- package/lib/module/index.js +1 -0
- package/lib/module/index.js.map +1 -1
- package/lib/module/index.native.js +65 -19
- package/lib/module/index.native.js.map +1 -1
- package/lib/module/native-stack/utils/useBackPressSubscription.js +50 -0
- package/lib/module/native-stack/utils/useBackPressSubscription.js.map +1 -0
- package/lib/module/native-stack/views/HeaderConfig.js +46 -5
- package/lib/module/native-stack/views/HeaderConfig.js.map +1 -1
- package/lib/module/reanimated/ReanimatedNativeStackScreen.js +40 -0
- package/lib/module/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
- package/lib/module/reanimated/ReanimatedScreen.js +6 -73
- package/lib/module/reanimated/ReanimatedScreen.js.map +1 -1
- package/lib/module/reanimated/ReanimatedScreenProvider.js +49 -0
- package/lib/module/reanimated/ReanimatedScreenProvider.js.map +1 -0
- package/lib/module/reanimated/index.js +1 -1
- package/lib/module/reanimated/index.js.map +1 -1
- package/lib/module/utils.js +8 -0
- package/lib/module/utils.js.map +1 -0
- package/lib/typescript/index.d.ts +1 -0
- package/lib/typescript/native-stack/types.d.ts +0 -2
- package/lib/typescript/native-stack/utils/useBackPressSubscription.d.ts +16 -0
- package/lib/typescript/reanimated/ReanimatedNativeStackScreen.d.ts +5 -0
- package/lib/typescript/reanimated/ReanimatedScreen.d.ts +5 -2
- package/lib/typescript/reanimated/ReanimatedScreenProvider.d.ts +2 -0
- package/lib/typescript/reanimated/index.d.ts +1 -1
- package/lib/typescript/types.d.ts +46 -1
- package/lib/typescript/utils.d.ts +2 -0
- package/native-stack/README.md +31 -5
- package/package.json +2 -1
- package/src/index.native.tsx +94 -36
- package/src/index.tsx +4 -0
- package/src/native-stack/types.tsx +0 -2
- package/src/native-stack/utils/useBackPressSubscription.tsx +66 -0
- package/src/native-stack/views/HeaderConfig.tsx +46 -3
- package/src/reanimated/ReanimatedNativeStackScreen.tsx +61 -0
- package/src/reanimated/ReanimatedScreen.tsx +6 -84
- package/src/reanimated/ReanimatedScreenProvider.tsx +42 -0
- package/src/reanimated/index.tsx +1 -1
- package/src/types.tsx +46 -1
- package/src/utils.ts +12 -0
package/README.md
CHANGED
|
@@ -35,6 +35,26 @@ protected void onCreate(Bundle savedInstanceState) {
|
|
|
35
35
|
|
|
36
36
|
For people that must handle cases like this, there is [a more detailed discussion of the difficulties in a series of related comments](https://github.com/software-mansion/react-native-screens/issues/17#issuecomment-424704633).
|
|
37
37
|
|
|
38
|
+
<details>
|
|
39
|
+
<summary>Need to use a custom Kotlin version?</summary>
|
|
40
|
+
<br>
|
|
41
|
+
|
|
42
|
+
Since `v3.6.0` `react-native-screens` has been rewritten with Kotlin. Kotlin version used in this library defaults to `1.4.10`.
|
|
43
|
+
|
|
44
|
+
If you need to use a different Kotlin version, set `kotlinVersion` ext property in your project's `android/build.gradle` and the library will use this version accordingly:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
buildscript {
|
|
48
|
+
ext {
|
|
49
|
+
...
|
|
50
|
+
kotlinVersion = "1.4.10"
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
**Disclaimer**: `react-native-screens` requires Kotlin `1.3.50` or higher.
|
|
56
|
+
</details>
|
|
57
|
+
|
|
38
58
|
### Windows
|
|
39
59
|
|
|
40
60
|
Installation on Windows should be completely handled with auto-linking when using React Native Windows 0.63+. For earlier versions, you must [manually link](https://microsoft.github.io/react-native-windows/docs/native-modules-using) the native module.
|
|
@@ -68,6 +88,24 @@ Just make sure that the version of [react-navigation](https://github.com/react-n
|
|
|
68
88
|
|
|
69
89
|
You are all set 🎉 – when screens are enabled in your application code react-navigation will automatically use them instead of relying on plain React Native Views.
|
|
70
90
|
|
|
91
|
+
### Experimental support for `react-freeze`
|
|
92
|
+
|
|
93
|
+
> You have to use React Native 0.64 or higher, react-navigation 5.x or 6.x and react-native-screens >= v3.9.0
|
|
94
|
+
|
|
95
|
+
Since `v3.9.0`, `react-native-screens` comes with experimental support for [`react-freeze`](https://github.com/software-mansion-labs/react-freeze). It uses the React `Suspense` mechanism to prevent parts of the React component tree from rendering, while keeping its state untouched.
|
|
96
|
+
|
|
97
|
+
To benefit from this feature, enable it in your entry file (e.g. `App.js`) with this snippet:
|
|
98
|
+
|
|
99
|
+
```js
|
|
100
|
+
import { enableFreeze } from 'react-native-screens';
|
|
101
|
+
|
|
102
|
+
enableFreeze(true);
|
|
103
|
+
```
|
|
104
|
+
|
|
105
|
+
Want to know more? Check out [react-freeze README](https://github.com/software-mansion-labs/react-freeze#readme)
|
|
106
|
+
|
|
107
|
+
Found a bug? File an issue [here](https://github.com/software-mansion/react-native-screens/issues) or directly in [react-freeze repository](https://github.com/software-mansion-labs/react-freeze/issues).
|
|
108
|
+
|
|
71
109
|
### Disabling `react-native-screens`
|
|
72
110
|
|
|
73
111
|
If, for whatever reason, you'd like to disable native screens support and use plain React Native Views add the following code in your entry file (e.g. `App.js`):
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
package com.swmansion.rnscreens
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.activity.OnBackPressedCallback
|
|
5
|
+
import androidx.appcompat.widget.SearchView
|
|
6
|
+
import androidx.fragment.app.Fragment
|
|
7
|
+
|
|
8
|
+
class CustomSearchView(context: Context?, fragment: Fragment) : SearchView(context) {
|
|
9
|
+
/*
|
|
10
|
+
CustomSearchView uses some variables from SearchView. They are listed below with links to documentation
|
|
11
|
+
isIconified - https://developer.android.com/reference/android/widget/SearchView#setIconified(boolean)
|
|
12
|
+
maxWidth - https://developer.android.com/reference/android/widget/SearchView#setMaxWidth(int)
|
|
13
|
+
setOnSearchClickListener - https://developer.android.com/reference/android/widget/SearchView#setOnSearchClickListener(android.view.View.OnClickListener)
|
|
14
|
+
setOnCloseListener - https://developer.android.com/reference/android/widget/SearchView#setOnCloseListener(android.widget.SearchView.OnCloseListener)
|
|
15
|
+
*/
|
|
16
|
+
private var mCustomOnCloseListener: OnCloseListener? = null
|
|
17
|
+
private var mCustomOnSearchClickedListener: OnClickListener? = null
|
|
18
|
+
|
|
19
|
+
private var mOnBackPressedCallback: OnBackPressedCallback =
|
|
20
|
+
object : OnBackPressedCallback(true) {
|
|
21
|
+
override fun handleOnBackPressed() {
|
|
22
|
+
isIconified = true
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
private val backPressOverrider = FragmentBackPressOverrider(fragment, mOnBackPressedCallback)
|
|
26
|
+
var overrideBackAction: Boolean
|
|
27
|
+
set(value) {
|
|
28
|
+
backPressOverrider.overrideBackAction = value
|
|
29
|
+
}
|
|
30
|
+
get() = backPressOverrider.overrideBackAction
|
|
31
|
+
|
|
32
|
+
fun focus() {
|
|
33
|
+
isIconified = false
|
|
34
|
+
requestFocusFromTouch()
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
override fun setOnCloseListener(listener: OnCloseListener?) {
|
|
38
|
+
mCustomOnCloseListener = listener
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
override fun setOnSearchClickListener(listener: OnClickListener?) {
|
|
42
|
+
mCustomOnSearchClickedListener = listener
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
override fun onAttachedToWindow() {
|
|
46
|
+
super.onAttachedToWindow()
|
|
47
|
+
if (!isIconified) {
|
|
48
|
+
backPressOverrider.maybeAddBackCallback()
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
override fun onDetachedFromWindow() {
|
|
53
|
+
super.onDetachedFromWindow()
|
|
54
|
+
backPressOverrider.removeBackCallbackIfAdded()
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
init {
|
|
58
|
+
super.setOnSearchClickListener { v ->
|
|
59
|
+
mCustomOnSearchClickedListener?.onClick(v)
|
|
60
|
+
backPressOverrider.maybeAddBackCallback()
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
super.setOnCloseListener {
|
|
64
|
+
val result = mCustomOnCloseListener?.onClose() ?: false
|
|
65
|
+
backPressOverrider.removeBackCallbackIfAdded()
|
|
66
|
+
result
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
maxWidth = Integer.MAX_VALUE
|
|
70
|
+
}
|
|
71
|
+
}
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
package com.swmansion.rnscreens
|
|
2
|
+
|
|
3
|
+
import android.content.Context
|
|
4
|
+
import androidx.appcompat.widget.Toolbar
|
|
5
|
+
|
|
6
|
+
// This class is used to store config closer to search bar
|
|
7
|
+
open class CustomToolbar(context: Context, val config: ScreenStackHeaderConfig) : Toolbar(context)
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
package com.swmansion.rnscreens
|
|
2
|
+
|
|
3
|
+
import androidx.activity.OnBackPressedCallback
|
|
4
|
+
import androidx.fragment.app.Fragment
|
|
5
|
+
|
|
6
|
+
class FragmentBackPressOverrider(
|
|
7
|
+
private val fragment: Fragment,
|
|
8
|
+
private val mOnBackPressedCallback: OnBackPressedCallback
|
|
9
|
+
) {
|
|
10
|
+
private var mIsBackCallbackAdded: Boolean = false
|
|
11
|
+
var overrideBackAction: Boolean = true
|
|
12
|
+
|
|
13
|
+
fun maybeAddBackCallback() {
|
|
14
|
+
if (!mIsBackCallbackAdded && overrideBackAction) {
|
|
15
|
+
fragment.activity?.onBackPressedDispatcher?.addCallback(
|
|
16
|
+
fragment,
|
|
17
|
+
mOnBackPressedCallback
|
|
18
|
+
)
|
|
19
|
+
mIsBackCallbackAdded = true
|
|
20
|
+
}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
fun removeBackCallbackIfAdded() {
|
|
24
|
+
if (mIsBackCallbackAdded) {
|
|
25
|
+
mOnBackPressedCallback.remove()
|
|
26
|
+
mIsBackCallbackAdded = false
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
}
|
|
@@ -1,18 +1,13 @@
|
|
|
1
1
|
package com.swmansion.rnscreens
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
|
-
import android.content.Context
|
|
5
4
|
import android.content.pm.ActivityInfo
|
|
6
5
|
import android.graphics.Paint
|
|
7
|
-
import android.os.Build
|
|
8
6
|
import android.os.Parcelable
|
|
9
7
|
import android.util.SparseArray
|
|
10
|
-
import android.view.View
|
|
11
8
|
import android.view.ViewGroup
|
|
12
9
|
import android.view.WindowManager
|
|
13
|
-
import android.view.inputmethod.InputMethodManager
|
|
14
10
|
import android.webkit.WebView
|
|
15
|
-
import android.widget.TextView
|
|
16
11
|
import com.facebook.react.bridge.GuardedRunnable
|
|
17
12
|
import com.facebook.react.bridge.ReactContext
|
|
18
13
|
import com.facebook.react.uimanager.UIManagerModule
|
|
@@ -87,28 +82,6 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
|
|
|
87
82
|
}
|
|
88
83
|
}
|
|
89
84
|
|
|
90
|
-
override fun onAttachedToWindow() {
|
|
91
|
-
super.onAttachedToWindow()
|
|
92
|
-
// This method implements a workaround for RN's autoFocus functionality. Because of the way
|
|
93
|
-
// autoFocus is implemented it sometimes gets triggered before native text view is mounted. As
|
|
94
|
-
// a result Android ignores calls for opening soft keyboard and here we trigger it manually
|
|
95
|
-
// again after the screen is attached.
|
|
96
|
-
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
97
|
-
var view = focusedChild
|
|
98
|
-
if (view != null) {
|
|
99
|
-
while (view is ViewGroup) {
|
|
100
|
-
view = view.focusedChild
|
|
101
|
-
}
|
|
102
|
-
if (view is TextView) {
|
|
103
|
-
val textView = view
|
|
104
|
-
if (textView.showSoftInputOnFocus) {
|
|
105
|
-
textView.addOnAttachStateChangeListener(sShowSoftKeyboardOnAttach)
|
|
106
|
-
}
|
|
107
|
-
}
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
}
|
|
111
|
-
|
|
112
85
|
val headerConfig: ScreenStackHeaderConfig?
|
|
113
86
|
get() {
|
|
114
87
|
val child = getChildAt(0)
|
|
@@ -183,6 +156,13 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
|
|
|
183
156
|
fragment?.let { ScreenWindowTraits.setOrientation(this, it.tryGetActivity()) }
|
|
184
157
|
}
|
|
185
158
|
|
|
159
|
+
// Accepts one of 4 accessibility flags
|
|
160
|
+
// developer.android.com/reference/android/view/View#attr_android:importantForAccessibility
|
|
161
|
+
fun changeAccessibilityMode(mode: Int) {
|
|
162
|
+
this.importantForAccessibility = mode
|
|
163
|
+
this.headerConfig?.toolbar?.importantForAccessibility = mode
|
|
164
|
+
}
|
|
165
|
+
|
|
186
166
|
var statusBarStyle: String?
|
|
187
167
|
get() = mStatusBarStyle
|
|
188
168
|
set(statusBarStyle) {
|
|
@@ -254,18 +234,4 @@ class Screen constructor(context: ReactContext?) : ViewGroup(context) {
|
|
|
254
234
|
enum class WindowTraits {
|
|
255
235
|
ORIENTATION, COLOR, STYLE, TRANSLUCENT, HIDDEN, ANIMATED
|
|
256
236
|
}
|
|
257
|
-
|
|
258
|
-
companion object {
|
|
259
|
-
private val sShowSoftKeyboardOnAttach: OnAttachStateChangeListener =
|
|
260
|
-
object : OnAttachStateChangeListener {
|
|
261
|
-
override fun onViewAttachedToWindow(view: View) {
|
|
262
|
-
val inputMethodManager =
|
|
263
|
-
view.context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
|
|
264
|
-
inputMethodManager.showSoftInput(view, 0)
|
|
265
|
-
view.removeOnAttachStateChangeListener(this)
|
|
266
|
-
}
|
|
267
|
-
|
|
268
|
-
override fun onViewDetachedFromWindow(view: View) {}
|
|
269
|
-
}
|
|
270
|
-
}
|
|
271
237
|
}
|
|
@@ -2,6 +2,7 @@ package com.swmansion.rnscreens
|
|
|
2
2
|
|
|
3
3
|
import android.annotation.SuppressLint
|
|
4
4
|
import android.app.Activity
|
|
5
|
+
import android.content.Context
|
|
5
6
|
import android.os.Bundle
|
|
6
7
|
import android.view.LayoutInflater
|
|
7
8
|
import android.view.View
|
|
@@ -61,7 +62,7 @@ open class ScreenFragment : Fragment {
|
|
|
61
62
|
container: ViewGroup?,
|
|
62
63
|
savedInstanceState: Bundle?
|
|
63
64
|
): View? {
|
|
64
|
-
val wrapper = context?.let {
|
|
65
|
+
val wrapper = context?.let { ScreensFrameLayout(it) }
|
|
65
66
|
|
|
66
67
|
val params = FrameLayout.LayoutParams(
|
|
67
68
|
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
|
|
@@ -71,6 +72,23 @@ open class ScreenFragment : Fragment {
|
|
|
71
72
|
return wrapper
|
|
72
73
|
}
|
|
73
74
|
|
|
75
|
+
private class ScreensFrameLayout(
|
|
76
|
+
context: Context,
|
|
77
|
+
) : FrameLayout(context) {
|
|
78
|
+
/**
|
|
79
|
+
* This method implements a workaround for RN's autoFocus functionality. Because of the way
|
|
80
|
+
* autoFocus is implemented it dismisses soft keyboard in fragment transition
|
|
81
|
+
* due to change of visibility of the view at the start of the transition. Here we override the
|
|
82
|
+
* call to `clearFocus` when the visibility of view is `INVISIBLE` since `clearFocus` triggers the
|
|
83
|
+
* hiding of the keyboard in `ReactEditText.java`.
|
|
84
|
+
*/
|
|
85
|
+
override fun clearFocus() {
|
|
86
|
+
if (visibility != INVISIBLE) {
|
|
87
|
+
super.clearFocus()
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
|
|
74
92
|
open fun onContainerUpdate() {
|
|
75
93
|
updateWindowTraits()
|
|
76
94
|
}
|
|
@@ -121,8 +121,10 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
121
121
|
// before, probably replace or reset was called, so we play the "close animation".
|
|
122
122
|
// Otherwise it's open animation
|
|
123
123
|
val containsTopScreen = mTopScreen?.let { mScreenFragments.contains(it) } == true
|
|
124
|
-
|
|
125
|
-
|
|
124
|
+
val isPushReplace = newTop.screen.replaceAnimation === Screen.ReplaceAnimation.PUSH
|
|
125
|
+
shouldUseOpenAnimation = containsTopScreen || isPushReplace
|
|
126
|
+
// if the replace animation is `push`, the new top screen provides the animation, otherwise the previous one
|
|
127
|
+
stackAnimation = if (shouldUseOpenAnimation) newTop.screen.stackAnimation else mTopScreen?.screen?.stackAnimation
|
|
126
128
|
} else if (mTopScreen == null && newTop != null) {
|
|
127
129
|
// mTopScreen was not present before so newTop is the first screen added to a stack
|
|
128
130
|
// and we don't want the animation when it is entering, but we want to send the
|
|
@@ -236,10 +238,35 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
|
|
|
236
238
|
mTopScreen = newTop
|
|
237
239
|
mStack.clear()
|
|
238
240
|
mStack.addAll(mScreenFragments)
|
|
241
|
+
|
|
242
|
+
turnOffA11yUnderTransparentScreen(visibleBottom)
|
|
243
|
+
|
|
239
244
|
it.commitNowAllowingStateLoss()
|
|
240
245
|
}
|
|
241
246
|
}
|
|
242
247
|
|
|
248
|
+
// only top visible screen should be accessible
|
|
249
|
+
private fun turnOffA11yUnderTransparentScreen(visibleBottom: ScreenStackFragment?) {
|
|
250
|
+
if (mScreenFragments.size > 1 && visibleBottom != null) {
|
|
251
|
+
mTopScreen?.let {
|
|
252
|
+
if (isTransparent(it)) {
|
|
253
|
+
val screenFragmentsBeneathTop = mScreenFragments.slice(0 until mScreenFragments.size - 1).asReversed()
|
|
254
|
+
// go from the top of the stack excluding the top screen
|
|
255
|
+
for (screenFragment in screenFragmentsBeneathTop) {
|
|
256
|
+
screenFragment.screen.changeAccessibilityMode(IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS)
|
|
257
|
+
|
|
258
|
+
// don't change a11y below non-transparent screens
|
|
259
|
+
if (screenFragment == visibleBottom) {
|
|
260
|
+
break
|
|
261
|
+
}
|
|
262
|
+
}
|
|
263
|
+
}
|
|
264
|
+
}
|
|
265
|
+
}
|
|
266
|
+
|
|
267
|
+
topScreen?.changeAccessibilityMode(IMPORTANT_FOR_ACCESSIBILITY_AUTO)
|
|
268
|
+
}
|
|
269
|
+
|
|
243
270
|
override fun notifyContainerUpdate() {
|
|
244
271
|
for (screen in mStack) {
|
|
245
272
|
screen.onContainerUpdate()
|
|
@@ -5,6 +5,9 @@ import android.content.Context
|
|
|
5
5
|
import android.graphics.Color
|
|
6
6
|
import android.os.Bundle
|
|
7
7
|
import android.view.LayoutInflater
|
|
8
|
+
import android.view.Menu
|
|
9
|
+
import android.view.MenuInflater
|
|
10
|
+
import android.view.MenuItem
|
|
8
11
|
import android.view.View
|
|
9
12
|
import android.view.ViewGroup
|
|
10
13
|
import android.view.animation.Animation
|
|
@@ -24,6 +27,9 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
24
27
|
private var mShadowHidden = false
|
|
25
28
|
private var mIsTranslucent = false
|
|
26
29
|
|
|
30
|
+
var searchView: CustomSearchView? = null
|
|
31
|
+
var onSearchViewCreate: ((searchView: CustomSearchView) -> Unit)? = null
|
|
32
|
+
|
|
27
33
|
@SuppressLint("ValidFragment")
|
|
28
34
|
constructor(screenView: Screen) : super(screenView)
|
|
29
35
|
|
|
@@ -64,7 +70,8 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
64
70
|
fun setToolbarTranslucent(translucent: Boolean) {
|
|
65
71
|
if (mIsTranslucent != translucent) {
|
|
66
72
|
val params = screen.layoutParams
|
|
67
|
-
(params as CoordinatorLayout.LayoutParams).behavior =
|
|
73
|
+
(params as CoordinatorLayout.LayoutParams).behavior =
|
|
74
|
+
if (translucent) null else ScrollingViewBehavior()
|
|
68
75
|
mIsTranslucent = translucent
|
|
69
76
|
}
|
|
70
77
|
}
|
|
@@ -120,7 +127,8 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
120
127
|
container: ViewGroup?,
|
|
121
128
|
savedInstanceState: Bundle?
|
|
122
129
|
): View? {
|
|
123
|
-
val view:
|
|
130
|
+
val view: ScreensCoordinatorLayout? =
|
|
131
|
+
context?.let { ScreensCoordinatorLayout(it, this) }
|
|
124
132
|
val params = CoordinatorLayout.LayoutParams(
|
|
125
133
|
LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.MATCH_PARENT
|
|
126
134
|
)
|
|
@@ -142,9 +150,48 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
142
150
|
mAppBarLayout?.targetElevation = 0f
|
|
143
151
|
}
|
|
144
152
|
mToolbar?.let { mAppBarLayout?.addView(recycleView(it)) }
|
|
153
|
+
setHasOptionsMenu(true)
|
|
145
154
|
return view
|
|
146
155
|
}
|
|
147
156
|
|
|
157
|
+
override fun onPrepareOptionsMenu(menu: Menu) {
|
|
158
|
+
updateToolbarMenu(menu)
|
|
159
|
+
return super.onPrepareOptionsMenu(menu)
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
|
|
163
|
+
updateToolbarMenu(menu)
|
|
164
|
+
return super.onCreateOptionsMenu(menu, inflater)
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
private fun shouldShowSearchBar(): Boolean {
|
|
168
|
+
val config = screen.headerConfig
|
|
169
|
+
val numberOfSubViews = config?.configSubviewsCount ?: 0
|
|
170
|
+
if (config != null && numberOfSubViews > 0) {
|
|
171
|
+
for (i in 0 until numberOfSubViews) {
|
|
172
|
+
val subView = config.getConfigSubview(i)
|
|
173
|
+
if (subView.type == ScreenStackHeaderSubview.Type.SEARCH_BAR) {
|
|
174
|
+
return true
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
return false
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
private fun updateToolbarMenu(menu: Menu) {
|
|
182
|
+
menu.clear()
|
|
183
|
+
if (shouldShowSearchBar()) {
|
|
184
|
+
if (searchView == null) {
|
|
185
|
+
val newSearchView = CustomSearchView(context, this)
|
|
186
|
+
searchView = newSearchView
|
|
187
|
+
onSearchViewCreate?.invoke(newSearchView)
|
|
188
|
+
}
|
|
189
|
+
val item = menu.add("")
|
|
190
|
+
item.setShowAsAction(MenuItem.SHOW_AS_ACTION_ALWAYS)
|
|
191
|
+
item.actionView = searchView
|
|
192
|
+
}
|
|
193
|
+
}
|
|
194
|
+
|
|
148
195
|
fun canNavigateBack(): Boolean {
|
|
149
196
|
val container: ScreenContainer<*>? = screen.container
|
|
150
197
|
check(container is ScreenStack) { "ScreenStackFragment added into a non-stack container" }
|
|
@@ -168,18 +215,22 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
168
215
|
container.dismiss(this)
|
|
169
216
|
}
|
|
170
217
|
|
|
171
|
-
private class
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
218
|
+
private class ScreensCoordinatorLayout(
|
|
219
|
+
context: Context,
|
|
220
|
+
private val mFragment: ScreenFragment
|
|
221
|
+
) : CoordinatorLayout(context) {
|
|
222
|
+
private val mAnimationListener: Animation.AnimationListener =
|
|
223
|
+
object : Animation.AnimationListener {
|
|
224
|
+
override fun onAnimationStart(animation: Animation) {
|
|
225
|
+
mFragment.onViewAnimationStart()
|
|
226
|
+
}
|
|
176
227
|
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
228
|
+
override fun onAnimationEnd(animation: Animation) {
|
|
229
|
+
mFragment.onViewAnimationEnd()
|
|
230
|
+
}
|
|
180
231
|
|
|
181
|
-
|
|
182
|
-
|
|
232
|
+
override fun onAnimationRepeat(animation: Animation) {}
|
|
233
|
+
}
|
|
183
234
|
|
|
184
235
|
override fun startAnimation(animation: Animation) {
|
|
185
236
|
// For some reason View##onAnimationEnd doesn't get called for
|
|
@@ -205,6 +256,19 @@ class ScreenStackFragment : ScreenFragment {
|
|
|
205
256
|
super.startAnimation(set)
|
|
206
257
|
}
|
|
207
258
|
}
|
|
259
|
+
|
|
260
|
+
/**
|
|
261
|
+
* This method implements a workaround for RN's autoFocus functionality. Because of the way
|
|
262
|
+
* autoFocus is implemented it dismisses soft keyboard in fragment transition
|
|
263
|
+
* due to change of visibility of the view at the start of the transition. Here we override the
|
|
264
|
+
* call to `clearFocus` when the visibility of view is `INVISIBLE` since `clearFocus` triggers the
|
|
265
|
+
* hiding of the keyboard in `ReactEditText.java`.
|
|
266
|
+
*/
|
|
267
|
+
override fun clearFocus() {
|
|
268
|
+
if (visibility != INVISIBLE) {
|
|
269
|
+
super.clearFocus()
|
|
270
|
+
}
|
|
271
|
+
}
|
|
208
272
|
}
|
|
209
273
|
|
|
210
274
|
private class ScreensAnimation(private val mFragment: ScreenFragment) : Animation() {
|
|
@@ -17,11 +17,13 @@ import androidx.fragment.app.Fragment
|
|
|
17
17
|
import com.facebook.react.ReactApplication
|
|
18
18
|
import com.facebook.react.bridge.JSApplicationIllegalArgumentException
|
|
19
19
|
import com.facebook.react.bridge.ReactContext
|
|
20
|
+
import com.facebook.react.bridge.WritableMap
|
|
21
|
+
import com.facebook.react.uimanager.events.RCTEventEmitter
|
|
20
22
|
import com.facebook.react.views.text.ReactTypefaceUtils
|
|
21
23
|
|
|
22
24
|
class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
23
25
|
private val mConfigSubviews = ArrayList<ScreenStackHeaderSubview>(3)
|
|
24
|
-
val toolbar:
|
|
26
|
+
val toolbar: CustomToolbar
|
|
25
27
|
private var mTitle: String? = null
|
|
26
28
|
private var mTitleColor = 0
|
|
27
29
|
private var mTitleFontFamily: String? = null
|
|
@@ -62,6 +64,11 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
62
64
|
}
|
|
63
65
|
}
|
|
64
66
|
|
|
67
|
+
private fun sendEvent(eventName: String, eventContent: WritableMap?) {
|
|
68
|
+
(context as ReactContext).getJSModule(RCTEventEmitter::class.java)
|
|
69
|
+
?.receiveEvent(id, eventName, eventContent)
|
|
70
|
+
}
|
|
71
|
+
|
|
65
72
|
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
|
66
73
|
// no-op
|
|
67
74
|
}
|
|
@@ -73,12 +80,14 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
73
80
|
override fun onAttachedToWindow() {
|
|
74
81
|
super.onAttachedToWindow()
|
|
75
82
|
mIsAttachedToWindow = true
|
|
83
|
+
sendEvent("onAttached", null)
|
|
76
84
|
onUpdate()
|
|
77
85
|
}
|
|
78
86
|
|
|
79
87
|
override fun onDetachedFromWindow() {
|
|
80
88
|
super.onDetachedFromWindow()
|
|
81
89
|
mIsAttachedToWindow = false
|
|
90
|
+
sendEvent("onDetached", null)
|
|
82
91
|
}
|
|
83
92
|
|
|
84
93
|
private val screen: Screen?
|
|
@@ -99,7 +108,7 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
99
108
|
}
|
|
100
109
|
return null
|
|
101
110
|
}
|
|
102
|
-
|
|
111
|
+
val screenFragment: ScreenStackFragment?
|
|
103
112
|
get() {
|
|
104
113
|
val screen = parent
|
|
105
114
|
if (screen is Screen) {
|
|
@@ -369,7 +378,7 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
369
378
|
mDirection = direction
|
|
370
379
|
}
|
|
371
380
|
|
|
372
|
-
private class DebugMenuToolbar(context: Context) :
|
|
381
|
+
private class DebugMenuToolbar(context: Context, config: ScreenStackHeaderConfig) : CustomToolbar(context, config) {
|
|
373
382
|
override fun showOverflowMenu(): Boolean {
|
|
374
383
|
(context.applicationContext as ReactApplication)
|
|
375
384
|
.reactNativeHost
|
|
@@ -381,7 +390,7 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
|
|
|
381
390
|
|
|
382
391
|
init {
|
|
383
392
|
visibility = GONE
|
|
384
|
-
toolbar = if (BuildConfig.DEBUG) DebugMenuToolbar(context) else
|
|
393
|
+
toolbar = if (BuildConfig.DEBUG) DebugMenuToolbar(context, this) else CustomToolbar(context, this)
|
|
385
394
|
mDefaultStartInset = toolbar.contentInsetStart
|
|
386
395
|
mDefaultStartInsetWithNavigation = toolbar.contentInsetStartWithNavigation
|
|
387
396
|
|
|
@@ -2,6 +2,7 @@ package com.swmansion.rnscreens
|
|
|
2
2
|
|
|
3
3
|
import android.view.View
|
|
4
4
|
import com.facebook.react.bridge.JSApplicationCausedNativeException
|
|
5
|
+
import com.facebook.react.common.MapBuilder
|
|
5
6
|
import com.facebook.react.module.annotations.ReactModule
|
|
6
7
|
import com.facebook.react.uimanager.ThemedReactContext
|
|
7
8
|
import com.facebook.react.uimanager.ViewGroupManager
|
|
@@ -129,6 +130,13 @@ class ScreenStackHeaderConfigViewManager : ViewGroupManager<ScreenStackHeaderCon
|
|
|
129
130
|
config.setDirection(direction)
|
|
130
131
|
}
|
|
131
132
|
|
|
133
|
+
override fun getExportedCustomDirectEventTypeConstants(): Map<String, Any>? {
|
|
134
|
+
return MapBuilder.builder<String, Any>()
|
|
135
|
+
.put("onAttached", MapBuilder.of("registrationName", "onAttached"))
|
|
136
|
+
.put("onDetached", MapBuilder.of("registrationName", "onDetached"))
|
|
137
|
+
.build()
|
|
138
|
+
}
|
|
139
|
+
|
|
132
140
|
companion object {
|
|
133
141
|
const val REACT_CLASS = "RNSScreenStackHeaderConfig"
|
|
134
142
|
}
|
|
@@ -10,6 +10,12 @@ class ScreenStackHeaderSubview(context: ReactContext?) : ReactViewGroup(context)
|
|
|
10
10
|
private var mReactWidth = 0
|
|
11
11
|
private var mReactHeight = 0
|
|
12
12
|
var type = Type.RIGHT
|
|
13
|
+
|
|
14
|
+
val config: ScreenStackHeaderConfig?
|
|
15
|
+
get() {
|
|
16
|
+
return (parent as? CustomToolbar)?.config
|
|
17
|
+
}
|
|
18
|
+
|
|
13
19
|
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
|
14
20
|
if (MeasureSpec.getMode(widthMeasureSpec) == MeasureSpec.EXACTLY &&
|
|
15
21
|
MeasureSpec.getMode(heightMeasureSpec) == MeasureSpec.EXACTLY
|
|
@@ -31,6 +37,6 @@ class ScreenStackHeaderSubview(context: ReactContext?) : ReactViewGroup(context)
|
|
|
31
37
|
}
|
|
32
38
|
|
|
33
39
|
enum class Type {
|
|
34
|
-
LEFT, CENTER, RIGHT, BACK
|
|
40
|
+
LEFT, CENTER, RIGHT, BACK, SEARCH_BAR
|
|
35
41
|
}
|
|
36
42
|
}
|
|
@@ -24,6 +24,7 @@ class ScreenStackHeaderSubviewManager : ReactViewManager() {
|
|
|
24
24
|
"center" -> ScreenStackHeaderSubview.Type.CENTER
|
|
25
25
|
"right" -> ScreenStackHeaderSubview.Type.RIGHT
|
|
26
26
|
"back" -> ScreenStackHeaderSubview.Type.BACK
|
|
27
|
+
"searchBar" -> ScreenStackHeaderSubview.Type.SEARCH_BAR
|
|
27
28
|
else -> throw JSApplicationIllegalArgumentException("Unknown type $type")
|
|
28
29
|
}
|
|
29
30
|
}
|