@apexnative/navigation 1.0.0-alpha.5 → 1.0.0-alpha.7

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 (358) hide show
  1. package/ApexNavigation.podspec +2 -1
  2. package/CHANGELOG.md +32 -0
  3. package/README.md +1 -1
  4. package/android/CLAUDE.md +31 -20
  5. package/android/CORE.md +12 -8
  6. package/android/consumer-rules.pro +6 -0
  7. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +36 -5
  8. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
  9. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
  10. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
  11. package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
  12. package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
  13. package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
  14. package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
  15. package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
  16. package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
  17. package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
  18. package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
  19. package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
  20. package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
  21. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +7 -3
  22. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
  23. package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
  24. package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +0 -2
  25. package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
  26. package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +12 -49
  27. package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
  28. package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -6
  29. package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
  30. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
  31. package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
  32. package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
  33. package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +9 -7
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +13 -10
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
  39. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +7 -8
  40. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
  41. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
  42. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
  43. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +443 -58
  44. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +33 -5
  45. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
  46. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
  47. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -0
  48. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
  49. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
  50. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
  51. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
  52. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
  53. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +50 -7
  54. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
  55. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +10 -4
  56. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
  57. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
  58. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
  59. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
  60. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
  61. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +36 -38
  62. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
  63. package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
  64. package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
  65. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
  66. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
  67. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +129 -179
  68. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
  69. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
  70. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +67 -3
  71. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +346 -36
  72. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
  73. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -42
  74. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextMorph.kt +176 -0
  75. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
  76. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
  77. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
  78. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
  79. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
  80. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
  81. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
  82. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
  83. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
  84. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
  85. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
  86. package/android/src/main/jni/ANNContentHeights.cpp +82 -0
  87. package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
  88. package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
  89. package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
  90. package/android/src/main/res/values/styles.xml +1 -1
  91. package/android/src/main/res/values-night/styles.xml +1 -1
  92. package/cli/doctor/checks/app.js +16 -1
  93. package/cli/project.js +4 -0
  94. package/cli/setup/index.js +5 -0
  95. package/cli/setup/steps/metro.js +46 -0
  96. package/docs-md/architecture/native-screens.md +4 -0
  97. package/docs-md/architecture/theme-and-context.md +131 -12
  98. package/docs-md/architecture/transitions.md +46 -5
  99. package/docs-md/containers/modals.md +2 -0
  100. package/docs-md/containers/overlays.md +1 -1
  101. package/docs-md/containers/side-menu.md +1 -1
  102. package/docs-md/containers/tabs.md +2 -0
  103. package/docs-md/flows/bottom-sheets.md +4 -2
  104. package/docs-md/fundamentals/screens-and-props.md +9 -1
  105. package/docs-md/fundamentals/starting-the-app.md +1 -1
  106. package/docs-md/guides/cli.md +3 -1
  107. package/docs-md/help/glossary.md +2 -2
  108. package/docs-md/help/troubleshooting.md +22 -1
  109. package/docs-md/index.json +41 -1
  110. package/docs-md/migration/from-expo-router.md +1 -1
  111. package/docs-md/migration/from-react-native-navigation.md +1 -1
  112. package/docs-md/migration/from-react-navigation.md +2 -2
  113. package/docs-md/screen-ui/back-and-gestures.md +17 -1
  114. package/docs-md/screen-ui/bar-buttons.md +5 -5
  115. package/docs-md/screen-ui/search.md +1 -1
  116. package/docs-md/screen-ui/system-ui.md +16 -11
  117. package/docs-md/screen-ui/toolbar-and-fab.md +4 -2
  118. package/docs-md/screen-ui/top-bar.md +8 -4
  119. package/docs-md/start/installation.md +16 -1
  120. package/ios/Animations/ANNFrameDriver.h +55 -15
  121. package/ios/Animations/ANNFrameDriver.mm +216 -60
  122. package/ios/Animations/ANNTransitionAnimator.h +26 -5
  123. package/ios/Animations/ANNTransitionAnimator.mm +49 -3
  124. package/ios/Animations/ANNTransitionPlan.h +2 -2
  125. package/ios/Animations/ANNTransitionPlan.mm +10 -0
  126. package/ios/Animations/Elements/ANNElementFinder.h +2 -2
  127. package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
  128. package/ios/Animations/Elements/ANNElementTransition.h +3 -3
  129. package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
  130. package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
  131. package/ios/Animations/Elements/ANNSharedElementFlight.mm +249 -45
  132. package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
  133. package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
  134. package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
  135. package/ios/Animations/Interpolators/ANNViewTracks.h +9 -6
  136. package/ios/Animations/Interpolators/ANNViewTracks.mm +100 -30
  137. package/ios/CLAUDE.md +32 -22
  138. package/ios/CORE.md +25 -21
  139. package/ios/Contract/ANNCommandRunner.h +8 -7
  140. package/ios/Contract/ANNCommandRunner.mm +21 -27
  141. package/ios/Contract/ANNLayoutNode.h +3 -3
  142. package/ios/Contract/ANNLayoutReader.h +3 -3
  143. package/ios/Core/ANNAppDelegate.mm +0 -2
  144. package/ios/Core/ANNDeepLinkQueue.h +6 -5
  145. package/ios/Core/ANNDeepLinkQueue.mm +22 -10
  146. package/ios/Core/ANNJavaScriptCalls.h +11 -12
  147. package/ios/Core/ANNJavaScriptCalls.mm +7 -16
  148. package/ios/Core/ANNReloadCoordinator.mm +4 -8
  149. package/ios/Core/ANNSceneDelegate.mm +2 -2
  150. package/ios/Core/ApexNavigation+Internal.h +1 -1
  151. package/ios/Core/ApexNavigation.mm +6 -2
  152. package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
  153. package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
  154. package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
  155. package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
  156. package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
  157. package/ios/Layouts/Screen/ANNComponentViewController.mm +13 -23
  158. package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
  159. package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
  160. package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
  161. package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
  162. package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
  163. package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
  164. package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
  165. package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
  166. package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
  167. package/ios/Layouts/Stack/ANNPreviewPush.mm +57 -5
  168. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
  169. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
  170. package/ios/Layouts/Stack/ANNStackController.h +8 -0
  171. package/ios/Layouts/Stack/ANNStackController.mm +153 -11
  172. package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
  173. package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
  174. package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
  175. package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
  176. package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
  177. package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
  178. package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
  179. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
  180. package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
  181. package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
  182. package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
  183. package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
  184. package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
  185. package/ios/Options/ANNAnimationOptions.h +2 -1
  186. package/ios/Options/ANNAnimationOptions.mm +1 -1
  187. package/ios/Options/ANNBottomTabsOptions.h +2 -0
  188. package/ios/Options/ANNBottomTabsOptions.mm +1 -0
  189. package/ios/Options/ANNOptions.h +25 -0
  190. package/ios/Options/ANNOptions.mm +157 -1
  191. package/ios/Options/ANNOptionsReader.mm +108 -0
  192. package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
  193. package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
  194. package/ios/Options/Params/ANNParam.h +15 -0
  195. package/ios/Options/Params/ANNParam.mm +61 -0
  196. package/ios/Options/Params/Color.h +3 -0
  197. package/ios/Options/Params/Color.mm +21 -0
  198. package/ios/Options/Params/ColorParser.h +4 -3
  199. package/ios/Options/Params/ColorParser.mm +22 -4
  200. package/ios/Options/Params/Image.h +4 -0
  201. package/ios/Options/Params/Image.mm +20 -0
  202. package/ios/React/ANNReactView.mm +6 -25
  203. package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
  204. package/ios/React/ANNSurfaceHostingView.h +14 -0
  205. package/ios/React/ANNSurfaceHostingView.mm +144 -1
  206. package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
  207. package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
  208. package/ios/TopBar/ANNBarAppearance.h +2 -1
  209. package/ios/TopBar/ANNBarAppearance.mm +111 -30
  210. package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
  211. package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
  212. package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
  213. package/ios/TopBar/ANNBarButtons.h +5 -3
  214. package/ios/TopBar/ANNBarButtons.mm +186 -17
  215. package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
  216. package/ios/TopBar/ANNMainList.h +3 -0
  217. package/ios/TopBar/ANNMainList.mm +4 -0
  218. package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
  219. package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
  220. package/ios/TopBar/ANNSearchPresenter.mm +2 -2
  221. package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
  222. package/ios/TopBar/ANNTopBarPresenter.h +3 -2
  223. package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
  224. package/ios/TurboModules/ANNTurboModule.mm +66 -16
  225. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
  226. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
  227. package/lib/module/ARCHITECTURE.md +17 -7
  228. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  229. package/lib/module/api/app/AppRegistry.js +14 -2
  230. package/lib/module/api/app/AppRegistry.js.map +1 -1
  231. package/lib/module/api/app/createNavigation.js +188 -66
  232. package/lib/module/api/app/createNavigation.js.map +1 -1
  233. package/lib/module/api/events/navigationEvents.js +6 -2
  234. package/lib/module/api/events/navigationEvents.js.map +1 -1
  235. package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
  236. package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
  237. package/lib/module/api/linking/linking.js +11 -0
  238. package/lib/module/api/linking/linking.js.map +1 -1
  239. package/lib/module/api/options/OptionsChain.js +2 -2
  240. package/lib/module/api/screens/defineScreen.js +45 -3
  241. package/lib/module/api/screens/defineScreen.js.map +1 -1
  242. package/lib/module/api/screens/nativeScreens.js +31 -6
  243. package/lib/module/api/screens/nativeScreens.js.map +1 -1
  244. package/lib/module/api/screens/registerScreens.js +172 -35
  245. package/lib/module/api/screens/registerScreens.js.map +1 -1
  246. package/lib/module/api/theme/theme.js +5 -0
  247. package/lib/module/api/theme/theme.js.map +1 -1
  248. package/lib/module/engine/commands.js +42 -2
  249. package/lib/module/engine/commands.js.map +1 -1
  250. package/lib/module/engine/layout.js +7 -2
  251. package/lib/module/engine/layout.js.map +1 -1
  252. package/lib/module/engine/nativeScreens.js +9 -0
  253. package/lib/module/engine/nativeScreens.js.map +1 -1
  254. package/lib/module/engine/options.js +5 -4
  255. package/lib/module/engine/options.js.map +1 -1
  256. package/lib/module/engine/optionsChange.js +102 -0
  257. package/lib/module/engine/optionsChange.js.map +1 -0
  258. package/lib/module/engine/refresh.js +85 -0
  259. package/lib/module/engine/refresh.js.map +1 -0
  260. package/lib/module/engine/rememberedOptions.js +60 -0
  261. package/lib/module/engine/rememberedOptions.js.map +1 -1
  262. package/lib/module/engine/screens.js +55 -4
  263. package/lib/module/engine/screens.js.map +1 -1
  264. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
  265. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  266. package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
  267. package/lib/typescript/api/app/createNavigation.d.ts +8 -3
  268. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  269. package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
  270. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  271. package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
  272. package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
  273. package/lib/typescript/api/index.d.ts +1 -1
  274. package/lib/typescript/api/index.d.ts.map +1 -1
  275. package/lib/typescript/api/linking/linking.d.ts +8 -0
  276. package/lib/typescript/api/linking/linking.d.ts.map +1 -1
  277. package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
  278. package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
  279. package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
  280. package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
  281. package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
  282. package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
  283. package/lib/typescript/api/theme/theme.d.ts +2 -0
  284. package/lib/typescript/api/theme/theme.d.ts.map +1 -1
  285. package/lib/typescript/engine/commands.d.ts +5 -0
  286. package/lib/typescript/engine/commands.d.ts.map +1 -1
  287. package/lib/typescript/engine/layout.d.ts.map +1 -1
  288. package/lib/typescript/engine/nativeScreens.d.ts +5 -0
  289. package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
  290. package/lib/typescript/engine/optionsChange.d.ts +28 -0
  291. package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
  292. package/lib/typescript/engine/refresh.d.ts +35 -0
  293. package/lib/typescript/engine/refresh.d.ts.map +1 -0
  294. package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
  295. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  296. package/lib/typescript/engine/screens.d.ts +27 -6
  297. package/lib/typescript/engine/screens.d.ts.map +1 -1
  298. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  299. package/lib/typescript/interfaces/options/animations.d.ts +44 -14
  300. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  301. package/lib/typescript/interfaces/options/bottomTabs.d.ts +19 -6
  302. package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
  303. package/lib/typescript/interfaces/options/buttons.d.ts +10 -10
  304. package/lib/typescript/interfaces/options/modals.d.ts +6 -7
  305. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  306. package/lib/typescript/interfaces/options/stack.d.ts +8 -3
  307. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  308. package/lib/typescript/interfaces/options/system.d.ts +8 -5
  309. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  310. package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
  311. package/lib/typescript/interfaces/options/topBar.d.ts +46 -29
  312. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  313. package/metro/acceptPlugin.js +57 -0
  314. package/metro/babelTransformer.js +45 -0
  315. package/metro/index.js +36 -0
  316. package/package.json +3 -1
  317. package/src/ARCHITECTURE.md +17 -7
  318. package/src/adapters/NativeANNTurboModule.ts +7 -0
  319. package/src/api/app/AppRegistry.ts +16 -2
  320. package/src/api/app/createNavigation.ts +201 -69
  321. package/src/api/events/navigationEvents.ts +9 -3
  322. package/src/api/hooks/useSafeAreaInsets.ts +4 -3
  323. package/src/api/index.ts +1 -0
  324. package/src/api/linking/linking.ts +22 -1
  325. package/src/api/options/OptionsChain.ts +2 -2
  326. package/src/api/screens/defineScreen.ts +49 -5
  327. package/src/api/screens/nativeScreens.ts +40 -4
  328. package/src/api/screens/registerScreens.tsx +170 -42
  329. package/src/api/theme/theme.ts +5 -0
  330. package/src/engine/commands.ts +51 -6
  331. package/src/engine/layout.ts +11 -2
  332. package/src/engine/nativeScreens.ts +10 -1
  333. package/src/engine/options.ts +6 -8
  334. package/src/engine/optionsChange.ts +107 -0
  335. package/src/engine/refresh.ts +97 -0
  336. package/src/engine/rememberedOptions.ts +70 -0
  337. package/src/engine/screens.tsx +53 -8
  338. package/src/interfaces/ComponentEvents.ts +2 -2
  339. package/src/interfaces/options/animations.ts +44 -14
  340. package/src/interfaces/options/bottomTabs.ts +19 -6
  341. package/src/interfaces/options/buttons.ts +10 -10
  342. package/src/interfaces/options/modals.ts +6 -7
  343. package/src/interfaces/options/stack.ts +8 -3
  344. package/src/interfaces/options/system.ts +8 -5
  345. package/src/interfaces/options/toolbar.ts +2 -2
  346. package/src/interfaces/options/topBar.ts +46 -29
  347. package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
  348. package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
  349. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
  350. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
  351. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -136
  352. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -86
  353. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
  354. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -64
  355. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +0 -177
  356. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -137
  357. package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
  358. package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
@@ -3,36 +3,25 @@ package dev.apexnative.navigation.react
3
3
  import android.app.Activity
4
4
  import android.graphics.Rect
5
5
  import androidx.core.view.WindowInsetsCompat
6
- import com.facebook.react.bridge.Arguments
7
- import com.facebook.react.bridge.WritableArray
8
- import com.facebook.react.bridge.WritableMap
9
- import dev.apexnative.navigation.react.events.EventContract
6
+ import dev.apexnative.navigation.react.ScreenEnvironment.Region
7
+ import dev.apexnative.navigation.react.ScreenEnvironment.Regions
10
8
  import dev.apexnative.navigation.react.events.EventEmitter
11
9
  import dev.apexnative.navigation.utils.WindowFolds
12
- import java.util.concurrent.ConcurrentHashMap
13
10
 
14
11
  /**
15
- * The reserved regions of each screen (`useReservedRegions`), in dp from the top left of its content: where it divides
16
- * (a fold the window is divided at, `division`) and where hardware covers it (a fold that hides what is under it, the
17
- * camera cutout: `occlusion`). Only those on the screen, cut to it: a fold at the edge of a screen of a pair is not on
18
- * it. Measured on the main thread as each screen lays out, and again after a change of the window's folds (which lays
19
- * the window out); JavaScript reads them from its own thread for a screen's first render, and gets every change as
20
- * `ANN.ScreenRegionsChanged`.
12
+ * Measures the reserved regions of each screen (`useReservedRegions`), in dp from the top left of its content: where
13
+ * it divides (a fold the window is divided at, `division`) and where hardware covers it (a fold that hides what is
14
+ * under it, the camera cutout: `occlusion`). Only those on the screen, cut to it: a fold at the edge of a screen of a
15
+ * pair is not on it. Measured on the main thread as each screen lays out, and again after a change of the window's
16
+ * folds (which lays the window out); [ScreenEnvironment] keeps them, answers JavaScript's reads and tells it of every
17
+ * change as `ANN.ScreenRegionsChanged`.
21
18
  */
22
19
  internal object ScreenRegions {
23
- data class Region(val x: Double, val y: Double, val width: Double, val height: Double)
24
-
25
- data class Regions(val division: List<Region>, val occlusion: List<Region>) {
26
- val isEmpty: Boolean get() = division.isEmpty() && occlusion.isEmpty()
27
- }
28
-
29
- private val NONE = Regions(emptyList(), emptyList())
30
- private val regions = ConcurrentHashMap<String, Regions>()
31
-
32
20
  /**
33
21
  * Measures the regions of the screen whose content the layout puts at [content] in the window (pixels), with the
34
- * window's [insets] (the camera cutout), and tells JavaScript when they changed, through [emitter], the one of the
35
- * JavaScript the screen was made for: silent once it went. Main thread only.
22
+ * window's [insets] (the camera cutout), and keeps them in [ScreenEnvironment], which tells JavaScript when they
23
+ * changed through [emitter], the one of the JavaScript the screen was made for: silent once it went. Main thread
24
+ * only.
36
25
  */
37
26
  fun update(
38
27
  activity: Activity,
@@ -49,17 +38,7 @@ internal object ScreenRegions {
49
38
  occlusion = (folds.filter { it.occludes }.map { it.bounds } + cutouts)
50
39
  .mapNotNull { onScreen(it, content, density) },
51
40
  )
52
- val previous = regions.put(screenId, value)
53
- // None at first is what JavaScript reads before the first layout.
54
- if (previous == value || (previous == null && value.isEmpty)) return
55
- emitter.emitScreenRegionsChanged(screenId, toMap(value))
56
- }
57
-
58
- /** `{ division, occlusion }`, empty lists for an unknown screen and before its first layout. Any thread. */
59
- fun get(screenId: String): WritableMap = toMap(regions[screenId] ?: NONE)
60
-
61
- fun remove(screenId: String) {
62
- regions.remove(screenId)
41
+ ScreenEnvironment.setRegions(screenId, value, ScreenEnvironment.changesOf(emitter))
63
42
  }
64
43
 
65
44
  // The part of [bounds] (window pixels) on the screen, in dp from its top left; null off it. A fold without width
@@ -74,20 +53,4 @@ internal object ScreenRegions {
74
53
  height = part.height() / density.toDouble(),
75
54
  )
76
55
  }
77
-
78
- private fun toMap(value: Regions): WritableMap = Arguments.createMap().apply {
79
- putArray(EventContract.ScreenRegionsChanged.DIVISION, toArray(value.division))
80
- putArray(EventContract.ScreenRegionsChanged.OCCLUSION, toArray(value.occlusion))
81
- }
82
-
83
- private fun toArray(regions: List<Region>): WritableArray = Arguments.createArray().apply {
84
- for (region in regions) {
85
- pushMap(Arguments.createMap().apply {
86
- putDouble(EventContract.ScreenRegionRect.X, region.x)
87
- putDouble(EventContract.ScreenRegionRect.Y, region.y)
88
- putDouble(EventContract.ScreenRegionRect.WIDTH, region.width)
89
- putDouble(EventContract.ScreenRegionRect.HEIGHT, region.height)
90
- })
91
- }
92
- }
93
56
  }
@@ -1,25 +1,17 @@
1
1
  package dev.apexnative.navigation.react
2
2
 
3
- import android.content.Context
4
- import android.util.Log
5
- import com.facebook.react.ReactApplication
6
- import com.facebook.react.uimanager.UIManagerHelper
7
- import com.facebook.react.uimanager.events.Event
8
- import com.facebook.react.uimanager.events.EventDispatcherListener
9
- import com.facebook.react.views.scroll.ScrollEvent
3
+ import android.view.ViewGroup
4
+ import android.widget.HorizontalScrollView
5
+ import com.facebook.react.views.scroll.ReactScrollViewHelper
10
6
  import com.facebook.react.views.scroll.ScrollEventType
11
- import dev.apexnative.navigation.utils.NavigationLog
12
- import dev.apexnative.navigation.utils.UiThread
13
- import dev.apexnative.navigation.viewcontrollers.viewcontroller.ScrollEventListener
14
- import java.lang.reflect.Field
15
- import java.util.Collections
16
- import java.util.WeakHashMap
17
7
 
18
8
  /**
19
- * The scroll events of React Native's lists, as the screens' scroll listeners hear them: from the event dispatcher of
20
- * the running React Native, every list of the app. React Native keeps the offset, the speed and the kind of a scroll
21
- * event in private fields: they are looked up once, with their types; if React Native renames them, one warning goes to
22
- * the log and the screens hear no scroll, with no error at each event.
9
+ * The scroll events of React Native's scroll views, as the screens' scroll listeners hear them: through React Native's
10
+ * public scroll listener ([ReactScrollViewHelper.addScrollListener]), which every `ReactScrollView`,
11
+ * `ReactHorizontalScrollView` and `ReactNestedScrollView` of the process calls on the main thread just before it
12
+ * dispatches the same event to JavaScript, after the same throttle. One listener serves every screen and every React
13
+ * context; it is added while a screen listens and removed when the last one stops. Horizontal scroll views are left
14
+ * out: a pager or a carousel doesn't move the bars. Main thread only.
23
15
  */
24
16
  internal object ScrollEvents {
25
17
  /** What a scroll event is. */
@@ -28,133 +20,62 @@ internal object ScrollEvents {
28
20
  /** One scroll event: the list's tag (its view's id), its kind, its vertical offset and speed, in pixels. */
29
21
  class Reading(val viewTag: Int, val kind: Kind, val offsetY: Float, val velocityY: Float)
30
22
 
31
- private class Fields(val type: Field, val offsetY: Field, val velocityY: Field)
23
+ // One screen's listening: its own object, so that a stop takes away that listening and no other.
24
+ private class Listening(val handler: (Reading) -> Unit)
32
25
 
33
- private var fields: Fields? = null
34
- private var lookedUp = false
26
+ // The screens' listenings, in the order they came.
27
+ private val listenings = mutableListOf<Listening>()
35
28
 
36
- // The screens' listeners that found no event dispatcher, held weakly until React Native has one. Main thread.
37
- private val waiting: MutableSet<ScrollEventListener> = Collections.newSetFromMap(WeakHashMap())
29
+ // React Native holds its scroll listeners weakly: this object keeps the one it added for as long as it is added.
30
+ private val listener = object : ReactScrollViewHelper.ScrollListener {
31
+ override fun onScroll(
32
+ scrollView: ViewGroup?,
33
+ scrollEventType: ScrollEventType?,
34
+ xVelocity: Float,
35
+ yVelocity: Float,
36
+ ) = hear(scrollView, scrollEventType, yVelocity)
38
37
 
39
- /** [listener] found no event dispatcher: it is asked to listen again once React Native has one. */
40
- fun waitForDispatcher(listener: ScrollEventListener) {
41
- waiting += listener
42
- }
43
-
44
- /** A React context is ready (`ReloadCoordinator`): the listeners waiting for an event dispatcher listen now. */
45
- fun dispatcherReady() {
46
- val listeners = waiting.toList()
47
- waiting.clear()
48
- listeners.forEach(ScrollEventListener::attachIfNeeded)
38
+ override fun onLayout(scrollView: ViewGroup?) = Unit
49
39
  }
50
40
 
51
41
  /**
52
- * Calls [handler] on the main thread with each scroll event of the app's React Native, until the function returned
53
- * is called. None when React Native has no event dispatcher now (around a reload, the caller asks again later) or
54
- * its scroll events can't be read.
42
+ * Calls [handler] with each scroll event of a vertical scroll view of the app, until the function returned is
43
+ * called.
55
44
  */
56
- fun listen(context: Context, handler: (Reading) -> Unit): (() -> Unit)? {
57
- if (fields() == null) return null
58
- val reactContext = (context.applicationContext as? ReactApplication)?.reactHost?.currentReactContext
59
- ?: return null
60
- val dispatcher = try {
61
- UIManagerHelper.getEventDispatcher(reactContext)
62
- } catch (e: RuntimeException) {
63
- null
64
- } ?: return null
65
- val listener = EventDispatcherListener { event ->
66
- // The event goes back to its pool after the dispatch: it is read now, on the thread that dispatches.
67
- val reading = read(event) ?: return@EventDispatcherListener
68
- if (UiThread.isMainThread()) handler(reading) else UiThread.post { handler(reading) }
45
+ fun listen(handler: (Reading) -> Unit): () -> Unit {
46
+ val listening = Listening(handler)
47
+ if (listenings.isEmpty()) ReactScrollViewHelper.addScrollListener(listener)
48
+ listenings += listening
49
+ return {
50
+ // By identity: the second call of a stop finds nothing to take away.
51
+ if (listenings.removeAll { it === listening } && listenings.isEmpty()) {
52
+ ReactScrollViewHelper.removeScrollListener(listener)
53
+ }
69
54
  }
70
- dispatcher.addListener(listener)
71
- return { dispatcher.removeListener(listener) }
72
55
  }
73
56
 
74
- private fun read(event: Event<*>): Reading? {
75
- if (event !is ScrollEvent) return null
76
- val fields = fields() ?: return null
77
- return try {
78
- val type = fields.type.get(event) as? ScrollEventType ?: return null
79
- val offsetY = readFloat(fields.offsetY, event) ?: return null
80
- val velocityY = readFloat(fields.velocityY, event) ?: return null
81
- Reading(event.viewTag, kindOf(type), offsetY, velocityY)
82
- } catch (e: ReflectiveOperationException) {
83
- null
84
- } catch (e: IllegalArgumentException) {
85
- null
86
- }
57
+ /** One call of React Native's listener: the handlers listening now hear its reading, if it has one. */
58
+ fun hear(scrollView: ViewGroup?, type: ScrollEventType?, velocityY: Float) {
59
+ val reading = read(scrollView, type, velocityY) ?: return
60
+ listenings.toList().forEach { it.handler(reading) }
87
61
  }
88
62
 
89
- private fun kindOf(type: ScrollEventType): Kind = when (type) {
63
+ /**
64
+ * The reading of one call of React Native's listener: none for a horizontal scroll view, or when React Native gives
65
+ * no view or no kind. The offset is the view's `scrollY` and the speed React Native's own, in pixels, as the event
66
+ * sent to JavaScript carries them before it converts them to dp.
67
+ */
68
+ fun read(scrollView: ViewGroup?, type: ScrollEventType?, velocityY: Float): Reading? {
69
+ if (scrollView == null || type == null || scrollView is HorizontalScrollView) return null
70
+ return Reading(scrollView.id, kindOf(type), scrollView.scrollY.toFloat(), velocityY)
71
+ }
72
+
73
+ /** The kind of a reading for React Native's [type]. */
74
+ fun kindOf(type: ScrollEventType): Kind = when (type) {
90
75
  ScrollEventType.BEGIN_DRAG -> Kind.BeginDrag
91
76
  ScrollEventType.END_DRAG -> Kind.EndDrag
92
77
  ScrollEventType.SCROLL -> Kind.Scroll
93
78
  ScrollEventType.MOMENTUM_BEGIN -> Kind.MomentumBegin
94
79
  ScrollEventType.MOMENTUM_END -> Kind.MomentumEnd
95
80
  }
96
-
97
- @Synchronized
98
- private fun fields(): Fields? {
99
- if (lookedUp) return fields
100
- lookedUp = true
101
- fields = try {
102
- val owner = ScrollEvent::class.java
103
- val type = declaredField(owner, TYPE_NAMES) { ScrollEventType::class.java.isAssignableFrom(it) }
104
- val offsetY = declaredField(owner, OFFSET_NAMES, ::isNumeric)
105
- val velocityY = declaredField(owner, VELOCITY_NAMES, ::isNumeric)
106
- if (type != null && offsetY != null && velocityY != null) Fields(type, offsetY, velocityY) else null
107
- } catch (e: ReflectiveOperationException) {
108
- null
109
- } catch (e: SecurityException) {
110
- null
111
- }
112
- if (fields == null) {
113
- Log.w(
114
- NavigationLog.TAG,
115
- "[ApexNavigation] React Native's scroll events changed: hideOnScroll and the large title " +
116
- "don't follow the scroll",
117
- )
118
- }
119
- return fields
120
- }
121
-
122
- // The names of the fields: React Native 0.87 first, then the earlier ones with the `m` prefix.
123
- private val TYPE_NAMES = listOf("scrollEventType", "mScrollEventType")
124
- private val OFFSET_NAMES = listOf("scrollY", "mScrollY")
125
- private val VELOCITY_NAMES = listOf("yVelocity", "mYVelocity")
126
-
127
- /**
128
- * The first of the fields [names] that [owner] declares with a type [accepted] takes, made readable; none when no
129
- * name fits. React Native renamed them and changed their types between versions.
130
- */
131
- fun declaredField(owner: Class<*>, names: List<String>, accepted: (Class<*>) -> Boolean): Field? {
132
- for (name in names) {
133
- val field = try {
134
- owner.getDeclaredField(name)
135
- } catch (e: NoSuchFieldException) {
136
- continue
137
- }
138
- if (!accepted(field.type)) continue
139
- field.isAccessible = true
140
- return field
141
- }
142
- return null
143
- }
144
-
145
- /** A number of any kind: a primitive one (`int`, `float`, `double`, …) or a boxed one. */
146
- fun isNumeric(type: Class<*>): Boolean =
147
- type in PRIMITIVE_NUMBERS || Number::class.java.isAssignableFrom(type)
148
-
149
- /** [field] of [target] as a float, whatever numeric type it has; none when it holds no number. */
150
- fun readFloat(field: Field, target: Any): Float? = (field.get(target) as? Number)?.toFloat()
151
-
152
- private val PRIMITIVE_NUMBERS = setOf<Class<*>?>(
153
- Byte::class.javaPrimitiveType,
154
- Short::class.javaPrimitiveType,
155
- Int::class.javaPrimitiveType,
156
- Long::class.javaPrimitiveType,
157
- Float::class.javaPrimitiveType,
158
- Double::class.javaPrimitiveType,
159
- )
160
81
  }
@@ -10,7 +10,7 @@ import com.facebook.react.uimanager.StateWrapper
10
10
  import com.facebook.react.uimanager.ThemedReactContext
11
11
  import com.facebook.react.uimanager.UIManagerHelper
12
12
  import com.facebook.react.views.view.ReactViewGroup
13
- import dev.apexnative.navigation.react.ScreenInsets
13
+ import dev.apexnative.navigation.react.ScreenEnvironment
14
14
  import dev.apexnative.navigation.viewcontrollers.component.ScreenReadiness
15
15
 
16
16
  /**
@@ -20,7 +20,7 @@ import dev.apexnative.navigation.viewcontrollers.component.ScreenReadiness
20
20
  @SuppressLint("ViewConstructor")
21
21
  class SafeAreaView(reactContext: ThemedReactContext) : ReactViewGroup(reactContext) {
22
22
  private var stopListening: (() -> Unit)? = null
23
- private var applied: ScreenInsets.Insets? = null
23
+ private var applied: ScreenEnvironment.Insets? = null
24
24
  private var readySent = false
25
25
  private var contentSignalled = false
26
26
 
@@ -48,7 +48,7 @@ class SafeAreaView(reactContext: ThemedReactContext) : ReactViewGroup(reactConte
48
48
  stopListening?.invoke()
49
49
  field = value
50
50
  applied = null
51
- stopListening = value?.let { id -> ScreenInsets.listenToPadding(id) { applyPadding() } }
51
+ stopListening = value?.let { id -> ScreenEnvironment.listenToPadding(id) { applyPadding() } }
52
52
  // The screen has a safe area view: it counts as rendered once this view says so.
53
53
  value?.let(ScreenReadiness::wrapped)
54
54
  applyPadding()
@@ -56,7 +56,7 @@ class SafeAreaView(reactContext: ThemedReactContext) : ReactViewGroup(reactConte
56
56
 
57
57
  private fun applyPadding() {
58
58
  val state = stateWrapper ?: return
59
- val padding = screenId?.let(ScreenInsets::padding) ?: return
59
+ val padding = screenId?.let(ScreenEnvironment::padding) ?: return
60
60
  if (padding == applied) return
61
61
  applied = padding
62
62
  state.updateState(
@@ -69,9 +69,9 @@ class SafeAreaView(reactContext: ThemedReactContext) : ReactViewGroup(reactConte
69
69
  )
70
70
  }
71
71
 
72
- private fun paddingOf(data: ReadableMap): ScreenInsets.Insets? {
72
+ private fun paddingOf(data: ReadableMap): ScreenEnvironment.Insets? {
73
73
  if (!data.hasKey("top")) return null
74
- return ScreenInsets.Insets(data.getDouble("top"), data.getDouble("right"), data.getDouble("bottom"),
74
+ return ScreenEnvironment.Insets(data.getDouble("top"), data.getDouble("right"), data.getDouble("bottom"),
75
75
  data.getDouble("left"))
76
76
  }
77
77
 
@@ -2,9 +2,13 @@ package dev.apexnative.navigation.slots
2
2
 
3
3
  import android.annotation.SuppressLint
4
4
  import android.content.Context
5
+ import android.util.Size
5
6
  import android.view.MotionEvent
6
7
  import android.view.ViewGroup
7
8
  import android.widget.FrameLayout
9
+ import com.facebook.react.uimanager.PixelUtil.dpToPx
10
+ import dev.apexnative.navigation.react.ContentHeights
11
+ import dev.apexnative.navigation.react.ContentMounts
8
12
  import dev.apexnative.navigation.react.events.AppearanceDispatcher
9
13
  import dev.apexnative.navigation.react.events.ComponentType
10
14
  import dev.apexnative.navigation.react.events.ScreenAppearance
@@ -16,9 +20,10 @@ import dev.apexnative.navigation.viewcontrollers.viewcontroller.ScrollEventListe
16
20
  * What a slot screen (`slot:<Name>`) shows: the empty container its slot of the app's React tree moves its children
17
21
  * into, under the screen's id ([SlotContainers]), told of each change of its size. It is the screen's content for the
18
22
  * core: ready once the slot's content has mounted into it and, when the screen has a safe area view, the screen
19
- * rendered into that view; its mounted handlers run at each mount. Its appearance events go through its record to the
20
- * core's dispatcher (`AppearanceDispatcher`), which holds them until it is ready or the hold limit. Destroyed while the
21
- * id is still its own, it tells JavaScript to take the slot's content away (`ANN.SlotDestroyed`).
23
+ * rendered into that view; its mounted handlers run at each mount of Fabric and as the content mounts into it. Its
24
+ * content measures without a height limit in the slot's own layout ([contentSize]). Its appearance events go through
25
+ * its record to the core's dispatcher (`AppearanceDispatcher`), which holds them until it is ready or the hold limit.
26
+ * Destroyed while the id is still its own, it tells JavaScript to take the slot's content away (`ANN.SlotDestroyed`).
22
27
  */
23
28
  @SuppressLint("ViewConstructor")
24
29
  class SlotScreenView(
@@ -30,7 +35,7 @@ class SlotScreenView(
30
35
  private val scrollEventListener = ScrollEventListener()
31
36
  private val whenMounted = mutableListOf<Runnable>()
32
37
  private val whenRendered = mutableListOf<Runnable>()
33
- private val mountedHandlers = LinkedHashMap<String, Runnable>()
38
+ private val mounts = ContentMounts { !destroyed && isAttachedToWindow }
34
39
  private var listening = false
35
40
 
36
41
  // The safe area view of a slot renders in the app's one React tree: every signal of the id is the screen's.
@@ -84,10 +89,11 @@ class SlotScreenView(
84
89
  destroyed = true
85
90
  whenMounted.clear()
86
91
  whenRendered.clear()
87
- mountedHandlers.clear()
92
+ mounts.clear()
88
93
  stopListening()
89
94
  // A later screen with the same id keeps the slot.
90
95
  if (SlotContainers.get(componentId) !== this) return
96
+ ContentHeights.forgetSlot(componentId)
91
97
  SlotContainers.remove(componentId)
92
98
  if (!isPreparedPush) AppearanceDispatcher.slotDestroyed(componentId, appearance.generation)
93
99
  }
@@ -120,7 +126,21 @@ class SlotScreenView(
120
126
  }
121
127
 
122
128
  override fun setOnContentMounted(key: String, handler: Runnable?) {
123
- if (handler == null) mountedHandlers.remove(key) else mountedHandlers[key] = handler
129
+ if (!destroyed) mounts.set(key, handler)
130
+ }
131
+
132
+ // The slot measures its content in its own layout: the first question asks for it and lays the slot out again; the
133
+ // height comes with that layout, before the mount the handlers hear. The width is the container's, at which the
134
+ // slot lays its content out.
135
+ override fun contentSize(maxWidth: Int): Size? {
136
+ if (destroyed) return null
137
+ if (!ContentHeights.isSlotRequested(componentId)) {
138
+ ContentHeights.requestSlot(componentId)
139
+ SlotContainers.changed(componentId)
140
+ return null
141
+ }
142
+ val height = ContentHeights.slotHeight(componentId)?.takeIf { it > 0f } ?: return null
143
+ return Size(if (width > 0) width else maxWidth, Math.round(height.dpToPx()))
124
144
  }
125
145
 
126
146
  // Only the container that holds the slot of the id: a destroyed one, or one a later screen replaced, takes no
@@ -136,7 +156,7 @@ class SlotScreenView(
136
156
  val waiting = whenMounted.toList()
137
157
  whenMounted.clear()
138
158
  waiting.forEach { it.run() }
139
- mountedHandlers.values.toList().forEach { it.run() }
159
+ mounts.run()
140
160
  checkReadiness()
141
161
  }
142
162
 
@@ -15,6 +15,7 @@ import com.facebook.react.uimanager.ThemedReactContext
15
15
  import com.facebook.react.uimanager.UIManagerHelper
16
16
  import com.facebook.react.uimanager.events.EventDispatcher
17
17
  import com.facebook.react.views.view.ReactViewGroup
18
+ import dev.apexnative.navigation.react.ContentHeights
18
19
 
19
20
  /**
20
21
  * The React children of a slot. They live outside the views of the host's surface, in the container of a screen, so
@@ -79,6 +80,8 @@ class SlotView(reactContext: ThemedReactContext) : ReactViewGroup(reactContext)
79
80
  }
80
81
  private var lastWidth = -1f
81
82
  private var lastHeight = -1f
83
+ // The slot was laid out once more, at the same size, for the measure of its content (`ContentHeights`).
84
+ private var measureAsked = false
82
85
 
83
86
  private val slotId: String?
84
87
  get() = getTag(R.id.view_tag_native_id) as? String
@@ -116,7 +119,14 @@ class SlotView(reactContext: ThemedReactContext) : ReactViewGroup(reactContext)
116
119
  if (container != null && state != null && container.width > 0 && container.height > 0) {
117
120
  val width = container.width.toFloat().pxToDp()
118
121
  val height = container.height.toFloat().pxToDp()
119
- if (width != lastWidth || height != lastHeight) {
122
+ // A container that measures the content gets one layout more for it, with the same size, until the height
123
+ // came.
124
+ val requested = ContentHeights.isSlotRequested(id)
125
+ // A request that went (its screen did) and comes again gets its layout again.
126
+ if (!requested) measureAsked = false
127
+ val measures = !measureAsked && requested && ContentHeights.slotHeight(id) == null
128
+ if (width != lastWidth || height != lastHeight || measures) {
129
+ measureAsked = measureAsked || measures
120
130
  lastWidth = width
121
131
  lastHeight = height
122
132
  state.updateState(
@@ -136,6 +146,7 @@ class SlotView(reactContext: ThemedReactContext) : ReactViewGroup(reactContext)
136
146
  }
137
147
 
138
148
  fun drop() {
149
+ measureAsked = false
139
150
  SlotContainers.unregister(this)
140
151
  (content.parent as? ViewGroup)?.removeView(content)
141
152
  }
@@ -28,7 +28,7 @@ internal object MaterialTheme {
28
28
  private val wrappers = WeakHashMap<Context, WeakReference<Context>>()
29
29
  // Themes of the navigation's own views, each made once over the activity's with a style of its own.
30
30
  private val followers = WeakHashMap<Context, Int>()
31
- // The popup theme of the bar's toolbars per activity: the app's theme doesn't change while the activity lives.
31
+ // The popup theme of the bar's "⋮" per activity: the app's theme doesn't change while the activity lives.
32
32
  private val popupThemes = WeakHashMap<Activity, Int>()
33
33
 
34
34
  fun context(context: Context): Context {
@@ -38,11 +38,10 @@ internal object MaterialTheme {
38
38
  }
39
39
 
40
40
  /**
41
- * The popup theme of the top bar's toolbars, for "⋮", set on them before their menu is made. None when the app's
42
- * theme follows light and dark: the menu takes the bar's own theme, which [rebase] keeps in the current mode, with
43
- * the app's colors. With a Material 3 theme fixed to light or to dark, a day/night overlay that gives the menu the
44
- * current mode's colors where the theme shows the other mode. Android makes the menu's theme from it at the menu's
45
- * first opening, in the mode of that moment, and no public API makes it again for a later switch.
41
+ * The popup theme of the top bar's "⋮", over the bar's theme at each opening of its popup, in the mode of that
42
+ * moment. None when the app's theme follows light and dark: the menu takes the bar's own theme, which [rebase]
43
+ * keeps in the current mode, with the app's colors. With a Material 3 theme fixed to light or to dark, a day/night
44
+ * overlay that gives the menu the current mode's colors where the theme shows the other mode.
46
45
  */
47
46
  fun popupTheme(context: Context): Int {
48
47
  val activity = activityOf(context) ?: return 0
@@ -0,0 +1,36 @@
1
+ package dev.apexnative.navigation.utils
2
+
3
+ import android.animation.ValueAnimator
4
+ import android.annotation.SuppressLint
5
+ import android.content.Context
6
+ import android.os.Build
7
+ import android.provider.Settings
8
+ import dev.apexnative.navigation.NavigationApplication
9
+
10
+ /**
11
+ * Whether the system plays no animations: "Remove animations" in the accessibility settings, or the animator duration
12
+ * scale at 0 in the developer options. Then a transition doesn't wait for its elements' targets and images and moves no
13
+ * element, and the side menu opens and closes at once. Any thread.
14
+ */
15
+ internal object SystemAnimations {
16
+ /**
17
+ * The animations are off now; [context] reads the scale below Android 8, else the application's. Lint can't see
18
+ * that the decision calls `areAnimatorsEnabled` (API 26) only from Android 8.
19
+ */
20
+ @SuppressLint("NewApi")
21
+ fun areOff(context: Context? = null): Boolean =
22
+ areOff(Build.VERSION.SDK_INT, { ValueAnimator.areAnimatorsEnabled() }) { durationScaleOf(context) }
23
+
24
+ /**
25
+ * The decision: from Android 8 the animator's own answer ([animatorsEnabled]), which follows both settings;
26
+ * below it the duration scale ([durationScale]) at 0.
27
+ */
28
+ fun areOff(sdk: Int, animatorsEnabled: () -> Boolean, durationScale: () -> Float): Boolean =
29
+ if (sdk >= Build.VERSION_CODES.O) !animatorsEnabled() else durationScale() == 0f
30
+
31
+ // Without a context and before the application started (the unit tests), the animations play.
32
+ private fun durationScaleOf(context: Context?): Float {
33
+ val resolver = (context ?: NavigationApplication.instanceOrNull)?.contentResolver ?: return 1f
34
+ return Settings.Global.getFloat(resolver, Settings.Global.ANIMATOR_DURATION_SCALE, 1f)
35
+ }
36
+ }
@@ -26,16 +26,3 @@ val View.borderRadius: Float
26
26
  */
27
27
  internal fun resolveBorderRadius(radius: LengthPercentage, width: Int, density: Float): Float =
28
28
  (radius.resolve(width / density) * density).coerceAtLeast(0f)
29
-
30
- /** The scale the parents of [view] give it, along x and along y: a view in a scaled parent shows smaller. */
31
- fun computeInheritedScale(view: View): Pair<Float, Float> {
32
- var scaleX = 1f
33
- var scaleY = 1f
34
- var parent = view.parent as? View
35
- while (parent != null) {
36
- scaleX *= parent.scaleX
37
- scaleY *= parent.scaleY
38
- parent = parent.parent as? View
39
- }
40
- return scaleX to scaleY
41
- }
@@ -9,7 +9,7 @@ import androidx.window.layout.WindowMetricsCalculator
9
9
  import dev.apexnative.navigation.NavigationActivity
10
10
  import dev.apexnative.navigation.options.DefaultOptions
11
11
  import dev.apexnative.navigation.options.Options
12
- import dev.apexnative.navigation.react.ArrangementStates
12
+ import dev.apexnative.navigation.react.ScreenEnvironment
13
13
  import dev.apexnative.navigation.react.events.EventEmitter
14
14
  import dev.apexnative.navigation.utils.SystemUiUtils
15
15
  import dev.apexnative.navigation.utils.WindowFolds
@@ -146,12 +146,14 @@ class ArrangementController internal constructor(
146
146
  val overlay = plan.layout == ArrangementPlan.Layout.OVERLAY
147
147
  val single = plan.layout == ArrangementPlan.Layout.SINGLE
148
148
  if (primary is ComponentViewController) {
149
- val state = ArrangementStates.State(PRIMARY, layout, front = overlay, hidden = false)
150
- ArrangementStates.set(primary.id, state, notify, EventEmitter.of(primary.jsGeneration))
149
+ val state = ScreenEnvironment.Arrangement(PRIMARY, layout, front = overlay, hidden = false)
150
+ val changes = ScreenEnvironment.changesOf(EventEmitter.of(primary.jsGeneration))
151
+ ScreenEnvironment.setArrangement(primary.id, state, notify, changes)
151
152
  }
152
153
  if (secondary is ComponentViewController) {
153
- val state = ArrangementStates.State(SECONDARY, layout, front = false, hidden = single)
154
- ArrangementStates.set(secondary.id, state, notify, EventEmitter.of(secondary.jsGeneration))
154
+ val state = ScreenEnvironment.Arrangement(SECONDARY, layout, front = false, hidden = single)
155
+ val changes = ScreenEnvironment.changesOf(EventEmitter.of(secondary.jsGeneration))
156
+ ScreenEnvironment.setArrangement(secondary.id, state, notify, changes)
155
157
  }
156
158
  }
157
159
 
@@ -244,9 +246,9 @@ class ArrangementController internal constructor(
244
246
  // The stack keeps the bar's title, buttons and search by the view of its screen on top: they go with the pair.
245
247
  if (!isDestroyed && peekView() != null) performOnParentController { it.onChildDestroyed(this) }
246
248
  folds?.removeListener(onFoldsChanged)
247
- ArrangementStates.remove(primary.id)
248
- ArrangementStates.remove(secondary.id)
249
249
  shownChildren.containerDisappeared()
250
+ // The screens go with the pair: each clears its own environment, its arrangement too, unless its id is
251
+ // another's now.
250
252
  super.destroy()
251
253
  }
252
254
 
@@ -2,6 +2,7 @@ package dev.apexnative.navigation.viewcontrollers.bottomtabs
2
2
 
3
3
  import android.view.View
4
4
  import dev.apexnative.navigation.viewcontrollers.viewcontroller.ScrollEventListener
5
+ import dev.apexnative.navigation.views.bottomtabs.BottomTabsLayout
5
6
 
6
7
  /**
7
8
  * bottomTabs.hideOnScroll: the tabs hide after a drag that scrolls the screen's content down and come back after one
@@ -43,9 +44,10 @@ class BottomTabsCollapseBehavior(private val container: () -> View?) :
43
44
  override fun onHide() {
44
45
  val view = container() ?: return
45
46
  hiddenByScroll = true
46
- // Out of the screen with what is under it: a compact bar floats over its bottom margin and the navigation bar.
47
- val under = ((view.parent as? View)?.let { it.height - view.bottom } ?: 0).coerceAtLeast(0)
48
- view.animate().translationY((view.height + under).toFloat()).setDuration(DURATION).start()
47
+ // Out of the screen with what is under it (a compact bar floats over its bottom margin and the navigation bar)
48
+ // and the custom row that rises above it.
49
+ val distance = (view.parent as? BottomTabsLayout)?.hideDistance ?: view.height
50
+ view.animate().translationY(distance.toFloat()).setDuration(DURATION).start()
49
51
  }
50
52
 
51
53
  private companion object {