react-native-screens 3.14.0 → 3.16.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 (94) hide show
  1. package/README.md +34 -1
  2. package/RNScreens.podspec +1 -4
  3. package/android/build.gradle +1 -1
  4. package/android/src/main/java/com/swmansion/rnscreens/Screen.kt +0 -5
  5. package/android/src/main/java/com/swmansion/rnscreens/ScreenContainer.kt +50 -21
  6. package/android/src/main/java/com/swmansion/rnscreens/ScreenFragment.kt +22 -21
  7. package/android/src/main/java/com/swmansion/rnscreens/ScreenStack.kt +7 -5
  8. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackHeaderConfig.kt +1 -2
  9. package/android/src/main/java/com/swmansion/rnscreens/ScreenStackViewManager.kt +9 -0
  10. package/android/src/main/java/com/swmansion/rnscreens/ScreenViewManager.kt +3 -6
  11. package/android/src/main/java/com/swmansion/rnscreens/ScreenWindowTraits.kt +4 -4
  12. package/android/src/paper/java/com/swmansion/rnscreens/FabricEnabledViewGroup.kt +4 -10
  13. package/ios/RNSScreen.h +1 -0
  14. package/ios/RNSScreen.mm +29 -10
  15. package/ios/RNSScreenStack.mm +61 -18
  16. package/ios/RNSScreenStackHeaderConfig.mm +2 -2
  17. package/ios/RNSScreenStackHeaderSubview.h +1 -1
  18. package/lib/commonjs/fabric/ScreenNativeComponent.js.map +1 -1
  19. package/lib/commonjs/index.js +4 -1
  20. package/lib/commonjs/index.js.map +1 -1
  21. package/lib/commonjs/index.native.js +60 -53
  22. package/lib/commonjs/index.native.js.map +1 -1
  23. package/lib/commonjs/native-stack/views/NativeStackView.js +3 -27
  24. package/lib/commonjs/native-stack/views/NativeStackView.js.map +1 -1
  25. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js +4 -2
  26. package/lib/commonjs/reanimated/ReanimatedNativeStackScreen.js.map +1 -1
  27. package/lib/commonjs/reanimated/ReanimatedScreen.js +1 -1
  28. package/lib/commonjs/reanimated/ReanimatedScreen.js.map +1 -1
  29. package/lib/module/fabric/ScreenNativeComponent.js.map +1 -1
  30. package/lib/module/index.js +1 -0
  31. package/lib/module/index.js.map +1 -1
  32. package/lib/module/index.native.js +61 -53
  33. package/lib/module/index.native.js.map +1 -1
  34. package/lib/module/native-stack/views/NativeStackView.js +3 -27
  35. package/lib/module/native-stack/views/NativeStackView.js.map +1 -1
  36. package/lib/module/reanimated/ReanimatedNativeStackScreen.js +5 -3
  37. package/lib/module/reanimated/ReanimatedNativeStackScreen.js.map +1 -1
  38. package/lib/module/reanimated/ReanimatedScreen.js +2 -2
  39. package/lib/module/reanimated/ReanimatedScreen.js.map +1 -1
  40. package/lib/typescript/index.d.ts +1 -0
  41. package/lib/typescript/native-stack/types.d.ts +5 -0
  42. package/lib/typescript/reanimated/ReanimatedNativeStackScreen.d.ts +1 -1
  43. package/lib/typescript/reanimated/ReanimatedScreen.d.ts +1 -1
  44. package/lib/typescript/types.d.ts +5 -0
  45. package/native-stack/README.md +6 -0
  46. package/package.json +1 -1
  47. package/src/fabric/ScreenNativeComponent.js +1 -1
  48. package/src/index.native.tsx +56 -56
  49. package/src/index.tsx +2 -0
  50. package/src/native-stack/types.tsx +5 -0
  51. package/src/native-stack/views/NativeStackView.tsx +2 -23
  52. package/src/reanimated/ReanimatedNativeStackScreen.tsx +5 -3
  53. package/src/reanimated/ReanimatedScreen.tsx +2 -2
  54. package/src/types.tsx +5 -0
  55. package/lib/commonjs/fabric/FullWindowOverlay.js +0 -26
  56. package/lib/commonjs/fabric/FullWindowOverlay.js.map +0 -1
  57. package/lib/commonjs/fabric/Screen.js +0 -29
  58. package/lib/commonjs/fabric/Screen.js.map +0 -1
  59. package/lib/commonjs/fabric/ScreenContainer.js +0 -28
  60. package/lib/commonjs/fabric/ScreenContainer.js.map +0 -1
  61. package/lib/commonjs/fabric/ScreenNavigationContainer.js +0 -28
  62. package/lib/commonjs/fabric/ScreenNavigationContainer.js.map +0 -1
  63. package/lib/commonjs/fabric/ScreenStack.js +0 -26
  64. package/lib/commonjs/fabric/ScreenStack.js.map +0 -1
  65. package/lib/commonjs/fabric/ScreenStackHeaderSubview.js +0 -37
  66. package/lib/commonjs/fabric/ScreenStackHeaderSubview.js.map +0 -1
  67. package/lib/commonjs/fabric/SearchBar.js +0 -37
  68. package/lib/commonjs/fabric/SearchBar.js.map +0 -1
  69. package/lib/commonjs/fabric/index.js +0 -72
  70. package/lib/commonjs/fabric/index.js.map +0 -1
  71. package/lib/module/fabric/FullWindowOverlay.js +0 -15
  72. package/lib/module/fabric/FullWindowOverlay.js.map +0 -1
  73. package/lib/module/fabric/Screen.js +0 -16
  74. package/lib/module/fabric/Screen.js.map +0 -1
  75. package/lib/module/fabric/ScreenContainer.js +0 -17
  76. package/lib/module/fabric/ScreenContainer.js.map +0 -1
  77. package/lib/module/fabric/ScreenNavigationContainer.js +0 -17
  78. package/lib/module/fabric/ScreenNavigationContainer.js.map +0 -1
  79. package/lib/module/fabric/ScreenStack.js +0 -15
  80. package/lib/module/fabric/ScreenStack.js.map +0 -1
  81. package/lib/module/fabric/ScreenStackHeaderSubview.js +0 -24
  82. package/lib/module/fabric/ScreenStackHeaderSubview.js.map +0 -1
  83. package/lib/module/fabric/SearchBar.js +0 -24
  84. package/lib/module/fabric/SearchBar.js.map +0 -1
  85. package/lib/module/fabric/index.js +0 -10
  86. package/lib/module/fabric/index.js.map +0 -1
  87. package/src/fabric/FullWindowOverlay.js +0 -13
  88. package/src/fabric/Screen.js +0 -15
  89. package/src/fabric/ScreenContainer.js +0 -16
  90. package/src/fabric/ScreenNavigationContainer.js +0 -16
  91. package/src/fabric/ScreenStack.js +0 -10
  92. package/src/fabric/ScreenStackHeaderSubview.js +0 -22
  93. package/src/fabric/SearchBar.js +0 -20
  94. package/src/fabric/index.js +0 -19
package/README.md CHANGED
@@ -18,7 +18,31 @@ To learn about how to use `react-native-screens` with Fabric architecture, head
18
18
 
19
19
  ### iOS
20
20
 
21
- Installation on iOS should be completely handled with auto-linking, if you have ensured pods are installed after adding this module, no other actions should be necessary.
21
+ On iOS obtaining current device orientation [requires asking the system to generate orientation notifications](https://developer.apple.com/documentation/uikit/uidevice/1620053-orientation?language=objc). Our library uses them to enforce correct interface orientation when navigating between screens.
22
+ To make sure that there are no issues with screen orientation you should put following code in your `AppDelegate.m`:
23
+
24
+ ```objective-c
25
+ - (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
26
+ {
27
+ ...
28
+ #if !TARGET_OS_TV
29
+ [[UIDevice currentDevice] beginGeneratingDeviceOrientationNotifications];
30
+ #endif // !TARGET_OS_TV
31
+ ...
32
+ return YES:
33
+ }
34
+
35
+ - (void)applicationWillTerminate:(UIApplication *)application
36
+ {
37
+ #if !TARGET_OS_TV
38
+ [[UIDevice currentDevice] endGeneratingDeviceOrientationNotifications];
39
+ #endif // !TARGET_OS_TV
40
+ }
41
+ ```
42
+
43
+ You can see example of these changes being introduced in our [example applications](https://github.com/software-mansion/react-native-screens/blob/main/TestsExample/ios/TestsExample/AppDelegate.mm).
44
+
45
+ Other aspects of installation should be completely handled with auto-linking, just ensure you installed pods after adding this module.
22
46
 
23
47
  ### Android
24
48
 
@@ -71,9 +95,18 @@ Screens are already integrated with the React Native's most popular navigation l
71
95
 
72
96
  | version | react-native version |
73
97
  | ------- | -------------------- |
98
+ | 3.14.0+ | 0.64.0+ |
74
99
  | 3.0.0+ | 0.62.0+ |
75
100
  | 2.0.0+ | 0.60.0+ |
76
101
 
102
+ ### Support for Fabric
103
+ [Fabric](https://reactnative.dev/architecture/fabric-renderer) is React Native's new rendering system. As of [version `3.14.0`](https://github.com/software-mansion/react-native-screens/releases/tag/3.14.0) of this project, Fabric is supported only for react-native 0.69+. Support for `0.68.x` has been dropped.
104
+
105
+ | version | react-native version |
106
+ | ------- | -------------------- |
107
+ | 3.14.0+ | 0.69.0+ |
108
+
109
+
77
110
  ## Usage with [react-navigation](https://github.com/react-navigation/react-navigation)
78
111
 
79
112
  Screens support is built into [react-navigation](https://github.com/react-navigation/react-navigation) starting from version [2.14.0](https://github.com/react-navigation/react-navigation/releases/tag/2.14.0) for all the different navigator types (stack, tab, drawer, etc).
package/RNScreens.podspec CHANGED
@@ -4,9 +4,6 @@ package = JSON.parse(File.read(File.join(__dir__, "package.json")))
4
4
 
5
5
  fabric_enabled = ENV['RCT_NEW_ARCH_ENABLED'] == '1'
6
6
 
7
- # folly_version must match the version used in React Native
8
- # See folly_version in react-native/React/FBReactNativeSpec/FBReactNativeSpec.podspec
9
- folly_version = '2021.06.28.00-v2'
10
7
  folly_compiler_flags = '-DFOLLY_NO_CONFIG -DFOLLY_MOBILE=1 -DFOLLY_USE_LIBCPP=1 -Wno-comma -Wno-shorten-64-to-32'
11
8
 
12
9
  Pod::Spec.new do |s|
@@ -36,7 +33,7 @@ Pod::Spec.new do |s|
36
33
  s.dependency "React"
37
34
  s.dependency "React-RCTFabric"
38
35
  s.dependency "React-Codegen"
39
- s.dependency "RCT-Folly", folly_version
36
+ s.dependency "RCT-Folly"
40
37
  s.dependency "RCTRequired"
41
38
  s.dependency "RCTTypeSafety"
42
39
  s.dependency "ReactCommon/turbomodule/core"
@@ -50,7 +50,7 @@ android {
50
50
  }
51
51
 
52
52
  defaultConfig {
53
- minSdkVersion safeExtGet('minSdkVersion', 16)
53
+ minSdkVersion safeExtGet('minSdkVersion', 21)
54
54
  targetSdkVersion safeExtGet('targetSdkVersion', 22)
55
55
  versionCode 1
56
56
  versionName "1.0"
@@ -8,13 +8,8 @@ import android.util.SparseArray
8
8
  import android.view.ViewGroup
9
9
  import android.view.WindowManager
10
10
  import android.webkit.WebView
11
- import androidx.annotation.UiThread
12
11
  import com.facebook.react.bridge.GuardedRunnable
13
12
  import com.facebook.react.bridge.ReactContext
14
- import com.facebook.react.bridge.ReadableMap
15
- import com.facebook.react.bridge.WritableMap
16
- import com.facebook.react.bridge.WritableNativeMap
17
- import com.facebook.react.uimanager.PixelUtil
18
13
  import com.facebook.react.uimanager.UIManagerModule
19
14
 
20
15
  @SuppressLint("ViewConstructor")
@@ -135,6 +135,41 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
135
135
  performUpdatesNow()
136
136
  }
137
137
 
138
+ private fun findFragmentManagerForReactRootView(rootView: ReactRootView): FragmentManager {
139
+ var context = rootView.context
140
+
141
+ // ReactRootView is expected to be initialized with the main React Activity as a context but
142
+ // in case of Expo the activity is wrapped in ContextWrapper and we need to unwrap it
143
+ while (context !is FragmentActivity && context is ContextWrapper) {
144
+ context = context.baseContext
145
+ }
146
+
147
+ check(context is FragmentActivity) {
148
+ "In order to use RNScreens components your app's activity need to extend ReactActivity"
149
+ }
150
+
151
+ // In case React Native is loaded on a Fragment (not directly in activity) we need to find
152
+ // fragment manager whose fragment's view is ReactRootView. As of now, we detect such case by
153
+ // checking whether any fragments are attached to activity which hosts ReactRootView.
154
+ // See: https://github.com/software-mansion/react-native-screens/issues/1506 on why the cases
155
+ // must be treated separately.
156
+ return if (context.supportFragmentManager.fragments.isEmpty()) {
157
+ // We are in standard React Native application w/o custom native navigation based on fragments.
158
+ context.supportFragmentManager
159
+ } else {
160
+ // We are in some custom setup & we want to use the closest fragment manager in hierarchy.
161
+ // `findFragment` method throws IllegalStateException when it fails to resolve appropriate
162
+ // fragment. It might happen when e.g. React Native is loaded directly in Activity
163
+ // but some custom fragments are still used. Such use case seems highly unlikely
164
+ // so, as for now we let application crash.
165
+ try {
166
+ FragmentManager.findFragment<Fragment>(rootView).childFragmentManager
167
+ } catch (ex: IllegalStateException) {
168
+ throw IllegalStateException("Failed to find fragment for React Root View")
169
+ }
170
+ }
171
+ }
172
+
138
173
  private fun setupFragmentManager() {
139
174
  var parent: ViewParent = this
140
175
  // We traverse view hierarchy up until we find screen parent or a root view
@@ -146,28 +181,22 @@ open class ScreenContainer<T : ScreenFragment>(context: Context?) : ViewGroup(co
146
181
  // If parent is of type Screen it means we are inside a nested fragment structure.
147
182
  // Otherwise we expect to connect directly with root view and get root fragment manager
148
183
  if (parent is Screen) {
149
- val screenFragment = parent.fragment
150
- check(screenFragment != null) { "Parent Screen does not have its Fragment attached" }
151
- mParentScreenFragment = screenFragment
152
- screenFragment.registerChildScreenContainer(this)
153
- setFragmentManager(screenFragment.childFragmentManager)
154
- return
155
- }
156
-
157
- // we expect top level view to be of type ReactRootView, this isn't really necessary but in
158
- // order to find root view we test if parent is null. This could potentially happen also when
159
- // the view is detached from the hierarchy and that test would not correctly indicate the root
160
- // view. So in order to make sure we indeed reached the root we test if it is of a correct type.
161
- // This allows us to provide a more descriptive error message for the aforementioned case.
162
- check(parent is ReactRootView) { "ScreenContainer is not attached under ReactRootView" }
163
- // ReactRootView is expected to be initialized with the main React Activity as a context but
164
- // in case of Expo the activity is wrapped in ContextWrapper and we need to unwrap it
165
- var context = parent.context
166
- while (context !is FragmentActivity && context is ContextWrapper) {
167
- context = context.baseContext
184
+ checkNotNull(
185
+ parent.fragment?.let { screenFragment ->
186
+ mParentScreenFragment = screenFragment
187
+ screenFragment.registerChildScreenContainer(this)
188
+ setFragmentManager(screenFragment.childFragmentManager)
189
+ }
190
+ ) { "Parent Screen does not have its Fragment attached" }
191
+ } else {
192
+ // we expect top level view to be of type ReactRootView, this isn't really necessary but in
193
+ // order to find root view we test if parent is null. This could potentially happen also when
194
+ // the view is detached from the hierarchy and that test would not correctly indicate the root
195
+ // view. So in order to make sure we indeed reached the root we test if it is of a correct type.
196
+ // This allows us to provide a more descriptive error message for the aforementioned case.
197
+ check(parent is ReactRootView) { "ScreenContainer is not attached under ReactRootView" }
198
+ setFragmentManager(findFragmentManagerForReactRootView(parent))
168
199
  }
169
- check(context is FragmentActivity) { "In order to use RNScreens components your app's activity need to extend ReactActivity" }
170
- setFragmentManager(context.supportFragmentManager)
171
200
  }
172
201
 
173
202
  protected fun createTransaction(): FragmentTransaction {
@@ -12,8 +12,9 @@ import android.widget.FrameLayout
12
12
  import androidx.fragment.app.Fragment
13
13
  import com.facebook.react.bridge.ReactContext
14
14
  import com.facebook.react.bridge.UiThreadUtil
15
- import com.facebook.react.uimanager.UIManagerModule
15
+ import com.facebook.react.uimanager.UIManagerHelper
16
16
  import com.facebook.react.uimanager.events.Event
17
+ import com.facebook.react.uimanager.events.EventDispatcher
17
18
  import com.swmansion.rnscreens.events.HeaderBackButtonClickedEvent
18
19
  import com.swmansion.rnscreens.events.ScreenAppearEvent
19
20
  import com.swmansion.rnscreens.events.ScreenDisappearEvent
@@ -204,10 +205,10 @@ open class ScreenFragment : Fragment {
204
205
  ScreenLifecycleEvent.WillDisappear -> ScreenWillDisappearEvent(it.id)
205
206
  ScreenLifecycleEvent.Disappear -> ScreenDisappearEvent(it.id)
206
207
  }
207
- (it.context as ReactContext)
208
- .getNativeModule(UIManagerModule::class.java)
209
- ?.eventDispatcher
210
- ?.dispatchEvent(lifecycleEvent)
208
+ val screenContext = screen.context as ReactContext
209
+ val eventDispatcher: EventDispatcher? =
210
+ UIManagerHelper.getEventDispatcherForReactTag(screenContext, screen.id)
211
+ eventDispatcher?.dispatchEvent(lifecycleEvent)
211
212
  fragment.dispatchEventInChildContainers(event)
212
213
  }
213
214
  }
@@ -224,10 +225,10 @@ open class ScreenFragment : Fragment {
224
225
  }
225
226
 
226
227
  fun dispatchHeaderBackButtonClickedEvent() {
227
- (screen.context as ReactContext)
228
- .getNativeModule(UIManagerModule::class.java)
229
- ?.eventDispatcher
230
- ?.dispatchEvent(HeaderBackButtonClickedEvent(screen.id))
228
+ val screenContext = screen.context as ReactContext
229
+ val eventDispatcher: EventDispatcher? =
230
+ UIManagerHelper.getEventDispatcherForReactTag(screenContext, screen.id)
231
+ eventDispatcher?.dispatchEvent(HeaderBackButtonClickedEvent(screen.id))
231
232
  }
232
233
 
233
234
  fun dispatchTransitionProgress(alpha: Float, closing: Boolean) {
@@ -242,14 +243,14 @@ open class ScreenFragment : Fragment {
242
243
  val coalescingKey = (if (mProgress == 0.0f) 1 else if (mProgress == 1.0f) 2 else 3).toShort()
243
244
  val container: ScreenContainer<*>? = screen.container
244
245
  val goingForward = if (container is ScreenStack) container.goingForward else false
245
- (screen.context as ReactContext)
246
- .getNativeModule(UIManagerModule::class.java)
247
- ?.eventDispatcher
248
- ?.dispatchEvent(
249
- ScreenTransitionProgressEvent(
250
- screen.id, mProgress, closing, goingForward, coalescingKey
251
- )
246
+ val screenContext = screen.context as ReactContext
247
+ val eventDispatcher: EventDispatcher? =
248
+ UIManagerHelper.getEventDispatcherForReactTag(screenContext, screen.id)
249
+ eventDispatcher?.dispatchEvent(
250
+ ScreenTransitionProgressEvent(
251
+ screen.id, mProgress, closing, goingForward, coalescingKey
252
252
  )
253
+ )
253
254
  }
254
255
  }
255
256
  }
@@ -302,11 +303,11 @@ open class ScreenFragment : Fragment {
302
303
  val container = screen.container
303
304
  if (container == null || !container.hasScreen(this)) {
304
305
  // we only send dismissed even when the screen has been removed from its container
305
- if (screen.context is ReactContext) {
306
- (screen.context as ReactContext)
307
- .getNativeModule(UIManagerModule::class.java)
308
- ?.eventDispatcher
309
- ?.dispatchEvent(ScreenDismissedEvent(screen.id))
306
+ val screenContext = screen.context
307
+ if (screenContext is ReactContext) {
308
+ val eventDispatcher: EventDispatcher? =
309
+ UIManagerHelper.getEventDispatcherForReactTag(screenContext, screen.id)
310
+ eventDispatcher?.dispatchEvent(ScreenDismissedEvent(screen.id))
310
311
  }
311
312
  }
312
313
  mChildScreenContainers.clear()
@@ -4,7 +4,8 @@ import android.content.Context
4
4
  import android.graphics.Canvas
5
5
  import android.view.View
6
6
  import com.facebook.react.bridge.ReactContext
7
- import com.facebook.react.uimanager.UIManagerModule
7
+ import com.facebook.react.uimanager.UIManagerHelper
8
+ import com.facebook.react.uimanager.events.EventDispatcher
8
9
  import com.swmansion.rnscreens.Screen.StackAnimation
9
10
  import com.swmansion.rnscreens.events.StackFinishTransitioningEvent
10
11
  import java.util.Collections
@@ -67,10 +68,11 @@ class ScreenStack(context: Context?) : ScreenContainer<ScreenStackFragment>(cont
67
68
  }
68
69
 
69
70
  private fun dispatchOnFinishTransitioning() {
70
- (context as ReactContext)
71
- .getNativeModule(UIManagerModule::class.java)
72
- ?.eventDispatcher
73
- ?.dispatchEvent(StackFinishTransitioningEvent(id))
71
+ val eventDispatcher: EventDispatcher? =
72
+ UIManagerHelper.getEventDispatcherForReactTag((context as ReactContext), id)
73
+ eventDispatcher?.dispatchEvent(
74
+ StackFinishTransitioningEvent(id)
75
+ )
74
76
  }
75
77
 
76
78
  override fun removeScreenAt(index: Int) {
@@ -130,7 +130,6 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
130
130
  return null
131
131
  }
132
132
 
133
- @SuppressLint("ObsoleteSdkInt") // to be removed when support for < 0.64 is dropped
134
133
  fun onUpdate() {
135
134
  val stack = screenStack
136
135
  val isTop = stack == null || stack.topScreen == parent
@@ -138,7 +137,7 @@ class ScreenStackHeaderConfig(context: Context) : ViewGroup(context) {
138
137
  return
139
138
  }
140
139
  val activity = screenFragment?.activity as AppCompatActivity? ?: return
141
- if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1 && mDirection != null) {
140
+ if (mDirection != null) {
142
141
  if (mDirection == "rtl") {
143
142
  toolbar.layoutDirection = LAYOUT_DIRECTION_RTL
144
143
  } else if (mDirection == "ltr") {
@@ -3,6 +3,7 @@ package com.swmansion.rnscreens
3
3
  import android.view.View
4
4
  import android.view.ViewGroup
5
5
  import com.facebook.react.bridge.ReactApplicationContext
6
+ import com.facebook.react.common.MapBuilder
6
7
  import com.facebook.react.module.annotations.ReactModule
7
8
  import com.facebook.react.uimanager.LayoutShadowNode
8
9
  import com.facebook.react.uimanager.ThemedReactContext
@@ -10,6 +11,7 @@ import com.facebook.react.uimanager.ViewGroupManager
10
11
  import com.facebook.react.uimanager.ViewManagerDelegate
11
12
  import com.facebook.react.viewmanagers.RNSScreenStackManagerDelegate
12
13
  import com.facebook.react.viewmanagers.RNSScreenStackManagerInterface
14
+ import com.swmansion.rnscreens.events.StackFinishTransitioningEvent
13
15
 
14
16
  @ReactModule(name = ScreenStackViewManager.REACT_CLASS)
15
17
  class ScreenStackViewManager : ViewGroupManager<ScreenStack>(), RNSScreenStackManagerInterface<ScreenStack> {
@@ -81,6 +83,13 @@ class ScreenStackViewManager : ViewGroupManager<ScreenStack>(), RNSScreenStackMa
81
83
  return mDelegate
82
84
  }
83
85
 
86
+ override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
87
+ return MapBuilder.of(
88
+ StackFinishTransitioningEvent.EVENT_NAME,
89
+ MapBuilder.of("registrationName", "onFinishTransitioning"),
90
+ )
91
+ }
92
+
84
93
  companion object {
85
94
  const val REACT_CLASS = "RNSScreenStack"
86
95
  }
@@ -162,7 +162,7 @@ class ScreenViewManager : ViewGroupManager<Screen>(), RNSScreenManagerInterface<
162
162
  override fun setSwipeDirection(view: Screen?, value: String?) = Unit
163
163
 
164
164
  override fun getExportedCustomDirectEventTypeConstants(): MutableMap<String, Any> {
165
- val map: MutableMap<String, Any> = MapBuilder.of(
165
+ return MapBuilder.of(
166
166
  ScreenDismissedEvent.EVENT_NAME,
167
167
  MapBuilder.of("registrationName", "onDismissed"),
168
168
  ScreenWillAppearEvent.EVENT_NAME,
@@ -173,14 +173,11 @@ class ScreenViewManager : ViewGroupManager<Screen>(), RNSScreenManagerInterface<
173
173
  MapBuilder.of("registrationName", "onWillDisappear"),
174
174
  ScreenDisappearEvent.EVENT_NAME,
175
175
  MapBuilder.of("registrationName", "onDisappear"),
176
- StackFinishTransitioningEvent.EVENT_NAME,
177
- MapBuilder.of("registrationName", "onFinishTransitioning"),
176
+ HeaderBackButtonClickedEvent.EVENT_NAME,
177
+ MapBuilder.of("registrationName", "onHeaderBackButtonClicked"),
178
178
  ScreenTransitionProgressEvent.EVENT_NAME,
179
179
  MapBuilder.of("registrationName", "onTransitionProgress")
180
180
  )
181
- // there is no `MapBuilder.of` with more than 7 items
182
- map[HeaderBackButtonClickedEvent.EVENT_NAME] = MapBuilder.of("registrationName", "onHeaderBackButtonClicked")
183
- return map
184
181
  }
185
182
 
186
183
  protected override fun getDelegate(): ViewManagerDelegate<Screen> {
@@ -48,7 +48,7 @@ object ScreenWindowTraits {
48
48
 
49
49
  @SuppressLint("ObsoleteSdkInt") // to be removed when support for < 0.64 is dropped
50
50
  internal fun setColor(screen: Screen, activity: Activity?, context: ReactContext?) {
51
- if (activity == null || context == null || Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
51
+ if (activity == null || context == null) {
52
52
  return
53
53
  }
54
54
  if (mDefaultStatusBarColor == null) {
@@ -112,8 +112,8 @@ object ScreenWindowTraits {
112
112
  // and consume all the top insets so no padding will be added under the status bar.
113
113
  val decorView = activity.window.decorView
114
114
  if (translucent) {
115
- decorView.setOnApplyWindowInsetsListener { v, insets ->
116
- val defaultInsets = v.onApplyWindowInsets(insets)
115
+ ViewCompat.setOnApplyWindowInsetsListener(decorView) { v, insets ->
116
+ val defaultInsets = ViewCompat.onApplyWindowInsets(v, insets)
117
117
  defaultInsets.replaceSystemWindowInsets(
118
118
  defaultInsets.systemWindowInsetLeft,
119
119
  0,
@@ -122,7 +122,7 @@ object ScreenWindowTraits {
122
122
  )
123
123
  }
124
124
  } else {
125
- decorView.setOnApplyWindowInsetsListener(null)
125
+ ViewCompat.setOnApplyWindowInsetsListener(decorView, null)
126
126
  }
127
127
  ViewCompat.requestApplyInsets(decorView)
128
128
  }
@@ -2,15 +2,9 @@ package com.swmansion.rnscreens
2
2
 
3
3
  import android.view.ViewGroup
4
4
  import com.facebook.react.bridge.ReactContext
5
- import androidx.annotation.UiThread
6
- import com.facebook.react.uimanager.PixelUtil
7
- import com.facebook.react.bridge.ReadableMap
8
- import com.facebook.react.bridge.WritableMap
9
- import com.facebook.react.bridge.WritableNativeMap
10
- import kotlin.math.abs
11
5
 
12
6
  abstract class FabricEnabledViewGroup constructor(context: ReactContext?) : ViewGroup(context) {
13
- protected fun updateScreenSizeFabric(width: Int, height: Int) {
14
- // do nothing
15
- }
16
- }
7
+ protected fun updateScreenSizeFabric(width: Int, height: Int) {
8
+ // do nothing
9
+ }
10
+ }
package/ios/RNSScreen.h CHANGED
@@ -103,6 +103,7 @@ NS_ASSUME_NONNULL_BEGIN
103
103
  #endif
104
104
 
105
105
  - (void)notifyTransitionProgress:(double)progress closing:(BOOL)closing goingForward:(BOOL)goingForward;
106
+ - (BOOL)isModal;
106
107
 
107
108
  @end
108
109
 
package/ios/RNSScreen.mm CHANGED
@@ -508,6 +508,11 @@
508
508
  }
509
509
  }
510
510
 
511
+ - (BOOL)isModal
512
+ {
513
+ return self.stackPresentation != RNSScreenStackPresentationPush;
514
+ }
515
+
511
516
  #pragma mark - Fabric specific
512
517
  #ifdef RN_FABRIC_ENABLED
513
518
 
@@ -545,6 +550,17 @@
545
550
  _dismissed = NO;
546
551
  _state.reset();
547
552
  _touchHandler = nil;
553
+
554
+ // We set this prop to default value here to workaround view-recycling.
555
+ // Let's assume the view has had _stackPresentation == <some modal stack presentation> set
556
+ // before below line was executed. Then, when instantiated again (with the same modal presentation)
557
+ // updateProps:oldProps: method would be called and setter for stack presentation would not be called.
558
+ // This is crucial as in that setter we register `self.controller` as a delegate
559
+ // (UIAdaptivePresentationControllerDelegate) to presentation controller and this leads to buggy modal behaviour as we
560
+ // rely on UIAdaptivePresentationControllerDelegate callbacks. Restoring the default value and then comparing against
561
+ // it in updateProps:oldProps: allows for setter to be called, however if there was some additional logic to execute
562
+ // when stackPresentation is set to "push" the setter would not be triggered.
563
+ _stackPresentation = RNSScreenStackPresentationPush;
548
564
  }
549
565
 
550
566
  - (void)updateProps:(facebook::react::Props::Shared const &)props
@@ -598,9 +614,12 @@
598
614
  }
599
615
  #endif
600
616
 
601
- if (newScreenProps.stackPresentation != oldScreenProps.stackPresentation) {
602
- [self
603
- setStackPresentation:[RNSConvert RNSScreenStackPresentationFromCppEquivalent:newScreenProps.stackPresentation]];
617
+ // Notice that we compare against _stackPresentation, not oldScreenProps.stackPresentation.
618
+ // See comment in prepareForRecycle method for explanation.
619
+ RNSScreenStackPresentation newStackPresentation =
620
+ [RNSConvert RNSScreenStackPresentationFromCppEquivalent:newScreenProps.stackPresentation];
621
+ if (newStackPresentation != _stackPresentation) {
622
+ [self setStackPresentation:newStackPresentation];
604
623
  }
605
624
 
606
625
  if (newScreenProps.stackAnimation != oldScreenProps.stackAnimation) {
@@ -739,7 +758,8 @@ Class<RCTComponentViewProtocol> RNSScreenCls(void)
739
758
  - (void)viewWillDisappear:(BOOL)animated
740
759
  {
741
760
  [super viewWillDisappear:animated];
742
- if (!self.transitionCoordinator.isInteractive) {
761
+ // self.navigationController might be null when we are dismissing a modal
762
+ if (!self.transitionCoordinator.isInteractive && self.navigationController != nil) {
743
763
  // user might have long pressed ios 14 back button item,
744
764
  // so he can go back more than one screen and we need to dismiss more screens in JS stack then.
745
765
  // We check it by calculating the difference between the index of currently displayed screen
@@ -1038,18 +1058,17 @@ Class<RCTComponentViewProtocol> RNSScreenCls(void)
1038
1058
  {
1039
1059
  return self.screenView.homeIndicatorHidden;
1040
1060
  }
1041
-
1042
- - (int)getIndexOfView:(UIView *)view
1043
- {
1044
- return (int)[[self.screenView.reactSuperview reactSubviews] indexOfObject:view];
1045
- }
1046
-
1047
1061
  - (int)getParentChildrenCount
1048
1062
  {
1049
1063
  return (int)[[self.screenView.reactSuperview reactSubviews] count];
1050
1064
  }
1051
1065
  #endif
1052
1066
 
1067
+ - (int)getIndexOfView:(UIView *)view
1068
+ {
1069
+ return (int)[[self.screenView.reactSuperview reactSubviews] indexOfObject:view];
1070
+ }
1071
+
1053
1072
  // since on Fabric the view of controller can be a snapshot of type `UIView`,
1054
1073
  // when we want to check props of ScreenView, we need to get them from _initialView
1055
1074
  - (RNSScreenView *)screenView
@@ -598,22 +598,16 @@
598
598
 
599
599
  - (BOOL)gestureRecognizerShouldBegin:(UIGestureRecognizer *)gestureRecognizer
600
600
  {
601
- RNSScreenView *topScreen = (RNSScreenView *)_controller.viewControllers.lastObject.view;
602
-
603
- if (![topScreen isKindOfClass:[RNSScreenView class]] || !topScreen.gestureEnabled ||
604
- _controller.viewControllers.count < 2) {
605
- return NO;
606
- }
601
+ RNSScreenView *topScreen = _reactSubviews.lastObject;
607
602
 
608
603
  #if TARGET_OS_TV
609
604
  [self cancelTouchesInParent];
610
605
  return YES;
611
606
  #else
612
- if (topScreen.fullScreenSwipeEnabled) {
613
- // we want only `RNSPanGestureRecognizer` to be able to recognize when
614
- // `fullScreenSwipeEnabled` is set, and we are in the bounds set by user
615
- if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]] &&
616
- [self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
607
+ // RNSPanGestureRecognizer will receive events iff topScreen.fullScreenSwipeEnabled == YES;
608
+ // Events are filtered in gestureRecognizer:shouldReceivePressOrTouchEvent: method
609
+ if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
610
+ if ([self isInGestureResponseDistance:gestureRecognizer topScreen:topScreen]) {
617
611
  _isFullWidthSwiping = YES;
618
612
  [self cancelTouchesInParent];
619
613
  return YES;
@@ -621,6 +615,7 @@
621
615
  return NO;
622
616
  }
623
617
 
618
+ // Now we're dealing with RNSScreenEdgeGestureRecognizer (or _UIParallaxTransitionPanGestureRecognizer)
624
619
  if (topScreen.customAnimationOnSwipe && [RNSScreenStackAnimator isCustomAnimation:topScreen.stackAnimation]) {
625
620
  if ([gestureRecognizer isKindOfClass:[RNSScreenEdgeGestureRecognizer class]]) {
626
621
  // if we do not set any explicit `semanticContentAttribute`, it is `UISemanticContentAttributeUnspecified` instead
@@ -639,10 +634,8 @@
639
634
  if ([gestureRecognizer isKindOfClass:[RNSScreenEdgeGestureRecognizer class]]) {
640
635
  // it should only recognize with `customAnimationOnSwipe` set
641
636
  return NO;
642
- } else if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
643
- // it should only recognize with `fullScreenSwipeEnabled` set
644
- return NO;
645
637
  }
638
+ // _UIParallaxTransitionPanGestureRecognizer (other...)
646
639
  [self cancelTouchesInParent];
647
640
  return YES;
648
641
  }
@@ -675,7 +668,7 @@
675
668
 
676
669
  - (void)handleSwipe:(UIPanGestureRecognizer *)gestureRecognizer
677
670
  {
678
- RNSScreenView *topScreen = ((RNSScreen *)_controller.viewControllers.lastObject).screenView;
671
+ RNSScreenView *topScreen = _reactSubviews.lastObject;
679
672
  float translation;
680
673
  float velocity;
681
674
  float distance;
@@ -807,6 +800,8 @@
807
800
  return [super hitTest:point withEvent:event];
808
801
  }
809
802
 
803
+ #if !TARGET_OS_TV
804
+
810
805
  - (BOOL)isScrollViewPanGestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
811
806
  {
812
807
  // NOTE: This hack is required to restore native behavior of edge swipe (interactive pop gesture)
@@ -819,18 +814,66 @@
819
814
  return scrollView.panGestureRecognizer == gestureRecognizer;
820
815
  }
821
816
 
817
+ // Custom method for compatibility with iOS < 13.4
818
+ // RNSScreenStackView is a UIGestureRecognizerDelegate for three types of gesture recognizers:
819
+ // RNSPanGestureRecognizer, RNSScreenEdgeGestureRecognizer, _UIParallaxTransitionPanGestureRecognizer
820
+ // Be careful when adding another type of gesture recognizer.
821
+ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePressOrTouchEvent:(NSObject *)event
822
+ {
823
+ RNSScreenView *topScreen = _reactSubviews.lastObject;
824
+
825
+ if (![topScreen isKindOfClass:[RNSScreenView class]] || !topScreen.gestureEnabled ||
826
+ _controller.viewControllers.count < 2 || [topScreen isModal]) {
827
+ return NO;
828
+ }
829
+
830
+ // We want to pass events to RNSPanGestureRecognizer iff full screen swipe is enabled.
831
+ if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]]) {
832
+ return topScreen.fullScreenSwipeEnabled;
833
+ }
834
+
835
+ // RNSScreenEdgeGestureRecognizer || _UIParallaxTransitionPanGestureRecognizer
836
+ return YES;
837
+ }
838
+
839
+ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceivePress:(UIPress *)press;
840
+ {
841
+ return [self gestureRecognizer:gestureRecognizer shouldReceivePressOrTouchEvent:press];
842
+ }
843
+
844
+ - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer shouldReceiveTouch:(UITouch *)touch;
845
+ {
846
+ return [self gestureRecognizer:gestureRecognizer shouldReceivePressOrTouchEvent:touch];
847
+ }
848
+
822
849
  - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
823
850
  shouldRecognizeSimultaneouslyWithGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
824
851
  {
825
- return [self isScrollViewPanGestureRecognizer:otherGestureRecognizer];
852
+ if ([gestureRecognizer isKindOfClass:[RNSPanGestureRecognizer class]] &&
853
+ [self isScrollViewPanGestureRecognizer:otherGestureRecognizer]) {
854
+ RNSPanGestureRecognizer *panGestureRecognizer = (RNSPanGestureRecognizer *)gestureRecognizer;
855
+ BOOL isBackGesture = [panGestureRecognizer translationInView:panGestureRecognizer.view].x > 0 &&
856
+ _controller.viewControllers.count > 1;
857
+
858
+ if (gestureRecognizer.state == UIGestureRecognizerStateBegan || isBackGesture) {
859
+ return NO;
860
+ }
861
+
862
+ return YES;
863
+ }
864
+ return NO;
826
865
  }
827
866
 
828
867
  - (BOOL)gestureRecognizer:(UIGestureRecognizer *)gestureRecognizer
829
868
  shouldBeRequiredToFailByGestureRecognizer:(UIGestureRecognizer *)otherGestureRecognizer
830
869
  {
831
- return [self isScrollViewPanGestureRecognizer:otherGestureRecognizer];
870
+ return (
871
+ [gestureRecognizer isKindOfClass:[UIScreenEdgePanGestureRecognizer class]] &&
872
+ [self isScrollViewPanGestureRecognizer:otherGestureRecognizer]);
832
873
  }
833
874
 
875
+ #endif // !TARGET_OS_TV
876
+
834
877
  - (void)insertReactSubview:(RNSScreenView *)subview atIndex:(NSInteger)atIndex
835
878
  {
836
879
  if (![subview isKindOfClass:[RNSScreenView class]]) {
@@ -926,7 +969,7 @@
926
969
  - (void)mountingTransactionWillMount:(facebook::react::MountingTransaction const &)transaction
927
970
  withSurfaceTelemetry:(facebook::react::SurfaceTelemetry const &)surfaceTelemetry
928
971
  {
929
- for (auto mutation : transaction.getMutations()) {
972
+ for (auto &mutation : transaction.getMutations()) {
930
973
  if (mutation.type == facebook::react::ShadowViewMutation::Type::Remove &&
931
974
  mutation.parentShadowView.componentName != nil &&
932
975
  strcmp(mutation.parentShadowView.componentName, "RNSScreenStack") == 0) {