@apexnative/navigation 1.0.0-alpha.1 → 1.0.0-alpha.2

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 (125) hide show
  1. package/CHANGELOG.md +16 -0
  2. package/README.md +147 -51
  3. package/android/CLAUDE.md +10 -4
  4. package/android/CORE.md +4 -1
  5. package/android/RECIPES.md +4 -3
  6. package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +5 -4
  7. package/android/src/main/java/dev/apexnative/navigation/react/ErrorTexts.kt +75 -60
  8. package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +3 -2
  9. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +1 -1
  10. package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +5 -4
  11. package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +7 -6
  12. package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +3 -35
  13. package/android/src/main/java/dev/apexnative/navigation/react/events/EventContract.kt +226 -0
  14. package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +101 -87
  15. package/android/src/main/java/dev/apexnative/navigation/react/events/ScreenAppearance.kt +0 -6
  16. package/android/src/main/java/dev/apexnative/navigation/utils/UiUtils.kt +3 -3
  17. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabItems.kt +3 -3
  18. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +12 -8
  19. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +12 -11
  20. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +2 -2
  21. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +25 -0
  22. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +1 -1
  23. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +1 -3
  24. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +4 -7
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuPresenter.kt +16 -7
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuRules.kt +8 -1
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +2 -1
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +2 -1
  29. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +5 -5
  30. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStep.kt +31 -0
  31. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +85 -27
  32. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +5 -4
  33. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionTargets.kt +9 -6
  34. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionViews.kt +100 -0
  35. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +45 -1
  36. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +3 -5
  37. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +3 -5
  38. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +3 -5
  39. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +2 -0
  40. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +4 -6
  41. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +4 -6
  42. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +4 -3
  43. package/cli/app.js +1 -1
  44. package/cli/project.js +6 -2
  45. package/cli/setup/index.js +13 -0
  46. package/cli/setup/steps/appDelegate.js +66 -11
  47. package/cli/setup/steps/sceneDelegate.js +108 -0
  48. package/docs-md/architecture/transitions.md +1 -1
  49. package/docs-md/guides/deep-links.md +15 -33
  50. package/docs-md/start/installation.md +1 -0
  51. package/ios/Animations/ANNTransitionAnimator.mm +13 -11
  52. package/ios/Animations/ANNTransitionWait.mm +6 -7
  53. package/ios/Animations/Elements/ANNElementFinder.h +57 -0
  54. package/ios/Animations/Elements/ANNElementFinder.mm +174 -2
  55. package/ios/Animations/Elements/ANNElementTransition.h +7 -5
  56. package/ios/Animations/Elements/ANNElementTransition.mm +17 -19
  57. package/ios/Animations/Elements/ANNSharedElementFlight.h +13 -8
  58. package/ios/Animations/Elements/ANNSharedElementFlight.mm +59 -24
  59. package/ios/CLAUDE.md +10 -2
  60. package/ios/CORE.md +3 -2
  61. package/ios/Contract/ANNCommandAnswer.h +4 -3
  62. package/ios/Contract/ANNCommandAnswer.mm +12 -2
  63. package/ios/Contract/ANNCommandRunner.h +2 -2
  64. package/ios/Contract/ANNCommandRunner.mm +1 -1
  65. package/ios/Contract/ANNErrorTexts.h +6 -2
  66. package/ios/Contract/ANNErrorTexts.mm +18 -10
  67. package/ios/Core/ANNDeepLinkQueue.h +20 -1
  68. package/ios/Core/ANNDeepLinkQueue.mm +107 -12
  69. package/ios/Core/ANNErrorHandler.h +8 -3
  70. package/ios/Core/ANNErrorHandler.mm +17 -1
  71. package/ios/Core/ANNEventContract.h +246 -0
  72. package/ios/Core/ANNEventContract.mm +225 -0
  73. package/ios/Core/ANNEventEmitter.h +5 -1
  74. package/ios/Core/ANNEventEmitter.mm +117 -80
  75. package/ios/Core/ANNHinge.mm +6 -4
  76. package/ios/Core/ANNSceneDelegate.mm +25 -15
  77. package/ios/Core/ANNSystemUI.h +3 -2
  78. package/ios/Core/ANNSystemUI.mm +7 -0
  79. package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +6 -4
  80. package/ios/Layouts/Modal/ANNModalCoordinator.mm +1 -1
  81. package/ios/Layouts/Modal/ANNModalPresentation.h +8 -5
  82. package/ios/Layouts/Modal/ANNModalPresentation.mm +12 -12
  83. package/ios/Layouts/Modal/ANNSheetPresenter.mm +83 -1
  84. package/ios/Layouts/Root/ANNRootCoordinator.mm +12 -3
  85. package/ios/Layouts/Screen/ANNComponentPresenter.h +11 -3
  86. package/ios/Layouts/Screen/ANNComponentPresenter.mm +34 -6
  87. package/ios/Layouts/Screen/ANNComponentViewController.mm +28 -7
  88. package/ios/Layouts/Screen/ANNNativeScreenContext.mm +7 -5
  89. package/ios/Layouts/Screen/ANNScreenInsets.mm +6 -4
  90. package/ios/Layouts/Screen/ANNScreenRegions.mm +9 -6
  91. package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -7
  92. package/ios/Options/ANNSheetOptions.h +4 -1
  93. package/ios/Public/ApexNavigation/ANNSceneDelegate.h +3 -0
  94. package/ios/RECIPES.md +2 -1
  95. package/ios/TurboModules/ANNTurboEventEmitter.mm +4 -35
  96. package/ios/TurboModules/ANNTurboModule.mm +5 -2
  97. package/lib/module/ARCHITECTURE.md +7 -4
  98. package/lib/module/api/app/createNavigation.js +2 -0
  99. package/lib/module/api/app/createNavigation.js.map +1 -1
  100. package/lib/module/api/linking/dispatchedLinks.js +23 -0
  101. package/lib/module/api/linking/dispatchedLinks.js.map +1 -0
  102. package/lib/module/interfaces/contract/errors.json +275 -0
  103. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  104. package/lib/typescript/api/linking/dispatchedLinks.d.ts +7 -0
  105. package/lib/typescript/api/linking/dispatchedLinks.d.ts.map +1 -0
  106. package/lib/typescript/interfaces/Events.d.ts +9 -0
  107. package/lib/typescript/interfaces/Events.d.ts.map +1 -1
  108. package/lib/typescript/interfaces/options/animations.d.ts +17 -13
  109. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  110. package/lib/typescript/interfaces/options/modals.d.ts +15 -11
  111. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  112. package/lib/typescript/interfaces/options/sideMenu.d.ts +7 -6
  113. package/lib/typescript/interfaces/options/sideMenu.d.ts.map +1 -1
  114. package/lib/typescript/interfaces/options/system.d.ts +4 -3
  115. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  116. package/package.json +1 -1
  117. package/src/ARCHITECTURE.md +7 -4
  118. package/src/api/app/createNavigation.ts +2 -0
  119. package/src/api/linking/dispatchedLinks.ts +18 -0
  120. package/src/interfaces/Events.ts +10 -0
  121. package/src/interfaces/contract/errors.json +275 -0
  122. package/src/interfaces/options/animations.ts +17 -13
  123. package/src/interfaces/options/modals.ts +15 -11
  124. package/src/interfaces/options/sideMenu.ts +7 -6
  125. package/src/interfaces/options/system.ts +4 -3
@@ -2,6 +2,7 @@
2
2
  #import <UIKit/UIKit.h>
3
3
 
4
4
  @class ANNSharedElementTransitionOptions;
5
+ @class ANNTransitionViews;
5
6
 
6
7
  NS_ASSUME_NONNULL_BEGIN
7
8
 
@@ -18,18 +19,19 @@ NS_ASSUME_NONNULL_BEGIN
18
19
  /// grows, the last when it shrinks.
19
20
  @interface ANNSharedElementFlight : NSObject <ANNFrameClient>
20
21
 
21
- /// The flights of the pairs whose two views are found (`fromId` in the leaving screen, `toId` in the arriving one) and
22
- /// have a size; the others are skipped and their views stay as they are. The pictures are taken now, then the React
23
- /// views hide and the copies go into `container` by the `zIndex` of their `to` views, ascending (no `layer.zPosition`).
22
+ /// The flights of the pairs whose two views are found (`fromId` in the leaving screen, `toId` in the arriving one); the
23
+ /// others are skipped and their views stay as they are. A pair found that can't fly — a view without a size, a
24
+ /// picture that can't be taken, a place that can't be worked out, an exception in its start (`ANNElementStep`) — is
25
+ /// skipped too, with a line in the log. The pictures are taken now, then the React views hide and the copies go into
26
+ /// `container` by the `zIndex` of their `to` views, ascending (no `layer.zPosition`).
24
27
  + (NSArray<ANNSharedElementFlight *> *)
25
- flightsOf:(nullable NSArray<ANNSharedElementTransitionOptions *> *)pairs
26
- leavingRoot:(nullable UIView *)leavingRoot
27
- arrivingRoot:(nullable UIView *)arrivingRoot
28
- container:(UIView *)container;
28
+ flightsOf:(nullable NSArray<ANNSharedElementTransitionOptions *> *)pairs
29
+ views:(ANNTransitionViews *)views
30
+ container:(UIView *)container;
29
31
 
30
32
  /// Every pair's `toId` is found in the arriving screen (the wait's test).
31
33
  + (BOOL)allTargetsFound:(nullable NSArray<ANNSharedElementTransitionOptions *> *)pairs
32
- arrivingRoot:(nullable UIView *)arrivingRoot;
34
+ views:(ANNTransitionViews *)views;
33
35
 
34
36
  - (instancetype)init NS_UNAVAILABLE;
35
37
  + (instancetype)new NS_UNAVAILABLE;
@@ -37,6 +39,9 @@ NS_ASSUME_NONNULL_BEGIN
37
39
  /// The copy that flies (a view of the core in the container).
38
40
  @property(nonatomic, strong, readonly) UIView *copyView;
39
41
 
42
+ /// The pair's two ids, in quotes: its name in the log.
43
+ @property(nonatomic, copy, readonly) NSString *name;
44
+
40
45
  @end
41
46
 
42
47
  NS_ASSUME_NONNULL_END
@@ -103,6 +103,25 @@ static ANNFlightEnd ANNFlightEndOf(UIView *view, UIView *screenRoot, UIView *con
103
103
  return end;
104
104
  }
105
105
 
106
+ static BOOL ANNIsFiniteRect(CGRect rect) {
107
+ return isfinite(rect.origin.x) && isfinite(rect.origin.y) && isfinite(rect.size.width) &&
108
+ isfinite(rect.size.height);
109
+ }
110
+
111
+ static BOOL ANNFlightEndIsFinite(ANNFlightEnd end) {
112
+ const CATransform3D t = end.transform;
113
+ const CGFloat values[] = {t.m11, t.m12, t.m13, t.m14, t.m21, t.m22, t.m23, t.m24,
114
+ t.m31, t.m32, t.m33, t.m34, t.m41, t.m42, t.m43, t.m44};
115
+ for (CGFloat value : values) {
116
+ if (!isfinite(value)) {
117
+ return NO;
118
+ }
119
+ }
120
+ return isfinite(end.center.x) && isfinite(end.center.y) && isfinite(end.size.width) &&
121
+ isfinite(end.size.height) && isfinite(end.cornerRadius) && isfinite(end.visibleRadius) &&
122
+ ANNIsFiniteRect(end.visible);
123
+ }
124
+
106
125
  // The picture of the view's layer and its sublayers at the screen's scale, in the layer's own coordinates.
107
126
  static UIImage *ANNPictureOf(UIView *view) {
108
127
  CGSize size = view.bounds.size;
@@ -140,15 +159,10 @@ static UIImage *ANNPictureOf(UIView *view) {
140
159
  BOOL _restored;
141
160
  }
142
161
 
143
- + (NSString *)textOf:(Text *)value {
144
- NSString *text = [value withDefault:nil];
145
- return [text isKindOfClass:NSString.class] && text.length > 0 ? text : nil;
146
- }
147
-
148
162
  + (BOOL)allTargetsFound:(NSArray<ANNSharedElementTransitionOptions *> *)pairs
149
- arrivingRoot:(UIView *)arrivingRoot {
163
+ views:(ANNTransitionViews *)views {
150
164
  for (ANNSharedElementTransitionOptions *pair in pairs) {
151
- if (![ANNElementFinder viewWithNativeID:[self textOf:pair.toId] inView:arrivingRoot]) {
165
+ if (![views arrivingViewWithNativeID:ANNNativeIDOf(pair.toId)]) {
152
166
  return NO;
153
167
  }
154
168
  }
@@ -156,27 +170,31 @@ static UIImage *ANNPictureOf(UIView *view) {
156
170
  }
157
171
 
158
172
  + (NSArray<ANNSharedElementFlight *> *)
159
- flightsOf:(NSArray<ANNSharedElementTransitionOptions *> *)pairs
160
- leavingRoot:(UIView *)leavingRoot
161
- arrivingRoot:(UIView *)arrivingRoot
162
- container:(UIView *)container {
173
+ flightsOf:(NSArray<ANNSharedElementTransitionOptions *> *)pairs
174
+ views:(ANNTransitionViews *)views
175
+ container:(UIView *)container {
163
176
  NSMutableArray<ANNSharedElementFlight *> *flights = [NSMutableArray new];
164
177
  NSHashTable<UIView *> *taken = [NSHashTable weakObjectsHashTable];
165
178
  for (ANNSharedElementTransitionOptions *pair in pairs) {
166
- UIView *from = [ANNElementFinder viewWithNativeID:[self textOf:pair.fromId]
167
- inView:leavingRoot];
168
- UIView *to = [ANNElementFinder viewWithNativeID:[self textOf:pair.toId]
169
- inView:arrivingRoot];
179
+ NSString *fromId = ANNNativeIDOf(pair.fromId);
180
+ NSString *toId = ANNNativeIDOf(pair.toId);
181
+ UIView *from = [views leavingViewWithNativeID:fromId];
182
+ UIView *to = [views arrivingViewWithNativeID:toId];
170
183
  if (!from || !to || from == to || [taken containsObject:from] ||
171
184
  [taken containsObject:to]) {
172
185
  continue;
173
186
  }
174
- ANNSharedElementFlight *flight = [[self alloc] initWithPair:pair
175
- from:from
176
- to:to
177
- leavingRoot:leavingRoot
178
- arrivingRoot:arrivingRoot
179
- container:container];
187
+ NSString *name = [NSString stringWithFormat:@"'%@' -> '%@'", fromId, toId];
188
+ __block ANNSharedElementFlight *flight = nil;
189
+ ANNElementStep(name, ^{
190
+ flight = [[self alloc] initWithPair:pair
191
+ from:from
192
+ to:to
193
+ leavingRoot:views.leavingRoot
194
+ arrivingRoot:views.arrivingRoot
195
+ container:container
196
+ name:name];
197
+ });
180
198
  if (flight) {
181
199
  [taken addObject:from];
182
200
  [taken addObject:to];
@@ -193,10 +211,18 @@ static UIImage *ANNPictureOf(UIView *view) {
193
211
  }
194
212
  return a->_zIndex < b->_zIndex ? NSOrderedAscending : NSOrderedDescending;
195
213
  }];
214
+ // A copy that can't take off goes away, and its pair's views show as they were.
215
+ NSMutableArray<ANNSharedElementFlight *> *flying = [NSMutableArray new];
196
216
  for (ANNSharedElementFlight *flight in ordered) {
197
- [flight takeOffIn:container];
217
+ if (ANNElementStep(flight.name, ^{
218
+ [flight takeOffIn:container];
219
+ })) {
220
+ [flying addObject:flight];
221
+ } else {
222
+ [flight restore];
223
+ }
198
224
  }
199
- return ordered;
225
+ return flying;
200
226
  }
201
227
 
202
228
  - (instancetype)initWithPair:(ANNSharedElementTransitionOptions *)pair
@@ -204,19 +230,27 @@ static UIImage *ANNPictureOf(UIView *view) {
204
230
  to:(UIView *)to
205
231
  leavingRoot:(UIView *)leavingRoot
206
232
  arrivingRoot:(UIView *)arrivingRoot
207
- container:(UIView *)container {
233
+ container:(UIView *)container
234
+ name:(NSString *)name {
208
235
  // A view of no size gives no picture: the pair is skipped and both views stay as they are.
209
236
  if (from.bounds.size.width <= 0 || from.bounds.size.height <= 0 || to.bounds.size.width <= 0 ||
210
237
  to.bounds.size.height <= 0) {
238
+ ANNElementSkipped(name, @"a view of the pair has no size, it isn't laid out");
211
239
  return nil;
212
240
  }
213
241
  if (!(self = [super init])) {
214
242
  return nil;
215
243
  }
244
+ _name = [name copy];
216
245
  _fromView = from;
217
246
  _toView = to;
218
247
  _from = ANNFlightEndOf(from, leavingRoot, container);
219
248
  _to = ANNFlightEndOf(to, arrivingRoot, container);
249
+ // A place that isn't a number would stop the copy's layer with an exception at its first frame.
250
+ if (!ANNFlightEndIsFinite(_from) || !ANNFlightEndIsFinite(_to)) {
251
+ ANNElementSkipped(name, @"the place of a view of the pair can't be worked out");
252
+ return nil;
253
+ }
220
254
  _text = ANNIsText(from) || ANNIsText(to);
221
255
  _grows = _to.size.height >= _from.size.height;
222
256
  _rightToLeft =
@@ -231,6 +265,7 @@ static UIImage *ANNPictureOf(UIView *view) {
231
265
  UIImage *fromImage = ANNPictureOf(from);
232
266
  UIImage *toImage = ANNPictureOf(to);
233
267
  if (!fromImage || !toImage) {
268
+ ANNElementSkipped(name, @"no picture of a view of the pair can be taken");
234
269
  return nil;
235
270
  }
236
271
  _copy = [UIView new];
package/ios/CLAUDE.md CHANGED
@@ -58,9 +58,9 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
58
58
  - Preview — `Layouts/Stack/`: `ANNPreviewPush`; `Layouts/Screen/`: `ANNPreviewInteraction`
59
59
  - Animations, transitions — `Animations/` (plan and animator at the top; `Elements/` for shared elements and zoom; `Interpolators/` for curves and tracks)
60
60
  - System bars, orientation, direction — `Core/`: `ANNSystemUI`, `ANNSystemUIRules`, `ANNAppDirection`, `ANNNodePresenter` (the answers to UIKit)
61
- - Events and state — `Core/`: `ANNEventEmitter`, `ANNAppearanceDispatcher`, `ANNScreenAppearance`, `ANNNavigationState`; `TurboModules/`: `ANNTurboEventEmitter`
61
+ - Events and state — `Core/`: `ANNEventEmitter`, `ANNEventContract` (names and keys, made by `scripts/contract.mjs`), `ANNAppearanceDispatcher`, `ANNScreenAppearance`, `ANNNavigationState`; `TurboModules/`: `ANNTurboEventEmitter`
62
62
  - Options: parsing, merging, defaults — `Options/`: `ANNOptions`, `ANNOptionField`, `ANNOptionsReader`, `ANNDefaultOptions`, `ANNOptionsCache`; `Core/`: `ANNOptionsRepaint`
63
- - Error texts — `Contract/`: `ANNErrorTexts`; `Core/`: `ANNErrorHandler`
63
+ - Error texts — `Contract/`: `ANNErrorTexts` (made from `src/interfaces/contract/errors.json` by `scripts/contract.mjs`); `Core/`: `ANNErrorHandler`
64
64
  - Links — `Core/`: `ANNDeepLinkQueue`; the `getLaunchLink` method of `ANNTurboModule`
65
65
  - Key commands — `Core/`: `ANNKeyCommands`; `Layouts/Screen/`: `ANNComponentViewController`
66
66
  - Hinge, regions — `Core/`: `ANNHinge`; `Layouts/Screen/`: `ANNScreenRegions`; `Layouts/Arrangement/`: `ANNArrangementState`
@@ -114,6 +114,8 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
114
114
  `descendantDidAppear:`.
115
115
  - Hold and release of the navigation state come in pairs; a `setRoot` chain with layout modals or overlays holds it
116
116
  until its answer.
117
+ - UIKit draws a dismissal without animation too (a sheet's dim goes a frame before its card): what goes out of sight
118
+ that way goes under a snapshot of the window (the old root's modals at `setRoot`, `startTransitionOf:done:`).
117
119
  - Use an `@available` check for newer OS APIs, and `#if __IPHONE_OS_VERSION_MAX_ALLOWED >= …` for names of a newer SDK.
118
120
  - React Native hears of the app's light or dark style only from its hosting views in the window. A view that stands in
119
121
  for one (`ANNSlotScreenView`, the screens of one tree) posts the trait change itself, with the window filter of
@@ -123,6 +125,12 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
123
125
  (the root, a modal, an overlay: their record is listed and parked by then), and the modal's end steps inside UIKit's
124
126
  completions and delegate calls (`protectEndOf:showing:step:`, `protectBatch:step:`); the overlay's guard is
125
127
  `protectWaitOf:step:`.
128
+ - Each step of a transition's element (a pair's start and take-off, an element's start, every frame and restore
129
+ through `ANNIsolatedElement`) runs in `ANNElementStep`: an exception skips that element only. A new step of an
130
+ element goes there too, or one bad element stops the transition (a frame's exception ends the app). The elements of
131
+ a transition are found in one walk per screen (`ANNTransitionViews`).
132
+ - An `ANNCommandAnswer` that goes away unanswered rejects (`notAnswered:`), but only once nothing holds it: a record,
133
+ list or queue that keeps an answer still answers it on every exit, or the promise waits as long as the record lives.
126
134
  - On iOS 26 UIKit draws a screen's large title, for a whole pop or swipe back, inside the scroll view the bar follows
127
135
  for the top edge: a detached scroll view or a list below the bar hides it. `ANNLargeTitlePresenter` lays its title
128
136
  host over the screen's view for the transition and fills the band behind the title from the screen's top
package/ios/CORE.md CHANGED
@@ -50,7 +50,8 @@ dispatcher.
50
50
  - **Appearance dispatcher** (`ANNAppearanceDispatcher`): the only sender of the appearance events; it also ends a
51
51
  transition in the fixed order and resolves the command's answer.
52
52
  - **Event emitter** (`ANNEventEmitter`, `ANNTurboEventEmitter`): sends `ANN.<Name>` to JS on the main queue, and drops
53
- events of a JavaScript that went.
53
+ events of a JavaScript that went. The names and keys come from `ANNEventContract`, made from the event types of
54
+ `src/interfaces/` (`scripts/contract.mjs`).
54
55
 
55
56
  ## How a command flows
56
57
 
@@ -127,7 +128,7 @@ Each of these owns one rule. Call it; do not copy it.
127
128
  - `ANNModalCoordinator`, `ANNModal`: One record per modal, its states and waiting commands. Called by: Commands, root, state, repaint, reload.
128
129
  - `ANNPresentationGate`: The one queue of every UIKit present and dismiss in the main window. Called by: Modals, root, reload, search.
129
130
  - `ANNOverlayCoordinator`: Overlays, each in its own window, kept over a new root. Called by: Commands, root, reload.
130
- - `ANNSystemUI`: The system UI of the windows: marks groups, coalesces, asks UIKit once per turn; who decides. `ANNSystemUIRules` has the pure rules. Called by: Screens, containers, `ANNShownScreens`, `mergeOptions`.
131
+ - `ANNSystemUI`: The system UI of the windows: marks groups, coalesces, asks UIKit once per turn; who decides; after a status bar ask, the screens' status bar blurs take its frame. `ANNSystemUIRules` has the pure rules. Called by: Screens, containers, `ANNShownScreens`, `mergeOptions`.
131
132
  - `ANNReloadCoordinator`: The generation of the JavaScript; the reset after a reload. Called by: Runner, emitter, answer.
132
133
  - Stack answer id: `ANNStackAnswerIdOf(entry, command)` in `ANNStackQueue.h`: the id a stack command answers with. `push` answers the node's own id (a pair's id); every other command, the entry's options screen (a pair's primary). Called by: The queue, `ANNStackCommands`, `ANNPreviewPush`, `ANNSplitViewCommands`.
133
134
  - Waited screens: `waitedComponentViewControllers`: the screens a wait is about (a pair: the ones it puts in the window). `ANNStackScreensOfEntry` is every screen of an entry, for what means all of them. Called by: `ANNRenderWait` owners: root, stack queue, modals, overlays, `ANNSplitViewCommands`.
@@ -3,14 +3,15 @@
3
3
  NS_ASSUME_NONNULL_BEGIN
4
4
 
5
5
  /// The one answer of a command with a promise: the first `resolve:` or `reject:` answers, any later one does nothing,
6
- /// and nothing answers once the JavaScript that sent the command went. `ANNCommandRunner` makes it. Main queue.
6
+ /// and nothing answers once the JavaScript that sent the command went. One that goes away unanswered while its
7
+ /// JavaScript runs rejects `[ANNErrorTexts notAnswered:]`. `ANNCommandRunner` makes it. Main queue.
7
8
  @interface ANNCommandAnswer : NSObject
8
9
 
9
10
  - (instancetype)initWithCommandName:(NSString *)commandName
10
11
  commandId:(nullable NSString *)commandId
11
12
  generation:(NSUInteger)generation
12
- resolve:(RCTPromiseResolveBlock)resolve
13
- reject:(RCTPromiseRejectBlock)reject NS_DESIGNATED_INITIALIZER;
13
+ resolve:(nullable RCTPromiseResolveBlock)resolve
14
+ reject:(nullable RCTPromiseRejectBlock)reject NS_DESIGNATED_INITIALIZER;
14
15
 
15
16
  - (instancetype)init NS_UNAVAILABLE;
16
17
  + (instancetype)new NS_UNAVAILABLE;
@@ -41,6 +41,16 @@ static NSSet<NSString *> *ANNCompletedCommandNames(void) {
41
41
  return self;
42
42
  }
43
43
 
44
+ // The last reference went without an answer: nothing took the command, or what took it let it go. The promise of a
45
+ // JavaScript that still runs rejects, so it never waits forever. Any thread: the promise blocks are React Native's,
46
+ // safe from any thread, and the generation is readable from any thread.
47
+ - (void)dealloc {
48
+ if (_answered || !self.isCurrent) {
49
+ return;
50
+ }
51
+ [ANNErrorHandler reject:_reject of:_commandName text:[ANNErrorTexts notAnswered:_commandName]];
52
+ }
53
+
44
54
  - (BOOL)isCurrent {
45
55
  return _generation == ANNReloadCoordinator.generation;
46
56
  }
@@ -77,7 +87,7 @@ static NSSet<NSString *> *ANNCompletedCommandNames(void) {
77
87
  reason:reason]];
78
88
  }
79
89
  }
80
- resolve(value);
90
+ [ANNErrorHandler resolve:resolve of:_commandName value:value];
81
91
  if (_commandId && [ANNCompletedCommandNames() containsObject:_commandName]) {
82
92
  double completionTime = floor(NSDate.date.timeIntervalSince1970 * 1000);
83
93
  [ApexNavigation.eventEmitter sendCommandCompleted:_commandName
@@ -93,7 +103,7 @@ static NSSet<NSString *> *ANNCompletedCommandNames(void) {
93
103
  RCTPromiseRejectBlock reject = _reject;
94
104
  _resolve = nil;
95
105
  _reject = nil;
96
- [ANNErrorHandler reject:reject text:text];
106
+ [ANNErrorHandler reject:reject of:_commandName text:text];
97
107
  }
98
108
 
99
109
  @end
@@ -22,8 +22,8 @@ typedef void (^ANNCommandBody)(id _Nullable layout, ANNCommandAnswer *answer);
22
22
  + (void)run:(NSString *)name
23
23
  commandId:(nullable NSString *)commandId
24
24
  what:(NSString *)what
25
- resolve:(RCTPromiseResolveBlock)resolve
26
- reject:(RCTPromiseRejectBlock)reject
25
+ resolve:(nullable RCTPromiseResolveBlock)resolve
26
+ reject:(nullable RCTPromiseRejectBlock)reject
27
27
  check:(nullable ANNCommandCheck)check
28
28
  body:(ANNCommandBody)body;
29
29
 
@@ -37,7 +37,7 @@ static NSString *ANNRejectionOf(NSException *exception, NSString *what) {
37
37
  error = ANNRejectionOf(exception, what);
38
38
  }
39
39
  if (error) {
40
- [ANNErrorHandler reject:reject text:error];
40
+ [ANNErrorHandler reject:reject of:name text:error];
41
41
  return;
42
42
  }
43
43
  }
@@ -1,3 +1,5 @@
1
+ // Made by scripts/contract.mjs from src/interfaces/contract/errors.json: change that file and run the script again.
2
+
1
3
  #import <Foundation/Foundation.h>
2
4
 
3
5
  NS_ASSUME_NONNULL_BEGIN
@@ -51,12 +53,12 @@ FOUNDATION_EXPORT NSExceptionName const ANNCommandRejectionException;
51
53
  + (NSString *)stackTakesScreens:(NSString *)stackId type:(NSString *)type;
52
54
  + (NSString *)emptyStack:(NSString *)stackId;
53
55
  + (NSString *)emptyTabs:(NSString *)tabsId;
56
+ /// Only Android checks it: its tab bar shows at most 5 tabs.
54
57
  + (NSString *)tooManyTabs:(NSString *)tabsId count:(NSUInteger)count;
55
58
  + (NSString *)splitViewColumns:(NSString *)splitViewId;
59
+ /// Only iOS checks it: Android has pairs on every version.
56
60
  + (NSString *)pairUnsupported:(NSString *)pairId;
57
61
  + (NSString *)pairNeedsTwoScreens:(NSString *)pairId;
58
-
59
- /// The other cases of the layout check: the texts both platforms use meanwhile.
60
62
  + (NSString *)nodeWithoutName:(NSString *)type nodeId:(NSString *)nodeId;
61
63
  + (NSString *)rootMissing;
62
64
  + (NSString *)tabsInTabs:(NSString *)tabsId;
@@ -77,6 +79,8 @@ FOUNDATION_EXPORT NSExceptionName const ANNCommandRejectionException;
77
79
  + (NSString *)couldNot:(NSString *)what reason:(NSString *)reason;
78
80
  /// A command whose part of the native core is not written yet.
79
81
  + (NSString *)notDoneYet:(NSString *)method;
82
+ /// A command that nothing in the native core answered: what took it let it go, or nothing took it.
83
+ + (NSString *)notAnswered:(NSString *)method;
80
84
 
81
85
  /// An `ANNCommandRejectionException` with `text` as its reason: the command that runs rejects with `text` as it is.
82
86
  + (NSException *)rejection:(NSString *)text;
@@ -1,3 +1,5 @@
1
+ // Made by scripts/contract.mjs from src/interfaces/contract/errors.json: change that file and run the script again.
2
+
1
3
  #import "ANNErrorTexts.h"
2
4
 
3
5
  NSExceptionName const ANNCommandRejectionException = @"ANNCommandRejectionException";
@@ -59,8 +61,8 @@ static NSString *ANNText(NSString *format, ...) {
59
61
  }
60
62
 
61
63
  + (NSString *)columnNotStack:(NSString *)componentId column:(NSString *)column {
62
- return ANNText(@"the '%@' column of the split view of the screen '%@' is not a stack", column,
63
- componentId);
64
+ return ANNText(@"the '%@' column of the split view of the screen '%@' is not a stack",
65
+ column, componentId);
64
66
  }
65
67
 
66
68
  + (NSString *)noScreensForStack:(NSString *)componentId {
@@ -74,7 +76,8 @@ static NSString *ANNText(NSString *format, ...) {
74
76
  #pragma mark - Root, modals, the app
75
77
 
76
78
  + (NSString *)setRootReplaced:(NSString *)rootId {
77
- return ANNText(@"setRoot was replaced by a newer setRoot before the root '%@' finished showing",
79
+ return ANNText(@"setRoot was replaced by a newer setRoot before the root '%@' finished "
80
+ @"showing",
78
81
  rootId);
79
82
  }
80
83
 
@@ -141,8 +144,8 @@ static NSString *ANNText(NSString *format, ...) {
141
144
  }
142
145
 
143
146
  + (NSString *)tooManyTabs:(NSString *)tabsId count:(NSUInteger)count {
144
- return ANNText(@"the tabs '%@' have %lu tabs: Android shows at most 5", tabsId,
145
- (unsigned long)count);
147
+ return ANNText(@"the tabs '%@' have %lu tabs: Android shows at most 5",
148
+ tabsId, (unsigned long)count);
146
149
  }
147
150
 
148
151
  + (NSString *)splitViewColumns:(NSString *)splitViewId {
@@ -181,8 +184,8 @@ static NSString *ANNText(NSString *format, ...) {
181
184
  }
182
185
 
183
186
  + (NSString *)sideMenuInSide:(NSString *)menuId {
184
- return ANNText(@"the side menu '%@' has a side menu in a side: a side menu doesn't go inside a "
185
- @"side menu",
187
+ return ANNText(@"the side menu '%@' has a side menu in a side: a side menu doesn't go inside "
188
+ @"a side menu",
186
189
  menuId);
187
190
  }
188
191
 
@@ -191,8 +194,8 @@ static NSString *ANNText(NSString *format, ...) {
191
194
  }
192
195
 
193
196
  + (NSString *)splitViewInColumn:(NSString *)splitViewId {
194
- return ANNText(@"the split view '%@' has a column of type 'SplitView': a split view doesn't go "
195
- @"inside a split view",
197
+ return ANNText(@"the split view '%@' has a column of type 'SplitView': a split view doesn't "
198
+ @"go inside a split view",
196
199
  splitViewId);
197
200
  }
198
201
 
@@ -208,7 +211,8 @@ static NSString *ANNText(NSString *format, ...) {
208
211
  }
209
212
 
210
213
  + (NSString *)unreadableDefaultOption:(NSString *)path {
211
- return ANNText(@"the option '%@' of the default options has a value the navigation can't read", path);
214
+ return ANNText(@"the option '%@' of the default options has a value the navigation can't read",
215
+ path);
212
216
  }
213
217
 
214
218
  + (NSString *)previewGone:(NSString *)previewId {
@@ -223,6 +227,10 @@ static NSString *ANNText(NSString *format, ...) {
223
227
  return ANNText(@"could not run '%@': the native core doesn't do it yet", method);
224
228
  }
225
229
 
230
+ + (NSString *)notAnswered:(NSString *)method {
231
+ return ANNText(@"could not run '%@': the native core let it go without an answer", method);
232
+ }
233
+
226
234
  + (NSException *)rejection:(NSString *)text {
227
235
  return [NSException exceptionWithName:ANNCommandRejectionException reason:text userInfo:nil];
228
236
  }
@@ -1,4 +1,4 @@
1
- #import <Foundation/Foundation.h>
1
+ #import <UIKit/UIKit.h>
2
2
 
3
3
  NS_ASSUME_NONNULL_BEGIN
4
4
 
@@ -20,6 +20,25 @@ typedef void (^ANNLaunchLinkCompletion)(NSURL *_Nullable url);
20
20
  /// Hands `url` to `Linking`, or keeps it until React content first appears and no `takeLaunchLink:` waits.
21
21
  + (void)dispatchURL:(NSURL *)url;
22
22
 
23
+ /**
24
+ * Runs `forward`, which hands the scene's link to the app delegate (`ANNSceneDelegate`). The URL the app delegate
25
+ * dispatches in it keeps the scene and its context, which React Native 0.88 and newer need to take a scene's link.
26
+ */
27
+ + (void)forwardURLContext:(UIOpenURLContext *)context
28
+ ofScene:(UIScene *)scene
29
+ through:(NS_NOESCAPE dispatch_block_t)forward;
30
+
31
+ /// `forwardURLContext:ofScene:through:` for a web link (`webpageURL`) of the scene.
32
+ + (void)forwardUserActivity:(NSUserActivity *)activity
33
+ ofScene:(UIScene *)scene
34
+ through:(NS_NOESCAPE dispatch_block_t)forward;
35
+
36
+ /// Hands the scene's link to `Linking` at once, past the queue: for an app delegate that doesn't take links.
37
+ + (void)openURLContext:(UIOpenURLContext *)context ofScene:(UIScene *)scene;
38
+
39
+ /// `openURLContext:ofScene:` for a web link (`webpageURL`) of the scene; an activity without one does nothing.
40
+ + (void)openUserActivity:(NSUserActivity *)activity ofScene:(UIScene *)scene;
41
+
23
42
  /// The app's window scene connected (`+[ApexNavigation attachToWindowScene:]`).
24
43
  + (void)windowSceneDidConnect;
25
44
 
@@ -1,12 +1,41 @@
1
1
  #import "ANNDeepLinkQueue.h"
2
2
  #import "ANNAssert.h"
3
+ #import "ANNEventEmitter.h"
4
+ #import "ApexNavigation+Internal.h"
3
5
  #import <React/RCTLinkingManager.h>
4
6
  #import <React/RCTRootView.h>
5
7
  #import <UIKit/UIKit.h>
6
8
 
9
+ // A link on its way to `Linking`, with the scene's own context or user activity it came in when it came from a scene.
10
+ @interface ANNDeepLink : NSObject
11
+ @property(nonatomic, readonly) NSURL *url;
12
+ @property(nonatomic, readonly, nullable) UIOpenURLContext *context;
13
+ @property(nonatomic, readonly, nullable) NSUserActivity *activity;
14
+ @property(nonatomic, readonly, weak, nullable) UIScene *scene;
15
+ @end
16
+
17
+ @implementation ANNDeepLink
18
+
19
+ - (instancetype)initWithURL:(NSURL *)url
20
+ context:(UIOpenURLContext *)context
21
+ activity:(NSUserActivity *)activity
22
+ scene:(UIScene *)scene {
23
+ if (self = [super init]) {
24
+ _url = url;
25
+ _context = context;
26
+ _activity = activity;
27
+ _scene = scene;
28
+ }
29
+ return self;
30
+ }
31
+
32
+ @end
33
+
7
34
  // Main queue only.
8
35
  static BOOL ANNDeepLinkQueueStarted = NO;
9
- static NSMutableArray<NSURL *> *ANNPendingDeepLinkURLs;
36
+ static NSMutableArray<ANNDeepLink *> *ANNPendingDeepLinks;
37
+ // The scene's link the scene delegate is handing to the app delegate: the app delegate dispatches its URL.
38
+ static ANNDeepLink *ANNForwardedSceneLink;
10
39
  // React content appeared: `RCTLinkingManager` exists and JS listens to `url` events.
11
40
  static BOOL ANNReactContentAppeared = NO;
12
41
  // Every URL that launched the app was dispatched.
@@ -20,7 +49,7 @@ static ANNLaunchLinkCompletion ANNWaitingLaunchLinkCompletion;
20
49
 
21
50
  + (void)initialize {
22
51
  if (self == ANNDeepLinkQueue.class) {
23
- ANNPendingDeepLinkURLs = [NSMutableArray new];
52
+ ANNPendingDeepLinks = [NSMutableArray new];
24
53
  }
25
54
  }
26
55
 
@@ -46,11 +75,44 @@ static ANNLaunchLinkCompletion ANNWaitingLaunchLinkCompletion;
46
75
 
47
76
  + (void)dispatchURL:(NSURL *)url {
48
77
  ANNAssertMainQueue();
78
+ // The URL of the scene's link keeps its context; one the app delegate changed is a URL of its own.
79
+ ANNDeepLink *link = [ANNForwardedSceneLink.url isEqual:url]
80
+ ? ANNForwardedSceneLink
81
+ : [[ANNDeepLink alloc] initWithURL:url context:nil activity:nil scene:nil];
49
82
  if (ANNReactContentAppeared && ANNWaitingLaunchLinkCompletion == nil) {
50
- [self openURL:url];
83
+ [self openLink:link];
84
+ return;
85
+ }
86
+ [ANNPendingDeepLinks addObject:link];
87
+ }
88
+
89
+ + (void)forwardURLContext:(UIOpenURLContext *)context
90
+ ofScene:(UIScene *)scene
91
+ through:(NS_NOESCAPE dispatch_block_t)forward {
92
+ [self forwardLink:[[ANNDeepLink alloc] initWithURL:context.URL context:context activity:nil scene:scene]
93
+ through:forward];
94
+ }
95
+
96
+ + (void)forwardUserActivity:(NSUserActivity *)activity
97
+ ofScene:(UIScene *)scene
98
+ through:(NS_NOESCAPE dispatch_block_t)forward {
99
+ if (activity.webpageURL == nil) {
100
+ forward();
101
+ return;
102
+ }
103
+ [self forwardLink:[[ANNDeepLink alloc] initWithURL:activity.webpageURL context:nil activity:activity scene:scene]
104
+ through:forward];
105
+ }
106
+
107
+ + (void)openURLContext:(UIOpenURLContext *)context ofScene:(UIScene *)scene {
108
+ [self openLink:[[ANNDeepLink alloc] initWithURL:context.URL context:context activity:nil scene:scene]];
109
+ }
110
+
111
+ + (void)openUserActivity:(NSUserActivity *)activity ofScene:(UIScene *)scene {
112
+ if (activity.webpageURL == nil) {
51
113
  return;
52
114
  }
53
- [ANNPendingDeepLinkURLs addObject:url];
115
+ [self openLink:[[ANNDeepLink alloc] initWithURL:activity.webpageURL context:nil activity:activity scene:scene]];
54
116
  }
55
117
 
56
118
  + (void)windowSceneDidConnect {
@@ -98,10 +160,10 @@ static ANNLaunchLinkCompletion ANNWaitingLaunchLinkCompletion;
98
160
  if (ANNWaitingLaunchLinkCompletion != nil) {
99
161
  return;
100
162
  }
101
- NSArray<NSURL *> *urls = [ANNPendingDeepLinkURLs copy];
102
- [ANNPendingDeepLinkURLs removeAllObjects];
103
- for (NSURL *url in urls) {
104
- [self openURL:url];
163
+ NSArray<ANNDeepLink *> *links = [ANNPendingDeepLinks copy];
164
+ [ANNPendingDeepLinks removeAllObjects];
165
+ for (ANNDeepLink *link in links) {
166
+ [self openLink:link];
105
167
  }
106
168
  }
107
169
 
@@ -124,13 +186,46 @@ static ANNLaunchLinkCompletion ANNWaitingLaunchLinkCompletion;
124
186
  // One link opens the app. The links queued before it are dropped: opened after it, they would take the app elsewhere.
125
187
  + (void)giveLaunchLink:(ANNLaunchLinkCompletion)completion {
126
188
  ANNLaunchLinkTaken = YES;
127
- NSURL *url = ANNPendingDeepLinkURLs.lastObject;
128
- [ANNPendingDeepLinkURLs removeAllObjects];
189
+ NSURL *url = ANNPendingDeepLinks.lastObject.url;
190
+ [ANNPendingDeepLinks removeAllObjects];
129
191
  completion(url);
130
192
  }
131
193
 
132
- + (void)openURL:(NSURL *)url {
133
- [RCTLinkingManager application:UIApplication.sharedApplication openURL:url options:@{}];
194
+ + (void)forwardLink:(ANNDeepLink *)link through:(NS_NOESCAPE dispatch_block_t)forward {
195
+ ANNAssertMainQueue();
196
+ ANNDeepLink *outer = ANNForwardedSceneLink;
197
+ ANNForwardedSceneLink = link;
198
+ forward();
199
+ ANNForwardedSceneLink = outer;
200
+ }
201
+
202
+ // React Native 0.88 and newer take a scene's links through its scene methods: in an app whose Info.plist names a scene
203
+ // delegate, its app delegate methods take none. The earlier versions have only the app delegate methods.
204
+ + (void)openLink:(ANNDeepLink *)link {
205
+ UIScene *scene = link.scene;
206
+ if (scene != nil && link.context != nil) {
207
+ SEL openContexts = NSSelectorFromString(@"scene:openURLContexts:");
208
+ if ([RCTLinkingManager respondsToSelector:openContexts]) {
209
+ IMP implementation = [RCTLinkingManager methodForSelector:openContexts];
210
+ ((void (*)(id, SEL, UIScene *, NSSet<UIOpenURLContext *> *))implementation)(
211
+ RCTLinkingManager.class, openContexts, scene, [NSSet setWithObject:link.context]);
212
+ return;
213
+ }
214
+ }
215
+ if (scene != nil && link.activity != nil) {
216
+ SEL continueActivity = NSSelectorFromString(@"scene:continueUserActivity:");
217
+ if ([RCTLinkingManager respondsToSelector:continueActivity]) {
218
+ IMP implementation = [RCTLinkingManager methodForSelector:continueActivity];
219
+ ((void (*)(id, SEL, UIScene *, NSUserActivity *))implementation)(
220
+ RCTLinkingManager.class, continueActivity, scene, link.activity);
221
+ return;
222
+ }
223
+ }
224
+ // A link of the app's own (`dispatchDeepLinkURL`) has no scene: on React Native 0.88 and newer, with a scene
225
+ // delegate in Info.plist, `Linking` doesn't take it from here. JavaScript hands it to `Linking` itself.
226
+ if (![RCTLinkingManager application:UIApplication.sharedApplication openURL:link.url options:@{}]) {
227
+ [ApexNavigation.eventEmitter sendDeepLinkDispatched:link.url];
228
+ }
134
229
  }
135
230
 
136
231
  @end
@@ -3,11 +3,16 @@
3
3
  NS_ASSUME_NONNULL_BEGIN
4
4
 
5
5
  /// How a failed command tells JavaScript: a rejection carries only a sentence, never a code of its own, and a command
6
- /// without a promise writes the sentence to the log. The sentences are those of `ANNErrorTexts`.
6
+ /// without a promise writes the sentence to the log. The sentences are those of `ANNErrorTexts`. The one place that
7
+ /// calls a command's promise blocks: a block that is nil (a caller that passed none) is never called.
7
8
  @interface ANNErrorHandler : NSObject
8
9
 
9
- /// Rejects a command's promise with the sentence alone: React Native puts its own default code on it.
10
- + (void)reject:(RCTPromiseRejectBlock)reject text:(NSString *)text;
10
+ /// Rejects a command's promise with the sentence alone: React Native puts its own default code on it. Without a
11
+ /// reject block the sentence goes to the log as `logFailureOf:text:` writes it.
12
+ + (void)reject:(nullable RCTPromiseRejectBlock)reject of:(NSString *)method text:(NSString *)text;
13
+
14
+ /// Resolves a command's promise with `value`. Without a resolve block one line goes to the log instead.
15
+ + (void)resolve:(nullable RCTPromiseResolveBlock)resolve of:(NSString *)method value:(nullable id)value;
11
16
 
12
17
  /// The log line of a command without a promise: `[ApexNavigation] <method>: <text>`.
13
18
  + (void)logFailureOf:(NSString *)method text:(NSString *)text;