react-native-screens 3.8.0 → 3.10.2

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 (79) hide show
  1. package/README.md +61 -3
  2. package/android/build.gradle +0 -2
  3. package/android/src/main/java/com/swmansion/rnscreens/CustomSearchView.kt +71 -0
  4. package/android/src/main/java/com/swmansion/rnscreens/CustomToolbar.kt +7 -0
  5. package/android/src/main/java/com/swmansion/rnscreens/FragmentBackPressOverrider.kt +29 -0
  6. package/android/src/main/java/com/swmansion/rnscreens/RNScreensPackage.kt +2 -1
  7. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +7 -41
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +55 -40
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +19 -1
  10. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +30 -5
  11. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackFragment.kt +77 -12
  12. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +13 -4
  13. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfigViewManager.kt +8 -0
  14. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubview.kt +7 -1
  15. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderSubviewManager.kt +1 -0
  16. package/android/src/main/java/com/swmansion/rnscreens/SearchBarManager.kt +90 -0
  17. package/android/src/main/java/com/swmansion/rnscreens/SearchBarView.kt +150 -0
  18. package/android/src/main/java/com/swmansion/rnscreens/SearchViewFormatter.kt +40 -0
  19. package/ios/RNSScreen.m +35 -0
  20. package/ios/RNSScreenStack.m +24 -6
  21. package/ios/RNSScreenStackHeaderConfig.m +41 -0
  22. package/lib/commonjs/index.js +24 -1
  23. package/lib/commonjs/index.js.map +1 -1
  24. package/lib/commonjs/index.native.js +101 -11
  25. package/lib/commonjs/index.native.js.map +1 -1
  26. package/lib/commonjs/native-stack/utils/useBackPressSubscription.js +67 -0
  27. package/lib/commonjs/native-stack/utils/useBackPressSubscription.js.map +1 -0
  28. package/lib/commonjs/native-stack/views/HeaderConfig.js +46 -4
  29. package/lib/commonjs/native-stack/views/HeaderConfig.js.map +1 -1
  30. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js +60 -0
  31. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
  32. package/lib/commonjs/reanimated/ReanimatedScreen.js +7 -79
  33. package/lib/commonjs/reanimated/ReanimatedScreen.js.map +1 -1
  34. package/lib/commonjs/reanimated/ReanimatedScreenProvider.js +61 -0
  35. package/lib/commonjs/reanimated/ReanimatedScreenProvider.js.map +1 -0
  36. package/lib/commonjs/reanimated/index.js +2 -2
  37. package/lib/commonjs/reanimated/index.js.map +1 -1
  38. package/lib/commonjs/utils.js +20 -0
  39. package/lib/commonjs/utils.js.map +1 -0
  40. package/lib/module/index.js +5 -0
  41. package/lib/module/index.js.map +1 -1
  42. package/lib/module/index.native.js +97 -13
  43. package/lib/module/index.native.js.map +1 -1
  44. package/lib/module/native-stack/utils/useBackPressSubscription.js +50 -0
  45. package/lib/module/native-stack/utils/useBackPressSubscription.js.map +1 -0
  46. package/lib/module/native-stack/views/HeaderConfig.js +46 -5
  47. package/lib/module/native-stack/views/HeaderConfig.js.map +1 -1
  48. package/lib/module/reanimated/ReanimatedNativeStackScreen.js +40 -0
  49. package/lib/module/reanimated/ReanimatedNativeStackScreen.js.map +1 -0
  50. package/lib/module/reanimated/ReanimatedScreen.js +6 -73
  51. package/lib/module/reanimated/ReanimatedScreen.js.map +1 -1
  52. package/lib/module/reanimated/ReanimatedScreenProvider.js +49 -0
  53. package/lib/module/reanimated/ReanimatedScreenProvider.js.map +1 -0
  54. package/lib/module/reanimated/index.js +1 -1
  55. package/lib/module/reanimated/index.js.map +1 -1
  56. package/lib/module/utils.js +8 -0
  57. package/lib/module/utils.js.map +1 -0
  58. package/lib/typescript/index.d.ts +2 -0
  59. package/lib/typescript/native-stack/types.d.ts +0 -2
  60. package/lib/typescript/native-stack/utils/useBackPressSubscription.d.ts +16 -0
  61. package/lib/typescript/reanimated/ReanimatedNativeStackScreen.d.ts +5 -0
  62. package/lib/typescript/reanimated/ReanimatedScreen.d.ts +5 -2
  63. package/lib/typescript/reanimated/ReanimatedScreenProvider.d.ts +2 -0
  64. package/lib/typescript/reanimated/index.d.ts +1 -1
  65. package/lib/typescript/types.d.ts +46 -1
  66. package/lib/typescript/utils.d.ts +2 -0
  67. package/native-stack/README.md +35 -7
  68. package/package.json +5 -2
  69. package/src/index.native.tsx +134 -38
  70. package/src/index.tsx +10 -0
  71. package/src/native-stack/types.tsx +0 -2
  72. package/src/native-stack/utils/useBackPressSubscription.tsx +66 -0
  73. package/src/native-stack/views/HeaderConfig.tsx +46 -3
  74. package/src/reanimated/ReanimatedNativeStackScreen.tsx +61 -0
  75. package/src/reanimated/ReanimatedScreen.tsx +6 -84
  76. package/src/reanimated/ReanimatedScreenProvider.tsx +42 -0
  77. package/src/reanimated/index.tsx +1 -1
  78. package/src/types.tsx +46 -1
  79. package/src/utils.ts +12 -0
package/README.md CHANGED
@@ -1,6 +1,5 @@
1
1
  <img src="https://user-images.githubusercontent.com/16062886/117443651-c13d9500-af38-11eb-888d-b6a0b580760c.png" width="100%" alt="React Native Screens by Software Mansion" >
2
2
 
3
-
4
3
  This project aims to expose native navigation container components to React Native. It is not designed to be used as a standalone library but rather as a dependency of a [full-featured navigation library](https://github.com/react-navigation/react-navigation).
5
4
 
6
5
  ## Supported platforms
@@ -36,6 +35,26 @@ protected void onCreate(Bundle savedInstanceState) {
36
35
 
37
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).
38
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
+
39
58
  ### Windows
40
59
 
41
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.
@@ -69,6 +88,24 @@ Just make sure that the version of [react-navigation](https://github.com/react-n
69
88
 
70
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.
71
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
+
72
109
  ### Disabling `react-native-screens`
73
110
 
74
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`):
@@ -84,9 +121,10 @@ You can also disable the usage of native screens per navigator with [`detachInac
84
121
  ### Using `createNativeStackNavigator` with React Navigation
85
122
 
86
123
  To take advantage of the native stack navigator primitive for React Navigation that leverages `UINavigationController` on iOS and `Fragment` on Android, please refer:
124
+
87
125
  - for React Navigation >= v6 to the [Native Stack Navigator part of React Navigation documentation](https://reactnavigation.org/docs/native-stack-navigator)
88
- - for React Navigation v5 to the [README in react-native-screens/native-stack](https://github.com/software-mansion/react-native-screens/tree/master/native-stack)
89
- - for older versions to the [README in react-native-screens/createNativeStackNavigator](https://github.com/software-mansion/react-native-screens/tree/master/createNativeStackNavigator)
126
+ - for React Navigation v5 to the [README in react-native-screens/native-stack](https://github.com/software-mansion/react-native-screens/tree/master/native-stack)
127
+ - for older versions to the [README in react-native-screens/createNativeStackNavigator](https://github.com/software-mansion/react-native-screens/tree/master/createNativeStackNavigator)
90
128
 
91
129
  ## Interop with [react-native-navigation](https://github.com/wix/react-native-navigation)
92
130
 
@@ -101,6 +139,26 @@ This library should work out of the box with all existing react-native libraries
101
139
  If you are building a navigation library you may want to use `react-native-screens` to have control over which parts of the React component tree are attached to the native view hierarchy.
102
140
  To do that, `react-native-screens` provides you with the components documented [here](https://github.com/kmagiera/react-native-screens/tree/master/guides/GUIDE_FOR_LIBRARY_AUTHORS.md).
103
141
 
142
+ ## Common problems
143
+
144
+ ### Problems with header on iOS
145
+
146
+ - [Focused search bar causes new screens to have incorrect header](https://github.com/software-mansion/react-native-screens/issues/996)
147
+ - [Scrollable content gets cut off by the header with a search bar](https://github.com/software-mansion/react-native-screens/issues/1120)
148
+ - [RefreshControl does not work properly with NativeStackNavigator and largeTitle](https://github.com/software-mansion/react-native-screens/issues/395)
149
+
150
+ #### Solution
151
+
152
+ Use `ScrollView` with prop `contentInsetAdjustmentBehavior=“automatic”` as a main container of the screen and set `headerTranslucent: true` in screen options.
153
+
154
+ ### Other problems
155
+
156
+ | Problem | Solution |
157
+ | -------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------- |
158
+ | [SVG component becomes transparent when goBack](https://github.com/software-mansion/react-native-screens/issues/773) | [related PRs](https://github.com/software-mansion/react-native-screens/issues/773#issuecomment-783469792) |
159
+ | [Memory leak while moving from one screen to another in the same stack](https://github.com/software-mansion/react-native-screens/issues/843) | [explanation](https://github.com/software-mansion/react-native-screens/issues/843#issuecomment-832034119) |
160
+ | [LargeHeader stays small after pop/goBack/swipe gesture on iOS 14+](https://github.com/software-mansion/react-native-screens/issues/649) | [potential fix](https://github.com/software-mansion/react-native-screens/issues/649#issuecomment-712199895) |
161
+
104
162
  ## Contributing
105
163
 
106
164
  There are many ways to contribute to this project. See [CONTRIBUTING](https://github.com/kmagiera/react-native-screens/tree/master/guides/CONTRIBUTING.md) guide for more information. Thank you for your interest in contributing!
@@ -4,7 +4,6 @@ buildscript {
4
4
  }
5
5
  repositories {
6
6
  google()
7
- jcenter()
8
7
  mavenCentral()
9
8
  }
10
9
  dependencies {
@@ -50,7 +49,6 @@ repositories {
50
49
  mavenCentral()
51
50
  mavenLocal()
52
51
  google()
53
- jcenter()
54
52
  }
55
53
 
56
54
  dependencies {
@@ -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
  }
@@ -11,6 +11,7 @@ import androidx.fragment.app.FragmentActivity
11
11
  import androidx.fragment.app.FragmentManager
12
12
  import androidx.fragment.app.FragmentTransaction
13
13
  import com.facebook.react.ReactRootView
14
+ import com.facebook.react.bridge.ReactContext
14
15
  import com.facebook.react.modules.core.ChoreographerCompat
15
16
  import com.facebook.react.modules.core.ReactChoreographer
16
17
  import com.swmansion.rnscreens.Screen.ActivityState
@@ -176,16 +177,12 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
176
177
  return transaction
177
178
  }
178
179
 
179
- private fun attachScreen(screenFragment: ScreenFragment) {
180
- createTransaction().add(id, screenFragment).commitNowAllowingStateLoss()
180
+ private fun attachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) {
181
+ transaction.add(id, screenFragment)
181
182
  }
182
183
 
183
- private fun moveToFront(screenFragment: ScreenFragment) {
184
- createTransaction().remove(screenFragment).add(id, screenFragment).commitNowAllowingStateLoss()
185
- }
186
-
187
- private fun detachScreen(screenFragment: ScreenFragment) {
188
- createTransaction().remove(screenFragment).commitNowAllowingStateLoss()
184
+ private fun detachScreen(transaction: FragmentTransaction, screenFragment: ScreenFragment) {
185
+ transaction.remove(screenFragment)
189
186
  }
190
187
 
191
188
  private fun getActivityState(screenFragment: ScreenFragment): ActivityState? {
@@ -276,10 +273,18 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
276
273
  // The exception to this rule is `updateImmediately` which is triggered by actions
277
274
  // not connected to React view hierarchy changes, but rather internal events
278
275
  mNeedUpdate = true
276
+ (context as? ReactContext)?.runOnUiQueueThread {
277
+ // We schedule the update here because LayoutAnimations of `react-native-reanimated`
278
+ // sometimes attach/detach screens after the layout block of `ScreensShadowNode` has
279
+ // already run, and we want to update the container then too. In the other cases,
280
+ // this code will do nothing since it will run after the UIBlock when `mNeedUpdate`
281
+ // will already be false.
282
+ performUpdates()
283
+ }
279
284
  }
280
285
 
281
286
  protected fun performUpdatesNow() {
282
- // we want to update the immediately when the fragment manager is set or native back button
287
+ // we want to update immediately when the fragment manager is set or native back button
283
288
  // dismiss is dispatched or Screen's activityState changes since it is not connected to React
284
289
  // view hierarchy changes and will not trigger `onBeforeLayout` method of `ScreensShadowNode`
285
290
  mNeedUpdate = true
@@ -287,7 +292,7 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
287
292
  }
288
293
 
289
294
  fun performUpdates() {
290
- if (!mNeedUpdate || !mIsAttached || mFragmentManager == null) {
295
+ if (!mNeedUpdate || !mIsAttached || mFragmentManager == null || mFragmentManager?.isDestroyed == true) {
291
296
  return
292
297
  }
293
298
  mNeedUpdate = false
@@ -296,43 +301,53 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
296
301
  }
297
302
 
298
303
  open fun onUpdate() {
299
- // detach screens that are no longer active
300
- val orphaned: MutableSet<Fragment> = HashSet(requireNotNull(mFragmentManager, { "mFragmentManager is null when performing update in ScreenContainer" }).fragments)
301
- for (screenFragment in mScreenFragments) {
302
- if (getActivityState(screenFragment) === ActivityState.INACTIVE &&
303
- screenFragment.isAdded
304
- ) {
305
- detachScreen(screenFragment)
304
+ createTransaction().let {
305
+ // detach screens that are no longer active
306
+ val orphaned: MutableSet<Fragment> = HashSet(requireNotNull(mFragmentManager, { "mFragmentManager is null when performing update in ScreenContainer" }).fragments)
307
+ for (screenFragment in mScreenFragments) {
308
+ if (getActivityState(screenFragment) === ActivityState.INACTIVE &&
309
+ screenFragment.isAdded
310
+ ) {
311
+ detachScreen(it, screenFragment)
312
+ }
313
+ orphaned.remove(screenFragment)
306
314
  }
307
- orphaned.remove(screenFragment)
308
- }
309
- if (orphaned.isNotEmpty()) {
310
- val orphanedAry = orphaned.toTypedArray()
311
- for (fragment in orphanedAry) {
312
- if (fragment is ScreenFragment) {
313
- if (fragment.screen.container == null) {
314
- detachScreen(fragment)
315
+ if (orphaned.isNotEmpty()) {
316
+ val orphanedAry = orphaned.toTypedArray()
317
+ for (fragment in orphanedAry) {
318
+ if (fragment is ScreenFragment) {
319
+ if (fragment.screen.container == null) {
320
+ detachScreen(it, fragment)
321
+ }
315
322
  }
316
323
  }
317
324
  }
318
- }
319
- var transitioning = true
320
- if (topScreen != null) {
325
+
321
326
  // if there is an "onTop" screen it means the transition has ended
322
- transitioning = false
323
- }
327
+ val transitioning = topScreen == null
324
328
 
325
- // attach newly activated screens
326
- var addedBefore = false
327
- for (screenFragment in mScreenFragments) {
328
- val activityState = getActivityState(screenFragment)
329
- if (activityState !== ActivityState.INACTIVE && !screenFragment.isAdded) {
330
- addedBefore = true
331
- attachScreen(screenFragment)
332
- } else if (activityState !== ActivityState.INACTIVE && addedBefore) {
333
- moveToFront(screenFragment)
329
+ // attach newly activated screens
330
+ var addedBefore = false
331
+ val pendingFront: ArrayList<T> = ArrayList()
332
+
333
+ for (screenFragment in mScreenFragments) {
334
+ val activityState = getActivityState(screenFragment)
335
+ if (activityState !== ActivityState.INACTIVE && !screenFragment.isAdded) {
336
+ addedBefore = true
337
+ attachScreen(it, screenFragment)
338
+ } else if (activityState !== ActivityState.INACTIVE && addedBefore) {
339
+ // we detach the screen and then reattach it later to make it appear on front
340
+ detachScreen(it, screenFragment)
341
+ pendingFront.add(screenFragment)
342
+ }
343
+ screenFragment.screen.setTransitioning(transitioning)
344
+ }
345
+
346
+ for (screenFragment in pendingFront) {
347
+ attachScreen(it, screenFragment)
334
348
  }
335
- screenFragment.screen.setTransitioning(transitioning)
349
+
350
+ it.commitNowAllowingStateLoss()
336
351
  }
337
352
  }
338
353
 
@@ -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
  }
@@ -120,11 +120,11 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
120
120
  // if the previous top screen does not exist anymore and the new top was not on the stack
121
121
  // before, probably replace or reset was called, so we play the "close animation".
122
122
  // Otherwise it's open animation
123
- shouldUseOpenAnimation = (
124
- mScreenFragments.contains(mTopScreen) ||
125
- newTop.screen.replaceAnimation !== Screen.ReplaceAnimation.POP
126
- )
127
- stackAnimation = newTop.screen.stackAnimation
123
+ val containsTopScreen = mTopScreen?.let { mScreenFragments.contains(it) } == true
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
128
128
  } else if (mTopScreen == null && newTop != null) {
129
129
  // mTopScreen was not present before so newTop is the first screen added to a stack
130
130
  // and we don't want the animation when it is entering, but we want to send the
@@ -238,10 +238,35 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
238
238
  mTopScreen = newTop
239
239
  mStack.clear()
240
240
  mStack.addAll(mScreenFragments)
241
+
242
+ turnOffA11yUnderTransparentScreen(visibleBottom)
243
+
241
244
  it.commitNowAllowingStateLoss()
242
245
  }
243
246
  }
244
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
+
245
270
  override fun notifyContainerUpdate() {
246
271
  for (screen in mStack) {
247
272
  screen.onContainerUpdate()