@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
@@ -2,39 +2,84 @@
2
2
  #import "ANNAssert.h"
3
3
  #import "ANNCurve.h"
4
4
  #import "ANNTransformParts.h"
5
- #import <QuartzCore/QuartzCore.h>
5
+ #include <cmath>
6
6
 
7
- // The display link holds its target strongly and the run loop holds the link: the driver is reached weakly, so a driver
8
- // nobody keeps any more stops at its next frame.
9
- @interface ANNFrameTarget : NSObject
10
- @property(nonatomic, weak) id owner;
11
- @end
7
+ // The samples per second of a client's values: a frame of a 120 Hz display each.
8
+ static const double ANNSamplesPerSecond = 120;
12
9
 
13
- @interface ANNFrameDriver ()
14
- - (void)frame:(CADisplayLink *)link;
15
- @end
10
+ // At most this many samples per client: a longer transition samples less often.
11
+ static const NSUInteger ANNMaxSamples = 2400;
16
12
 
17
- @implementation ANNFrameTarget
13
+ NSArray<NSNumber *> *ANNSampleTimes(NSTimeInterval end, NSTimeInterval length,
14
+ NSArray<NSNumber *> *marks) {
15
+ end = MIN(MAX(end, 0), length);
16
+ NSMutableArray<NSNumber *> *candidates = [NSMutableArray arrayWithObject:@0];
17
+ double step = MAX(1 / ANNSamplesPerSecond, end / ANNMaxSamples);
18
+ NSUInteger count = (NSUInteger)std::floor(end / step);
19
+ for (NSUInteger index = 1; index <= count; index++) {
20
+ [candidates addObject:@(index * step)];
21
+ }
22
+ for (NSNumber *mark in marks) {
23
+ double time = mark.doubleValue;
24
+ if (std::isfinite(time) && time > 0 && time < end) {
25
+ [candidates addObject:mark];
26
+ }
27
+ }
28
+ [candidates addObject:@(end)];
29
+ [candidates addObject:@(length)];
30
+ [candidates sortUsingSelector:@selector(compare:)];
31
+ NSMutableArray<NSNumber *> *times = [NSMutableArray arrayWithCapacity:candidates.count];
32
+ for (NSNumber *time in candidates) {
33
+ // Samples closer than a microsecond are one.
34
+ if (times.count == 0 || time.doubleValue - times.lastObject.doubleValue > 1e-6) {
35
+ [times addObject:time];
36
+ }
37
+ }
38
+ return times;
39
+ }
18
40
 
19
- - (void)frame:(CADisplayLink *)link {
20
- ANNFrameDriver *driver = _owner;
21
- if (!driver) {
22
- [link invalidate];
23
- return;
41
+ CAKeyframeAnimation *ANNKeyframes(NSString *keyPath, NSArray *values, NSArray<NSNumber *> *times,
42
+ NSTimeInterval length) {
43
+ if (length <= 0 || values.count == 0 || values.count != times.count) {
44
+ return nil;
45
+ }
46
+ NSMutableArray<NSNumber *> *keyTimes = [NSMutableArray arrayWithCapacity:times.count];
47
+ for (NSNumber *time in times) {
48
+ [keyTimes addObject:@(MIN(MAX(time.doubleValue / length, 0.0), 1.0))];
24
49
  }
25
- [driver frame:link];
50
+ CAKeyframeAnimation *animation = [CAKeyframeAnimation animationWithKeyPath:keyPath];
51
+ animation.values = values;
52
+ animation.keyTimes = keyTimes;
53
+ animation.calculationMode = kCAAnimationLinear;
54
+ animation.duration = length;
55
+ animation.fillMode = kCAFillModeBoth;
56
+ animation.removedOnCompletion = NO;
57
+ return animation;
26
58
  }
27
59
 
28
- @end
60
+ NSString *ANNAnimationKey(NSString *keyPath) {
61
+ return [@"ApexNavigation." stringByAppendingString:keyPath];
62
+ }
29
63
 
30
64
  @implementation ANNFrameDriver {
31
65
  NSArray<id<ANNFrameClient>> *_clients;
32
66
  NSTimeInterval _length;
33
- CADisplayLink *_link;
34
- CFTimeInterval _start;
35
67
  dispatch_block_t _done;
36
- // The driver keeps itself while its frames run: the transition that started it needn't.
37
- ANNFrameDriver *_running;
68
+ BOOL _running;
69
+ BOOL _starting;
70
+ // When the animations were added; they start at the commit of the current transaction, a moment later.
71
+ CFTimeInterval _start;
72
+ // The driver keeps itself while its animations run: the transition that started it needn't.
73
+ ANNFrameDriver *_keep;
74
+ // A transition that follows a gesture: the layer whose time stops, its timing before, the clock's time the
75
+ // animations started at, the `done` that tells how it ended, and whether its end plays.
76
+ CALayer *_clock;
77
+ float _clockSpeed;
78
+ CFTimeInterval _clockBegin;
79
+ CFTimeInterval _clockOffset;
80
+ CFTimeInterval _base;
81
+ void (^_pausedDone)(BOOL finished);
82
+ BOOL _ending;
38
83
  }
39
84
 
40
85
  - (instancetype)initWithClients:(NSArray<id<ANNFrameClient>> *)clients
@@ -51,7 +96,11 @@
51
96
  }
52
97
 
53
98
  - (BOOL)isRunning {
54
- return _link != nil;
99
+ return _running;
100
+ }
101
+
102
+ - (NSTimeInterval)length {
103
+ return _length;
55
104
  }
56
105
 
57
106
  - (void)startWithDone:(dispatch_block_t)done {
@@ -60,69 +109,174 @@
60
109
  ANNCurveSelfCheck();
61
110
  ANNTransformPartsSelfCheck();
62
111
  #endif
63
- if (_link) {
112
+ if (_running || _done || _pausedDone) {
64
113
  return;
65
114
  }
66
115
  _done = [done copy];
67
- _running = self;
68
- [self applyAtTime:0];
69
- ANNFrameTarget *target = [ANNFrameTarget new];
70
- target.owner = self;
71
- _link = [CADisplayLink displayLinkWithTarget:target selector:@selector(frame:)];
72
- [_link addToRunLoop:NSRunLoop.mainRunLoop forMode:NSRunLoopCommonModes];
116
+ _running = YES;
117
+ _keep = self;
118
+ _starting = YES;
73
119
  _start = CACurrentMediaTime();
120
+ __weak ANNFrameDriver *weakSelf = self;
121
+ [CATransaction begin];
122
+ [CATransaction setDisableActions:YES];
123
+ // Called once every animation added in this transaction ended or was removed (with its layer too), and at once
124
+ // without any.
125
+ [CATransaction setCompletionBlock:^{
126
+ [weakSelf animationsEnded];
127
+ }];
128
+ @try {
129
+ for (id<ANNFrameClient> client in _clients) {
130
+ [client addAnimationsWithLength:_length];
131
+ }
132
+ } @finally {
133
+ [CATransaction commit];
134
+ _starting = NO;
135
+ }
74
136
  }
75
137
 
76
- - (void)frame:(CADisplayLink *)link {
77
- if (link != _link) {
78
- [link invalidate];
138
+ - (void)animationsEnded {
139
+ if (!_running) {
79
140
  return;
80
141
  }
81
- NSTimeInterval time = link.targetTimestamp - _start;
82
- if (time < 0) {
83
- time = 0;
84
- }
85
- if (time >= _length) {
86
- [self applyAtTime:_length];
87
- [self stop];
88
- dispatch_block_t done = _done;
89
- _done = nil;
90
- _running = nil;
91
- if (done) {
92
- done();
93
- }
142
+ // `done` never comes inside `startWithDone:`, nor before the length passed: a transition with nothing to play (no
143
+ // client, every element skipped) or whose animations went early still takes its time, as on Android.
144
+ __weak ANNFrameDriver *weakSelf = self;
145
+ NSTimeInterval left = _length - (CACurrentMediaTime() - _start);
146
+ if (_starting || left > 0) {
147
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(MAX(left, 0) * NSEC_PER_SEC)),
148
+ dispatch_get_main_queue(), ^{
149
+ [weakSelf animationsEnded];
150
+ });
94
151
  return;
95
152
  }
96
- [self applyAtTime:time];
153
+ _running = NO;
154
+ dispatch_block_t done = _done;
155
+ _done = nil;
156
+ _keep = nil;
157
+ if (done) {
158
+ done();
159
+ }
160
+ }
161
+
162
+ - (void)cancel {
163
+ ANNAssertMainQueue();
164
+ if (!_running) {
165
+ return;
166
+ }
167
+ _running = NO;
168
+ _done = nil;
169
+ _pausedDone = nil;
170
+ _keep = nil;
171
+ [self releaseClock];
97
172
  }
98
173
 
99
- - (void)applyAtTime:(NSTimeInterval)time {
174
+ #pragma mark - Following a gesture
175
+
176
+ - (void)startPausedOn:(CALayer *)clock done:(void (^)(BOOL finished))done {
177
+ ANNAssertMainQueue();
178
+ if (_running || _done || _pausedDone) {
179
+ return;
180
+ }
181
+ _pausedDone = [done copy];
182
+ _running = YES;
183
+ _keep = self;
184
+ _clock = clock;
185
+ _clockSpeed = clock.speed;
186
+ _clockBegin = clock.beginTime;
187
+ _clockOffset = clock.timeOffset;
188
+ // The clock stops at its time now: what else animates under it holds still, and the animations added now start
189
+ // there (Core Animation gives an animation without a begin time the layer's time at the commit).
190
+ _base = [clock convertTime:CACurrentMediaTime() fromLayer:nil];
100
191
  [CATransaction begin];
101
192
  [CATransaction setDisableActions:YES];
102
- for (id<ANNFrameClient> client in _clients) {
103
- [client applyAtTime:time];
193
+ clock.speed = 0;
194
+ clock.beginTime = 0;
195
+ clock.timeOffset = _base;
196
+ @try {
197
+ for (id<ANNFrameClient> client in _clients) {
198
+ [client addAnimationsWithLength:_length];
199
+ }
200
+ } @finally {
201
+ [CATransaction commit];
202
+ }
203
+ }
204
+
205
+ - (void)setProgress:(CGFloat)progress {
206
+ ANNAssertMainQueue();
207
+ if (!_running || !_pausedDone || _ending) {
208
+ return;
104
209
  }
210
+ [CATransaction begin];
211
+ [CATransaction setDisableActions:YES];
212
+ _clock.timeOffset = _base + MIN(MAX(progress, 0.0), 1.0) * _length;
105
213
  [CATransaction commit];
106
214
  }
107
215
 
108
- - (void)stop {
109
- [_link invalidate];
110
- _link = nil;
216
+ - (void)finishWithSpeed:(CGFloat)speed {
217
+ [self playOnForward:YES speed:speed];
111
218
  }
112
219
 
113
- - (void)cancel {
220
+ - (void)rewindWithSpeed:(CGFloat)speed {
221
+ [self playOnForward:NO speed:speed];
222
+ }
223
+
224
+ // The clock runs again from where it shows, forward or back, and `done` comes once it reached the end or the start.
225
+ - (void)playOnForward:(BOOL)forward speed:(CGFloat)speed {
114
226
  ANNAssertMainQueue();
115
- if (!_link) {
227
+ if (!_running || !_pausedDone || _ending) {
116
228
  return;
117
229
  }
118
- [self applyAtTime:_length];
119
- [self stop];
120
- _done = nil;
121
- _running = nil;
230
+ _ending = YES;
231
+ speed = MAX(speed, 1.0);
232
+ CALayer *clock = _clock;
233
+ CFTimeInterval at = clock.timeOffset - _base;
234
+ CFTimeInterval left = forward ? _length - at : at;
235
+ CALayer *parent = clock.superlayer;
236
+ CFTimeInterval now = parent ? [parent convertTime:CACurrentMediaTime() fromLayer:nil]
237
+ : CACurrentMediaTime();
238
+ [CATransaction begin];
239
+ [CATransaction setDisableActions:YES];
240
+ // The clock's time is (parent's time - beginTime) * speed + timeOffset: it goes on from where it shows.
241
+ clock.beginTime = now;
242
+ clock.speed = (float)(forward ? speed : -speed);
243
+ [CATransaction commit];
244
+ __weak ANNFrameDriver *weakSelf = self;
245
+ dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(MAX(left / speed, 0) * NSEC_PER_SEC)),
246
+ dispatch_get_main_queue(), ^{
247
+ [weakSelf pausedEndedForward:forward];
248
+ });
122
249
  }
123
250
 
124
- - (void)dealloc {
125
- [_link invalidate];
251
+ - (void)pausedEndedForward:(BOOL)forward {
252
+ if (!_running || !_pausedDone) {
253
+ return;
254
+ }
255
+ _running = NO;
256
+ void (^done)(BOOL) = _pausedDone;
257
+ _pausedDone = nil;
258
+ _keep = nil;
259
+ // The clock's time back and the caller's restore in one commit: between two, the animations would show at the
260
+ // clock's own time for a frame (past their end, or part way).
261
+ [CATransaction begin];
262
+ [self releaseClock];
263
+ done(forward);
264
+ [CATransaction commit];
265
+ }
266
+
267
+ // The clock gets back the timing it had before the gesture.
268
+ - (void)releaseClock {
269
+ CALayer *clock = _clock;
270
+ _clock = nil;
271
+ if (!clock) {
272
+ return;
273
+ }
274
+ [CATransaction begin];
275
+ [CATransaction setDisableActions:YES];
276
+ clock.speed = _clockSpeed;
277
+ clock.beginTime = _clockBegin;
278
+ clock.timeOffset = _clockOffset;
279
+ [CATransaction commit];
126
280
  }
127
281
 
128
282
  @end
@@ -12,10 +12,10 @@ NS_ASSUME_NONNULL_BEGIN
12
12
  /// above the leaving one (push, `setStackRoot`, show) or below it (pop, dismissal), gets its final frame and lays out
13
13
  /// (React gets its final size), and hides (alpha 0) until the start; a forward transition starts after its wait
14
14
  /// (`ANNTransitionWait`), a backward one at once. Then one frame driver moves the sides that play, the elements in
15
- /// place and the shared elements' copies; a side that doesn't play doesn't move. At the end, before
16
- /// `completeTransition:`, the elements get their values back, the copies go and the React views show again, and both
17
- /// views get back the transform and the alpha they had. Never interactive, never cancelled; `completeTransition:` never
18
- /// comes inside `animateTransition:`. Main queue.
15
+ /// place and the shared elements' copies, in Core Animation; a side that doesn't play doesn't move. At the end, before
16
+ /// `completeTransition:`, the animations go, the copies go and the React views show again, and both views get back the
17
+ /// transform and the alpha they had. Only a pop of the swipe back follows a gesture (`animateFollowingGesture:`) and
18
+ /// can end where it began; `completeTransition:` never comes inside `animateTransition:`. Main queue.
19
19
  @interface ANNTransitionAnimator : NSObject <UIViewControllerAnimatedTransitioning>
20
20
 
21
21
  - (instancetype)initWithPlan:(ANNTransitionPlan *)plan NS_DESIGNATED_INITIALIZER;
@@ -25,8 +25,29 @@ NS_ASSUME_NONNULL_BEGIN
25
25
 
26
26
  @property(nonatomic, strong, readonly) ANNTransitionPlan *plan;
27
27
 
28
+ /// The start of an interactive transition (a pop of the swipe back, which never waits): as `animateTransition:`, with
29
+ /// the frames held at their start until `setProgress:` moves them (`ANNFrameDriver startPausedOn:`); then
30
+ /// `finishWithSpeed:` or `rewindWithSpeed:`, after the context's own finish or cancel, plays the end.
31
+ - (void)animateFollowingGesture:(id<UIViewControllerContextTransitioning>)transitionContext;
32
+
33
+ /// The frames show at `progress` (0 to 1) of the length.
34
+ - (void)setProgress:(CGFloat)progress;
35
+
36
+ /// The rest plays (`speed` times as fast, 1 or more), and the transition completes.
37
+ - (void)finishWithSpeed:(CGFloat)speed;
38
+
39
+ /// The frames play back to the start (`speed` times as fast, 1 or more), and the transition completes cancelled.
40
+ - (void)rewindWithSpeed:(CGFloat)speed;
41
+
42
+ /// The seconds the frames take: the plan's length, or the end of a longer element.
43
+ @property(nonatomic, readonly) NSTimeInterval length;
44
+
45
+ /// The transition completed (or was never started).
46
+ @property(nonatomic, readonly) BOOL isCompleted;
47
+
28
48
  /// A destroy or a reload during the transition: the wait or the frames stop at the end state, every value written goes
29
- /// back, and the transition completes so that UIKit isn't left half way. Nothing once it completed.
49
+ /// back, and the transition completes so that UIKit isn't left half way (an interactive one finishes first). Nothing
50
+ /// once it completed.
30
51
  - (void)cancel;
31
52
 
32
53
  @end
@@ -33,6 +33,9 @@ static void ANNUpdateRootLayoutOf(UIViewController *node) {
33
33
  NSArray<id<ANNFrameClient>> *_clients;
34
34
  BOOL _began;
35
35
  BOOL _completed;
36
+ // The transition follows a gesture (`animateFollowingGesture:`), and its end was chosen.
37
+ BOOL _followsGesture;
38
+ BOOL _endChosen;
36
39
  // The container's views of the transition that got a depth for it, and the depth each had.
37
40
  NSArray<UIView *> *_depthViews;
38
41
  NSArray<NSNumber *> *_depthsBefore;
@@ -118,6 +121,33 @@ static void ANNUpdateRootLayoutOf(UIViewController *node) {
118
121
  [self begin];
119
122
  }
120
123
 
124
+ - (void)animateFollowingGesture:(id<UIViewControllerContextTransitioning>)transitionContext {
125
+ _followsGesture = YES;
126
+ [self animateTransition:transitionContext];
127
+ }
128
+
129
+ - (void)setProgress:(CGFloat)progress {
130
+ [_driver setProgress:progress];
131
+ }
132
+
133
+ - (void)finishWithSpeed:(CGFloat)speed {
134
+ _endChosen = YES;
135
+ [_driver finishWithSpeed:speed];
136
+ }
137
+
138
+ - (void)rewindWithSpeed:(CGFloat)speed {
139
+ _endChosen = YES;
140
+ [_driver rewindWithSpeed:speed];
141
+ }
142
+
143
+ - (NSTimeInterval)length {
144
+ return _driver ? _driver.length : _plan.length;
145
+ }
146
+
147
+ - (BOOL)isCompleted {
148
+ return _completed || !_context;
149
+ }
150
+
121
151
  - (void)animationEnded:(BOOL)transitionCompleted {
122
152
  _wait = nil;
123
153
  _driver = nil;
@@ -187,8 +217,9 @@ static void ANNUpdateRootLayoutOf(UIViewController *node) {
187
217
  duration:_plan.length
188
218
  alphaBefore:_leavingAlpha];
189
219
  }
190
- // The arriving view shows from the start with its own alpha unless a track moves it.
191
- if (_arriving && !arrivingTracks.animatesAlpha) {
220
+ // The arriving view gets its own alpha back: an `opacity` animation of its tracks shows over it from the start, in
221
+ // the same transaction.
222
+ if (_arriving) {
192
223
  _arriving.alpha = _arrivingAlpha;
193
224
  }
194
225
  if (arrivingTracks.hasTracks) {
@@ -197,7 +228,7 @@ static void ANNUpdateRootLayoutOf(UIViewController *node) {
197
228
  if (leavingTracks.hasTracks) {
198
229
  [clients addObject:leavingTracks];
199
230
  }
200
- // Each element plays on its own: one whose frame fails goes back and stops, the others and the screens go on.
231
+ // Each element plays on its own: one whose animations fail goes back and stays, the others and the screens go on.
201
232
  for (ANNElementTransition *element in elements) {
202
233
  [clients addObject:[[ANNIsolatedElement alloc] initWithClient:element name:element.name]];
203
234
  }
@@ -210,6 +241,13 @@ static void ANNUpdateRootLayoutOf(UIViewController *node) {
210
241
  }
211
242
  __weak ANNTransitionAnimator *weakSelf = self;
212
243
  _driver = [[ANNFrameDriver alloc] initWithClients:clients length:_plan.length];
244
+ if (_followsGesture) {
245
+ [_driver startPausedOn:container.layer
246
+ done:^(BOOL finished) {
247
+ [weakSelf complete];
248
+ }];
249
+ return;
250
+ }
213
251
  [_driver startWithDone:^{
214
252
  [weakSelf complete];
215
253
  }];
@@ -288,8 +326,16 @@ static BOOL ANNSideTilts(ANNViewAnimationOptions *side) {
288
326
  }
289
327
  [_wait cancel];
290
328
  _wait = nil;
329
+ // An interactive transition ends at its end state too: UIKit completes only one whose end was chosen.
330
+ if (_followsGesture && !_endChosen) {
331
+ _endChosen = YES;
332
+ [_context finishInteractiveTransition];
333
+ }
334
+ // A stopped clock gets its time back in the commit that removes the animations.
335
+ [CATransaction begin];
291
336
  [_driver cancel];
292
337
  [self complete];
338
+ [CATransaction commit];
293
339
  }
294
340
 
295
341
  @end
@@ -19,8 +19,8 @@ typedef NS_ENUM(NSInteger, ANNTransitionKind) {
19
19
  ANNTransitionKindDismiss,
20
20
  };
21
21
 
22
- /// The screen fade of a transition with elements and no screen animation of its own, or of a dismissed popover with
23
- /// nothing to play.
22
+ /// The screen fade of a transition with elements and no screen animation of its own, of a dismissed popover with
23
+ /// nothing to play, or of any transition while the system's Reduce Motion is on (then nothing else plays).
24
24
  typedef NS_ENUM(NSInteger, ANNScreenFade) {
25
25
  ANNScreenFadeNone,
26
26
  /// The arriving screen from 0 to its alpha.
@@ -43,6 +43,16 @@ static const NSTimeInterval ANNElementsScreenFade = 0.3;
43
43
  length = MAX(length, [ANNAnimationRules lengthOfElements:_elements shared:_sharedElements]);
44
44
  length = MAX(length, _screenFadeDuration);
45
45
  _length = length;
46
+ // With the system's Reduce Motion on, nothing moves: the transition is a fade of the same length, the arriving
47
+ // screen in going forward, the leaving one out otherwise.
48
+ if (UIAccessibilityIsReduceMotionEnabled() && length > 0) {
49
+ _enter = nil;
50
+ _exit = nil;
51
+ _elements = @[];
52
+ _sharedElements = @[];
53
+ _screenFade = _isForward ? ANNScreenFadeArrivingIn : ANNScreenFadeLeavingOut;
54
+ _screenFadeDuration = length;
55
+ }
46
56
  }
47
57
  return self;
48
58
  }
@@ -69,8 +69,8 @@ FOUNDATION_EXPORT BOOL ANNElementStep(NSString *element, NS_NOESCAPE dispatch_bl
69
69
  /// The line in the log for the element named `element`, skipped for `reason`: it shows and hides with its screen.
70
70
  FOUNDATION_EXPORT void ANNElementSkipped(NSString *element, NSString *reason);
71
71
 
72
- /// An element's frames, each in `ANNElementStep`: after a frame that fails, the element's values go back
73
- /// (`-restore`), and it gets no frame more.
72
+ /// An element's animations, made in `ANNElementStep`: when making them fails, the element's values go back
73
+ /// (`-restore`) and it doesn't move; the rest of the transition plays.
74
74
  @interface ANNIsolatedElement : NSObject <ANNFrameClient>
75
75
 
76
76
  - (instancetype)initWithClient:(id<ANNFrameClient>)client
@@ -182,13 +182,13 @@ BOOL ANNElementStep(NSString *element, NS_NOESCAPE dispatch_block_t work) {
182
182
 
183
183
  #pragma mark - ANNFrameClient
184
184
 
185
- - (void)applyAtTime:(NSTimeInterval)time {
185
+ - (void)addAnimationsWithLength:(NSTimeInterval)length {
186
186
  if (_failed) {
187
187
  return;
188
188
  }
189
189
  id<ANNFrameClient> client = _client;
190
190
  if (!ANNElementStep(_name, ^{
191
- [client applyAtTime:time];
191
+ [client addAnimationsWithLength:length];
192
192
  })) {
193
193
  _failed = YES;
194
194
  [self restore];
@@ -6,9 +6,9 @@
6
6
 
7
7
  NS_ASSUME_NONNULL_BEGIN
8
8
 
9
- /// One `elementTransitions[]` item on its React view: the view's own `transform` and `alpha` animate in place, its
10
- /// parents clip it as usual; at the end, at a cancel and at a destroy the values read before come back. Nothing else of
11
- /// the React view changes (no frame, no subview, never `layer.zPosition`).
9
+ /// One `elementTransitions[]` item on its React view: its `transform` and `opacity` animate in place, its parents clip
10
+ /// it as usual; at the end, at a cancel and at a destroy the animations go and the view shows its own values. Nothing
11
+ /// of the React view is written (no value, no frame, no subview, never `layer.zPosition`).
12
12
  @interface ANNElementTransition : NSObject <ANNFrameClient>
13
13
 
14
14
  /// The items found: each `id` first in the leaving screen's React view, then in the arriving one; an item not found or
@@ -58,8 +58,8 @@
58
58
 
59
59
  #pragma mark - ANNFrameClient
60
60
 
61
- - (void)applyAtTime:(NSTimeInterval)time {
62
- [_tracks applyAtTime:time];
61
+ - (void)addAnimationsWithLength:(NSTimeInterval)length {
62
+ [_tracks addAnimationsWithLength:length];
63
63
  }
64
64
 
65
65
  - (NSTimeInterval)endTime {
@@ -9,9 +9,9 @@ NS_ASSUME_NONNULL_BEGIN
9
9
  /// One shared pair in flight: a plain view of the core, the copy, flies in the transition's container from the `from`
10
10
  /// view to the `to` view with two pictures of them (`renderInContext:` at the screen's scale). The React views are
11
11
  /// neither moved nor changed: they are only `hidden` while the copy flies and get back what they had before the
12
- /// transition completes. All geometry is in the container's coordinates. Each frame the copy's size, center, visible
13
- /// part (a mask), corner radius and layer transform go from the `from` values to the `to` values on the pair's curve,
14
- /// delay and duration.
12
+ /// transition completes. All geometry is in the container's coordinates. The copy's size, center, visible part (a
13
+ /// mask), corner radius and layer transform go from the `from` values to the `to` values on the pair's curve, delay and
14
+ /// duration, in Core Animation's animations of the copy's layers.
15
15
  ///
16
16
  /// A view (not a text): both pictures fill the copy, the copy clips to its corners, the `to` picture fades in over the
17
17
  /// flight. A text (React's paragraph): the pictures keep their aspect, as tall as the copy, at the line's start (the