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
@@ -2,16 +2,18 @@ package com.swmansion.rnscreens.stack.header
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
- import android.util.Log
5
+ import android.content.res.Configuration
6
+ import android.os.Parcelable
7
+ import android.util.SparseArray
6
8
  import android.view.ViewGroup.LayoutParams.MATCH_PARENT
7
9
  import android.widget.FrameLayout
8
- import androidx.activity.OnBackPressedDispatcherOwner
9
10
  import androidx.appcompat.view.ContextThemeWrapper
10
11
  import androidx.coordinatorlayout.widget.CoordinatorLayout
11
- import com.facebook.react.bridge.ReactContext
12
12
  import com.google.android.material.R
13
13
  import com.google.android.material.appbar.AppBarLayout
14
- import com.google.android.material.appbar.MaterialToolbar
14
+ import com.swmansion.rnscreens.common.colorscheme.ColorSchemeCoordinator
15
+ import com.swmansion.rnscreens.common.colorscheme.ColorSchemeListener
16
+ import com.swmansion.rnscreens.common.colorscheme.ColorSchemeProviding
15
17
  import com.swmansion.rnscreens.stack.header.appbar.StackHeaderAppBarLayout
16
18
  import com.swmansion.rnscreens.stack.header.appbar.StackHeaderScrollingViewBehavior
17
19
  import com.swmansion.rnscreens.stack.header.config.OnHeaderConfigurationAttachListener
@@ -19,9 +21,6 @@ import com.swmansion.rnscreens.stack.header.config.StackHeaderConfigurationObser
19
21
  import com.swmansion.rnscreens.stack.header.config.StackHeaderConfigurationProviding
20
22
  import com.swmansion.rnscreens.stack.header.config.StackHeaderDelegate
21
23
  import com.swmansion.rnscreens.stack.header.config.StackHeaderInvalidationFlags
22
- import com.swmansion.rnscreens.stack.header.toolbar.StackHeaderToolbarMenuApplicator
23
- import com.swmansion.rnscreens.stack.header.toolbar.StackHeaderToolbarMenuSelectionController
24
- import com.swmansion.rnscreens.stack.header.toolbar.update.StackHeaderToolbarMenuElementUpdate
25
24
  import com.swmansion.rnscreens.stack.screen.StackScreen
26
25
 
27
26
  /**
@@ -34,7 +33,9 @@ internal class StackHeaderCoordinatorLayout(
34
33
  context: Context,
35
34
  internal val stackScreen: StackScreen,
36
35
  private val canNavigateBack: Boolean,
37
- ) : CoordinatorLayout(context) {
36
+ private val backPressHandler: StackHeaderBackPressHandler,
37
+ ) : CoordinatorLayout(context),
38
+ ColorSchemeProviding {
38
39
  // region Config attach / detach
39
40
 
40
41
  private var currentProvider: StackHeaderConfigurationProviding? = null
@@ -52,7 +53,10 @@ internal class StackHeaderCoordinatorLayout(
52
53
  delegate: StackHeaderDelegate?,
53
54
  ) {
54
55
  // Disconnect old config to prevent spurious updates from a detached config.
55
- currentProvider?.setConfigurationObserver(null)
56
+ currentProvider?.let {
57
+ it.setConfigurationObserver(null)
58
+ it.toolbarMenuController.detach()
59
+ }
56
60
 
57
61
  currentProvider = provider
58
62
  currentDelegate = delegate
@@ -72,34 +76,6 @@ internal class StackHeaderCoordinatorLayout(
72
76
  private val configObserver =
73
77
  object : StackHeaderConfigurationObserver {
74
78
  override fun onConfigChanged(config: StackHeaderConfigurationProviding) = processUpdate(config)
75
-
76
- override fun onMenuElementsUpdated(updates: List<StackHeaderToolbarMenuElementUpdate>) {
77
- val toolbar = appBarLayout?.toolbar
78
- if (toolbar == null) {
79
- Log.w(
80
- TAG,
81
- "[RNScreens] Dropping ${updates.size} resolved toolbar menu update(s): " +
82
- "the header toolbar is not currently attached (header hidden or detached).",
83
- )
84
- return
85
- }
86
- // Apply every element first, collecting the groups whose selection changed,
87
- // then emit a single coalesced event per affected group.
88
- val affectedGroups = LinkedHashSet<String>()
89
- for (update in updates) {
90
- StackHeaderToolbarMenuApplicator.updateToolbarMenuElement(
91
- toolbar,
92
- selectionController.forwardIdMap,
93
- update.id,
94
- update.options,
95
- )
96
- val checked = update.options.checked
97
- if (checked != null) {
98
- selectionController.applyGroupItemStateChange(toolbar, update.id, checked)?.let(affectedGroups::add)
99
- }
100
- }
101
- affectedGroups.forEach { groupId -> emitGroupSelection(toolbar, groupId) }
102
- }
103
79
  }
104
80
 
105
81
  // endregion
@@ -107,7 +83,8 @@ internal class StackHeaderCoordinatorLayout(
107
83
  // region Layout callbacks
108
84
 
109
85
  private val appBarOffsetListener =
110
- AppBarLayout.OnOffsetChangedListener { _, _ ->
86
+ AppBarLayout.OnOffsetChangedListener { appBar, verticalOffset ->
87
+ evaluateCollapseState(appBar, verticalOffset)
111
88
  onMaybeHeaderLayoutChanged()
112
89
  }
113
90
 
@@ -133,6 +110,20 @@ internal class StackHeaderCoordinatorLayout(
133
110
  StackHeaderFrameSynchronizer.sync(appBar, provider, delegate)
134
111
  }
135
112
 
113
+ // Tracks whether the app bar is currently scrolled to its fully collapsed offset. Used to work
114
+ // around a Material offset bug when the title/subtitle changes at runtime — see
115
+ // StackHeaderApplicator.applyTitleAndSubtitle. This should be equivalent to Material's
116
+ // `collapsingTitleHelper.getExpansionFraction() == 1f` condition.
117
+ private var isAppBarFullyCollapsed = false
118
+
119
+ private fun evaluateCollapseState(
120
+ appBar: AppBarLayout,
121
+ verticalOffset: Int,
122
+ ) {
123
+ val totalScrollRange = appBar.totalScrollRange
124
+ isAppBarFullyCollapsed = totalScrollRange > 0 && -verticalOffset >= totalScrollRange
125
+ }
126
+
136
127
  // endregion
137
128
 
138
129
  // region Header updates
@@ -145,19 +136,18 @@ internal class StackHeaderCoordinatorLayout(
145
136
 
146
137
  private val applicator = StackHeaderApplicator(wrappedContext)
147
138
 
148
- private val selectionController = StackHeaderToolbarMenuSelectionController()
149
-
150
139
  private var appBarLayout: StackHeaderAppBarLayout? = null
151
140
 
152
141
  private val onNavigationIconClick: () -> Unit = {
153
- val activity =
154
- (stackScreen.context as? ReactContext)?.currentActivity
155
- as? OnBackPressedDispatcherOwner
156
- activity?.onBackPressedDispatcher?.onBackPressed()
142
+ backPressHandler.handleHeaderBackButtonPress(stackScreen)
157
143
  }
158
144
 
159
- private fun processUpdate(provider: StackHeaderConfigurationProviding) {
160
- val needsRebuild = provider.invalidationFlags.needsRebuild
145
+ private fun processUpdate(
146
+ provider: StackHeaderConfigurationProviding,
147
+ forcedFlags: StackHeaderInvalidationFlags = StackHeaderInvalidationFlags.NONE,
148
+ ) {
149
+ val effectiveFlags = provider.invalidationFlags or forcedFlags
150
+ val needsRebuild = effectiveFlags.needsRebuild
161
151
  if (needsRebuild) {
162
152
  resetHeader()
163
153
  if (provider.hidden) {
@@ -179,22 +169,42 @@ internal class StackHeaderCoordinatorLayout(
179
169
 
180
170
  val appBar = appBarLayout
181
171
  if (appBar != null) {
182
- if (needsRebuild || provider.invalidationFlags.containsAny(StackHeaderInvalidationFlags.TITLE)) {
183
- applicator.applyTitle(appBar, provider)
172
+ if (needsRebuild || effectiveFlags.containsAny(StackHeaderInvalidationFlags.TITLE)) {
173
+ applicator.applyTitleAndSubtitle(appBar, provider, isAppBarFullyCollapsed)
184
174
  provider.clearInvalidationFlags(StackHeaderInvalidationFlags.TITLE)
185
175
  }
186
176
 
187
- if (needsRebuild || provider.invalidationFlags.containsAny(StackHeaderInvalidationFlags.BACK_BUTTON)) {
177
+ if (needsRebuild || effectiveFlags.containsAny(StackHeaderInvalidationFlags.TITLE_APPEARANCE)) {
178
+ applicator.applyTitleAndSubtitleAppearance(appBar, provider)
179
+ provider.clearInvalidationFlags(StackHeaderInvalidationFlags.TITLE_APPEARANCE)
180
+ }
181
+
182
+ if (needsRebuild || effectiveFlags.containsAny(StackHeaderInvalidationFlags.TITLE_POSITIONING)) {
183
+ applicator.applyTitlePositioning(appBar, provider)
184
+ provider.clearInvalidationFlags(StackHeaderInvalidationFlags.TITLE_POSITIONING)
185
+ }
186
+
187
+ if (needsRebuild || effectiveFlags.containsAny(StackHeaderInvalidationFlags.CONTENT_INSETS)) {
188
+ applicator.applyContentInsets(appBar, provider)
189
+ provider.clearInvalidationFlags(StackHeaderInvalidationFlags.CONTENT_INSETS)
190
+ }
191
+
192
+ if (needsRebuild || effectiveFlags.containsAny(StackHeaderInvalidationFlags.BACK_BUTTON)) {
188
193
  applicator.applyBackButton(appBar.toolbar, provider, canNavigateBack, onNavigationIconClick)
189
194
  provider.clearInvalidationFlags(StackHeaderInvalidationFlags.BACK_BUTTON)
190
195
  }
191
196
 
192
- if (needsRebuild || provider.invalidationFlags.containsAny(StackHeaderInvalidationFlags.SCROLL_FLAGS)) {
197
+ if (needsRebuild || effectiveFlags.containsAny(StackHeaderInvalidationFlags.SCROLL_FLAGS)) {
193
198
  applicator.applyScrollFlags(appBar, provider)
194
199
  provider.clearInvalidationFlags(StackHeaderInvalidationFlags.SCROLL_FLAGS)
195
200
  }
196
201
 
197
- if (needsRebuild || provider.invalidationFlags.containsAny(StackHeaderInvalidationFlags.LIFT_ON_SCROLL)) {
202
+ if (needsRebuild || effectiveFlags.containsAny(StackHeaderInvalidationFlags.BACKGROUND_COLORS)) {
203
+ applicator.applyBackgroundColors(appBar, provider)
204
+ provider.clearInvalidationFlags(StackHeaderInvalidationFlags.BACKGROUND_COLORS)
205
+ }
206
+
207
+ if (needsRebuild || effectiveFlags.containsAny(StackHeaderInvalidationFlags.LIFT_ON_SCROLL)) {
198
208
  // Lift-on-scroll is disabled in transparent mode: there is no content
199
209
  // scrolling behavior installed and the app bar overlays the content.
200
210
  applicator.applyLiftOnScroll(
@@ -205,44 +215,15 @@ internal class StackHeaderCoordinatorLayout(
205
215
  provider.clearInvalidationFlags(StackHeaderInvalidationFlags.LIFT_ON_SCROLL)
206
216
  }
207
217
 
208
- if (provider.invalidationFlags.containsAny(StackHeaderInvalidationFlags.TOOLBAR_MENU)) {
209
- val (forwardIdMap, reverseIdMap) =
210
- StackHeaderToolbarMenuApplicator.generateToolbarMenuItemMappings(
211
- provider.toolbarMenu,
212
- )
213
- val forwardGroupIdMap =
214
- StackHeaderToolbarMenuApplicator.generateToolbarMenuGroupMappings(
215
- provider.toolbarMenu,
216
- )
217
- val groupMetadata =
218
- StackHeaderToolbarMenuApplicator.computeGroupMetadata(
219
- provider.toolbarMenu,
220
- )
221
-
222
- StackHeaderToolbarMenuApplicator.validateRadioInitialSelection(provider.toolbarMenu)
223
-
224
- selectionController.setMenuMaps(forwardIdMap, groupMetadata)
225
-
226
- StackHeaderToolbarMenuApplicator.rebuildToolbarMenu(
227
- appBar.toolbar,
228
- provider.toolbarMenu,
229
- forwardIdMap,
230
- reverseIdMap,
231
- forwardGroupIdMap,
232
- groupDividerEnabled = provider.toolbarMenuGroupDividerEnabled,
233
- onItemClicked = { id, menuItem ->
234
- if (menuItem.isCheckable) {
235
- selectionController.applyGroupItemStateChange(appBar.toolbar, id)?.let { groupId ->
236
- emitGroupSelection(appBar.toolbar, groupId)
237
- }
238
- } else {
239
- currentDelegate?.onMenuItemClicked(id)
240
- }
241
- },
242
- )
243
-
218
+ if (needsRebuild || effectiveFlags.containsAny(StackHeaderInvalidationFlags.TOOLBAR_MENU)) {
219
+ provider.toolbarMenuController.attach(appBar.toolbar)
244
220
  provider.clearInvalidationFlags(StackHeaderInvalidationFlags.TOOLBAR_MENU)
245
221
  }
222
+
223
+ if (needsRebuild || effectiveFlags.containsAny(StackHeaderInvalidationFlags.OVERFLOW_ICON)) {
224
+ applicator.applyOverflowIcon(appBar.toolbar, provider)
225
+ provider.clearInvalidationFlags(StackHeaderInvalidationFlags.OVERFLOW_ICON)
226
+ }
246
227
  }
247
228
 
248
229
  onMaybeHeaderLayoutChanged()
@@ -250,13 +231,61 @@ internal class StackHeaderCoordinatorLayout(
250
231
 
251
232
  // endregion
252
233
 
253
- // region Group selection
234
+ // region Color scheme
254
235
 
255
- private fun emitGroupSelection(
256
- toolbar: MaterialToolbar,
257
- groupId: String,
258
- ) {
259
- currentDelegate?.onGroupSelectionChanged(groupId, selectionController.collectSelectedIds(toolbar, groupId))
236
+ private val colorSchemeCoordinator = ColorSchemeCoordinator()
237
+
238
+ // Night mode the header visuals were last applied against. Unlike the coordinator's
239
+ // internal dedupe (reset on every setup()), this survives detach/reattach, skipping
240
+ // redundant full re-applies e.g. on tab switches.
241
+ private var appliedUiNightMode: Int =
242
+ context.resources.configuration.uiMode and Configuration.UI_MODE_NIGHT_MASK
243
+
244
+ override fun getResolvedUiNightMode() = colorSchemeCoordinator.getResolvedUiNightMode()
245
+
246
+ override fun addColorSchemeListener(listener: ColorSchemeListener) = colorSchemeCoordinator.addColorSchemeListener(listener)
247
+
248
+ override fun removeColorSchemeListener(listener: ColorSchemeListener) = colorSchemeCoordinator.removeColorSchemeListener(listener)
249
+
250
+ // No onConfigurationChanged override is needed: this view never sets its own colorScheme,
251
+ // so resolution always delegates to the parent provider, which does handle system changes.
252
+
253
+ override fun onAttachedToWindow() {
254
+ super.onAttachedToWindow()
255
+ colorSchemeCoordinator.setup(this) { applyUiNightMode(it) }
256
+ }
257
+
258
+ override fun onDetachedFromWindow() {
259
+ colorSchemeCoordinator.teardown()
260
+ super.onDetachedFromWindow()
261
+ }
262
+
263
+ private fun applyUiNightMode(uiNightMode: Int) {
264
+ wrappedContext.setTheme(
265
+ when (uiNightMode) {
266
+ Configuration.UI_MODE_NIGHT_YES -> R.style.Theme_Material3Expressive_Dark_NoActionBar
267
+ Configuration.UI_MODE_NIGHT_NO -> R.style.Theme_Material3Expressive_Light_NoActionBar
268
+ else -> R.style.Theme_Material3Expressive_DayNight_NoActionBar
269
+ },
270
+ )
271
+
272
+ if (uiNightMode == appliedUiNightMode) {
273
+ return
274
+ }
275
+
276
+ appliedUiNightMode = uiNightMode
277
+ currentProvider?.let {
278
+ // A rebuild is forced because MaterialToolbar snapshots its theme at construction:
279
+ // ripples, the overflow popup and menu item views resolve from that frozen copy, so
280
+ // only view recreation refreshes them.
281
+ val wasFullyCollapsed = isAppBarFullyCollapsed
282
+ processUpdate(it, forcedFlags = StackHeaderInvalidationFlags.STRUCTURE)
283
+ // The rebuilt app bar starts expanded; restore the fully-collapsed resting state
284
+ // so a scrolled-down screen doesn't jump. Fractional offsets reset.
285
+ if (wasFullyCollapsed) {
286
+ appBarLayout?.setExpanded(false, false)
287
+ }
288
+ }
260
289
  }
261
290
 
262
291
  // endregion
@@ -269,7 +298,9 @@ internal class StackHeaderCoordinatorLayout(
269
298
  removeView(it)
270
299
  }
271
300
  appBarLayout = null
272
- selectionController.clear()
301
+ // A rebuilt header starts fully expanded; drop any stale collapsed state from the old one.
302
+ isAppBarFullyCollapsed = false
303
+ currentProvider?.toolbarMenuController?.detach()
273
304
  }
274
305
 
275
306
  private fun removeHeader() {
@@ -306,6 +337,22 @@ internal class StackHeaderCoordinatorLayout(
306
337
 
307
338
  // endregion
308
339
 
340
+ // region Instance state
341
+
342
+ override fun dispatchSaveInstanceState(container: SparseArray<Parcelable>) {
343
+ // Do nothing. This view is the root of a fragment-managed, react-owned hierarchy that
344
+ // React Native keeps alive, so there is no need to serialize/deserialize native view
345
+ // state. View ids in this subtree are react tags, which are not stable identities -
346
+ // restoring state by id can apply state saved by one view type to a different one,
347
+ // crashing e.g. in CompoundButton (see #4523).
348
+ }
349
+
350
+ override fun dispatchRestoreInstanceState(container: SparseArray<Parcelable>) {
351
+ // Ignore restoring instance state too, as we are not saving anything anyways.
352
+ }
353
+
354
+ // endregion
355
+
309
356
  // region Init
310
357
 
311
358
  internal var stackScreenWrapper: FrameLayout
@@ -334,6 +381,8 @@ internal class StackHeaderCoordinatorLayout(
334
381
  // region Teardown
335
382
 
336
383
  internal fun tearDown() {
384
+ colorSchemeCoordinator.teardown()
385
+
337
386
  removeHeader()
338
387
 
339
388
  stackScreenWrapper.removeView(stackScreen)
@@ -346,8 +395,4 @@ internal class StackHeaderCoordinatorLayout(
346
395
  }
347
396
 
348
397
  // endregion
349
-
350
- companion object {
351
- private const val TAG = "StackHeaderCoordinatorLayout"
352
- }
353
398
  }
@@ -2,14 +2,20 @@ package com.swmansion.rnscreens.stack.header.appbar
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
+ import android.graphics.Color
6
+ import android.graphics.drawable.PaintDrawable
7
+ import android.view.LayoutInflater
5
8
  import android.view.ViewGroup.LayoutParams.MATCH_PARENT
6
9
  import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
7
- import androidx.appcompat.widget.AppCompatTextView
10
+ import android.view.WindowInsets
11
+ import android.widget.TextView
8
12
  import androidx.coordinatorlayout.widget.CoordinatorLayout
9
- import com.google.android.material.R
13
+ import androidx.core.view.WindowInsetsCompat
14
+ import androidx.core.view.children
10
15
  import com.google.android.material.appbar.AppBarLayout
11
16
  import com.google.android.material.appbar.CollapsingToolbarLayout
12
17
  import com.google.android.material.appbar.MaterialToolbar
18
+ import com.swmansion.rnscreens.stack.header.config.StackHeaderCollapsedTitleGravityMode
13
19
  import com.swmansion.rnscreens.stack.header.config.StackHeaderType
14
20
  import com.swmansion.rnscreens.utils.resolveDimensionAttr
15
21
 
@@ -18,6 +24,9 @@ internal sealed class StackHeaderAppBarLayout(
18
24
  ) : AppBarLayout(context) {
19
25
  abstract val toolbar: MaterialToolbar
20
26
 
27
+ internal abstract val defaultContentInsetStart: Int
28
+ internal abstract val defaultContentInsetEnd: Int
29
+
21
30
  init {
22
31
  layoutParams =
23
32
  CoordinatorLayout.LayoutParams(MATCH_PARENT, WRAP_CONTENT).apply {
@@ -38,11 +47,66 @@ internal sealed class StackHeaderAppBarLayout(
38
47
  layoutParams = LayoutParams(MATCH_PARENT, WRAP_CONTENT)
39
48
  }
40
49
 
41
- // We need to manually manage the title to handle leading subview's positioning.
42
- internal var managedTitleView: AppCompatTextView? = null
50
+ override val defaultContentInsetStart: Int = toolbar.contentInsetStart
51
+ override val defaultContentInsetEnd: Int = toolbar.contentInsetEnd
52
+
53
+ // Setting text size and typeface separately (Toolbar exposes only a whole text appearance)
54
+ // needs the title/subtitle TextViews, and Toolbar has no getter for them. Material locates
55
+ // them by matching the current text (internal ToolbarUtils.getTitleTextView); we attempt
56
+ // the same, but since we don't have current text, we use distinct placeholders - they force
57
+ // both views into existence and tell them apart — and clear the text only after capturing
58
+ // the references. Toolbar keeps both instances for its lifetime (empty text only detaches
59
+ // them), so the references stay valid.
60
+ internal val titleTextView: TextView
61
+ internal val subtitleTextView: TextView
62
+
63
+ // Keeps the status bar strip in sync with the bar's effective color through
64
+ // the lift animation (same per-frame mixed color Material applies to the
65
+ // background). Detached when an explicit statusBarScrimColor is set.
66
+ private var statusBarScrimSyncEnabled = false
67
+ private val statusBarScrimSync =
68
+ object : LiftOnScrollProgressListener() {
69
+ override fun onUpdate(
70
+ elevation: Float,
71
+ backgroundColor: Int,
72
+ progress: Float,
73
+ ) {
74
+ statusBarForeground?.setTint(backgroundColor)
75
+ }
76
+ }
43
77
 
44
78
  init {
45
79
  addView(toolbar)
80
+
81
+ // PaintDrawable is not color-extractable, so AppBarLayout's built-in
82
+ // colorSurface-keyed strip tint sync can never activate and fight the
83
+ // listener above (or an explicit scrim color) — regardless of theme.
84
+ statusBarForeground = PaintDrawable().apply { setTint(Color.TRANSPARENT) }
85
+
86
+ toolbar.title = TITLE_PLACEHOLDER
87
+ toolbar.subtitle = SUBTITLE_PLACEHOLDER
88
+ titleTextView = toolbar.findTextViewWithText(TITLE_PLACEHOLDER)
89
+ subtitleTextView = toolbar.findTextViewWithText(SUBTITLE_PLACEHOLDER)
90
+ toolbar.title = ""
91
+ toolbar.subtitle = ""
92
+ }
93
+
94
+ internal fun setStatusBarScrimSyncEnabled(enabled: Boolean) {
95
+ if (statusBarScrimSyncEnabled == enabled) return
96
+ statusBarScrimSyncEnabled = enabled
97
+ if (enabled) {
98
+ addLiftOnScrollProgressListener(statusBarScrimSync)
99
+ } else {
100
+ removeLiftOnScrollProgressListener(statusBarScrimSync)
101
+ }
102
+ }
103
+
104
+ private fun MaterialToolbar.findTextViewWithText(text: String): TextView =
105
+ children.filterIsInstance<TextView>().first { it.text?.toString() == text }
106
+
107
+ private companion object {
108
+ private const val TITLE_PLACEHOLDER = "rns_title"
109
+ private const val SUBTITLE_PLACEHOLDER = "rns_subtitle"
46
110
  }
47
111
  }
48
112
 
@@ -50,6 +114,7 @@ internal sealed class StackHeaderAppBarLayout(
50
114
  internal class Collapsing(
51
115
  context: Context,
52
116
  val type: StackHeaderType,
117
+ collapsedTitleGravityMode: StackHeaderCollapsedTitleGravityMode,
53
118
  ) : StackHeaderAppBarLayout(context) {
54
119
  override val toolbar =
55
120
  MaterialToolbar(context).apply {
@@ -64,25 +129,21 @@ internal sealed class StackHeaderAppBarLayout(
64
129
  }
65
130
  }
66
131
 
132
+ override val defaultContentInsetStart: Int = toolbar.contentInsetStart
133
+ override val defaultContentInsetEnd: Int = toolbar.contentInsetEnd
134
+
135
+ // collapsedTitleGravityMode is a construction-time-only attr (no public setter), so both
136
+ // gravity modes are inflated from XML — differing only in that attribute — to stay 1:1.
67
137
  internal val collapsingToolbarLayout: CollapsingToolbarLayout =
68
- run {
69
- val (styleAttr, sizeAttr) =
70
- when (type) {
71
- StackHeaderType.MEDIUM ->
72
- Pair(R.attr.collapsingToolbarLayoutMediumStyle, R.attr.collapsingToolbarLayoutMediumSize)
73
- StackHeaderType.LARGE ->
74
- Pair(R.attr.collapsingToolbarLayoutLargeStyle, R.attr.collapsingToolbarLayoutLargeSize)
75
- else -> error("[RNScreens] Invalid header mode.")
76
- }
77
- CollapsingToolbarLayout(context, null, styleAttr).apply {
78
- layoutParams =
79
- LayoutParams(
80
- MATCH_PARENT,
81
- resolveDimensionAttr(context, sizeAttr),
82
- )
83
- addView(toolbar)
84
- }
85
- }
138
+ (
139
+ LayoutInflater
140
+ .from(context)
141
+ .inflate(layoutResFor(type, collapsedTitleGravityMode), this, false)
142
+ as CollapsingToolbarLayout
143
+ ).apply { addView(toolbar) }
144
+
145
+ private var trackedTopInset = 0
146
+ private var currentVerticalOffset = 0
86
147
 
87
148
  init {
88
149
  require(
@@ -92,6 +153,75 @@ internal sealed class StackHeaderAppBarLayout(
92
153
  "[RNScreens] Collapsing StackHeaderAppBarLayout must be MEDIUM or LARGE type."
93
154
  }
94
155
  addView(collapsingToolbarLayout)
156
+
157
+ addOnOffsetChangedListener { _, verticalOffset ->
158
+ currentVerticalOffset = verticalOffset
159
+ updateContentScrimExclusion()
160
+ }
161
+ }
162
+
163
+ // Observed here because both AppBarLayout and CollapsingToolbarLayout
164
+ // install their own OnApplyWindowInsetsListener in their constructors —
165
+ // setting ours would silently replace Material's. Reads the same inset
166
+ // CTL uses for its status bar scrim bounds.
167
+ override fun dispatchApplyWindowInsets(insets: WindowInsets): WindowInsets {
168
+ @Suppress("DEPRECATION")
169
+ trackedTopInset =
170
+ if (fitsSystemWindows) {
171
+ WindowInsetsCompat.toWindowInsetsCompat(insets, this).systemWindowInsetTop
172
+ } else {
173
+ 0
174
+ }
175
+ updateContentScrimExclusion()
176
+ return super.dispatchApplyWindowInsets(insets)
177
+ }
178
+
179
+ /**
180
+ * While a status bar scrim is installed, the content scrim skips the
181
+ * status-bar strip that the status bar scrim paints alone. Otherwise,
182
+ * the two scrims stack there while fading (they share the same partial
183
+ * alpha), showing a darker band during the transition. The strip spans
184
+ * `[-verticalOffset, -verticalOffset + topInset]` in CTL coordinates,
185
+ * matching the status bar scrim bounds set in
186
+ * `CollapsingToolbarLayout.draw`.
187
+ */
188
+ internal fun updateContentScrimExclusion() {
189
+ val contentScrim =
190
+ collapsingToolbarLayout.contentScrim
191
+ check(contentScrim is StackHeaderContentScrimDrawable) {
192
+ "[RNScreens] Unexpected contentScrim class."
193
+ }
194
+
195
+ contentScrim.exclusionBottom =
196
+ if (collapsingToolbarLayout.statusBarScrim != null && trackedTopInset > 0) {
197
+ trackedTopInset - currentVerticalOffset
198
+ } else {
199
+ 0
200
+ }
201
+ }
202
+
203
+ private companion object {
204
+ fun layoutResFor(
205
+ type: StackHeaderType,
206
+ mode: StackHeaderCollapsedTitleGravityMode,
207
+ ): Int =
208
+ when (type) {
209
+ StackHeaderType.MEDIUM ->
210
+ when (mode) {
211
+ StackHeaderCollapsedTitleGravityMode.ENTIRE_SPACE ->
212
+ com.swmansion.rnscreens.R.layout.rns_collapsing_toolbar_medium_entire_space
213
+ StackHeaderCollapsedTitleGravityMode.AVAILABLE_SPACE ->
214
+ com.swmansion.rnscreens.R.layout.rns_collapsing_toolbar_medium_available_space
215
+ }
216
+ StackHeaderType.LARGE ->
217
+ when (mode) {
218
+ StackHeaderCollapsedTitleGravityMode.ENTIRE_SPACE ->
219
+ com.swmansion.rnscreens.R.layout.rns_collapsing_toolbar_large_entire_space
220
+ StackHeaderCollapsedTitleGravityMode.AVAILABLE_SPACE ->
221
+ com.swmansion.rnscreens.R.layout.rns_collapsing_toolbar_large_available_space
222
+ }
223
+ else -> error("[RNScreens] Invalid header mode.")
224
+ }
95
225
  }
96
226
  }
97
227
 
@@ -99,10 +229,12 @@ internal sealed class StackHeaderAppBarLayout(
99
229
  fun create(
100
230
  context: Context,
101
231
  type: StackHeaderType,
232
+ collapsedTitleGravityMode: StackHeaderCollapsedTitleGravityMode,
102
233
  ): StackHeaderAppBarLayout =
103
234
  when (type) {
104
235
  StackHeaderType.SMALL -> Small(context)
105
- StackHeaderType.MEDIUM, StackHeaderType.LARGE -> Collapsing(context, type)
236
+ StackHeaderType.MEDIUM, StackHeaderType.LARGE ->
237
+ Collapsing(context, type, collapsedTitleGravityMode)
106
238
  }
107
239
  }
108
240
  }
@@ -0,0 +1,36 @@
1
+ package com.swmansion.rnscreens.stack.header.appbar
2
+
3
+ import android.graphics.Canvas
4
+ import android.graphics.drawable.ColorDrawable
5
+ import androidx.core.graphics.withClip
6
+
7
+ /**
8
+ * Content scrim that can skip the status-bar strip painted by the
9
+ * CollapsingToolbarLayout status bar scrim. Both scrims share the same alpha
10
+ * while fading, so wherever they overlap the strip composites darker than the
11
+ * rest of the scrim during the transition.
12
+ */
13
+ internal class StackHeaderContentScrimDrawable(
14
+ color: Int,
15
+ ) : ColorDrawable(color) {
16
+ // Bottom of the skipped band in CollapsingToolbarLayout coordinates; the
17
+ // visible top edge of the scrim always aligns with the strip top. Values
18
+ // <= bounds.top disable the exclusion.
19
+ internal var exclusionBottom: Int = 0
20
+ set(value) {
21
+ if (field != value) {
22
+ field = value
23
+ invalidateSelf()
24
+ }
25
+ }
26
+
27
+ override fun draw(canvas: Canvas) {
28
+ if (exclusionBottom <= bounds.top) {
29
+ super.draw(canvas)
30
+ return
31
+ }
32
+ canvas.withClip(bounds.left, exclusionBottom, bounds.right, bounds.bottom) {
33
+ super.draw(canvas)
34
+ }
35
+ }
36
+ }
@@ -0,0 +1,6 @@
1
+ package com.swmansion.rnscreens.stack.header.config
2
+
3
+ internal enum class StackHeaderCollapsedTitleGravityMode {
4
+ ENTIRE_SPACE,
5
+ AVAILABLE_SPACE,
6
+ }