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,7 +1,5 @@
1
1
  package com.swmansion.rnscreens.stack.header.toolbar.model
2
2
 
3
- import android.graphics.drawable.Drawable
4
-
5
3
  internal data class StackHeaderToolbarMenuItemConfig(
6
4
  val id: String,
7
5
  val title: String?,
@@ -11,11 +9,11 @@ internal data class StackHeaderToolbarMenuItemConfig(
11
9
  val hidden: Boolean,
12
10
  val disabled: Boolean,
13
11
  val showAsAction: StackHeaderToolbarMenuItemShowAsAction,
14
- val icon: Drawable?,
15
12
  val iconTintColorNormal: Int?,
16
13
  val iconTintColorPressed: Int?,
17
14
  val iconTintColorFocused: Int?,
18
15
  val iconTintColorDisabled: Int?,
16
+ val iconSource: StackHeaderToolbarMenuItemIconSource,
19
17
  val groupId: String?,
20
18
  val itemType: StackHeaderToolbarMenuItemType,
21
19
  val initialToggleState: Boolean,
@@ -0,0 +1,180 @@
1
+ package com.swmansion.rnscreens.stack.header.toolbar.model
2
+
3
+ /**
4
+ * Immutable snapshot of a parsed toolbar menu: the config tree plus every
5
+ * derived index the runtime needs. Built and validated in one step by [from],
6
+ * so a successfully constructed model is always internally consistent.
7
+ */
8
+ internal class StackHeaderToolbarMenuModel private constructor(
9
+ val config: StackHeaderToolbarMenuConfig,
10
+ val elementById: Map<String, StackHeaderToolbarMenuElementConfig>,
11
+ // Element string id <-> int id expected by Android menus.
12
+ val forwardIdMap: Map<String, Int>,
13
+ val reverseIdMap: Map<Int, String>,
14
+ val forwardGroupIdMap: Map<String, Int>,
15
+ val groupMetadata: StackHeaderToolbarMenuGroupMetadata,
16
+ ) {
17
+ companion object {
18
+ val EMPTY = from(StackHeaderToolbarMenuConfig(emptyList(), emptyList()))
19
+
20
+ /** Derives all indices and validates [config]; throws on an invalid menu. */
21
+ fun from(config: StackHeaderToolbarMenuConfig): StackHeaderToolbarMenuModel {
22
+ val forwardIdMap = mutableMapOf<String, Int>()
23
+ val reverseIdMap = mutableMapOf<Int, String>()
24
+ var elementCounter = 1
25
+ assignElementIds(config.children, forwardIdMap, reverseIdMap) { elementCounter++ }
26
+
27
+ val forwardGroupIdMap = mutableMapOf<String, Int>()
28
+ var groupCounter = 1
29
+ assignGroupIds(config, forwardGroupIdMap) { groupCounter++ }
30
+
31
+ val itemGroupMap = mutableMapOf<String, String>()
32
+ val groupSingleSelection = mutableMapOf<String, Boolean>()
33
+ val groupMemberItems = mutableMapOf<String, MutableList<String>>()
34
+ collectGroupMetadata(config, itemGroupMap, groupSingleSelection, groupMemberItems)
35
+
36
+ validateRadioInitialSelection(config)
37
+ validateItemTypes(config)
38
+
39
+ val elementById = mutableMapOf<String, StackHeaderToolbarMenuElementConfig>()
40
+ collectElements(config, elementById)
41
+
42
+ return StackHeaderToolbarMenuModel(
43
+ config = config,
44
+ elementById = elementById.toMap(),
45
+ forwardIdMap = forwardIdMap.toMap(),
46
+ reverseIdMap = reverseIdMap.toMap(),
47
+ forwardGroupIdMap = forwardGroupIdMap.toMap(),
48
+ groupMetadata =
49
+ StackHeaderToolbarMenuGroupMetadata(
50
+ itemGroupMap.toMap(),
51
+ groupSingleSelection.toMap(),
52
+ groupMemberItems.mapValues { it.value.toList() },
53
+ ),
54
+ )
55
+ }
56
+ }
57
+ }
58
+
59
+ private fun assignElementIds(
60
+ elements: List<StackHeaderToolbarMenuElementConfig>,
61
+ forwardIdMap: MutableMap<String, Int>,
62
+ reverseIdMap: MutableMap<Int, String>,
63
+ nextId: () -> Int,
64
+ ) {
65
+ for (element in elements) {
66
+ require(element.item.id !in forwardIdMap) {
67
+ "[RNScreens] Duplicate toolbar menu item id: '${element.item.id}'. Item IDs must be unique across the entire menu."
68
+ }
69
+ val nativeId = nextId()
70
+ forwardIdMap[element.item.id] = nativeId
71
+ reverseIdMap[nativeId] = element.item.id
72
+ if (element is StackHeaderToolbarMenuElementConfig.Submenu) {
73
+ assignElementIds(element.menu.children, forwardIdMap, reverseIdMap, nextId)
74
+ }
75
+ }
76
+ }
77
+
78
+ private fun assignGroupIds(
79
+ menuConfig: StackHeaderToolbarMenuConfig,
80
+ forwardMap: MutableMap<String, Int>,
81
+ nextId: () -> Int,
82
+ ) {
83
+ for (group in menuConfig.groups) {
84
+ require(group.groupId !in forwardMap) {
85
+ "[RNScreens] Duplicate toolbar menu group id: '${group.groupId}'. Group IDs must be unique across the entire menu."
86
+ }
87
+ forwardMap[group.groupId] = nextId()
88
+ }
89
+ for (element in menuConfig.children) {
90
+ if (element is StackHeaderToolbarMenuElementConfig.Submenu) {
91
+ assignGroupIds(element.menu, forwardMap, nextId)
92
+ }
93
+ }
94
+ }
95
+
96
+ private fun collectGroupMetadata(
97
+ config: StackHeaderToolbarMenuConfig,
98
+ itemGroupMap: MutableMap<String, String>,
99
+ groupSingleSelection: MutableMap<String, Boolean>,
100
+ groupMemberItems: MutableMap<String, MutableList<String>>,
101
+ ) {
102
+ val localGroupIds = config.groups.map { it.groupId }.toSet()
103
+ for (group in config.groups) {
104
+ groupSingleSelection[group.groupId] = group.singleSelection
105
+ groupMemberItems.getOrPut(group.groupId) { mutableListOf() }
106
+ }
107
+ for (element in config.children) {
108
+ element.item.groupId?.let { gid ->
109
+ require(element is StackHeaderToolbarMenuElementConfig.MenuItem) {
110
+ "[RNScreens] Submenu '${element.item.id}' cannot belong to a group. " +
111
+ "Only elements of type 'menuItem' can have a groupId."
112
+ }
113
+ require(gid in localGroupIds) {
114
+ "[RNScreens] Menu item '${element.item.id}' references group '$gid' " +
115
+ "which is not defined at the same menu level. " +
116
+ "Groups cannot span submenus."
117
+ }
118
+ itemGroupMap[element.item.id] = gid
119
+ groupMemberItems[gid]!!.add(element.item.id)
120
+ }
121
+ if (element is StackHeaderToolbarMenuElementConfig.Submenu) {
122
+ collectGroupMetadata(element.menu, itemGroupMap, groupSingleSelection, groupMemberItems)
123
+ }
124
+ }
125
+ }
126
+
127
+ private fun validateRadioInitialSelection(menuConfig: StackHeaderToolbarMenuConfig) {
128
+ for (group in menuConfig.groups) {
129
+ if (!group.singleSelection) continue
130
+ var count = 0
131
+ for (element in menuConfig.children) {
132
+ if (element.item.groupId == group.groupId && element.item.initialToggleState) {
133
+ count++
134
+ }
135
+ }
136
+ require(count <= 1) {
137
+ "[RNScreens] Radio group '${group.groupId}' has $count items with " +
138
+ "initialToggleState=true. At most 1 is allowed for single-selection groups."
139
+ }
140
+ }
141
+ for (element in menuConfig.children) {
142
+ if (element is StackHeaderToolbarMenuElementConfig.Submenu) {
143
+ validateRadioInitialSelection(element.menu)
144
+ }
145
+ }
146
+ }
147
+
148
+ private fun validateItemTypes(menuConfig: StackHeaderToolbarMenuConfig) {
149
+ for (element in menuConfig.children) {
150
+ val item = element.item
151
+ when (item.itemType) {
152
+ StackHeaderToolbarMenuItemType.TOGGLE ->
153
+ require(item.groupId != null) {
154
+ "[RNScreens] Menu item '${item.id}' has itemType=TOGGLE but no groupId. " +
155
+ "Toggle items must belong to a group."
156
+ }
157
+ StackHeaderToolbarMenuItemType.ACTION ->
158
+ require(item.groupId == null) {
159
+ "[RNScreens] Menu item '${item.id}' has itemType=ACTION " +
160
+ "and belongs to a group. Action items cannot belong to groups."
161
+ }
162
+ StackHeaderToolbarMenuItemType.AUTOMATIC -> Unit
163
+ }
164
+ if (element is StackHeaderToolbarMenuElementConfig.Submenu) {
165
+ validateItemTypes(element.menu)
166
+ }
167
+ }
168
+ }
169
+
170
+ private fun collectElements(
171
+ config: StackHeaderToolbarMenuConfig,
172
+ elements: MutableMap<String, StackHeaderToolbarMenuElementConfig>,
173
+ ) {
174
+ for (element in config.children) {
175
+ elements[element.item.id] = element
176
+ if (element is StackHeaderToolbarMenuElementConfig.Submenu) {
177
+ collectElements(element.menu, elements)
178
+ }
179
+ }
180
+ }
@@ -1,6 +1,7 @@
1
1
  package com.swmansion.rnscreens.stack.header.toolbar.update
2
2
 
3
3
  import android.graphics.drawable.Drawable
4
+ import com.swmansion.rnscreens.stack.header.toolbar.model.StackHeaderToolbarMenuElementConfig
4
5
  import com.swmansion.rnscreens.stack.header.toolbar.model.StackHeaderToolbarMenuItemShowAsAction
5
6
 
6
7
  /**
@@ -16,12 +17,11 @@ internal data class StackHeaderToolbarMenuElementOptions(
16
17
  val hidden: Boolean? = null,
17
18
  val disabled: Boolean? = null,
18
19
  val showAsAction: StackHeaderToolbarMenuItemShowAsAction? = null,
19
- val icon: StackHeaderToolbarFieldUpdate<Drawable>?,
20
- val iconTintColorNormal: StackHeaderToolbarFieldUpdate<Int>?,
21
- val iconTintColorPressed: StackHeaderToolbarFieldUpdate<Int>?,
22
- val iconTintColorFocused: StackHeaderToolbarFieldUpdate<Int>?,
23
- val iconTintColorDisabled: StackHeaderToolbarFieldUpdate<Int>?,
24
- val checked: Boolean? = null,
20
+ val icon: StackHeaderToolbarFieldUpdate<Drawable>? = null,
21
+ val iconTintColorNormal: StackHeaderToolbarFieldUpdate<Int>? = null,
22
+ val iconTintColorPressed: StackHeaderToolbarFieldUpdate<Int>? = null,
23
+ val iconTintColorFocused: StackHeaderToolbarFieldUpdate<Int>? = null,
24
+ val iconTintColorDisabled: StackHeaderToolbarFieldUpdate<Int>? = null,
25
25
  val menuTitle: StackHeaderToolbarFieldUpdate<String>? = null,
26
26
  ) {
27
27
  val requiresIconTintColorUpdate: Boolean
@@ -30,4 +30,55 @@ internal data class StackHeaderToolbarMenuElementOptions(
30
30
  iconTintColorPressed != null ||
31
31
  iconTintColorFocused != null ||
32
32
  iconTintColorDisabled != null
33
+
34
+ val isEmpty: Boolean
35
+ get() = this == DEFAULT
36
+
37
+ /** Field-wise merge where [newer]'s non-null fields win. */
38
+ internal fun mergedWith(newer: StackHeaderToolbarMenuElementOptions) =
39
+ StackHeaderToolbarMenuElementOptions(
40
+ title = newer.title ?: title,
41
+ titleCondensed = newer.titleCondensed ?: titleCondensed,
42
+ tooltipText = newer.tooltipText ?: tooltipText,
43
+ accessibilityLabel = newer.accessibilityLabel ?: accessibilityLabel,
44
+ hidden = newer.hidden ?: hidden,
45
+ disabled = newer.disabled ?: disabled,
46
+ showAsAction = newer.showAsAction ?: showAsAction,
47
+ icon = newer.icon ?: icon,
48
+ iconTintColorNormal = newer.iconTintColorNormal ?: iconTintColorNormal,
49
+ iconTintColorPressed = newer.iconTintColorPressed ?: iconTintColorPressed,
50
+ iconTintColorFocused = newer.iconTintColorFocused ?: iconTintColorFocused,
51
+ iconTintColorDisabled = newer.iconTintColorDisabled ?: iconTintColorDisabled,
52
+ menuTitle = newer.menuTitle ?: menuTitle,
53
+ )
54
+
55
+ private companion object {
56
+ val DEFAULT = StackHeaderToolbarMenuElementOptions()
57
+ }
33
58
  }
59
+
60
+ /**
61
+ * The element's declared configuration as absolute options — every field is
62
+ * Set/Reset, never "leave unchanged". The icon slot is always Reset: parsed
63
+ * configs carry no Drawables; resolved icons overlay this at application time.
64
+ * [StackHeaderToolbarMenuElementOptions.menuTitle] stays "unchanged" for a
65
+ * plain item, which has no submenu header to write.
66
+ */
67
+ internal fun StackHeaderToolbarMenuElementConfig.toOptions() =
68
+ StackHeaderToolbarMenuElementOptions(
69
+ title = StackHeaderToolbarFieldUpdate.from(item.title),
70
+ titleCondensed = StackHeaderToolbarFieldUpdate.from(item.titleCondensed),
71
+ tooltipText = StackHeaderToolbarFieldUpdate.from(item.tooltipText),
72
+ accessibilityLabel = StackHeaderToolbarFieldUpdate.from(item.accessibilityLabel),
73
+ hidden = item.hidden,
74
+ disabled = item.disabled,
75
+ showAsAction = item.showAsAction,
76
+ icon = StackHeaderToolbarFieldUpdate.Reset,
77
+ iconTintColorNormal = StackHeaderToolbarFieldUpdate.from(item.iconTintColorNormal),
78
+ iconTintColorPressed = StackHeaderToolbarFieldUpdate.from(item.iconTintColorPressed),
79
+ iconTintColorFocused = StackHeaderToolbarFieldUpdate.from(item.iconTintColorFocused),
80
+ iconTintColorDisabled = StackHeaderToolbarFieldUpdate.from(item.iconTintColorDisabled),
81
+ menuTitle =
82
+ (this as? StackHeaderToolbarMenuElementConfig.Submenu)
83
+ ?.let { StackHeaderToolbarFieldUpdate.from(it.menuTitle) },
84
+ )
@@ -4,10 +4,12 @@ import com.swmansion.rnscreens.stack.header.toolbar.model.StackHeaderToolbarMenu
4
4
 
5
5
  /**
6
6
  * A single toolbar menu element update carried by an `updateToolbarMenuElements`
7
- * view command, before its icon (if any) has been resolved.
7
+ * view command, before its icon (if any) has been resolved. `checked` travels
8
+ * outside [options]: it mutates group selections rather than the item itself.
8
9
  */
9
10
  internal data class StackHeaderToolbarMenuElementRawUpdate(
10
11
  val id: String,
11
12
  val options: StackHeaderToolbarMenuElementOptions,
12
13
  val iconSource: StackHeaderToolbarMenuItemIconSource?,
14
+ val checked: Boolean?,
13
15
  )
@@ -7,4 +7,5 @@ package com.swmansion.rnscreens.stack.header.toolbar.update
7
7
  internal data class StackHeaderToolbarMenuElementUpdate(
8
8
  val id: String,
9
9
  val options: StackHeaderToolbarMenuElementOptions,
10
+ val checked: Boolean?,
10
11
  )
@@ -4,14 +4,15 @@ import android.graphics.drawable.Drawable
4
4
  import com.swmansion.rnscreens.stack.header.toolbar.model.StackHeaderToolbarMenuItemIconSource
5
5
 
6
6
  /**
7
- * Resolves the icon for a single menu element update.
7
+ * Loads the icon a menu element's source points at.
8
8
  *
9
9
  * [onResolved] must be invoked exactly once — synchronously or asynchronously, always on the
10
- * main thread — with the resolved icon to merge into the element's options.
10
+ * main thread — with the loaded drawable, or `null` when the source resolves to no icon or
11
+ * fails to load.
11
12
  */
12
13
  internal fun interface StackHeaderToolbarMenuIconResolver {
13
14
  fun resolve(
14
15
  iconSource: StackHeaderToolbarMenuItemIconSource,
15
- onResolved: (icon: StackHeaderToolbarFieldUpdate<Drawable>) -> Unit,
16
+ onResolved: (icon: Drawable?) -> Unit,
16
17
  )
17
18
  }
@@ -23,6 +23,7 @@ internal class StackHeaderToolbarMenuUpdateQueue(
23
23
  private val pendingBatches = ArrayDeque<List<StackHeaderToolbarMenuElementRawUpdate>>()
24
24
  private var isProcessing = false
25
25
  private var isTornDown = false
26
+ private var generation = 0
26
27
 
27
28
  internal fun enqueue(batch: List<StackHeaderToolbarMenuElementRawUpdate>) {
28
29
  if (batch.isEmpty()) {
@@ -34,6 +35,18 @@ internal class StackHeaderToolbarMenuUpdateQueue(
34
35
  }
35
36
  }
36
37
 
38
+ /**
39
+ * Drops every batch that has not been applied yet, including one whose
40
+ * icons are still resolving — its eventual dispatch becomes a no-op. Used
41
+ * when the menu configuration is replaced: commands sent against the old
42
+ * menu must never touch the new one. The queue stays usable.
43
+ */
44
+ internal fun clearPending() {
45
+ pendingBatches.clear()
46
+ generation++
47
+ isProcessing = false
48
+ }
49
+
37
50
  internal fun tearDown() {
38
51
  pendingBatches.clear()
39
52
  isTornDown = true
@@ -48,11 +61,12 @@ internal class StackHeaderToolbarMenuUpdateQueue(
48
61
  return
49
62
  }
50
63
  isProcessing = true
64
+ val batchGeneration = generation
51
65
 
52
66
  val updates = arrayOfNulls<StackHeaderToolbarMenuElementUpdate>(batch.size)
53
67
  var outstanding = batch.size
54
68
 
55
- var isProcessingBatch = true
69
+ var inSyncDispatchLoop = true
56
70
 
57
71
  fun dispatchResolvedBatch() {
58
72
  pendingBatches.removeFirst()
@@ -63,20 +77,20 @@ internal class StackHeaderToolbarMenuUpdateQueue(
63
77
  index: Int,
64
78
  options: StackHeaderToolbarMenuElementOptions,
65
79
  ) {
66
- check(updates[index] == null) {
67
- "[RNScreens] Unexpected duplicated element resolution."
80
+ if (isTornDown || batchGeneration != generation) {
81
+ return
68
82
  }
69
83
 
70
- if (isTornDown) {
71
- return
84
+ check(updates[index] == null) {
85
+ "[RNScreens] Unexpected duplicated element resolution."
72
86
  }
73
87
 
74
- updates[index] = StackHeaderToolbarMenuElementUpdate(batch[index].id, options)
88
+ updates[index] = StackHeaderToolbarMenuElementUpdate(batch[index].id, options, batch[index].checked)
75
89
  outstanding -= 1
76
90
 
77
91
  // A synchronous completion is applied by the loop below; only an asynchronous one
78
92
  // advances the queue processing from here.
79
- if (outstanding == 0 && !isProcessingBatch) {
93
+ if (outstanding == 0 && !inSyncDispatchLoop) {
80
94
  dispatchResolvedBatch()
81
95
  processNext()
82
96
  }
@@ -88,11 +102,11 @@ internal class StackHeaderToolbarMenuUpdateQueue(
88
102
  onElementResolved(index, update.options)
89
103
  } else {
90
104
  iconResolver.resolve(iconSource) { icon ->
91
- onElementResolved(index, update.options.copy(icon = icon))
105
+ onElementResolved(index, update.options.copy(icon = StackHeaderToolbarFieldUpdate.from(icon)))
92
106
  }
93
107
  }
94
108
  }
95
- isProcessingBatch = false
109
+ inSyncDispatchLoop = false
96
110
 
97
111
  if (outstanding > 0) {
98
112
  // An icon is still loading asynchronously; its callback will apply the batch
@@ -2,18 +2,26 @@ package com.swmansion.rnscreens.stack.host
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
+ import android.content.res.Configuration
5
6
  import android.util.Log
6
7
  import android.view.ViewGroup
7
8
  import android.widget.FrameLayout
8
9
  import androidx.fragment.app.Fragment
9
10
  import androidx.fragment.app.FragmentManager
11
+ import com.swmansion.rnscreens.common.colorscheme.ColorScheme
12
+ import com.swmansion.rnscreens.common.colorscheme.ColorSchemeCoordinator
13
+ import com.swmansion.rnscreens.common.colorscheme.ColorSchemeListener
14
+ import com.swmansion.rnscreens.common.colorscheme.ColorSchemeProviding
10
15
  import com.swmansion.rnscreens.common.container.Container
16
+ import com.swmansion.rnscreens.common.container.ContainerItem
11
17
  import com.swmansion.rnscreens.common.container.ParentContainerItemRegistry
12
18
  import com.swmansion.rnscreens.ext.isMeasured
13
19
  import com.swmansion.rnscreens.helpers.FragmentManagerHelper
14
20
  import com.swmansion.rnscreens.helpers.ViewIdGenerator
21
+ import com.swmansion.rnscreens.stack.header.StackHeaderBackPressHandler
15
22
  import com.swmansion.rnscreens.stack.screen.StackScreen
16
23
  import com.swmansion.rnscreens.stack.screen.StackScreenFragment
24
+ import com.swmansion.rnscreens.stack.screen.StackScreenFragmentDelegate
17
25
  import com.swmansion.rnscreens.utils.RNSLog
18
26
  import java.lang.ref.WeakReference
19
27
 
@@ -23,7 +31,10 @@ internal class StackContainer(
23
31
  private val delegate: WeakReference<StackContainerDelegate>,
24
32
  ) : FrameLayout(context),
25
33
  Container,
26
- FragmentManager.OnBackStackChangedListener {
34
+ FragmentManager.OnBackStackChangedListener,
35
+ ColorSchemeProviding,
36
+ StackHeaderBackPressHandler,
37
+ StackScreenFragmentDelegate {
27
38
  private var fragmentManager: FragmentManager? = null
28
39
 
29
40
  private fun requireFragmentManager(): FragmentManager =
@@ -51,6 +62,20 @@ internal class StackContainer(
51
62
  private val fragmentOpExecutor: FragmentOperationExecutor = FragmentOperationExecutor()
52
63
  private val fragmentOps: MutableList<FragmentOperation> = arrayListOf()
53
64
 
65
+ // region Color Scheme
66
+
67
+ private val colorSchemeCoordinator = ColorSchemeCoordinator()
68
+
69
+ internal var colorScheme: ColorScheme by colorSchemeCoordinator::colorScheme
70
+
71
+ override fun getResolvedUiNightMode() = colorSchemeCoordinator.getResolvedUiNightMode()
72
+
73
+ override fun addColorSchemeListener(listener: ColorSchemeListener) = colorSchemeCoordinator.addColorSchemeListener(listener)
74
+
75
+ override fun removeColorSchemeListener(listener: ColorSchemeListener) = colorSchemeCoordinator.removeColorSchemeListener(listener)
76
+
77
+ // endregion
78
+
54
79
  init {
55
80
  id = ViewIdGenerator.generateViewId()
56
81
  }
@@ -62,6 +87,10 @@ internal class StackContainer(
62
87
  parentContainerRegistry.attach(this)
63
88
  setupFragmentManger()
64
89
 
90
+ // StackContainer only provides container-level color scheme configuration for its screens
91
+ // but doesn't use any color scheme-dependent views, so we don't need the callback.
92
+ colorSchemeCoordinator.setup(this, null)
93
+
65
94
  // Following line works with a couple of assumptions.
66
95
  // First, that this view is laid out by our parent view, which is a component view.
67
96
  // Component views on new architecture receive their first layout after the view hierarchy is
@@ -81,8 +110,16 @@ internal class StackContainer(
81
110
  requireFragmentManager().removeOnBackStackChangedListener(this)
82
111
  fragmentManager = null
83
112
  parentContainerRegistry.detach(this)
113
+ colorSchemeCoordinator.teardown()
114
+ }
115
+
116
+ override fun onConfigurationChanged(newConfig: Configuration?) {
117
+ super.onConfigurationChanged(newConfig)
118
+ colorSchemeCoordinator.onConfigurationChanged(newConfig)
84
119
  }
85
120
 
121
+ override fun onFragmentConfigurationChanged(config: Configuration) = onConfigurationChanged(config)
122
+
86
123
  internal fun setupFragmentManger() {
87
124
  fragmentManager =
88
125
  checkNotNull(FragmentManagerHelper.findFragmentManagerForView(this)) {
@@ -164,7 +201,8 @@ internal class StackContainer(
164
201
  }
165
202
 
166
203
  pendingPushOperations.forEach { operation ->
167
- val newFragment = createFragmentForScreen(operation.screen, canNavigateBack = stackModel.isNotEmpty())
204
+ val newFragment =
205
+ createFragmentForScreen(operation.screen, canNavigateBack = stackModel.isNotEmpty())
168
206
 
169
207
  fragmentOps.add(
170
208
  AddAndSetAsPrimaryOp(
@@ -205,7 +243,12 @@ internal class StackContainer(
205
243
  screen: StackScreen,
206
244
  canNavigateBack: Boolean,
207
245
  ): StackScreenFragment =
208
- StackScreenFragment(screen, canNavigateBack).also {
246
+ StackScreenFragment(
247
+ screen,
248
+ canNavigateBack,
249
+ WeakReference(this),
250
+ backPressHandler = WeakReference(this),
251
+ ).also {
209
252
  Log.d(TAG, "Created Fragment $it for screen ${screen.screenKey}")
210
253
  }
211
254
 
@@ -290,6 +333,63 @@ internal class StackContainer(
290
333
  ?.stackScreen
291
334
  ?.findContentScrollView()
292
335
 
336
+ // Asked when this container's whole subtree is about to be dismissed (the screen
337
+ // hosting this container is popped) - every item gets a vote, back-to-front, so
338
+ // the deepest preventing screen wins.
339
+ override fun wantsToPreventStackNativeDismiss(): ContainerItem? =
340
+ stackModel
341
+ .asReversed()
342
+ .firstNotNullOfOrNull { it.stackScreen.wantsToPreventStackNativeDismiss() }
343
+
344
+ // endregion
345
+
346
+ // region Header back button
347
+
348
+ override fun handleHeaderBackButtonPress(pressedScreen: StackScreen) {
349
+ val fragmentManager = fragmentManager
350
+ if (fragmentManager == null) {
351
+ Log.w(TAG, "[RNScreens] Ignoring header back button press - container is detached")
352
+ return
353
+ }
354
+
355
+ val topScreen = stackModel.lastOrNull()?.stackScreen
356
+ if (topScreen !== pressedScreen || stackModel.size <= 1) {
357
+ Log.w(
358
+ TAG,
359
+ "[RNScreens] Ignoring header back button press for non-top screen ${pressedScreen.screenKey}",
360
+ )
361
+ return
362
+ }
363
+
364
+ // This pop dismisses only the top screen (together with its subtree), therefore
365
+ // only the top item is asked - screens below the top get no vote at this level.
366
+ val vetoingItem = topScreen.wantsToPreventStackNativeDismiss()
367
+ if (vetoingItem != null) {
368
+ // Only a StackScreen can veto - TabsScreen has no own flag and only forwards.
369
+ val vetoingScreen = vetoingItem as? StackScreen
370
+ if (vetoingScreen != null) {
371
+ vetoingScreen.onNativeDismissPrevented()
372
+ } else {
373
+ Log.w(
374
+ TAG,
375
+ "[RNScreens] Unexpected vetoing item type: ${vetoingItem.javaClass.simpleName}",
376
+ )
377
+ }
378
+ return
379
+ }
380
+
381
+ // Mirrors FragmentManager's internal OnBackPressedCallback, which also runs
382
+ // popBackStackImmediate. The synchronous pop makes the top-screen guard above
383
+ // reliable against double taps: onBackStackChangeCommitted -> onNativeFragmentPop
384
+ // updates stackModel before this call returns.
385
+ fragmentManager.popBackStackImmediate(
386
+ // key MUST BE present, otherwise the navigation action will be delegated to child primary navigation
387
+ // fragment.
388
+ checkNotNull(pressedScreen.screenKey) { "[RNScreens] Screen key is required" },
389
+ FragmentManager.POP_BACK_STACK_INCLUSIVE,
390
+ )
391
+ }
392
+
293
393
  // endregion
294
394
 
295
395
  companion object {
@@ -8,6 +8,7 @@ import com.facebook.react.bridge.UIManagerListener
8
8
  import com.facebook.react.common.annotations.UnstableReactNativeAPI
9
9
  import com.facebook.react.uimanager.ThemedReactContext
10
10
  import com.facebook.react.uimanager.UIManagerHelper
11
+ import com.swmansion.rnscreens.common.colorscheme.ColorScheme
11
12
  import com.swmansion.rnscreens.helpers.getFabricUIManagerNotNull
12
13
  import com.swmansion.rnscreens.stack.screen.StackScreen
13
14
  import com.swmansion.rnscreens.utils.RNSLog
@@ -26,6 +27,8 @@ class StackHost(
26
27
  private val containerUpdateCoordinator = StackContainerUpdateCoordinator()
27
28
  private var isLayoutEnqueued = false
28
29
 
30
+ internal var colorScheme: ColorScheme by container::colorScheme
31
+
29
32
  init {
30
33
  addView(container)
31
34
 
@@ -119,6 +122,16 @@ class StackHost(
119
122
  refreshLayout()
120
123
  }
121
124
 
125
+ // On window configuration changes ViewRootImpl calls forceLayout() recursively on every view
126
+ // in the hierarchy, bypassing requestLayout(). React views never lay out their children during
127
+ // framework traversals, so nothing would clear the force-layout flags in our subtree — and once
128
+ // they are set, any subsequent requestLayout() from a descendant short-circuits before reaching
129
+ // the override above, permanently disabling the manual pass. Schedule it from here as well.
130
+ override fun forceLayout() {
131
+ super.forceLayout()
132
+ refreshLayout()
133
+ }
134
+
122
135
  private fun refreshLayout() {
123
136
  if (!isLayoutEnqueued) {
124
137
  isLayoutEnqueued = true
@@ -1,12 +1,14 @@
1
1
  package com.swmansion.rnscreens.stack.host
2
2
 
3
3
  import android.view.View
4
+ import com.facebook.react.bridge.JSApplicationIllegalArgumentException
4
5
  import com.facebook.react.module.annotations.ReactModule
5
6
  import com.facebook.react.uimanager.ThemedReactContext
6
7
  import com.facebook.react.uimanager.ViewGroupManager
7
8
  import com.facebook.react.uimanager.ViewManagerDelegate
8
9
  import com.facebook.react.viewmanagers.RNSStackHostManagerDelegate
9
10
  import com.facebook.react.viewmanagers.RNSStackHostManagerInterface
11
+ import com.swmansion.rnscreens.common.colorscheme.ColorScheme
10
12
  import com.swmansion.rnscreens.stack.screen.StackScreen
11
13
 
12
14
  @ReactModule(name = StackHostViewManager.REACT_CLASS)
@@ -70,6 +72,18 @@ class StackHostViewManager :
70
72
  */
71
73
  override fun needsCustomLayoutForChildren() = true
72
74
 
75
+ override fun setColorScheme(
76
+ view: StackHost,
77
+ value: String?,
78
+ ) {
79
+ when (value) {
80
+ null, "inherit" -> view.colorScheme = ColorScheme.INHERIT
81
+ "light" -> view.colorScheme = ColorScheme.LIGHT
82
+ "dark" -> view.colorScheme = ColorScheme.DARK
83
+ else -> throw JSApplicationIllegalArgumentException("[RNScreens] Invalid color scheme: $value.")
84
+ }
85
+ }
86
+
73
87
  companion object {
74
88
  const val REACT_CLASS = "RNSStackHost"
75
89
  }
@@ -175,4 +175,8 @@ class StackScreen(
175
175
  override fun resolveNestedContainer(): Container? = containerItemSupport.resolveNestedContainer()
176
176
 
177
177
  override fun findContentScrollView(): ViewGroup? = containerItemSupport.findContentScrollView(this)
178
+
179
+ override fun wantsToPreventStackNativeDismiss(): ContainerItem? =
180
+ resolveNestedContainer()?.wantsToPreventStackNativeDismiss()
181
+ ?: takeIf { isPreventNativeDismissEnabled }
178
182
  }