@apexnative/navigation 1.0.0-alpha.6 → 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 (236) hide show
  1. package/ApexNavigation.podspec +2 -1
  2. package/CHANGELOG.md +7 -0
  3. package/android/CLAUDE.md +19 -10
  4. package/android/CORE.md +3 -2
  5. package/android/consumer-rules.pro +6 -0
  6. package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
  7. package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
  8. package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
  9. package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
  10. package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
  11. package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
  12. package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
  13. package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
  14. package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
  15. package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
  16. package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
  17. package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
  18. package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +4 -0
  19. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
  20. package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
  21. package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
  22. package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
  23. package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
  24. package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +329 -55
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +26 -5
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +46 -7
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +7 -4
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +36 -38
  39. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
  40. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
  41. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
  42. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
  43. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
  44. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
  45. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
  46. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
  47. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
  48. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
  49. package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
  50. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
  51. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
  52. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
  53. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
  54. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
  55. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
  56. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
  57. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
  58. package/android/src/main/jni/ANNContentHeights.cpp +82 -0
  59. package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
  60. package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
  61. package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
  62. package/android/src/main/res/values/styles.xml +1 -1
  63. package/android/src/main/res/values-night/styles.xml +1 -1
  64. package/cli/doctor/checks/app.js +16 -1
  65. package/cli/project.js +4 -0
  66. package/cli/setup/index.js +5 -0
  67. package/cli/setup/steps/metro.js +46 -0
  68. package/docs-md/architecture/native-screens.md +4 -0
  69. package/docs-md/architecture/theme-and-context.md +131 -12
  70. package/docs-md/architecture/transitions.md +43 -4
  71. package/docs-md/flows/bottom-sheets.md +4 -2
  72. package/docs-md/fundamentals/screens-and-props.md +9 -1
  73. package/docs-md/guides/cli.md +3 -1
  74. package/docs-md/help/troubleshooting.md +1 -1
  75. package/docs-md/index.json +29 -1
  76. package/docs-md/screen-ui/back-and-gestures.md +3 -1
  77. package/docs-md/screen-ui/bar-buttons.md +1 -1
  78. package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
  79. package/docs-md/start/installation.md +16 -1
  80. package/ios/Animations/ANNFrameDriver.h +55 -15
  81. package/ios/Animations/ANNFrameDriver.mm +214 -60
  82. package/ios/Animations/ANNTransitionAnimator.h +26 -5
  83. package/ios/Animations/ANNTransitionAnimator.mm +49 -3
  84. package/ios/Animations/ANNTransitionPlan.h +2 -2
  85. package/ios/Animations/ANNTransitionPlan.mm +10 -0
  86. package/ios/Animations/Elements/ANNElementFinder.h +2 -2
  87. package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
  88. package/ios/Animations/Elements/ANNElementTransition.h +3 -3
  89. package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
  90. package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
  91. package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
  92. package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
  93. package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
  94. package/ios/CLAUDE.md +15 -7
  95. package/ios/CORE.md +1 -1
  96. package/ios/Core/ANNAppDelegate.mm +0 -2
  97. package/ios/Core/ANNDeepLinkQueue.h +6 -5
  98. package/ios/Core/ANNDeepLinkQueue.mm +22 -10
  99. package/ios/Core/ANNSceneDelegate.mm +2 -2
  100. package/ios/Core/ApexNavigation.mm +6 -2
  101. package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
  102. package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
  103. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
  104. package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
  105. package/ios/Layouts/Stack/ANNStackController.h +8 -0
  106. package/ios/Layouts/Stack/ANNStackController.mm +153 -11
  107. package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
  108. package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
  109. package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
  110. package/ios/Options/ANNAnimationOptions.h +2 -1
  111. package/ios/Options/ANNAnimationOptions.mm +1 -1
  112. package/ios/Options/ANNOptions.h +7 -0
  113. package/ios/Options/ANNOptions.mm +45 -0
  114. package/ios/Options/ANNOptionsReader.mm +2 -4
  115. package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
  116. package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
  117. package/ios/Options/Params/ColorParser.h +4 -3
  118. package/ios/Options/Params/ColorParser.mm +22 -4
  119. package/ios/React/ANNSurfaceHostingView.h +8 -0
  120. package/ios/React/ANNSurfaceHostingView.mm +28 -1
  121. package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
  122. package/ios/TurboModules/ANNTurboModule.mm +46 -0
  123. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
  124. package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
  125. package/lib/module/ARCHITECTURE.md +9 -3
  126. package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
  127. package/lib/module/api/app/AppRegistry.js +14 -2
  128. package/lib/module/api/app/AppRegistry.js.map +1 -1
  129. package/lib/module/api/app/createNavigation.js +188 -66
  130. package/lib/module/api/app/createNavigation.js.map +1 -1
  131. package/lib/module/api/events/navigationEvents.js +6 -2
  132. package/lib/module/api/events/navigationEvents.js.map +1 -1
  133. package/lib/module/api/linking/linking.js +11 -0
  134. package/lib/module/api/linking/linking.js.map +1 -1
  135. package/lib/module/api/screens/defineScreen.js +45 -3
  136. package/lib/module/api/screens/defineScreen.js.map +1 -1
  137. package/lib/module/api/screens/nativeScreens.js +31 -6
  138. package/lib/module/api/screens/nativeScreens.js.map +1 -1
  139. package/lib/module/api/screens/registerScreens.js +172 -35
  140. package/lib/module/api/screens/registerScreens.js.map +1 -1
  141. package/lib/module/api/theme/theme.js +5 -0
  142. package/lib/module/api/theme/theme.js.map +1 -1
  143. package/lib/module/engine/commands.js +21 -1
  144. package/lib/module/engine/commands.js.map +1 -1
  145. package/lib/module/engine/layout.js +7 -2
  146. package/lib/module/engine/layout.js.map +1 -1
  147. package/lib/module/engine/nativeScreens.js +9 -0
  148. package/lib/module/engine/nativeScreens.js.map +1 -1
  149. package/lib/module/engine/options.js +5 -4
  150. package/lib/module/engine/options.js.map +1 -1
  151. package/lib/module/engine/optionsChange.js +102 -0
  152. package/lib/module/engine/optionsChange.js.map +1 -0
  153. package/lib/module/engine/refresh.js +85 -0
  154. package/lib/module/engine/refresh.js.map +1 -0
  155. package/lib/module/engine/rememberedOptions.js +60 -0
  156. package/lib/module/engine/rememberedOptions.js.map +1 -1
  157. package/lib/module/engine/screens.js +26 -0
  158. package/lib/module/engine/screens.js.map +1 -1
  159. package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
  160. package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
  161. package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
  162. package/lib/typescript/api/app/createNavigation.d.ts +5 -0
  163. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  164. package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
  165. package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
  166. package/lib/typescript/api/index.d.ts +1 -1
  167. package/lib/typescript/api/index.d.ts.map +1 -1
  168. package/lib/typescript/api/linking/linking.d.ts +8 -0
  169. package/lib/typescript/api/linking/linking.d.ts.map +1 -1
  170. package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
  171. package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
  172. package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
  173. package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
  174. package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
  175. package/lib/typescript/api/theme/theme.d.ts +2 -0
  176. package/lib/typescript/api/theme/theme.d.ts.map +1 -1
  177. package/lib/typescript/engine/commands.d.ts +5 -0
  178. package/lib/typescript/engine/commands.d.ts.map +1 -1
  179. package/lib/typescript/engine/layout.d.ts.map +1 -1
  180. package/lib/typescript/engine/nativeScreens.d.ts +5 -0
  181. package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
  182. package/lib/typescript/engine/optionsChange.d.ts +28 -0
  183. package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
  184. package/lib/typescript/engine/refresh.d.ts +35 -0
  185. package/lib/typescript/engine/refresh.d.ts.map +1 -0
  186. package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
  187. package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
  188. package/lib/typescript/engine/screens.d.ts +7 -0
  189. package/lib/typescript/engine/screens.d.ts.map +1 -1
  190. package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
  191. package/lib/typescript/interfaces/options/animations.d.ts +40 -15
  192. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  193. package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
  194. package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
  195. package/lib/typescript/interfaces/options/modals.d.ts +6 -7
  196. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  197. package/lib/typescript/interfaces/options/stack.d.ts +8 -3
  198. package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
  199. package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
  200. package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
  201. package/metro/acceptPlugin.js +57 -0
  202. package/metro/babelTransformer.js +45 -0
  203. package/metro/index.js +36 -0
  204. package/package.json +3 -1
  205. package/src/ARCHITECTURE.md +9 -3
  206. package/src/adapters/NativeANNTurboModule.ts +7 -0
  207. package/src/api/app/AppRegistry.ts +16 -2
  208. package/src/api/app/createNavigation.ts +198 -66
  209. package/src/api/events/navigationEvents.ts +9 -3
  210. package/src/api/index.ts +1 -0
  211. package/src/api/linking/linking.ts +22 -1
  212. package/src/api/screens/defineScreen.ts +49 -5
  213. package/src/api/screens/nativeScreens.ts +40 -4
  214. package/src/api/screens/registerScreens.tsx +170 -42
  215. package/src/api/theme/theme.ts +5 -0
  216. package/src/engine/commands.ts +21 -0
  217. package/src/engine/layout.ts +11 -2
  218. package/src/engine/nativeScreens.ts +10 -1
  219. package/src/engine/options.ts +6 -8
  220. package/src/engine/optionsChange.ts +107 -0
  221. package/src/engine/refresh.ts +97 -0
  222. package/src/engine/rememberedOptions.ts +70 -0
  223. package/src/engine/screens.tsx +24 -0
  224. package/src/interfaces/ComponentEvents.ts +2 -2
  225. package/src/interfaces/options/animations.ts +40 -15
  226. package/src/interfaces/options/bottomTabs.ts +2 -2
  227. package/src/interfaces/options/buttons.ts +2 -2
  228. package/src/interfaces/options/modals.ts +6 -7
  229. package/src/interfaces/options/stack.ts +8 -3
  230. package/src/interfaces/options/topBar.ts +14 -0
  231. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
  232. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
  233. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
  234. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
  235. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
  236. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
@@ -83,6 +83,10 @@ typedef NS_ENUM(NSInteger, ANNRootShowState) {
83
83
  @property(nonatomic, strong, nullable) ANNFrameDriver *driver;
84
84
  @property(nonatomic, copy, nullable) NSArray<ANNViewTracks *> *tracks;
85
85
  @property(nonatomic) BOOL animating;
86
+ // The new root's view takes no touches while it animates, and whether it took them before: Core Animation draws it
87
+ // elsewhere than where it is (the view itself stays in place), so a tap on the old screen would reach it.
88
+ @property(nonatomic, weak, nullable) UIView *untouchableRoot;
89
+ @property(nonatomic) BOOL rootTouchesBefore;
86
90
  @property(nonatomic) BOOL layersShown;
87
91
  @end
88
92
 
@@ -533,20 +537,43 @@ static __weak ANNRootShow *ANNAnimatingShow;
533
537
  NSMutableArray<ANNViewTracks *> *tracks = [NSMutableArray new];
534
538
  ANNViewAnimationOptions *enter = [ANNAnimationRules playingSide:animation.enter];
535
539
  ANNViewAnimationOptions *exit = [ANNAnimationRules playingSide:animation.exit];
536
- if (enter && rootView) {
537
- [tracks addObject:[[ANNViewTracks alloc] initWithView:rootView
538
- side:enter
539
- alphaBefore:rootView.alpha]];
540
- }
541
- if (exit && snapshot) {
542
- [tracks addObject:[[ANNViewTracks alloc] initWithView:snapshot
543
- side:exit
544
- alphaBefore:snapshot.alpha]];
545
- }
546
540
  NSTimeInterval length =
547
541
  MAX([ANNAnimationRules lengthOfSide:enter], [ANNAnimationRules lengthOfSide:exit]);
542
+ if (UIAccessibilityIsReduceMotionEnabled() && length > 0) {
543
+ // With the system's Reduce Motion on, nothing moves: the new root fades in over the snapshot in the same
544
+ // length, or the snapshot fades out without a new root's view.
545
+ if (rootView) {
546
+ [tracks addObject:[[ANNViewTracks alloc] initWithView:rootView
547
+ fadeFrom:0
548
+ to:rootView.alpha
549
+ duration:length
550
+ alphaBefore:rootView.alpha]];
551
+ } else if (snapshot) {
552
+ [tracks addObject:[[ANNViewTracks alloc] initWithView:snapshot
553
+ fadeFrom:snapshot.alpha
554
+ to:0
555
+ duration:length
556
+ alphaBefore:snapshot.alpha]];
557
+ }
558
+ } else {
559
+ if (enter && rootView) {
560
+ [tracks addObject:[[ANNViewTracks alloc] initWithView:rootView
561
+ side:enter
562
+ alphaBefore:rootView.alpha]];
563
+ }
564
+ if (exit && snapshot) {
565
+ [tracks addObject:[[ANNViewTracks alloc] initWithView:snapshot
566
+ side:exit
567
+ alphaBefore:snapshot.alpha]];
568
+ }
569
+ }
548
570
  show.tracks = tracks;
549
571
  show.animating = YES;
572
+ if (rootView) {
573
+ show.untouchableRoot = rootView;
574
+ show.rootTouchesBefore = rootView.userInteractionEnabled;
575
+ rootView.userInteractionEnabled = NO;
576
+ }
550
577
  ANNAnimatingShow = show;
551
578
  show.driver = [[ANNFrameDriver alloc] initWithClients:tracks length:length];
552
579
  // The layers' state from here on: the chain may start and a newer `setRoot` stops it.
@@ -583,8 +610,8 @@ static __weak ANNRootShow *ANNAnimatingShow;
583
610
  [self finishLayersOf:show];
584
611
  }
585
612
 
586
- // The animation stops at its end state (its end, a newer `setRoot`, a reload, a failure): the views get back what they
587
- // had and the snapshot goes.
613
+ // The animation stops (its end, a newer `setRoot`, a reload, a failure): its animations go, the views get back what
614
+ // they had and the snapshot goes.
588
615
  + (void)stopAnimationOf:(ANNRootShow *)show {
589
616
  if (!show.animating) {
590
617
  return;
@@ -592,13 +619,16 @@ static __weak ANNRootShow *ANNAnimatingShow;
592
619
  show.animating = NO;
593
620
  [show.driver cancel];
594
621
  show.driver = nil;
622
+ show.untouchableRoot.userInteractionEnabled = show.rootTouchesBefore;
623
+ show.untouchableRoot = nil;
595
624
  for (ANNViewTracks *tracks in show.tracks) {
596
625
  [tracks restore];
597
626
  }
598
627
  show.tracks = nil;
599
- // UIKit works out the safe area of the window's root view from where the view is drawn: moved away from the top
600
- // edge by `enter` (from the bottom, scaled down) it has no top inset, and putting the transform back doesn't make
601
- // UIKit work it out again. A layout now gives the root, its bars and its screens the window's safe area back.
628
+ // UIKit works out the safe area of the window's root view from where the view is: moved away from the top edge by
629
+ // a transform it has no top inset, and putting the transform back doesn't make UIKit work it out again. `enter`
630
+ // moves only what Core Animation draws now; the layout stays, so the root, its bars and its screens end with the
631
+ // window's safe area whatever moved them.
602
632
  UIView *rootView = show.root.viewIfLoaded;
603
633
  [rootView setNeedsLayout];
604
634
  [rootView layoutIfNeeded];
@@ -14,6 +14,7 @@
14
14
  #import "ANNScreenRegions.h"
15
15
  #import "ANNScreenSafeArea.h"
16
16
  #import "ANNStackController.h"
17
+ #import "ANNSurfaceHostingView.h"
17
18
  #import "ANNSystemUI.h"
18
19
  #import "ANNTopBarPresenter.h"
19
20
  #import "ANNUtils.h"
@@ -103,10 +104,12 @@
103
104
  }
104
105
 
105
106
  // UIKit names the size transition as the moment the status bar's frame changes (a rotation, a new window size): the
106
- // blur follows it along the animation and takes the final frame at its end.
107
+ // blur follows it along the animation and takes the final frame at its end. The React content takes its new size
108
+ // without animation meanwhile, or what it draws itself is stretched along the animation (`ANNSurfaceHostingView`).
107
109
  - (void)viewWillTransitionToSize:(CGSize)size
108
110
  withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
109
111
  [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
112
+ [ANNSurfaceHostingView followSizeTransition:coordinator];
110
113
  __weak ANNComponentViewController *weakSelf = self;
111
114
  [coordinator
112
115
  animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
@@ -12,13 +12,18 @@ NS_ASSUME_NONNULL_BEGIN
12
12
  from:(UIViewController *)from
13
13
  to:(UIViewController *)to;
14
14
 
15
+ /// The interaction of the swipe back that plays `animator`; nil for any other transition.
16
+ - (nullable id<UIViewControllerInteractiveTransitioning>)interactionForAnimator:
17
+ (id<UIViewControllerAnimatedTransitioning>)animator;
18
+
15
19
  @end
16
20
 
17
21
  /// The stack's delegate while a custom animation may be needed: it answers what the stack answers as its own delegate —
18
22
  /// every method the stack implements goes to the stack — and also the choice of the transition object. A delegate with
19
23
  /// that method turns the iOS 26 content swipe off, so the stack sets this one only while the top's `animations.pop`,
20
- /// the pushed screen's `animations.push` or a running `setStackRoot` is its own, and itself otherwise. Never an
21
- /// interaction controller: the custom transitions aren't interactive, the swipe stays UIKit's.
24
+ /// the pushed screen's `animations.push` or a running `setStackRoot` is its own, and itself otherwise. An interaction
25
+ /// controller only while `answersInteraction`: the stack's own swipe back then plays the top's own pop under the
26
+ /// finger; otherwise UIKit doesn't see the method, and the swipe stays UIKit's.
22
27
  @interface ANNStackAnimatingDelegate : NSObject <UINavigationControllerDelegate>
23
28
 
24
29
  - (instancetype)initWithStack:(UINavigationController<ANNStackAnimating> *)stack
@@ -27,6 +32,10 @@ NS_ASSUME_NONNULL_BEGIN
27
32
  - (instancetype)init NS_UNAVAILABLE;
28
33
  + (instancetype)new NS_UNAVAILABLE;
29
34
 
35
+ /// The top's pop follows the swipe back (`animations.pop.interactive`). UIKit reads the delegate's methods when it is
36
+ /// set: the stack sets the delegate again after a change.
37
+ @property(nonatomic) BOOL answersInteraction;
38
+
30
39
  @end
31
40
 
32
41
  NS_ASSUME_NONNULL_END
@@ -12,16 +12,16 @@
12
12
  return self;
13
13
  }
14
14
 
15
- // The choice of the transition object is this delegate's own; the interaction controller is never answered. The other
16
- // methods of `UINavigationControllerDelegate` are the stack's when the stack has them; nothing outside the protocol is
17
- // forwarded (design 11).
15
+ // The choice of the transition object is this delegate's own; the interaction controller too, answered only while
16
+ // the top's pop follows the swipe. The other methods of `UINavigationControllerDelegate` are the stack's when the
17
+ // stack has them; nothing outside the protocol is forwarded (design 11).
18
18
  static BOOL ANNIsOwnSelector(SEL selector) {
19
19
  SEL animation = @selector(navigationController:
20
20
  animationControllerForOperation:fromViewController:toViewController:);
21
21
  return selector == animation;
22
22
  }
23
23
 
24
- static BOOL ANNIsNeverAnswered(SEL selector) {
24
+ static BOOL ANNIsInteractionSelector(SEL selector) {
25
25
  return selector == @selector(navigationController:interactionControllerForAnimationController:);
26
26
  }
27
27
 
@@ -33,7 +33,7 @@ static BOOL ANNIsDelegateSelector(SEL selector) {
33
33
 
34
34
  // A method of the protocol this delegate passes to the stack.
35
35
  - (BOOL)forwardsToStack:(SEL)selector {
36
- return !ANNIsOwnSelector(selector) && !ANNIsNeverAnswered(selector) &&
36
+ return !ANNIsOwnSelector(selector) && !ANNIsInteractionSelector(selector) &&
37
37
  ANNIsDelegateSelector(selector) && [_stack respondsToSelector:selector];
38
38
  }
39
39
 
@@ -41,8 +41,8 @@ static BOOL ANNIsDelegateSelector(SEL selector) {
41
41
  if (ANNIsOwnSelector(selector)) {
42
42
  return YES;
43
43
  }
44
- if (ANNIsNeverAnswered(selector)) {
45
- return NO;
44
+ if (ANNIsInteractionSelector(selector)) {
45
+ return _answersInteraction;
46
46
  }
47
47
  if ([super respondsToSelector:selector]) {
48
48
  return YES;
@@ -89,4 +89,11 @@ static BOOL ANNIsDelegateSelector(SEL selector) {
89
89
  return [_stack animatorForOperation:operation from:fromVC to:toVC];
90
90
  }
91
91
 
92
+ - (id<UIViewControllerInteractiveTransitioning>)
93
+ navigationController:(UINavigationController *)navigationController
94
+ interactionControllerForAnimationController:
95
+ (id<UIViewControllerAnimatedTransitioning>)animationController {
96
+ return [_stack interactionForAnimator:animationController];
97
+ }
98
+
92
99
  @end
@@ -93,6 +93,14 @@ FOUNDATION_EXPORT NSString *_Nullable ANNStackKeyOfLayout(ANNLayoutNode *node);
93
93
  /// it), or UIKit's More navigation shows the stack's first screen only (its swipe goes back to the More list).
94
94
  - (BOOL)swipeBackIsUIKits:(UIGestureRecognizer *)recognizer;
95
95
 
96
+ /// The swipe of `recognizer` may play the pop: the stack's own swipe (from the leading edge, toward the trailing side)
97
+ /// only while the top's pop follows the finger (`animations.pop.interactive`) and the screen under is in place, UIKit's
98
+ /// swipes of the stack's navigation only while it doesn't. Every other swipe may.
99
+ - (BOOL)swipeBackTakes:(UIGestureRecognizer *)recognizer;
100
+
101
+ /// `recognizer` is the stack's own swipe back, not one of UIKit's.
102
+ - (BOOL)swipeBackIsStacks:(UIGestureRecognizer *)recognizer;
103
+
96
104
  /// The stack has one entry: there is nowhere to go back to.
97
105
  @property(nonatomic, readonly) BOOL hasOneEntry;
98
106
 
@@ -12,7 +12,9 @@
12
12
  #import "ANNSheetPresenter.h"
13
13
  #import "ANNShownScreens.h"
14
14
  #import "ANNStackAnimatingDelegate.h"
15
+ #import "ANNStackPopAnimationOptions.h"
15
16
  #import "ANNStackPresenter.h"
17
+ #import "ANNSwipePopInteraction.h"
16
18
  #import "ANNSystemUI.h"
17
19
  #import "ANNTopBarPresenter.h"
18
20
  #import "ANNTransitionAnimator.h"
@@ -54,6 +56,10 @@ static BOOL ANNIsActive(UIGestureRecognizer *recognizer) {
54
56
  return state == UIGestureRecognizerStateBegan || state == UIGestureRecognizerStateChanged;
55
57
  }
56
58
 
59
+ // How far from the leading edge of the stack's view the stack's own swipe back starts, in points: the strip the side
60
+ // menu leaves to the swipe back (`ANNSideMenuEdgeWidth`).
61
+ static const CGFloat ANNOwnSwipeEdgeWidth = 20;
62
+
57
63
  @interface ANNStackController () <ANNStackDisplay, ANNStackAnimating>
58
64
  @end
59
65
 
@@ -90,6 +96,12 @@ static BOOL ANNIsActive(UIGestureRecognizer *recognizer) {
90
96
  ANNTransitionPlan *_pendingPlan;
91
97
  ANNStackChange _pendingChange;
92
98
  __weak ANNTransitionAnimator *_runningAnimator;
99
+ // The interaction of the swipe back that plays the top's own pop, until UIKit asks for it.
100
+ __weak ANNSwipePopInteraction *_swipeInteraction;
101
+ // The stack's own swipe from the leading edge, on while the top's pop follows the finger: UIKit runs its swipe
102
+ // without asking the delegate for a transition, so that one would only slide.
103
+ UIPanGestureRecognizer *_ownSwipe;
104
+ InteractivePopGestureDelegate *_ownSwipeDelegate;
93
105
  BOOL _gateUpdateScheduled;
94
106
  // The stack is made (its helper set up): its options can be resolved.
95
107
  BOOL _madeUp;
@@ -260,6 +272,16 @@ static BOOL ANNIsActive(UIGestureRecognizer *recognizer) {
260
272
  content:YES
261
273
  barSwipe:barSwipe];
262
274
  }
275
+ if (!_ownSwipe) {
276
+ _ownSwipe = [[UIPanGestureRecognizer alloc] initWithTarget:self
277
+ action:@selector(ownSwipeBack:)];
278
+ _ownSwipe.enabled = _animatingDelegate.answersInteraction;
279
+ [self.view addGestureRecognizer:_ownSwipe];
280
+ }
281
+ _ownSwipeDelegate = [InteractivePopGestureDelegate installOn:_ownSwipe
282
+ stack:self
283
+ content:NO
284
+ barSwipe:barSwipe];
263
285
  [self updateSwipeBack];
264
286
  }
265
287
 
@@ -520,6 +542,9 @@ static BOOL ANNIsActive(UIGestureRecognizer *recognizer) {
520
542
 
521
543
  - (BOOL)isSwipingBack {
522
544
  UINavigationController *navigation = self.shownNavigationController;
545
+ if (navigation == self && ANNIsActive(_ownSwipe)) {
546
+ return YES;
547
+ }
523
548
  if (ANNIsActive(navigation.interactivePopGestureRecognizer)) {
524
549
  return YES;
525
550
  }
@@ -813,15 +838,20 @@ static BOOL ANNIsActive(UIGestureRecognizer *recognizer) {
813
838
  #pragma mark - The custom transitions
814
839
 
815
840
  // UIKit asks while it starts the change the queue asked for: the queue's plan when the operation and the arriving
816
- // entry are the plan's (the back button and the commands go through the queue, so its plan covers them); nil for
817
- // anything else — UIKit's own pops, the interactive swipe (UIKit never asks for it), a zoom alone.
841
+ // entry are the plan's (the back button and the commands go through the queue, so its plan covers them); the swipe
842
+ // back gets the top's own pop only with `animations.pop.interactive` (`swipeAnimatorFrom:to:`); nil for anything
843
+ // else — UIKit's own pops, a zoom alone.
818
844
  - (id<UIViewControllerAnimatedTransitioning>)
819
845
  animatorForOperation:(UINavigationControllerOperation)operation
820
846
  from:(UIViewController *)from
821
847
  to:(UIViewController *)to {
848
+ if ([self isSwipingBack]) {
849
+ return operation == UINavigationControllerOperationPop
850
+ ? [self swipeAnimatorFrom:from to:to]
851
+ : nil;
852
+ }
822
853
  ANNTransitionPlan *plan = _pendingPlan;
823
- if (!plan || operation == UINavigationControllerOperationNone || to != plan.toNode ||
824
- [self isSwipingBack]) {
854
+ if (!plan || operation == UINavigationControllerOperationNone || to != plan.toNode) {
825
855
  return nil;
826
856
  }
827
857
  BOOL matches = _pendingChange == ANNStackChangeReplace ||
@@ -838,16 +868,73 @@ static BOOL ANNIsActive(UIGestureRecognizer *recognizer) {
838
868
  return animator;
839
869
  }
840
870
 
871
+ // The stack's own swipe back of a top whose pop follows the finger (`animations.pop.interactive`, not
872
+ // `enabled: false`): the pop plays under it. Nil otherwise, also when UIKit won't ask for the interaction (the gate
873
+ // hasn't seen the option yet: a change of the default options) or the screen under hasn't settled
874
+ // (`revealedEntrySettled:`): UIKit asks for the transition before the queue takes the swipe and moves those, and the
875
+ // frames take the views as they are now.
876
+ - (nullable ANNTransitionAnimator *)swipeAnimatorFrom:(UIViewController *)from
877
+ to:(UIViewController *)to {
878
+ if (!ANNIsActive(_ownSwipe) || !_animatingDelegate.answersInteraction ||
879
+ ![self isOwnViewController:from] || ![self revealedEntrySettled:to]) {
880
+ return nil;
881
+ }
882
+ ANNStackPopAnimationOptions *pop = [from resolveOptionsWithDefault].animations.pop;
883
+ if (![ANNStackController popFollowsSwipe:pop]) {
884
+ return nil;
885
+ }
886
+ ANNTransitionPlan *plan = [ANNTransitionPlan stackPlanOfKind:ANNTransitionKindPop
887
+ animation:pop
888
+ waitForRender:nil
889
+ waitLimit:0
890
+ from:from
891
+ to:to];
892
+ if (!plan) {
893
+ return nil;
894
+ }
895
+ ANNTransitionAnimator *animator = [[ANNTransitionAnimator alloc] initWithPlan:plan];
896
+ // The interaction keeps itself while it follows the gesture; UIKit keeps it once it starts it.
897
+ ANNSwipePopInteraction *interaction =
898
+ [[ANNSwipePopInteraction alloc] initWithAnimator:animator recognizer:_ownSwipe view:self.view];
899
+ _swipeInteraction = interaction;
900
+ _runningAnimator = animator;
901
+ return animator;
902
+ }
903
+
904
+ // The screen under is in place for the frames: its view made, not parked, not rendering as a lower screen.
905
+ - (BOOL)revealedEntrySettled:(nullable UIViewController *)revealed {
906
+ return revealed.isViewLoaded && ![_lowerRendering objectForKey:revealed] &&
907
+ ![_lowerToRender containsObject:revealed] &&
908
+ ![[self parkedEntries] containsObject:revealed];
909
+ }
910
+
911
+ + (BOOL)popFollowsSwipe:(nullable ANNStackPopAnimationOptions *)pop {
912
+ return [ANNAnimationRules stackIsOwn:pop] && [pop.interactive withDefault:NO] &&
913
+ [pop.enabled withDefault:YES];
914
+ }
915
+
916
+ - (nullable id<UIViewControllerInteractiveTransitioning>)interactionForAnimator:
917
+ (id<UIViewControllerAnimatedTransitioning>)animator {
918
+ ANNSwipePopInteraction *interaction = _swipeInteraction;
919
+ return interaction && interaction.animator == animator ? interaction : nil;
920
+ }
921
+
841
922
  // The animating delegate while a custom animation may be needed: a change with its own plan now, or a top whose
842
923
  // `animations.pop` is its own; the stack itself otherwise. UIKit reads the delegate's methods when it is set, so it is
843
- // set again at each change.
924
+ // set again at each change, and again when the interaction of the swipe comes or goes with the top's
925
+ // `animations.pop.interactive`.
844
926
  - (void)updateAnimationGateWith:(ANNTransitionPlan *)plan {
845
- BOOL needed = plan != nil;
846
- if (!needed) {
847
- UIViewController *top = [self getCurrentChild];
848
- needed = top && [ANNAnimationRules
849
- stackIsOwn:[top resolveOptionsWithDefault].animations.pop];
927
+ UIViewController *top = [self getCurrentChild];
928
+ ANNStackPopAnimationOptions *pop = top ? [top resolveOptionsWithDefault].animations.pop : nil;
929
+ BOOL needed = plan != nil || [ANNAnimationRules stackIsOwn:pop];
930
+ BOOL interactive = [ANNStackController popFollowsSwipe:pop];
931
+ if (_animatingDelegate.answersInteraction != interactive) {
932
+ _animatingDelegate.answersInteraction = interactive;
933
+ if (self.delegate == _animatingDelegate) {
934
+ self.delegate = self;
935
+ }
850
936
  }
937
+ _ownSwipe.enabled = interactive;
851
938
  id<UINavigationControllerDelegate> delegate =
852
939
  needed ? (id<UINavigationControllerDelegate>)_animatingDelegate : self;
853
940
  if (self.delegate != delegate) {
@@ -934,13 +1021,66 @@ static BOOL ANNIsActive(UIGestureRecognizer *recognizer) {
934
1021
  if (top != nil && top != [self getCurrentChild]) {
935
1022
  return YES;
936
1023
  }
937
- BOOL own = recognizer == self.interactivePopGestureRecognizer;
1024
+ BOOL own = recognizer == self.interactivePopGestureRecognizer || recognizer == _ownSwipe;
938
1025
  if (@available(iOS 26.0, *)) {
939
1026
  own = own || recognizer == self.interactiveContentPopGestureRecognizer;
940
1027
  }
941
1028
  return !own && _queue.shown.count < 2;
942
1029
  }
943
1030
 
1031
+ - (BOOL)swipeBackIsStacks:(UIGestureRecognizer *)recognizer {
1032
+ return recognizer != nil && recognizer == _ownSwipe;
1033
+ }
1034
+
1035
+ - (BOOL)swipeBackTakes:(UIGestureRecognizer *)recognizer {
1036
+ BOOL uikits = recognizer == self.interactivePopGestureRecognizer;
1037
+ if (@available(iOS 26.0, *)) {
1038
+ uikits = uikits || recognizer == self.interactiveContentPopGestureRecognizer;
1039
+ }
1040
+ if (recognizer != _ownSwipe && !uikits) {
1041
+ return YES;
1042
+ }
1043
+ NSArray<UIViewController *> *shown = _queue.shown;
1044
+ UIViewController *revealed = shown.count >= 2 ? shown[shown.count - 2] : nil;
1045
+ // The options as they are now, as `swipeAnimatorFrom:to:` reads them: the gate may lag a change of the defaults,
1046
+ // and a swipe of ours without the pop would be a pop the finger can't hold.
1047
+ UIViewController *top = self.topViewController;
1048
+ BOOL ownPlays = _ownSwipe.enabled && _animatingDelegate.answersInteraction && top &&
1049
+ top == [self getCurrentChild] && [self isOwnViewController:top] &&
1050
+ [ANNStackController
1051
+ popFollowsSwipe:[top resolveOptionsWithDefault].animations.pop] &&
1052
+ [self revealedEntrySettled:revealed];
1053
+ if (uikits) {
1054
+ return !ownPlays;
1055
+ }
1056
+ return ownPlays && [self panStartsAtLeadingEdge:_ownSwipe];
1057
+ }
1058
+
1059
+ // A pan that starts near the leading edge of the stack's view and moves more across than along, toward the trailing
1060
+ // side: where UIKit's edge swipe starts.
1061
+ - (BOOL)panStartsAtLeadingEdge:(UIPanGestureRecognizer *)pan {
1062
+ UIView *view = self.view;
1063
+ CGPoint at = [pan locationInView:view];
1064
+ CGPoint move = [pan translationInView:view];
1065
+ BOOL rightToLeft =
1066
+ view.effectiveUserInterfaceLayoutDirection == UIUserInterfaceLayoutDirectionRightToLeft;
1067
+ CGFloat start = at.x - move.x;
1068
+ CGFloat fromEdge = rightToLeft ? view.bounds.size.width - start : start;
1069
+ if (move.x == 0 && move.y == 0) {
1070
+ move = [pan velocityInView:view];
1071
+ }
1072
+ CGFloat along = rightToLeft ? -move.x : move.x;
1073
+ return fromEdge <= ANNOwnSwipeEdgeWidth && along > fabs(move.y);
1074
+ }
1075
+
1076
+ // The stack's own swipe began: a pop as UIKit's swipe makes it, which the stack follows as the swipe back
1077
+ // (`followUIKitTransition`); the transition asks for the top's pop and its interaction (`swipeAnimatorFrom:to:`).
1078
+ - (void)ownSwipeBack:(UIPanGestureRecognizer *)recognizer {
1079
+ if (recognizer.state == UIGestureRecognizerStateBegan) {
1080
+ [self popViewControllerAnimated:YES];
1081
+ }
1082
+ }
1083
+
944
1084
  - (BOOL)hasOneEntry {
945
1085
  return _queue.entries.count < 2;
946
1086
  }
@@ -983,6 +1123,7 @@ static BOOL ANNIsActive(UIGestureRecognizer *recognizer) {
983
1123
  BOOL edge = !(isBool && ![value boolValue]);
984
1124
  BOOL content = !value || (isBool && [value boolValue]);
985
1125
  _edgeSwipeDelegate.enabled = edge;
1126
+ _ownSwipeDelegate.enabled = edge;
986
1127
  _moreEdgeSwipeDelegate.enabled = edge;
987
1128
  _contentSwipeDelegate.enabled = content;
988
1129
  _moreContentSwipeDelegate.enabled = content;
@@ -992,6 +1133,7 @@ static BOOL ANNIsActive(UIGestureRecognizer *recognizer) {
992
1133
  - (void)swipeBackFollowsBar {
993
1134
  BOOL hidden = [ANNSystemUI barHiddenOf:self.shownNavigationController];
994
1135
  _edgeSwipeDelegate.barHidden = hidden;
1136
+ _ownSwipeDelegate.barHidden = hidden;
995
1137
  _contentSwipeDelegate.barHidden = hidden;
996
1138
  _moreEdgeSwipeDelegate.barHidden = hidden;
997
1139
  _moreContentSwipeDelegate.barHidden = hidden;
@@ -0,0 +1,26 @@
1
+ #import <UIKit/UIKit.h>
2
+
3
+ @class ANNTransitionAnimator;
4
+
5
+ NS_ASSUME_NONNULL_BEGIN
6
+
7
+ /// The interaction of a swipe back that plays the screen's own pop (`animations.pop.interactive`). It follows the
8
+ /// stack's own swipe from the leading edge (UIKit runs its swipe without asking the delegate for a transition), and
9
+ /// shows the animator's frames as far as the finger went across `view`, in the direction of the swipe back. Letting go
10
+ /// finishes the pop after a flick back or past half way, and puts the screen back otherwise; the rest plays faster
11
+ /// after a quick flick, never slower, and UIKit ends its part at the same pace. An interaction UIKit never starts goes
12
+ /// with the end of the gesture. Main queue.
13
+ @interface ANNSwipePopInteraction : NSObject <UIViewControllerInteractiveTransitioning>
14
+
15
+ - (instancetype)initWithAnimator:(ANNTransitionAnimator *)animator
16
+ recognizer:(UIPanGestureRecognizer *)recognizer
17
+ view:(UIView *)view NS_DESIGNATED_INITIALIZER;
18
+
19
+ - (instancetype)init NS_UNAVAILABLE;
20
+ + (instancetype)new NS_UNAVAILABLE;
21
+
22
+ @property(nonatomic, weak, readonly, nullable) ANNTransitionAnimator *animator;
23
+
24
+ @end
25
+
26
+ NS_ASSUME_NONNULL_END