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.
Files changed (77) hide show
  1. package/README.md +38 -0
  2. package/android/src/main/java/com/swmansion/rnscreens/CustomSearchView.kt +71 -0
  3. package/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +7 -0
  4. package/android/src/main/java/com/swmansion/rnscreens/FragmentBackPressOverrider.kt +29 -0
  5. package/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt +2 -1
  6. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +7 -41
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +19 -1
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +29 -2
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +76 -12
  10. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +13 -4
  11. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +8 -0
  12. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt +7 -1
  13. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt +1 -0
  14. package/android/src/main/java/com/swmansion/rnscreens/SearchBarManager.kt +90 -0
  15. package/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt +150 -0
  16. package/android/src/main/java/com/swmansion/rnscreens/SearchViewFormatter.kt +40 -0
  17. package/createNativeStackNavigator/README.md +33 -9
  18. package/ios/RNSScreen.m +4 -0
  19. package/ios/RNSScreenStack.m +7 -6
  20. package/lib/commonjs/index.js +17 -1
  21. package/lib/commonjs/index.js.map +1 -1
  22. package/lib/commonjs/index.native.js +66 -18
  23. package/lib/commonjs/index.native.js.map +1 -1
  24. package/lib/commonjs/native-stack/utils/useBackPressSubscription.js +67 -0
  25. package/lib/commonjs/native-stack/utils/useBackPressSubscription.js.map +1 -0
  26. package/lib/commonjs/native-stack/views/HeaderConfig.js +46 -4
  27. package/lib/commonjs/native-stack/views/HeaderConfig.js.map +1 -1
  28. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js +60 -0
  29. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
  30. package/lib/commonjs/reanimated/ReanimatedScreen.js +7 -79
  31. package/lib/commonjs/reanimated/ReanimatedScreen.js.map +1 -1
  32. package/lib/commonjs/reanimated/ReanimatedScreenProvider.js +61 -0
  33. package/lib/commonjs/reanimated/ReanimatedScreenProvider.js.map +1 -0
  34. package/lib/commonjs/reanimated/index.js +2 -2
  35. package/lib/commonjs/reanimated/index.js.map +1 -1
  36. package/lib/commonjs/utils.js +20 -0
  37. package/lib/commonjs/utils.js.map +1 -0
  38. package/lib/module/index.js +1 -0
  39. package/lib/module/index.js.map +1 -1
  40. package/lib/module/index.native.js +65 -19
  41. package/lib/module/index.native.js.map +1 -1
  42. package/lib/module/native-stack/utils/useBackPressSubscription.js +50 -0
  43. package/lib/module/native-stack/utils/useBackPressSubscription.js.map +1 -0
  44. package/lib/module/native-stack/views/HeaderConfig.js +46 -5
  45. package/lib/module/native-stack/views/HeaderConfig.js.map +1 -1
  46. package/lib/module/reanimated/ReanimatedNativeStackScreen.js +40 -0
  47. package/lib/module/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
  48. package/lib/module/reanimated/ReanimatedScreen.js +6 -73
  49. package/lib/module/reanimated/ReanimatedScreen.js.map +1 -1
  50. package/lib/module/reanimated/ReanimatedScreenProvider.js +49 -0
  51. package/lib/module/reanimated/ReanimatedScreenProvider.js.map +1 -0
  52. package/lib/module/reanimated/index.js +1 -1
  53. package/lib/module/reanimated/index.js.map +1 -1
  54. package/lib/module/utils.js +8 -0
  55. package/lib/module/utils.js.map +1 -0
  56. package/lib/typescript/index.d.ts +1 -0
  57. package/lib/typescript/native-stack/types.d.ts +0 -2
  58. package/lib/typescript/native-stack/utils/useBackPressSubscription.d.ts +16 -0
  59. package/lib/typescript/reanimated/ReanimatedNativeStackScreen.d.ts +5 -0
  60. package/lib/typescript/reanimated/ReanimatedScreen.d.ts +5 -2
  61. package/lib/typescript/reanimated/ReanimatedScreenProvider.d.ts +2 -0
  62. package/lib/typescript/reanimated/index.d.ts +1 -1
  63. package/lib/typescript/types.d.ts +46 -1
  64. package/lib/typescript/utils.d.ts +2 -0
  65. package/native-stack/README.md +31 -5
  66. package/package.json +2 -1
  67. package/src/index.native.tsx +94 -36
  68. package/src/index.tsx +4 -0
  69. package/src/native-stack/types.tsx +0 -2
  70. package/src/native-stack/utils/useBackPressSubscription.tsx +66 -0
  71. package/src/native-stack/views/HeaderConfig.tsx +46 -3
  72. package/src/reanimated/ReanimatedNativeStackScreen.tsx +61 -0
  73. package/src/reanimated/ReanimatedScreen.tsx +6 -84
  74. package/src/reanimated/ReanimatedScreenProvider.tsx +42 -0
  75. package/src/reanimated/index.tsx +1 -1
  76. package/src/types.tsx +46 -1
  77. 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
+ }
@@ -15,6 +15,7 @@ class RNScreensPackage : ReactPackage {
15
15
  ScreenViewManager(),
16
16
  ScreenStackViewManager(),
17
17
  ScreenStackHeaderConfigViewManager(),
18
- ScreenStackHeaderSubviewManager()
18
+ ScreenStackHeaderSubviewManager(),
19
+ SearchBarManager()
19
20
  )
20
21
  }
@@ -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 { FrameLayout(it) }
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
- shouldUseOpenAnimation = containsTopScreen || newTop.screen.replaceAnimation !== Screen.ReplaceAnimation.POP
125
- stackAnimation = newTop.screen.stackAnimation
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 = if (translucent) null else ScrollingViewBehavior()
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: NotifyingCoordinatorLayout? = context?.let { NotifyingCoordinatorLayout(it, this) }
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 NotifyingCoordinatorLayout(context: Context, private val mFragment: ScreenFragment) : CoordinatorLayout(context) {
172
- private val mAnimationListener: Animation.AnimationListener = object : Animation.AnimationListener {
173
- override fun onAnimationStart(animation: Animation) {
174
- mFragment.onViewAnimationStart()
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
- override fun onAnimationEnd(animation: Animation) {
178
- mFragment.onViewAnimationEnd()
179
- }
228
+ override fun onAnimationEnd(animation: Animation) {
229
+ mFragment.onViewAnimationEnd()
230
+ }
180
231
 
181
- override fun onAnimationRepeat(animation: Animation) {}
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: 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
- private val screenFragment: ScreenStackFragment?
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) : Toolbar(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 Toolbar(context)
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
  }