react-native-screens 5.0.0-alpha.2 → 5.0.0-alpha.3

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 (237) hide show
  1. package/Package.swift +87 -0
  2. package/README.md +0 -7
  3. package/android/src/main/cpp/NativeProxy.cpp +30 -6
  4. package/android/src/main/cpp/NativeProxy.h +7 -1
  5. package/android/src/main/java/com/swmansion/rnscreens/common/container/Container.kt +8 -0
  6. package/android/src/main/java/com/swmansion/rnscreens/common/container/ContainerItem.kt +13 -1
  7. package/android/src/main/java/com/swmansion/rnscreens/common/text/ReactTextAppearance.kt +62 -0
  8. package/android/src/main/java/com/swmansion/rnscreens/common/text/TextAppearance.kt +20 -0
  9. package/android/src/main/java/com/swmansion/rnscreens/common/text/TextAppearanceDefaults.kt +65 -0
  10. package/android/src/main/java/com/swmansion/rnscreens/helpers/PropIconResolver.kt +17 -23
  11. package/android/src/main/java/com/swmansion/rnscreens/legacy/Screen.kt +3 -1
  12. package/android/src/main/java/com/swmansion/rnscreens/legacy/ScreenStack.kt +2 -0
  13. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/coordinator/FormSheetBehaviorController.kt +49 -18
  14. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/coordinator/FormSheetDimensionsCoordinator.kt +65 -40
  15. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/coordinator/FormSheetKeyboardCoordinator.kt +101 -0
  16. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/coordinator/FormSheetNativeDismissCoordinator.kt +0 -6
  17. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/core/FormSheetAvailableHeightProvider.kt +44 -0
  18. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/core/FormSheetDialog.kt +35 -3
  19. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/core/FormSheetDialogManager.kt +33 -106
  20. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/interfaces/FormSheetDialogEventEmitter.kt +2 -0
  21. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/model/FormSheetDetents.kt +61 -15
  22. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetAnimatorFactory.kt +8 -9
  23. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetDimmingManager.kt +115 -0
  24. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetDismissalOrigin.kt +22 -0
  25. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetPresentation.kt +173 -0
  26. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetPresentationManager.kt +141 -49
  27. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetPresentationState.kt +1 -1
  28. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/native/presentation/FormSheetStackRegistry.kt +28 -0
  29. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/react/event/FormSheetDismissEvent.kt +29 -0
  30. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/react/host/FormSheetHostEventEmitter.kt +7 -0
  31. package/android/src/main/java/com/swmansion/rnscreens/modals/formsheet/react/host/FormSheetHostViewManager.kt +2 -0
  32. package/android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderApplicator.kt +369 -58
  33. package/android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderBackPressHandler.kt +11 -0
  34. package/android/src/main/java/com/swmansion/rnscreens/stack/header/StackHeaderCoordinatorLayout.kt +143 -98
  35. package/android/src/main/java/com/swmansion/rnscreens/stack/header/appbar/StackHeaderAppBarLayout.kt +155 -23
  36. package/android/src/main/java/com/swmansion/rnscreens/stack/header/appbar/StackHeaderContentScrimDrawable.kt +36 -0
  37. package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderCollapsedTitleGravityMode.kt +6 -0
  38. package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfig.kt +170 -167
  39. package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigViewManager.kt +385 -12
  40. package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigurationObserver.kt +0 -4
  41. package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderConfigurationProviding.kt +32 -3
  42. package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderDelegate.kt +0 -7
  43. package/android/src/main/java/com/swmansion/rnscreens/stack/header/config/StackHeaderInvalidationFlags.kt +7 -2
  44. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/StackHeaderToolbarMenuApplicator.kt +27 -280
  45. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/StackHeaderToolbarMenuController.kt +448 -0
  46. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/StackHeaderToolbarMenuDelegate.kt +10 -0
  47. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/StackHeaderToolbarMenuMapper.kt +28 -20
  48. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/model/StackHeaderToolbarMenuConfig.kt +3 -55
  49. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/model/StackHeaderToolbarMenuGroupMetadata.kt +1 -10
  50. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/model/StackHeaderToolbarMenuItemConfig.kt +1 -3
  51. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/model/StackHeaderToolbarMenuModel.kt +180 -0
  52. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/update/StackHeaderToolbarMenuElementOptions.kt +57 -6
  53. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/update/StackHeaderToolbarMenuElementRawUpdate.kt +3 -1
  54. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/update/StackHeaderToolbarMenuElementUpdate.kt +1 -0
  55. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/update/StackHeaderToolbarMenuIconResolver.kt +4 -3
  56. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/update/StackHeaderToolbarMenuUpdateQueue.kt +23 -9
  57. package/android/src/main/java/com/swmansion/rnscreens/stack/host/StackContainer.kt +103 -3
  58. package/android/src/main/java/com/swmansion/rnscreens/stack/host/StackHost.kt +13 -0
  59. package/android/src/main/java/com/swmansion/rnscreens/stack/host/StackHostViewManager.kt +14 -0
  60. package/android/src/main/java/com/swmansion/rnscreens/stack/screen/StackScreen.kt +4 -0
  61. package/android/src/main/java/com/swmansion/rnscreens/stack/screen/StackScreenFragment.kt +20 -1
  62. package/android/src/main/java/com/swmansion/rnscreens/stack/screen/StackScreenFragmentDelegate.kt +12 -0
  63. package/android/src/main/java/com/swmansion/rnscreens/tabs/appearance/TabsAppearanceCoordinator.kt +2 -4
  64. package/android/src/main/java/com/swmansion/rnscreens/tabs/container/MenuHelpers.kt +5 -23
  65. package/android/src/main/java/com/swmansion/rnscreens/tabs/container/TabsContainer.kt +46 -25
  66. package/android/src/main/java/com/swmansion/rnscreens/tabs/host/TabsHost.kt +10 -0
  67. package/android/src/main/java/com/swmansion/rnscreens/tabs/host/TabsHostA11yCoordinator.kt +23 -2
  68. package/android/src/main/java/com/swmansion/rnscreens/tabs/screen/TabsScreen.kt +17 -0
  69. package/android/src/main/java/com/swmansion/rnscreens/tabs/screen/TabsScreenFragment.kt +3 -0
  70. package/android/src/main/java/com/swmansion/rnscreens/utils/DimensionUtils.kt +7 -0
  71. package/android/src/main/java/com/swmansion/rnscreens/utils/StyleUtils.kt +22 -0
  72. package/android/src/main/res/base/layout/rns_collapsing_toolbar_large_available_space.xml +8 -0
  73. package/android/src/main/res/base/layout/rns_collapsing_toolbar_large_entire_space.xml +8 -0
  74. package/android/src/main/res/base/layout/rns_collapsing_toolbar_medium_available_space.xml +8 -0
  75. package/android/src/main/res/base/layout/rns_collapsing_toolbar_medium_entire_space.xml +8 -0
  76. package/cpp/legacy/RNSScreenRemovalListener.cpp +31 -1
  77. package/cpp/legacy/RNSScreenRemovalListener.h +18 -3
  78. package/ios/RNSEnums.h +2 -0
  79. package/ios/conversion/RNSConversions.h +7 -108
  80. package/ios/legacy/RNSFullWindowOverlay.mm +1 -1
  81. package/ios/legacy/RNSModalScreen.mm +1 -1
  82. package/ios/legacy/RNSScreen.mm +8 -19
  83. package/ios/legacy/RNSScreenStackHeaderConfig.mm +3 -1
  84. package/ios/legacy/RNSScreenStackHeaderSubview.mm +1 -1
  85. package/ios/legacy/RNSScreenWindowTraits.mm +5 -13
  86. package/ios/modals/form-sheet/RNSFormSheetConfigurationApplicator.h +1 -3
  87. package/ios/modals/form-sheet/RNSFormSheetConfigurationApplicator.mm +11 -22
  88. package/ios/modals/form-sheet/RNSFormSheetContentController.h +1 -1
  89. package/ios/modals/form-sheet/RNSFormSheetContentController.mm +7 -18
  90. package/ios/modals/form-sheet/RNSFormSheetHostComponentView.mm +4 -4
  91. package/ios/modals/form-sheet/RNSFormSheetHostShadowStateProxy.mm +2 -2
  92. package/ios/modals/form-sheet/RNSFormSheetUpdateFlags.h +3 -0
  93. package/ios/safe-area/RNSSafeAreaViewComponentView.mm +2 -2
  94. package/ios/{conversion → scroll-view-marker/conversion}/RNSConversions-ScrollViewMarker.mm +2 -3
  95. package/ios/split/RNSSplitAppearanceApplicator.h +4 -4
  96. package/ios/split/RNSSplitAppearanceApplicator.mm +53 -56
  97. package/ios/split/RNSSplitHostComponentEventEmitter.h +1 -0
  98. package/ios/split/RNSSplitHostComponentEventEmitter.mm +1 -1
  99. package/ios/split/RNSSplitHostComponentView.h +5 -15
  100. package/ios/split/RNSSplitHostComponentView.mm +55 -6
  101. package/ios/split/RNSSplitHostController.h +25 -6
  102. package/ios/split/RNSSplitHostController.mm +24 -92
  103. package/ios/split/RNSSplitHostProviders.h +67 -0
  104. package/ios/split/RNSSplitScreenComponentView.h +0 -39
  105. package/ios/split/RNSSplitScreenComponentView.mm +27 -10
  106. package/ios/split/RNSSplitScreenController.h +26 -3
  107. package/ios/split/RNSSplitScreenController.mm +13 -30
  108. package/ios/split/RNSSplitScreenShadowStateProxy.h +1 -40
  109. package/ios/split/RNSSplitScreenShadowStateProxy.mm +1 -26
  110. package/ios/split/conversion/RNSConversions-SplitView.h +56 -0
  111. package/ios/{conversion → split/conversion}/RNSConversions-SplitView.mm +4 -1
  112. package/ios/stack/conversion/RNSConversions-Stack.h +32 -0
  113. package/ios/stack/conversion/RNSConversions-Stack.mm +55 -0
  114. package/ios/stack/header/RNSStackHeaderAppearanceMapper.h +26 -0
  115. package/ios/stack/header/RNSStackHeaderAppearanceMapper.mm +88 -0
  116. package/ios/stack/header/RNSStackHeaderConfigComponentView.h +10 -1
  117. package/ios/stack/header/RNSStackHeaderConfigComponentView.mm +67 -35
  118. package/ios/stack/header/RNSStackHeaderConfigDataProviding.h +6 -0
  119. package/ios/stack/header/RNSStackHeaderConfigShadowStateProxy.mm +1 -1
  120. package/ios/stack/header/RNSStackHeaderIconResolver.mm +7 -0
  121. package/ios/stack/header/RNSStackHeaderItemComponentView.h +3 -1
  122. package/ios/stack/header/RNSStackHeaderItemComponentView.mm +15 -2
  123. package/ios/stack/header/RNSStackHeaderItemDataProviding.h +2 -0
  124. package/ios/stack/header/RNSStackHeaderItemShadowStateProxy.mm +1 -1
  125. package/ios/stack/header/RNSStackHeaderItemSpacerComponentView.mm +2 -1
  126. package/ios/stack/header/RNSStackHeaderMenuMapper.mm +4 -0
  127. package/ios/stack/host/RNSStackNavigationBar.h +25 -0
  128. package/ios/stack/host/RNSStackNavigationBar.mm +80 -0
  129. package/ios/stack/host/RNSStackNavigationBarCoordinator.h +8 -0
  130. package/ios/stack/host/RNSStackNavigationBarCoordinator.mm +11 -0
  131. package/ios/stack/host/RNSStackNavigationController.mm +13 -5
  132. package/ios/stack/host/RNSStackOperation.h +0 -2
  133. package/ios/stack/screen/RNSStackScreenComponentView.mm +9 -2
  134. package/ios/stack/screen/RNSStackScreenController.mm +12 -0
  135. package/ios/stack/screen/RNSStackScreenHeaderCoordinator.h +8 -0
  136. package/ios/stack/screen/RNSStackScreenHeaderCoordinator.mm +98 -16
  137. package/ios/tabs/RNSTabBarAppearanceCoordinator.mm +40 -9
  138. package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryComponentView.h +1 -1
  139. package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryComponentView.mm +1 -1
  140. package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryContentComponentView.mm +1 -1
  141. package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryEventEmitter.mm +1 -1
  142. package/ios/tabs/bottom-accessory/RNSTabsBottomAccessoryShadowStateProxy.mm +1 -1
  143. package/ios/tabs/conversion/RNSConversions-Tabs.h +78 -0
  144. package/ios/{conversion → tabs/conversion}/RNSConversions-Tabs.mm +8 -71
  145. package/ios/tabs/extensions/RNSTabsHostComponentView+RNSImageLoader.h +1 -1
  146. package/ios/tabs/host/RNSTabsHostComponentView.h +2 -0
  147. package/ios/tabs/host/RNSTabsHostComponentView.mm +10 -3
  148. package/ios/tabs/host/RNSTabsHostEventEmitter.mm +1 -1
  149. package/ios/tabs/screen/RNSTabsScreenComponentView.mm +20 -5
  150. package/ios/utils/RNSDefines.h +3 -0
  151. package/lib/commonjs/components/stack/header/StackHeaderConfig.android.js +34 -19
  152. package/lib/commonjs/components/stack/header/StackHeaderConfig.android.js.map +1 -1
  153. package/lib/commonjs/components/stack/header/StackHeaderConfig.ios.js +33 -1
  154. package/lib/commonjs/components/stack/header/StackHeaderConfig.ios.js.map +1 -1
  155. package/lib/commonjs/components/stack/host/StackHost.js +9 -6
  156. package/lib/commonjs/components/stack/host/StackHost.js.map +1 -1
  157. package/lib/commonjs/components/stack/index.js.map +1 -1
  158. package/lib/commonjs/components/tabs/host/TabsHost.ios.js +1 -0
  159. package/lib/commonjs/components/tabs/host/TabsHost.ios.js.map +1 -1
  160. package/lib/commonjs/fabric/stack/StackHeaderConfigAndroidNativeComponent.js.map +1 -1
  161. package/lib/commonjs/fabric/stack/StackHeaderConfigIOSNativeComponent.js.map +1 -1
  162. package/lib/commonjs/fabric/stack/StackHeaderItemIOSNativeComponent.js.map +1 -1
  163. package/lib/commonjs/fabric/stack/StackHostNativeComponent.js.map +1 -1
  164. package/lib/commonjs/fabric/tabs/TabsHostIOSNativeComponent.js.map +1 -1
  165. package/lib/module/components/stack/header/StackHeaderConfig.android.js +35 -20
  166. package/lib/module/components/stack/header/StackHeaderConfig.android.js.map +1 -1
  167. package/lib/module/components/stack/header/StackHeaderConfig.ios.js +34 -2
  168. package/lib/module/components/stack/header/StackHeaderConfig.ios.js.map +1 -1
  169. package/lib/module/components/stack/host/StackHost.js +9 -6
  170. package/lib/module/components/stack/host/StackHost.js.map +1 -1
  171. package/lib/module/components/stack/index.js.map +1 -1
  172. package/lib/module/components/tabs/host/TabsHost.ios.js +1 -0
  173. package/lib/module/components/tabs/host/TabsHost.ios.js.map +1 -1
  174. package/lib/module/fabric/stack/StackHeaderConfigAndroidNativeComponent.js.map +1 -1
  175. package/lib/module/fabric/stack/StackHeaderConfigIOSNativeComponent.js.map +1 -1
  176. package/lib/module/fabric/stack/StackHeaderItemIOSNativeComponent.js.map +1 -1
  177. package/lib/module/fabric/stack/StackHostNativeComponent.js.map +1 -1
  178. package/lib/module/fabric/tabs/TabsHostIOSNativeComponent.js.map +1 -1
  179. package/lib/typescript/components/modals/form-sheet/FormSheet.types.d.ts +1 -1
  180. package/lib/typescript/components/stack/header/StackHeaderConfig.android.d.ts.map +1 -1
  181. package/lib/typescript/components/stack/header/StackHeaderConfig.android.types.d.ts +616 -21
  182. package/lib/typescript/components/stack/header/StackHeaderConfig.android.types.d.ts.map +1 -1
  183. package/lib/typescript/components/stack/header/StackHeaderConfig.ios.d.ts.map +1 -1
  184. package/lib/typescript/components/stack/header/StackHeaderConfig.ios.types.d.ts +226 -2
  185. package/lib/typescript/components/stack/header/StackHeaderConfig.ios.types.d.ts.map +1 -1
  186. package/lib/typescript/components/stack/header/StackHeaderConfig.types.d.ts +2 -2
  187. package/lib/typescript/components/stack/header/ios/StackHeaderItem.ios.types.d.ts +2 -0
  188. package/lib/typescript/components/stack/header/ios/StackHeaderItem.ios.types.d.ts.map +1 -1
  189. package/lib/typescript/components/stack/host/StackHost.d.ts +1 -1
  190. package/lib/typescript/components/stack/host/StackHost.d.ts.map +1 -1
  191. package/lib/typescript/components/stack/host/StackHost.types.d.ts +24 -0
  192. package/lib/typescript/components/stack/host/StackHost.types.d.ts.map +1 -1
  193. package/lib/typescript/components/stack/index.d.ts +2 -2
  194. package/lib/typescript/components/stack/index.d.ts.map +1 -1
  195. package/lib/typescript/components/tabs/host/TabsHost.ios.d.ts.map +1 -1
  196. package/lib/typescript/components/tabs/host/TabsHost.ios.types.d.ts +16 -0
  197. package/lib/typescript/components/tabs/host/TabsHost.ios.types.d.ts.map +1 -1
  198. package/lib/typescript/components/tabs/host/TabsHost.types.d.ts +3 -0
  199. package/lib/typescript/components/tabs/host/TabsHost.types.d.ts.map +1 -1
  200. package/lib/typescript/fabric/stack/StackHeaderConfigAndroidNativeComponent.d.ts +52 -0
  201. package/lib/typescript/fabric/stack/StackHeaderConfigAndroidNativeComponent.d.ts.map +1 -1
  202. package/lib/typescript/fabric/stack/StackHeaderConfigIOSNativeComponent.d.ts +25 -1
  203. package/lib/typescript/fabric/stack/StackHeaderConfigIOSNativeComponent.d.ts.map +1 -1
  204. package/lib/typescript/fabric/stack/StackHeaderItemIOSNativeComponent.d.ts +2 -0
  205. package/lib/typescript/fabric/stack/StackHeaderItemIOSNativeComponent.d.ts.map +1 -1
  206. package/lib/typescript/fabric/stack/StackHostNativeComponent.d.ts +3 -1
  207. package/lib/typescript/fabric/stack/StackHostNativeComponent.d.ts.map +1 -1
  208. package/lib/typescript/fabric/tabs/TabsHostIOSNativeComponent.d.ts +1 -0
  209. package/lib/typescript/fabric/tabs/TabsHostIOSNativeComponent.d.ts.map +1 -1
  210. package/package.json +8 -7
  211. package/react-native.config.js +3 -0
  212. package/src/components/modals/form-sheet/FormSheet.types.ts +1 -1
  213. package/src/components/stack/header/StackHeaderConfig.android.tsx +48 -23
  214. package/src/components/stack/header/StackHeaderConfig.android.types.ts +637 -21
  215. package/src/components/stack/header/StackHeaderConfig.ios.tsx +45 -1
  216. package/src/components/stack/header/StackHeaderConfig.ios.types.ts +238 -2
  217. package/src/components/stack/header/StackHeaderConfig.types.ts +2 -2
  218. package/src/components/stack/header/ios/StackHeaderItem.ios.types.ts +2 -0
  219. package/src/components/stack/host/StackHost.tsx +4 -2
  220. package/src/components/stack/host/StackHost.types.ts +26 -0
  221. package/src/components/stack/index.ts +6 -1
  222. package/src/components/tabs/host/TabsHost.ios.tsx +1 -0
  223. package/src/components/tabs/host/TabsHost.ios.types.ts +16 -0
  224. package/src/components/tabs/host/TabsHost.types.ts +3 -0
  225. package/src/fabric/stack/StackHeaderConfigAndroidNativeComponent.ts +78 -0
  226. package/src/fabric/stack/StackHeaderConfigIOSNativeComponent.ts +32 -0
  227. package/src/fabric/stack/StackHeaderItemIOSNativeComponent.ts +2 -0
  228. package/src/fabric/stack/StackHostNativeComponent.ts +11 -2
  229. package/src/fabric/tabs/TabsHostIOSNativeComponent.ts +1 -0
  230. package/android/src/main/java/com/swmansion/rnscreens/modals/dimmingview/DimmingView.kt +0 -83
  231. package/android/src/main/java/com/swmansion/rnscreens/modals/dimmingview/DimmingViewManager.kt +0 -89
  232. package/android/src/main/java/com/swmansion/rnscreens/modals/dimmingview/DimmingViewPointerEvents.kt +0 -18
  233. package/android/src/main/java/com/swmansion/rnscreens/stack/header/toolbar/StackHeaderToolbarMenuSelectionController.kt +0 -83
  234. package/ios/conversion/RNSConversions-Stack.h +0 -32
  235. package/ios/conversion/RNSConversions-Stack.mm +0 -41
  236. package/ios/conversion/always_false.h +0 -31
  237. /package/ios/{conversion → scroll-view-marker/conversion}/RNSConversions-ScrollViewMarker.h +0 -0
@@ -1,17 +1,23 @@
1
1
  package com.swmansion.rnscreens.stack.screen
2
2
 
3
+ import android.content.res.Configuration
3
4
  import android.os.Bundle
5
+ import android.util.Log
4
6
  import android.view.Gravity
5
7
  import android.view.LayoutInflater
6
8
  import android.view.View
7
9
  import android.view.ViewGroup
8
10
  import androidx.fragment.app.Fragment
9
11
  import androidx.transition.Slide
12
+ import com.swmansion.rnscreens.stack.header.StackHeaderBackPressHandler
10
13
  import com.swmansion.rnscreens.stack.header.StackHeaderCoordinatorLayout
14
+ import java.lang.ref.WeakReference
11
15
 
12
16
  internal class StackScreenFragment(
13
17
  internal val stackScreen: StackScreen,
14
18
  private val canNavigateBack: Boolean,
19
+ private val delegate: WeakReference<StackScreenFragmentDelegate>,
20
+ private val backPressHandler: WeakReference<StackHeaderBackPressHandler>,
15
21
  ) : Fragment() {
16
22
  private var screenLifecycleEventEmitter: StackScreenAppearanceEventsEmitter? = null
17
23
 
@@ -45,7 +51,11 @@ internal class StackScreenFragment(
45
51
  inflater: LayoutInflater,
46
52
  container: ViewGroup?,
47
53
  savedInstanceState: Bundle?,
48
- ): View = StackHeaderCoordinatorLayout(requireContext(), stackScreen, canNavigateBack)
54
+ ): View =
55
+ StackHeaderCoordinatorLayout(requireContext(), stackScreen, canNavigateBack) { pressedScreen ->
56
+ backPressHandler.get()?.handleHeaderBackButtonPress(pressedScreen)
57
+ ?: Log.w(TAG, "[RNScreens] Header back button press dropped - handler is gone")
58
+ }
49
59
 
50
60
  override fun onViewCreated(
51
61
  view: View,
@@ -71,6 +81,11 @@ internal class StackScreenFragment(
71
81
  teardownPreventNativeDismissCallback()
72
82
  }
73
83
 
84
+ override fun onConfigurationChanged(newConfig: Configuration) {
85
+ super.onConfigurationChanged(newConfig)
86
+ delegate.get()?.onFragmentConfigurationChanged(newConfig)
87
+ }
88
+
74
89
  /**
75
90
  * Notifies this fragment that it has become "top fragment" in its fragment manager.
76
91
  * Call this only if the lifecycle of the fragment is at least at CREATED.
@@ -109,4 +124,8 @@ internal class StackScreenFragment(
109
124
  requireNativeDismissBackPressedCallback.remove()
110
125
  preventNativeDismissBackPressedCallback = null
111
126
  }
127
+
128
+ companion object {
129
+ private const val TAG = "StackScreenFragment"
130
+ }
112
131
  }
@@ -0,0 +1,12 @@
1
+ package com.swmansion.rnscreens.stack.screen
2
+
3
+ import android.content.res.Configuration
4
+
5
+ internal interface StackScreenFragmentDelegate {
6
+ /**
7
+ * Notifies the delegate that the fragment received a configuration update,
8
+ * e.g. theme change from JS via Appearance.setColorScheme. Fragments are
9
+ * the only place where such updates are delivered reliably.
10
+ */
11
+ fun onFragmentConfigurationChanged(config: Configuration)
12
+ }
@@ -4,7 +4,6 @@ import android.content.Context
4
4
  import android.view.MenuItem
5
5
  import com.google.android.material.bottomnavigation.BottomNavigationView
6
6
  import com.swmansion.rnscreens.tabs.container.TabsContainer
7
- import com.swmansion.rnscreens.tabs.container.menuItemIdForFragmentAtIndex
8
7
  import com.swmansion.rnscreens.tabs.screen.TabsScreen
9
8
  import com.swmansion.rnscreens.tabs.screen.TabsScreenFragment
10
9
 
@@ -28,13 +27,12 @@ internal class TabsAppearanceCoordinator(
28
27
  context: Context,
29
28
  tabsAppearance: TabsAppearance?,
30
29
  ) {
31
- tabsScreenFragments.forEachIndexed { index, fragment ->
32
- val menuItemId = menuItemIdForFragmentAtIndex(index)
30
+ tabsScreenFragments.forEach { fragment ->
31
+ val menuItemId = fragment.menuItemId
33
32
  val menuItem =
34
33
  checkNotNull(bottomNavigationView.menu.findItem(menuItemId)) {
35
34
  "[RNScreens] Missing MenuItem for id: $menuItemId"
36
35
  }
37
- check(menuItem.itemId == menuItemId) { "[RNScreens] Illegal state: menu items are shuffled" }
38
36
  updateMenuItemAppearance(context, menuItem, fragment.tabsScreen, tabsAppearance)
39
37
  }
40
38
  }
@@ -2,30 +2,12 @@ package com.swmansion.rnscreens.tabs.container
2
2
 
3
3
  import android.view.Menu
4
4
  import android.view.MenuItem
5
- import com.swmansion.rnscreens.tabs.screen.TabsScreen
5
+ import com.swmansion.rnscreens.tabs.screen.TabsScreenFragment
6
6
 
7
- // MenuItem ids are offset by one, because 0 has special meaning in the BottomNavigationMenuView API.
8
-
9
- /**
10
- * Compute MenuItem id for a fragment at given index in "tabsModel" of TabsContainer
11
- */
12
- internal fun menuItemIdForFragmentAtIndex(fragmentIndex: Int): Int = fragmentIndex + 1
13
-
14
- /**
15
- * Compute fragment index in "tabsModel" of TabsContainer for a MenuItem with given id.
16
- */
17
- internal fun fragmentIndexForMenuItemId(menuItemId: Int): Int {
18
- check(menuItemId >= 1) { "[RNScreens] MenuItem id must not be less than 1" }
19
- return menuItemId - 1
20
- }
21
-
22
- internal fun Menu.getOrCreateMenuItemForFragmentAt(
23
- index: Int,
24
- tabsScreen: TabsScreen,
25
- ): MenuItem =
26
- this.findItem(menuItemIdForFragmentAtIndex(index)) ?: this.add(
7
+ internal fun Menu.getOrCreateMenuItemForFragment(fragment: TabsScreenFragment): MenuItem =
8
+ this.findItem(fragment.menuItemId) ?: this.add(
27
9
  Menu.NONE,
28
- menuItemIdForFragmentAtIndex(index),
10
+ fragment.menuItemId,
29
11
  Menu.NONE,
30
- tabsScreen.tabTitle,
12
+ fragment.tabsScreen.tabTitle,
31
13
  )
@@ -24,6 +24,7 @@ import com.swmansion.rnscreens.common.colorscheme.ColorSchemeCoordinator
24
24
  import com.swmansion.rnscreens.common.colorscheme.ColorSchemeListener
25
25
  import com.swmansion.rnscreens.common.colorscheme.ColorSchemeProviding
26
26
  import com.swmansion.rnscreens.common.container.Container
27
+ import com.swmansion.rnscreens.common.container.ContainerItem
27
28
  import com.swmansion.rnscreens.common.container.ParentContainerItemRegistry
28
29
  import com.swmansion.rnscreens.helpers.FragmentManagerHelper
29
30
  import com.swmansion.rnscreens.helpers.ViewFinder
@@ -67,7 +68,8 @@ class TabsContainer internal constructor(
67
68
  val selectedTabScreen = this@TabsContainer.selectedTab.tabsScreen
68
69
 
69
70
  if (selectedTabScreen.shouldUseRepeatedTabSelectionPopToRootSpecialEffect) {
70
- val screenStack = ViewFinder.findScreenStackInFirstDescendantChain(selectedTabScreen)
71
+ val screenStack =
72
+ ViewFinder.findScreenStackInFirstDescendantChain(selectedTabScreen)
71
73
  if (screenStack != null && screenStack.popToRoot()) {
72
74
  return true
73
75
  }
@@ -492,7 +494,10 @@ class TabsContainer internal constructor(
492
494
 
493
495
  private fun performSelectedTabUpdate() {
494
496
  if (pendingStateUpdateRequest == null) {
495
- RNSLog.w(TAG, "TabsContainer::performSelectedTabUpdate called w/o pending operation; skipping update")
497
+ RNSLog.w(
498
+ TAG,
499
+ "TabsContainer::performSelectedTabUpdate called w/o pending operation; skipping update",
500
+ )
496
501
  return
497
502
  }
498
503
 
@@ -516,7 +521,10 @@ class TabsContainer internal constructor(
516
521
  if (bottomNavigationView.selectedItemId != nextSelectedMenuItemId || navState.isEmpty()) {
517
522
  isInExternalOperationContext = true
518
523
  // This triggers on OnMenuItemClicked callback, where we perform actual update from
519
- bottomNavigationView.setSelectedItemIdWithActionOrigin(nextSelectedMenuItemId, stateUpdateRequest.actionOrigin)
524
+ bottomNavigationView.setSelectedItemIdWithActionOrigin(
525
+ nextSelectedMenuItemId,
526
+ stateUpdateRequest.actionOrigin,
527
+ )
520
528
  isInExternalOperationContext = false
521
529
  } else {
522
530
  observerRegistry.emitOnNavigationStateUpdateRejected(
@@ -530,17 +538,19 @@ class TabsContainer internal constructor(
530
538
  }
531
539
 
532
540
  private fun updateNavigationMenuStructure() {
533
- if (bottomNavigationView.menu.size != tabsModel.size) {
534
- // Most likely first render or some tab has been removed. Let's nuke the menu (easiest option).
535
- bottomNavigationView.menu.clear()
536
- }
537
- tabsModel.forEachIndexed { index, fragment ->
538
- val menuItem =
539
- bottomNavigationView.menu.getOrCreateMenuItemForFragmentAt(
540
- index,
541
- fragment.tabsScreen,
542
- )
543
- check(fragmentIndexForMenuItemId(menuItem.itemId) == index) { "[RNScreens] Illegal state: menu items are shuffled" }
541
+ val menu = bottomNavigationView.menu
542
+ val isMenuInSync =
543
+ menu.size == tabsModel.size &&
544
+ tabsModel.withIndex().all { (index, fragment) ->
545
+ menu.getItem(index).itemId == fragment.menuItemId
546
+ }
547
+
548
+ if (isMenuInSync) {
549
+ return
550
+ }
551
+ menu.clear()
552
+ tabsModel.forEach { fragment ->
553
+ menu.getOrCreateMenuItemForFragment(fragment)
544
554
  }
545
555
  }
546
556
 
@@ -573,7 +583,10 @@ class TabsContainer internal constructor(
573
583
  }
574
584
 
575
585
  progressNavigationState(nextSelectedFragment.requireScreenKey, actionOrigin)
576
- applyNextSelectedFragmentToFragmentManagerSync(currentSelectedFragment, nextSelectedFragment)
586
+ applyNextSelectedFragmentToFragmentManagerSync(
587
+ currentSelectedFragment,
588
+ nextSelectedFragment,
589
+ )
577
590
  return true
578
591
  }
579
592
 
@@ -643,7 +656,8 @@ class TabsContainer internal constructor(
643
656
  return false
644
657
  }
645
658
 
646
- val stateChanged = updateNavigationStateAndSelectedFragment(nextSelectedFragment, actionOrigin)
659
+ val stateChanged =
660
+ updateNavigationStateAndSelectedFragment(nextSelectedFragment, actionOrigin)
647
661
 
648
662
  val hasTriggeredSpecialEffect =
649
663
  if (isRepeated) specialEffectsHandler.handleRepeatedTabSelection() else false
@@ -688,7 +702,8 @@ class TabsContainer internal constructor(
688
702
 
689
703
  // We expect at most a single fragment added (detached fragments are not returned from
690
704
  // FragmentManager.getFragments call) and it being the currently selected tab.
691
- val isInUpToDateState = currentFragments.size == 1 && currentFragments.first() === selectedTab
705
+ val isInUpToDateState =
706
+ currentFragments.size == 1 && currentFragments.first() === selectedTab
692
707
  if (isInUpToDateState) {
693
708
  return
694
709
  } else if (currentFragments.isEmpty()) {
@@ -717,12 +732,10 @@ class TabsContainer internal constructor(
717
732
  appearanceCoordinator.updateTabAppearance(themedContext, this)
718
733
  }
719
734
 
720
- private fun getFragmentForMenuItemId(itemId: Int): TabsScreenFragment? = tabsModel.getOrNull(fragmentIndexForMenuItemId(itemId))
735
+ private fun getFragmentForMenuItemId(itemId: Int): TabsScreenFragment? = tabsModel.find { it.menuItemId == itemId }
721
736
 
722
737
  private fun getMenuItemIdForFragment(tabsScreenFragment: TabsScreenFragment): Int? =
723
- tabsModel.indexOfFirst { it === tabsScreenFragment }.takeIf { it != -1 }?.let {
724
- menuItemIdForFragmentAtIndex(it)
725
- }
738
+ tabsScreenFragment.menuItemId.takeIf { tabsModel.any { it === tabsScreenFragment } }
726
739
 
727
740
  private fun getSelectedTabsScreenFragmentId(): Int? =
728
741
  tabsModel
@@ -731,10 +744,9 @@ class TabsContainer internal constructor(
731
744
 
732
745
  private fun getMenuItemForTabsScreen(tabsScreen: TabsScreen): MenuItem? =
733
746
  tabsModel
734
- .indexOfFirst { it.tabsScreen === tabsScreen }
735
- .takeIf { it != -1 }
736
- ?.let { index ->
737
- bottomNavigationView.menu.findItem(menuItemIdForFragmentAtIndex(index))
747
+ .find { it.tabsScreen === tabsScreen }
748
+ ?.let { fragment ->
749
+ bottomNavigationView.menu.findItem(fragment.menuItemId)
738
750
  }
739
751
 
740
752
  private fun getFragmentForScreenKey(screenKey: String): TabsScreenFragment? = tabsModel.find { it.requireScreenKey == screenKey }
@@ -780,6 +792,15 @@ class TabsContainer internal constructor(
780
792
  return selectedTab.tabsScreen.findContentScrollView()
781
793
  }
782
794
 
795
+ // Only the active item is consulted - a preventing screen inside an inactive tab
796
+ // does not veto the dismissal.
797
+ override fun wantsToPreventStackNativeDismiss(): ContainerItem? =
798
+ if (navState.isNotEmpty()) {
799
+ selectedTab.tabsScreen.wantsToPreventStackNativeDismiss()
800
+ } else {
801
+ null
802
+ }
803
+
783
804
  // endregion
784
805
 
785
806
  companion object {
@@ -161,6 +161,16 @@ class TabsHost(
161
161
  refreshLayout()
162
162
  }
163
163
 
164
+ // On window configuration changes ViewRootImpl calls forceLayout() recursively on every view
165
+ // in the hierarchy, bypassing requestLayout(). React views never lay out their children during
166
+ // framework traversals, so nothing would clear the force-layout flags in our subtree — and once
167
+ // they are set, any subsequent requestLayout() from a descendant short-circuits before reaching
168
+ // the override above, permanently disabling the manual pass. Schedule it from here as well.
169
+ override fun forceLayout() {
170
+ super.forceLayout()
171
+ refreshLayout()
172
+ }
173
+
164
174
  private fun forceSubtreeMeasureAndLayoutPass() {
165
175
  measure(
166
176
  MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
@@ -3,6 +3,9 @@ package com.swmansion.rnscreens.tabs.host
3
3
  import android.os.Build
4
4
  import android.view.MenuItem
5
5
  import android.view.View
6
+ import androidx.core.view.AccessibilityDelegateCompat
7
+ import androidx.core.view.ViewCompat
8
+ import androidx.core.view.accessibility.AccessibilityNodeInfoCompat
6
9
  import androidx.core.view.children
7
10
  import androidx.core.view.get
8
11
  import com.google.android.material.bottomnavigation.BottomNavigationView
@@ -23,8 +26,26 @@ class TabsHostA11yCoordinator(
23
26
  menuItem.contentDescription = tabsScreen.tabBarItemAccessibilityLabel
24
27
  }
25
28
 
26
- // when matching view by id, espresso driver seems to look for tag property
27
- findTabItemView(menuItem.itemId)?.tag = tabsScreen.tabBarItemTestID
29
+ findTabItemView(menuItem.itemId)?.let { tabItemView ->
30
+ // when matching view by id, espresso driver seems to look for tag property
31
+ tabItemView.tag = tabsScreen.tabBarItemTestID
32
+
33
+ // UiAutomator-based drivers (Maestro, Appium) read the node's viewIdResourceName instead,
34
+ // so expose the testID there as well — this is what React Native's ReactAccessibilityDelegate
35
+ // does for every other view. Read through to the screen so a prop update is reflected.
36
+ ViewCompat.setAccessibilityDelegate(
37
+ tabItemView,
38
+ object : AccessibilityDelegateCompat() {
39
+ override fun onInitializeAccessibilityNodeInfo(
40
+ host: View,
41
+ info: AccessibilityNodeInfoCompat,
42
+ ) {
43
+ super.onInitializeAccessibilityNodeInfo(host, info)
44
+ info.viewIdResourceName = tabsScreen.tabBarItemTestID
45
+ }
46
+ },
47
+ )
48
+ }
28
49
  }
29
50
 
30
51
  // Third-party SDKs can freely assign their own ids to id-less views in CustomBottomNavigationView's subtree and there is no way to
@@ -2,6 +2,8 @@ package com.swmansion.rnscreens.tabs.screen
2
2
 
3
3
  import android.content.res.Configuration
4
4
  import android.graphics.drawable.Drawable
5
+ import android.os.Parcelable
6
+ import android.util.SparseArray
5
7
  import android.view.ViewGroup
6
8
  import androidx.fragment.app.Fragment
7
9
  import com.facebook.react.uimanager.ThemedReactContext
@@ -127,6 +129,17 @@ class TabsScreen(
127
129
  b: Int,
128
130
  ) = Unit
129
131
 
132
+ override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
133
+ // Do nothing. React Native owns this view hierarchy and keeps it alive, so there is no
134
+ // need to serialize/deserialize native view state. View ids in this subtree are react
135
+ // tags, which are not stable identities - restoring state by id can apply state saved
136
+ // by one view type to a different one, crashing e.g. in CompoundButton (see #4523).
137
+ }
138
+
139
+ override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
140
+ // Ignore restoring instance state too, as we are not saving anything anyways.
141
+ }
142
+
130
143
  internal fun setTabsScreenDelegate(delegate: TabsScreenDelegate?) {
131
144
  tabsScreenDelegate = WeakReference(delegate)
132
145
  }
@@ -176,6 +189,10 @@ class TabsScreen(
176
189
 
177
190
  override fun findContentScrollView(): ViewGroup? = containerItemSupport.findContentScrollView(this)
178
191
 
192
+ // TabsScreen has no `preventNativeDismiss` flag of its own - it only forwards
193
+ // the question to its nested container.
194
+ override fun wantsToPreventStackNativeDismiss(): ContainerItem? = resolveNestedContainer()?.wantsToPreventStackNativeDismiss()
195
+
179
196
  // endregion
180
197
 
181
198
  companion object {
@@ -6,6 +6,7 @@ import android.view.LayoutInflater
6
6
  import android.view.View
7
7
  import android.view.ViewGroup
8
8
  import androidx.fragment.app.Fragment
9
+ import com.swmansion.rnscreens.helpers.ViewIdGenerator
9
10
 
10
11
  class TabsScreenFragment(
11
12
  internal val tabsScreen: TabsScreen,
@@ -13,6 +14,8 @@ class TabsScreenFragment(
13
14
  internal val requireScreenKey: String by tabsScreen::requireScreenKey
14
15
  internal val isPreventNativeSelectionEnabled: Boolean by tabsScreen::preventNativeSelection
15
16
 
17
+ internal val menuItemId: Int = ViewIdGenerator.generateViewId()
18
+
16
19
  override fun onCreateView(
17
20
  inflater: LayoutInflater,
18
21
  container: ViewGroup?,
@@ -49,6 +49,13 @@ internal fun dpToPx(
49
49
  */
50
50
  internal fun View.dpToPx(dp: Float): Float = dpToPx(dp, resources.displayMetrics.density)
51
51
 
52
+ /**
53
+ * Converts an SP value to pixels using the density of the display this [View] is
54
+ * attached to (including the current font scale). Uses the view's own resources
55
+ * rather than the process-global density read by [PixelUtil]. See [pxToDp].
56
+ */
57
+ internal fun View.spToPx(sp: Float): Float = TypedValue.applyDimension(TypedValue.COMPLEX_UNIT_SP, sp, resources.displayMetrics)
58
+
52
59
  internal fun resolveDimensionAttr(
53
60
  context: Context,
54
61
  attrId: Int,
@@ -0,0 +1,22 @@
1
+ package com.swmansion.rnscreens.utils
2
+
3
+ import android.content.Context
4
+ import android.util.TypedValue
5
+ import androidx.annotation.StyleRes
6
+
7
+ /**
8
+ * Resolves a theme attribute pointing at a style (e.g. `?attr/textAppearanceTitleLarge`)
9
+ * to its concrete style resource id. Used to recover Material's default text
10
+ * appearances so they can be re-applied at runtime.
11
+ */
12
+ @StyleRes
13
+ internal fun resolveStyleResAttr(
14
+ context: Context,
15
+ attr: Int,
16
+ ): Int {
17
+ val typedValue = TypedValue()
18
+ require(context.theme.resolveAttribute(attr, typedValue, true)) {
19
+ "[RNScreens] Unable to resolve Material theme style attribute."
20
+ }
21
+ return typedValue.resourceId
22
+ }
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <com.google.android.material.appbar.CollapsingToolbarLayout
3
+ xmlns:android="http://schemas.android.com/apk/res/android"
4
+ xmlns:app="http://schemas.android.com/apk/res-auto"
5
+ style="?attr/collapsingToolbarLayoutLargeStyle"
6
+ android:layout_width="match_parent"
7
+ android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
8
+ app:collapsedTitleGravityMode="availableSpace" />
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <com.google.android.material.appbar.CollapsingToolbarLayout
3
+ xmlns:android="http://schemas.android.com/apk/res/android"
4
+ xmlns:app="http://schemas.android.com/apk/res-auto"
5
+ style="?attr/collapsingToolbarLayoutLargeStyle"
6
+ android:layout_width="match_parent"
7
+ android:layout_height="?attr/collapsingToolbarLayoutLargeSize"
8
+ app:collapsedTitleGravityMode="entireSpace" />
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <com.google.android.material.appbar.CollapsingToolbarLayout
3
+ xmlns:android="http://schemas.android.com/apk/res/android"
4
+ xmlns:app="http://schemas.android.com/apk/res-auto"
5
+ style="?attr/collapsingToolbarLayoutMediumStyle"
6
+ android:layout_width="match_parent"
7
+ android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
8
+ app:collapsedTitleGravityMode="availableSpace" />
@@ -0,0 +1,8 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <com.google.android.material.appbar.CollapsingToolbarLayout
3
+ xmlns:android="http://schemas.android.com/apk/res/android"
4
+ xmlns:app="http://schemas.android.com/apk/res-auto"
5
+ style="?attr/collapsingToolbarLayoutMediumStyle"
6
+ android:layout_width="match_parent"
7
+ android:layout_height="?attr/collapsingToolbarLayoutMediumSize"
8
+ app:collapsedTitleGravityMode="entireSpace" />
@@ -1,12 +1,42 @@
1
1
  #include "RNSScreenRemovalListener.h"
2
2
  #include <react/renderer/mounting/ShadowViewMutation.h>
3
+ #include <utility>
3
4
  using namespace facebook::react;
4
5
 
6
+ uint64_t RNSScreenRemovalListener::setListener(
7
+ std::function<void(int)> &&listenerFunction) {
8
+ std::lock_guard<std::mutex> lock(listenerMutex_);
9
+ listenerFunction_ = std::move(listenerFunction);
10
+ return ++currentToken_;
11
+ }
12
+
13
+ void RNSScreenRemovalListener::clearListener(uint64_t token) {
14
+ std::lock_guard<std::mutex> lock(listenerMutex_);
15
+ if (token == currentToken_) {
16
+ listenerFunction_ = nullptr;
17
+ }
18
+ }
19
+
5
20
  std::optional<MountingTransaction> RNSScreenRemovalListener::pullTransaction(
6
21
  SurfaceId surfaceId,
7
22
  MountingTransaction::Number transactionNumber,
8
23
  const TransactionTelemetry &telemetry,
9
24
  ShadowViewMutationList mutations) const {
25
+ // Copy under the lock, invoke outside it: the callback makes a JNI call and
26
+ // must not run while holding a lock the module thread also takes.
27
+ std::function<void(int)> listener;
28
+ {
29
+ std::lock_guard<std::mutex> lock(listenerMutex_);
30
+ listener = listenerFunction_;
31
+ }
32
+
33
+ if (!listener) {
34
+ // Orphaned between module teardown and the next install: pass the
35
+ // transaction through untouched.
36
+ return MountingTransaction{
37
+ surfaceId, transactionNumber, std::move(mutations), telemetry};
38
+ }
39
+
10
40
  for (const ShadowViewMutation &mutation : mutations) {
11
41
  // When using RNSModalScreen on Android it should be added here.
12
42
  if (mutation.type == ShadowViewMutation::Type::Remove &&
@@ -16,7 +46,7 @@ std::optional<MountingTransaction> RNSScreenRemovalListener::pullTransaction(
16
46
  // We call the listener function even if this screen has not been owned
17
47
  // by RNSScreenStack as since RN 0.78 we do not have enough information
18
48
  // here. This final filter is applied later in NativeProxy.
19
- listenerFunction_(mutation.oldChildShadowView.tag);
49
+ listener(mutation.oldChildShadowView.tag);
20
50
  }
21
51
  }
22
52
 
@@ -4,12 +4,22 @@
4
4
  #include <react/renderer/mounting/MountingOverrideDelegate.h>
5
5
  #include <react/renderer/mounting/ShadowView.h>
6
6
 
7
+ #include <cstdint>
8
+ #include <functional>
9
+ #include <mutex>
10
+
7
11
  using namespace facebook::react;
8
12
 
9
13
  struct RNSScreenRemovalListener : public MountingOverrideDelegate {
10
- std::function<void(int)> listenerFunction_;
11
- RNSScreenRemovalListener(std::function<void(int)> &&listenerFunction_)
12
- : listenerFunction_(std::move(listenerFunction_)) {}
14
+ RNSScreenRemovalListener() = default;
15
+
16
+ // RN core lacks a `removeMountingOverrideDelegate` API, so this instance is
17
+ // process-immortal and swaps its callback instead of being replaced. The
18
+ // ownership token keeps a stale proxy's teardown from clearing a newer
19
+ // proxy's listener.
20
+ // See https://github.com/software-mansion/react-native-screens/pull/4413
21
+ uint64_t setListener(std::function<void(int)> &&listenerFunction);
22
+ void clearListener(uint64_t token);
13
23
 
14
24
  bool shouldOverridePullTransaction() const override;
15
25
  std::optional<MountingTransaction> pullTransaction(
@@ -17,4 +27,9 @@ struct RNSScreenRemovalListener : public MountingOverrideDelegate {
17
27
  MountingTransaction::Number number,
18
28
  const TransactionTelemetry &telemetry,
19
29
  ShadowViewMutationList mutations) const override;
30
+
31
+ private:
32
+ mutable std::mutex listenerMutex_;
33
+ std::function<void(int)> listenerFunction_;
34
+ uint64_t currentToken_{0};
20
35
  };
package/ios/RNSEnums.h CHANGED
@@ -1,5 +1,7 @@
1
1
  #pragma once
2
2
 
3
+ #import <UIKit/UIKit.h>
4
+
3
5
  typedef NS_ENUM(NSInteger, RNSScrollEdgeEffect) {
4
6
  RNSScrollEdgeEffectAutomatic,
5
7
  RNSScrollEdgeEffectHard,