@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
@@ -7,14 +7,15 @@ import dev.apexnative.navigation.viewcontrollers.stack.StackDisplay.BarMotion
7
7
  * row and the large title lies over them and the real row turns transparent; then the picture goes with the old screen
8
8
  * and the row comes with the new one ([RowTimeline]). Each change of the stack's current child owns the bar: a newer
9
9
  * one takes it over (the older picture goes, its animator stops), and only the end of the transition that owns it puts
10
- * the row back at rest. Without a picture (a `HARDWARE` bitmap in the bar) the old row just hides. Main thread only.
10
+ * the row back at rest. Without a picture (a `HARDWARE` bitmap in the bar on Android 8) the old row just hides. Main
11
+ * thread only.
11
12
  */
12
13
  internal class BarRowTransition(private val target: Target) {
13
14
  /** What the transition moves: the bar's views, or the unit tests' fake. */
14
15
  interface Target {
15
16
  /**
16
17
  * Lays a picture of the row and the large title over them, laid out at once and taking the touches; false when
17
- * the bar can't be drawn in software (a `HARDWARE` bitmap): no picture then.
18
+ * the bar can't be recorded (a `HARDWARE` bitmap on Android 8): no picture then.
18
19
  */
19
20
  fun takeSnapshot(): Boolean
20
21
 
@@ -1,11 +1,11 @@
1
1
  package dev.apexnative.navigation.viewcontrollers.stack.topbar
2
2
 
3
3
  /**
4
- * Where the right buttons of the bar go: the bar has as many places as AppCompat gives a toolbar for the window now
5
- * ([maxActionButtons]), one less for "⋮" when it shows; the pinned buttons take theirs first, then the buttons with the
6
- * highest `priority` (ties by the list's order); the rest go into "⋮". The core counts it, not AppCompat: AppCompat
7
- * would share the places in the menu's order, where the first button of the list is the last, and keep the count of the
8
- * orientation before a rotation. A pure function.
4
+ * Where the buttons of the bar go. The right ones: the bar has as many places as AppCompat gives a toolbar for the
5
+ * window now ([maxActionButtons]), one less for "⋮" when it shows; the pinned buttons take theirs first, then the
6
+ * buttons with the highest `priority` (ties by the list's order); the rest go into "⋮". In the line ([line]) the left
7
+ * buttons stand from its start in the list's order, and the right ones toward its end, the first one last. A pure
8
+ * function.
9
9
  */
10
10
  internal object ButtonPlaces {
11
11
  /** A right button as the count sees it: its `priority` ([HIGH], [STANDARD], [LOW]) and `pinned`. */
@@ -36,6 +36,12 @@ internal object ButtonPlaces {
36
36
  else -> 2
37
37
  }
38
38
 
39
+ /**
40
+ * The buttons of a side in the bar, [inBar] in the list's order, in the line's order from its start: the left ones
41
+ * as they are, the right ones the other way round, so the first right button stands at the end of the line.
42
+ */
43
+ fun <T> line(inBar: List<T>, left: Boolean): List<T> = if (left) inBar else inBar.reversed()
44
+
39
45
  /**
40
46
  * The plan for [buttons] with [maxPlaces] places, [taken] of them already used by what the core always puts in the
41
47
  * bar (the search's magnifier), and [overflowItems] items of `overflowMenu`, which put "⋮" in the bar whatever
@@ -7,17 +7,13 @@ import android.app.Activity
7
7
  import android.content.Context
8
8
  import android.content.pm.ApplicationInfo
9
9
  import android.graphics.Color
10
- import android.graphics.drawable.BitmapDrawable
11
10
  import android.graphics.drawable.Drawable
12
- import android.view.Menu
13
- import android.view.MenuItem
14
11
  import android.view.MotionEvent
15
12
  import android.view.View
16
13
  import android.view.ViewGroup
17
14
  import android.view.animation.LinearInterpolator
18
15
  import android.view.animation.PathInterpolator
19
16
  import android.widget.FrameLayout
20
- import android.widget.ImageView
21
17
  import androidx.appcompat.content.res.AppCompatResources
22
18
  import androidx.core.graphics.drawable.DrawableCompat
23
19
  import androidx.core.view.WindowInsetsCompat
@@ -59,6 +55,7 @@ import dev.apexnative.navigation.views.stack.topbar.TitleBar
59
55
  import dev.apexnative.navigation.views.stack.topbar.TopBar
60
56
  import dev.apexnative.navigation.views.stack.topbar.TopBarDefaults
61
57
  import dev.apexnative.navigation.views.stack.topbar.titlebar.BarButtonComponentCell
58
+ import dev.apexnative.navigation.views.stack.topbar.titlebar.BarMenu
62
59
  import dev.apexnative.navigation.views.stack.topbar.titlebar.ButtonBar
63
60
  import dev.apexnative.navigation.views.stack.topbar.titlebar.IconBackgroundDrawable
64
61
  import kotlin.math.roundToInt
@@ -142,8 +139,8 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
142
139
  this.layout = layout
143
140
  val bar = layout.topBar
144
141
  this.bar = bar
145
- leftButtons = ButtonBar(bar.leftButtonBar, true, ::loadIcon, Buttons())
146
- rightButtons = ButtonBar(bar.rightButtonBar, false, ::loadIcon, Buttons())
142
+ leftButtons = ButtonBar(bar.leftButtonBar, ::loadIcon, Buttons())
143
+ rightButtons = ButtonBar(bar.rightButtonBar, ::loadIcon, Buttons())
147
144
  visibility = TopBarVisibility(VisibilityTarget(bar) { collapse?.reset() })
148
145
  rowTransition = BarRowTransition(RowTarget(bar).also { rowTarget = it })
149
146
  collapse = TopBarCollapseBehavior(bar)
@@ -212,8 +209,8 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
212
209
  * bar as it is goes with [leaving], and the bar, which takes [incoming]'s options next, is drawn flat as [incoming]
213
210
  * moves — under [leaving] on a pop ([StackDisplay.BarTravel.Back]) (`StackLayout.barTravels`). No picture when the
214
211
  * bar doesn't show. Nothing travels when the new top hides the bar by a side of its own
215
- * (`animations.*.topBar.exit`), which plays instead, or when the bar can't be drawn in software (a `HARDWARE`
216
- * bitmap in it): the bar takes the options in its place.
212
+ * (`animations.*.topBar.exit`), which plays instead, or when the bar can't be recorded (a `HARDWARE` bitmap in it
213
+ * on Android 8): the bar takes the options in its place.
217
214
  */
218
215
  private fun beginTravel(
219
216
  bar: TopBar,
@@ -238,10 +235,10 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
238
235
 
239
236
  // The bar's picture where the bar is in the stack, its moves counted.
240
237
  private fun pictureOfBar(bar: TopBar): Drawable? {
241
- val bitmap = bar.picture() ?: return null
238
+ val picture = bar.picture() ?: return null
242
239
  val left = (bar.left + bar.translationX).roundToInt()
243
240
  val top = (bar.top + bar.translationY).roundToInt()
244
- return BitmapDrawable(bar.resources, bitmap).apply { setBounds(left, top, left + bitmap.width, top + bitmap.height) }
241
+ return picture.apply { setBounds(left, top, left + intrinsicWidth, top + intrinsicHeight) }
245
242
  }
246
243
 
247
244
  // The bar's travel ends: the picture goes, the bar is drawn in its place again with its shadow.
@@ -284,6 +281,8 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
284
281
  val bar = bar ?: return
285
282
  val screen = host.optionsScreen(entry)
286
283
  if (screen !== shownScreen) return apply(entry)
284
+ // An open menu has the colors of before.
285
+ if (delta.topBar.menuBackgroundColor.hasValue() || delta.topBar.menuTextColor.hasValue()) closeMenus()
287
286
  val options = host.resolvedOf(entry)
288
287
  applyAll(bar, entry, screen, options, merge = true, delta = delta)
289
288
  }
@@ -293,6 +292,8 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
293
292
  * now.
294
293
  */
295
294
  fun repaint(entry: ViewController<*>?) {
295
+ // An open menu has the old colors: it closes.
296
+ closeMenus()
296
297
  keys.clear()
297
298
  apply(entry)
298
299
  val options = entry?.let(host::resolvedOf) ?: return
@@ -582,16 +583,15 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
582
583
  keys[Part.Left] = leftKey
583
584
  reportWithoutId(topBar.buttons.left, "leftButtons")
584
585
  leftButtons?.set(
585
- left.map { button(screen, it, roots) },
586
+ ButtonPlaces.line(left, left = true).map { button(screen, it, roots) },
586
587
  emptyList(),
587
588
  null,
588
589
  merge && topBar.animateLeftButtons.isTrue,
589
590
  )
590
591
  }
591
592
  val overflow = topBar.overflowMenu?.takeIf { it.items.isNotEmpty() }
592
- val menu = bar.rightButtonBar.menu
593
593
  search.applyMagnifier()
594
- val taken = (0 until menu.size()).count { menu.getItem(it).itemId != Menu.NONE }
594
+ val taken = if (bar.rightButtonBar.trailingView != null) 1 else 0
595
595
  val configuration = context.resources.configuration
596
596
  val plan = ButtonPlaces.plan(
597
597
  right.map { ButtonPlaces.Button(ButtonPlaces.priorityOf(it.priority.get(null)), it.pinned.isTrue) },
@@ -612,12 +612,11 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
612
612
  keys[Part.Right] = rightKey
613
613
  reportWithoutId(topBar.buttons.right, "rightButtons")
614
614
  rightButtons?.set(
615
- plan.inBar.map { button(screen, right[it], roots) },
615
+ ButtonPlaces.line(plan.inBar, left = false).map { button(screen, right[it], roots) },
616
616
  plan.inOverflow.map { button(screen, right[it], roots) },
617
617
  overflow,
618
618
  merge && topBar.animateRightButtons.isTrue,
619
619
  )
620
- search.applyMagnifier()
621
620
  rightButtons?.setOverflowColor(overflowColor)
622
621
  }
623
622
  }
@@ -639,9 +638,9 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
639
638
  ).joinToString()
640
639
  if (keys[Part.Back] == key) return
641
640
  keys[Part.Back] = key
642
- val toolbar = bar.leftButtonBar
641
+ val row = bar.leftButtonBar
643
642
  if (!shown) {
644
- toolbar.navigationIcon = null
643
+ row.navigationIcon = null
645
644
  return
646
645
  }
647
646
  val own = back.icon.get(null)?.let(::loadIcon)?.mutate()
@@ -668,19 +667,13 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
668
667
  dp(background.height.get(0.0)),
669
668
  )
670
669
  } ?: icon
671
- toolbar.navigationIcon = drawable
672
- // Taking the icon, the navigation button gives it its own direction, which is stale while the button is new
673
- // or the bar's direction has just changed; the toolbar corrects it only after the icon was drawn. So the arrow
674
- // gets the bar's direction at once, on the drawable the button shows; a background passes it to the arrow.
675
- val held = toolbar.navigationIcon ?: drawable
676
- DrawableCompat.setLayoutDirection(held, arrowDirection)
677
- if (held !== drawable) DrawableCompat.setLayoutDirection(drawable, arrowDirection)
678
- toolbar.navigationContentDescription = label
679
- // The toolbar uses its navigation button again from screen to screen: its id is set every time.
680
- for (index in 0 until toolbar.childCount) {
681
- val child = toolbar.getChildAt(index)
682
- if ((child as? ImageView)?.drawable === held) TestIds.apply(child, testId)
683
- }
670
+ row.navigationIcon = drawable
671
+ // Taking the icon, the back button gives it its own direction, which is stale while the button is new or the
672
+ // bar's direction has just changed; the button corrects it only once its direction is resolved again. So the
673
+ // arrow gets the bar's direction at once; a background passes it to the arrow.
674
+ DrawableCompat.setLayoutDirection(drawable, arrowDirection)
675
+ row.navigationContentDescription = label
676
+ row.navigationButton?.let { TestIds.apply(it, testId) }
684
677
  }
685
678
 
686
679
  // The background (`topBar.background.component`).
@@ -719,15 +712,14 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
719
712
 
720
713
  private fun pictureUnderRow(bar: TopBar, leaving: ViewController<*>) {
721
714
  val view = leaving.peekView() ?: return
722
- val (bitmap, top) = bar.pictureUnderRow() ?: return
715
+ val (picture, top) = bar.pictureUnderRow() ?: return
723
716
  val barPlace = IntArray(2)
724
717
  val viewPlace = IntArray(2)
725
718
  bar.getLocationInWindow(barPlace)
726
719
  view.getLocationInWindow(viewPlace)
727
- val picture = BitmapDrawable(bar.resources, bitmap)
728
720
  val left = barPlace[0] - viewPlace[0]
729
721
  val y = barPlace[1] + top - viewPlace[1]
730
- picture.setBounds(left, y, left + bitmap.width, y + bitmap.height)
722
+ picture.setBounds(left, y, left + picture.intrinsicWidth, y + picture.intrinsicHeight)
731
723
  view.overlay.add(picture)
732
724
  underRow = leaving to picture
733
725
  }
@@ -1037,11 +1029,16 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
1037
1029
  return (buttons.left.orEmpty() + buttons.right.orEmpty()).firstOrNull { it.id.get(null) == buttonId }?.menu
1038
1030
  }
1039
1031
 
1040
- // The search's magnifier, the one item of the bar with an id of its own: `<topBar.searchBar.testID>.button`.
1041
- override fun testIdOf(item: MenuItem): String? {
1042
- if (item.itemId == Menu.NONE) return null
1043
- val entry = shownEntry ?: return null
1044
- return host.resolvedOf(entry).topBar.searchBar.testID.get(null)?.let { "$it.button" }
1032
+ // Read as a menu opens, so they follow light and dark mode: `menuBackgroundColor`, else the bar's color as it
1033
+ // shows (none when it lets something through), and then `menuTextColor` or else the title's color, the pair
1034
+ // the bar shows; with neither, the theme's.
1035
+ override fun menuColors(): BarMenu.Colors {
1036
+ val topBar = shownEntry?.let(host::resolvedOf)?.topBar ?: return BarMenu.Colors(null, null)
1037
+ val context = bar?.context ?: activity
1038
+ val text = topBar.menuTextColor.get(context, null)
1039
+ topBar.menuBackgroundColor.get(context, null)?.let { return BarMenu.Colors(it, text) }
1040
+ val barColor = bar?.barColor?.takeIf { Color.alpha(it) == OPAQUE } ?: return BarMenu.Colors(null, text)
1041
+ return BarMenu.Colors(barColor, text ?: topBar.title.color.get(context, null))
1045
1042
  }
1046
1043
  }
1047
1044
 
@@ -1183,6 +1180,7 @@ internal class TopBarPresenter(private val activity: Activity, private val host:
1183
1180
 
1184
1181
  private companion object {
1185
1182
  const val DEFAULT_HEIGHT_DP = 64.0
1183
+ const val OPAQUE = 255
1186
1184
  const val DEFAULT_TITLE_SP = 22.0
1187
1185
  const val DEFAULT_SUBTITLE_SP = 14.0
1188
1186
  const val CENTER = "center"
@@ -13,8 +13,8 @@ import java.lang.ref.WeakReference
13
13
  * tag) is in the screen, found once per tag —, not the horizontal ones, nor those of a modal, an overlay or another
14
14
  * screen; a slot screen's lists are in its container too. While a list is dragged, each follower gets the drag as a
15
15
  * translation to follow; when the drag ends, the way to settle; at every scroll event, the drags' and the inertia's,
16
- * the offset. It listens to React Native's events only while it has followers; without React Native's event dispatcher
17
- * (around a reload) it waits and listens once there is one. Released with the screen. Main thread only.
16
+ * the offset. It listens to React Native's scroll views ([ScrollEvents]) only while it has followers and its screen's
17
+ * view. Released with the screen. Main thread only.
18
18
  */
19
19
  class ScrollEventListener() {
20
20
  /** Follows the content while it is dragged. */
@@ -105,15 +105,12 @@ class ScrollEventListener() {
105
105
  }
106
106
 
107
107
  /**
108
- * Listens to React Native's scroll events when followers wait for them and it doesn't yet: after a reload there may
109
- * be no event dispatcher at first: then it is asked again once React Native has one, and as the screen joins a
110
- * window.
108
+ * Listens to React Native's scroll views when followers wait for them and it doesn't yet, once it has the screen's
109
+ * view to tell its own lists by; asked again as the screen joins a window.
111
110
  */
112
111
  internal fun attachIfNeeded() {
113
- if (released || stopListening != null || registrations.isEmpty()) return
114
- val context = scope?.get()?.context ?: return
115
- stopListening = ScrollEvents.listen(context, ::onScrollEvent)
116
- if (stopListening == null) ScrollEvents.waitForDispatcher(this)
112
+ if (released || stopListening != null || registrations.isEmpty() || scope?.get() == null) return
113
+ stopListening = ScrollEvents.listen(::onScrollEvent)
117
114
  }
118
115
 
119
116
  /** The screen is destroyed: no follower hears it any more. */
@@ -0,0 +1,73 @@
1
+ package dev.apexnative.navigation.views.animations
2
+
3
+ import android.animation.Animator
4
+ import android.animation.AnimatorListenerAdapter
5
+ import android.animation.AnimatorSet
6
+ import android.animation.ObjectAnimator
7
+ import android.view.View
8
+
9
+ /**
10
+ * A hardware layer on a view for the time of an animator that fades it. A view whose `alpha` is below 1 and whose
11
+ * rendering overlaps (any `ViewGroup` by default) is drawn into an offscreen buffer at every frame; through a layer it
12
+ * is drawn once and the GPU only composites it. The layer is taken as the animator starts and only from
13
+ * `LAYER_TYPE_NONE` (a layer someone else set stays theirs), and given back as it ends or is cancelled. A pure
14
+ * translation or scale needs none: RenderThread draws it. The stack's default transition has its own
15
+ * (`StackViews.dress`). Main thread only.
16
+ */
17
+ internal object AnimationLayer {
18
+ private const val ALPHA = "alpha"
19
+
20
+ /**
21
+ * [view] draws through a hardware layer while [animator] plays, when [animator] changes the `alpha` of [view] and
22
+ * the view's rendering overlaps; [animator] itself, for chaining.
23
+ */
24
+ fun during(view: View, animator: Animator): Animator {
25
+ if (!changesAlpha(animator, view)) return animator
26
+ val hold = Hold({ view.layerType }, { view.setLayerType(it, null) })
27
+ animator.addListener(object : AnimatorListenerAdapter() {
28
+ override fun onAnimationStart(animation: Animator) {
29
+ hold.take(changesAlpha = true, overlapping = view.hasOverlappingRendering())
30
+ }
31
+
32
+ override fun onAnimationCancel(animation: Animator) = hold.release()
33
+
34
+ override fun onAnimationEnd(animation: Animator) = hold.release()
35
+ })
36
+ return animator
37
+ }
38
+
39
+ /** Whether an animation takes a layer: it fades, its view's rendering overlaps, and the view has no layer. */
40
+ fun takes(changesAlpha: Boolean, overlapping: Boolean, layerType: Int): Boolean =
41
+ changesAlpha && overlapping && layerType == View.LAYER_TYPE_NONE
42
+
43
+ /**
44
+ * The layer one animation took on one view, through [layerType] and [setLayerType]: taken at most once at a time,
45
+ * given back once, and only while it is still the hardware layer it set.
46
+ */
47
+ class Hold(private val layerType: () -> Int, private val setLayerType: (Int) -> Unit) {
48
+ /** Whether the animation holds the view's layer now. */
49
+ var isTaken = false
50
+ private set
51
+
52
+ /** The animation starts: the layer, when [takes] says so. */
53
+ fun take(changesAlpha: Boolean, overlapping: Boolean) {
54
+ if (isTaken || !takes(changesAlpha, overlapping, layerType())) return
55
+ setLayerType(View.LAYER_TYPE_HARDWARE)
56
+ isTaken = true
57
+ }
58
+
59
+ /** The animation ended or stopped: the layer goes, unless someone changed it meanwhile. */
60
+ fun release() {
61
+ if (!isTaken) return
62
+ isTaken = false
63
+ if (layerType() == View.LAYER_TYPE_HARDWARE) setLayerType(View.LAYER_TYPE_NONE)
64
+ }
65
+ }
66
+
67
+ // Whether [animator], or an animator in it, writes the alpha of [view].
68
+ private fun changesAlpha(animator: Animator, view: View): Boolean = when (animator) {
69
+ is AnimatorSet -> animator.childAnimations.any { changesAlpha(it, view) }
70
+ is ObjectAnimator -> animator.target === view && animator.values.orEmpty().any { it.propertyName == ALPHA }
71
+ else -> false
72
+ }
73
+ }
@@ -18,6 +18,18 @@ internal object ValueInterpolation {
18
18
  else -> from + fraction * (to - from)
19
19
  }
20
20
 
21
+ /**
22
+ * An angle in degrees at [fraction] of the way along the shorter arc, as iOS turns a shared element: 0 → 270 turns
23
+ * back by 90, 170 → −170 turns on by 20; half a turn goes on. At the end of the time exactly [to].
24
+ */
25
+ fun angle(from: Float, to: Float, fraction: Float, time: Float = fraction): Float {
26
+ if (time <= 0f) return from
27
+ if (time >= 1f) return to
28
+ val forward = ((to - from) % FULL_TURN + FULL_TURN) % FULL_TURN
29
+ val turn = if (forward > HALF_TURN) forward - FULL_TURN else forward
30
+ return from + fraction * turn
31
+ }
32
+
21
33
  /**
22
34
  * A color (an sRGB color int, as React Native keeps a background; none is transparent) at [fraction] of the way
23
35
  * from [from] to [to]: L, a and b in Lab (D65), the alpha linear, the result rounded; at the end of the time
@@ -90,4 +102,6 @@ internal object ValueInterpolation {
90
102
  private const val WHITE_Z = 108.883
91
103
  private const val EPSILON = 0.008856
92
104
  private const val KAPPA = 903.3
105
+ private const val FULL_TURN = 360f
106
+ private const val HALF_TURN = 180f
93
107
  }
@@ -10,13 +10,15 @@ import androidx.annotation.RestrictTo
10
10
  import androidx.core.view.ViewCompat
11
11
  import androidx.core.view.WindowInsetsCompat
12
12
  import dev.apexnative.navigation.utils.MaterialTheme
13
+ import dev.apexnative.navigation.viewcontrollers.bottomtabs.CustomTabRowGeometry
13
14
 
14
15
  /**
15
16
  * The view of bottom tabs: the view of each tab made so far fills it, the selected one visible and the others invisible
16
17
  * but laid out, so a switch shows a tab at once and its screens render; the bar's [container] lies over them at the
17
18
  * bottom, the module's bar ([bar], `R.id.bottomTabs`) in it. The container's distance from the bottom ([barOffset]:
18
19
  * `bottomMargin`, and the navigation bar under a compact bar) is this view's own inset, not a layout parameter a root
19
- * may ignore. The window insets go on to every child as they came. Main thread only.
20
+ * may ignore. The custom [row], when the tabs have one, lies right after the container and over the bar. The window
21
+ * insets go on to every child as they came. Main thread only.
20
22
  */
21
23
  @SuppressLint("ViewConstructor")
22
24
  @RestrictTo(RestrictTo.Scope.LIBRARY)
@@ -34,8 +36,25 @@ class BottomTabsLayout(context: Context) : FrameLayout(context) {
34
36
  /** The window insets reached this view: the bar's room and the screens' room may have changed. */
35
37
  internal var onInsets: (() -> Unit)? = null
36
38
 
37
- /** The custom row of the tabs, when every tab draws its cell (`bottomTab.component`); the placer puts it. */
39
+ /**
40
+ * The custom row of the tabs, when every tab draws its cell (`bottomTab.component`): a child right after the
41
+ * container, laid out over the bar, going with the container.
42
+ */
38
43
  internal var row: CustomTabRow? = null
44
+ set(value) {
45
+ if (field === value) return
46
+ field?.let {
47
+ removeView(it)
48
+ it.container = null
49
+ }
50
+ field = value
51
+ if (value == null) return
52
+ (value.parent as? ViewGroup)?.removeView(value)
53
+ value.container = container
54
+ value.place = null
55
+ // Its frame is absolute: the layout below puts it in its parameters.
56
+ addView(value, indexOfChild(container) + 1, LayoutParams(0, 0, Gravity.TOP or Gravity.LEFT))
57
+ }
39
58
 
40
59
  /** The container's distance from the bottom, in pixels. */
41
60
  internal var barOffset = 0
@@ -59,6 +78,16 @@ class BottomTabsLayout(context: Context) : FrameLayout(context) {
59
78
  }
60
79
  }
61
80
 
81
+ /**
82
+ * How far the container goes down to leave the screen whole: from the top of what the bar shows — the container, or
83
+ * the custom row that rises above it — to this view's bottom, in pixels.
84
+ */
85
+ internal val hideDistance: Int
86
+ get() {
87
+ val rowTop = row?.takeIf { it.visibility != View.GONE }?.top ?: container.top
88
+ return (height - minOf(container.top, rowTop)).coerceAtLeast(0)
89
+ }
90
+
62
91
  /** [view] of a tab joins under the bar, invisible until its tab shows. */
63
92
  internal fun addTab(view: View) {
64
93
  if (view.parent === this) return
@@ -85,5 +114,35 @@ class BottomTabsLayout(context: Context) : FrameLayout(context) {
85
114
  override fun onLayout(changed: Boolean, left: Int, top: Int, right: Int, bottom: Int) {
86
115
  super.onLayout(changed, left, top, right, bottom)
87
116
  if (barOffset != 0) container.offsetTopAndBottom(-barOffset)
117
+ row?.let(::placeRow)
118
+ }
119
+
120
+ // The row over the bar where the bar rests (its container's motion the row takes before each frame), when its place
121
+ // changed: laid out there at once, and kept in its parameters, so the next passes measure it and put it there the
122
+ // same way. A hidden bar keeps the row where it was.
123
+ private fun placeRow(row: CustomTabRow) {
124
+ if (container.visibility == View.GONE || bar.width == 0) return
125
+ val barLeft = container.left + bar.left
126
+ val barTop = container.top + bar.top
127
+ val place = row.placeOver(
128
+ barLeft = barLeft,
129
+ barRight = barLeft + bar.width,
130
+ barBottom = barTop + bar.height,
131
+ barHeight = bar.height,
132
+ tabsHeight = height,
133
+ navigationBar = navigationBarInset,
134
+ )
135
+ if (!CustomTabRowGeometry.needsPlacing(row.place, place)) return
136
+ row.place = place
137
+ val params = row.layoutParams as LayoutParams
138
+ params.width = place.right - place.left
139
+ params.height = place.bottom - place.top
140
+ params.leftMargin = place.left
141
+ params.topMargin = place.top
142
+ row.measure(
143
+ MeasureSpec.makeMeasureSpec(params.width, MeasureSpec.EXACTLY),
144
+ MeasureSpec.makeMeasureSpec(params.height, MeasureSpec.EXACTLY),
145
+ )
146
+ row.layout(place.left, place.top, place.right, place.bottom)
88
147
  }
89
148
  }
@@ -11,18 +11,21 @@ import android.view.MotionEvent
11
11
  import android.view.View
12
12
  import android.view.ViewGroup
13
13
  import android.view.ViewOutlineProvider
14
+ import android.view.ViewTreeObserver
14
15
  import android.widget.FrameLayout
15
16
  import android.widget.LinearLayout
16
17
  import dev.apexnative.navigation.customrow.BottomTabsCustomRowOptions
17
18
  import dev.apexnative.navigation.utils.TestIds
19
+ import dev.apexnative.navigation.utils.UiUtils
18
20
  import dev.apexnative.navigation.viewcontrollers.bottomtabs.CustomTabRowGeometry
19
21
 
20
22
  /**
21
- * The custom row of bottom tabs on Android (`bottomTabs.customRow`): a container over the bar, in the window's content,
22
- * that holds the tabs' cells in one row of equal widths in tab order. It owns its place, its look, its visibility and
23
- * the touches: each cell's place takes them, the React elements in the cell don't, and a tap or a long press of a place
24
- * goes to [onPress] or [onLongPress] with the tab's index. Where it lies is the placer's to say (`CustomTabRowPlacer`);
25
- * what it looks like its tabs'. Main thread only.
23
+ * The custom row of bottom tabs on Android (`bottomTabs.customRow`): a container over the bar, a child of the tabs'
24
+ * view right after the bar's container, that holds the tabs' cells in one row of equal widths in tab order. It owns its
25
+ * look, its visibility and the touches: each cell's place takes them, the React elements in the cell don't, and a tap
26
+ * or a long press of a place goes to [onPress] or [onLongPress] with the tab's index. The tabs' view lays it out over
27
+ * the bar ([placeOver]); before each frame it goes with the bar's [container]. What it looks like is its tabs'. Main
28
+ * thread only.
26
29
  */
27
30
  @SuppressLint("ViewConstructor")
28
31
  internal class CustomTabRow(context: Context) : FrameLayout(context) {
@@ -42,11 +45,23 @@ internal class CustomTabRow(context: Context) : FrameLayout(context) {
42
45
  /** A cell's place was long-pressed. */
43
46
  var onLongPress: ((Int) -> Unit)? = null
44
47
 
45
- /** `bottomTabs.customRow` of the tabs, resolved: the placer reads its height and margins. */
48
+ /** `bottomTabs.customRow` of the tabs, resolved: its height and margins say where the row lies. */
46
49
  var options = BottomTabsCustomRowOptions()
47
50
 
48
51
  /**
49
- * Where the placer put the row last, in its parent; none before it did. The frame holds `bottomMargin` below the
52
+ * The bar's container the row goes with: its motion (a ride, a slide, the hide on scroll), its alpha, its height
53
+ * over the screens and whether it shows. The tabs' view sets it as the row joins it.
54
+ */
55
+ var container: View? = null
56
+
57
+ // Before each frame of the window the row is in: it goes with the container.
58
+ private val following = ViewTreeObserver.OnPreDrawListener {
59
+ container?.let(::follow)
60
+ true
61
+ }
62
+
63
+ /**
64
+ * Where the tabs' view put the row last, in it; none before it did. The frame holds `bottomMargin` below the
50
65
  * content: the row draws, clips, casts its shadow, lays its cells and takes touches only over the content, and the
51
66
  * margin below lets the touches through to what is under it.
52
67
  */
@@ -117,10 +132,57 @@ internal class CustomTabRow(context: Context) : FrameLayout(context) {
117
132
  return super.dispatchTouchEvent(event)
118
133
  }
119
134
 
120
- /** Whether the row shows its cells: it hides with the bar. */
121
- fun showWith(bar: View) {
122
- val wanted = if (bar.visibility != View.GONE) View.VISIBLE else View.GONE
135
+ /**
136
+ * Where the row lies over the bar whose edges are [barLeft], [barRight] and [barBottom] and whose height is
137
+ * [barHeight], in the tabs' view of [tabsHeight] with the navigation bar's [navigationBar] at its bottom: its
138
+ * height and margins from [options].
139
+ */
140
+ fun placeOver(barLeft: Int, barRight: Int, barBottom: Int, barHeight: Int, tabsHeight: Int, navigationBar: Int) =
141
+ CustomTabRowGeometry.inTabs(
142
+ barLeft = barLeft,
143
+ barRight = barRight,
144
+ barBottom = barBottom,
145
+ tabsHeight = tabsHeight,
146
+ navigationBar = navigationBar,
147
+ margin = UiUtils.dpToPixels(
148
+ context,
149
+ options.horizontalMargin.get(CustomTabRowGeometry.HORIZONTAL_MARGIN_DP.toDouble()),
150
+ ),
151
+ content = CustomTabRowGeometry.content(
152
+ barHeight = barHeight,
153
+ height = options.height.get(null)?.let { UiUtils.dpToPixels(context, it) },
154
+ extra = UiUtils.dpToPixels(context, CustomTabRowGeometry.EXTRA_HEIGHT_DP.toDouble()),
155
+ ),
156
+ bottomMargin = UiUtils.dpToPixels(context, options.bottomMargin.get(0.0)),
157
+ )
158
+
159
+ override fun onAttachedToWindow() {
160
+ super.onAttachedToWindow()
161
+ viewTreeObserver.addOnPreDrawListener(following)
162
+ }
163
+
164
+ override fun onDetachedFromWindow() {
165
+ viewTreeObserver.removeOnPreDrawListener(following)
166
+ super.onDetachedFromWindow()
167
+ }
168
+
169
+ // The row hides with the bar and takes its motion and its alpha (the tabs' own alpha and motion, and their
170
+ // parents', the row has as their child); it stands as high as the container, and leaves accessibility while its
171
+ // tabs wait to show.
172
+ private fun follow(container: View) {
173
+ val wanted = if (container.visibility != View.GONE) View.VISIBLE else View.GONE
123
174
  if (visibility != wanted) visibility = wanted
175
+ if (alpha != container.alpha) alpha = container.alpha
176
+ if (translationX != container.translationX) translationX = container.translationX
177
+ if (translationY != container.translationY) translationY = container.translationY
178
+ val lift = CustomTabRowGeometry.lift(container.z, elevation)
179
+ if (translationZ != lift) translationZ = lift
180
+ val mode = if (hiddenFromAccessibility()) {
181
+ View.IMPORTANT_FOR_ACCESSIBILITY_NO_HIDE_DESCENDANTS
182
+ } else {
183
+ View.IMPORTANT_FOR_ACCESSIBILITY_AUTO
184
+ }
185
+ if (importantForAccessibility != mode) importantForAccessibility = mode
124
186
  }
125
187
 
126
188
  /** Draws [look]; nothing changes when it is the one drawn. */