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

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 (163) hide show
  1. package/CHANGELOG.md +38 -0
  2. package/README.md +147 -51
  3. package/android/CLAUDE.md +31 -6
  4. package/android/CORE.md +7 -2
  5. package/android/RECIPES.md +4 -3
  6. package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +29 -1
  7. package/android/src/main/java/dev/apexnative/navigation/options/StatusBarOptions.kt +7 -3
  8. package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +5 -4
  9. package/android/src/main/java/dev/apexnative/navigation/react/ErrorTexts.kt +75 -60
  10. package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +3 -2
  11. package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +1 -1
  12. package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +5 -4
  13. package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +7 -6
  14. package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +3 -35
  15. package/android/src/main/java/dev/apexnative/navigation/react/events/EventContract.kt +226 -0
  16. package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +101 -87
  17. package/android/src/main/java/dev/apexnative/navigation/react/events/ScreenAppearance.kt +0 -6
  18. package/android/src/main/java/dev/apexnative/navigation/utils/UiUtils.kt +3 -3
  19. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/SecondaryScreenPresenter.kt +13 -5
  20. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +76 -13
  21. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabItems.kt +3 -3
  22. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentPresenter.kt +28 -7
  23. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenFrame.kt +1 -1
  24. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalAnimations.kt +22 -24
  25. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +86 -16
  26. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalRecord.kt +21 -6
  27. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +41 -23
  28. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +24 -20
  29. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +25 -0
  30. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +4 -0
  31. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +42 -4
  32. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +29 -6
  33. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +2 -0
  34. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ContainerBackground.kt +3 -3
  35. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -0
  36. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +5 -3
  37. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +4 -7
  38. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuPresenter.kt +16 -7
  39. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuRules.kt +8 -1
  40. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +16 -2
  41. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +287 -0
  42. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +148 -6
  43. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +24 -0
  44. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +51 -11
  45. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackTransitions.kt +26 -8
  46. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +105 -2
  47. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +123 -8
  48. package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +19 -0
  49. package/android/src/main/java/dev/apexnative/navigation/views/animations/Curves.kt +29 -11
  50. package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +5 -5
  51. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStep.kt +31 -0
  52. package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +85 -27
  53. package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +5 -4
  54. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionTargets.kt +9 -6
  55. package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionViews.kt +100 -0
  56. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +45 -1
  57. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +3 -5
  58. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +3 -5
  59. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +3 -5
  60. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +2 -0
  61. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +4 -6
  62. package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +4 -6
  63. package/android/src/main/java/dev/apexnative/navigation/views/stack/ScreenEdge.kt +15 -0
  64. package/android/src/main/java/dev/apexnative/navigation/views/stack/StackLayout.kt +137 -1
  65. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +8 -1
  66. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +26 -4
  67. package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +4 -3
  68. package/cli/app.js +1 -1
  69. package/cli/project.js +6 -2
  70. package/cli/setup/index.js +13 -0
  71. package/cli/setup/steps/appDelegate.js +66 -11
  72. package/cli/setup/steps/sceneDelegate.js +108 -0
  73. package/docs-md/architecture/transitions.md +7 -1
  74. package/docs-md/containers/modals.md +4 -2
  75. package/docs-md/containers/overlays.md +3 -1
  76. package/docs-md/containers/side-menu.md +4 -2
  77. package/docs-md/containers/tabs.md +2 -0
  78. package/docs-md/flows/forms-in-a-modal.md +1 -1
  79. package/docs-md/guides/deep-links.md +15 -33
  80. package/docs-md/help/glossary.md +1 -1
  81. package/docs-md/index.json +8 -0
  82. package/docs-md/screen-ui/back-and-gestures.md +6 -0
  83. package/docs-md/screen-ui/system-ui.md +1 -1
  84. package/docs-md/start/installation.md +1 -0
  85. package/ios/Animations/ANNTransitionAnimator.mm +13 -11
  86. package/ios/Animations/ANNTransitionWait.mm +6 -7
  87. package/ios/Animations/Elements/ANNElementFinder.h +57 -0
  88. package/ios/Animations/Elements/ANNElementFinder.mm +174 -2
  89. package/ios/Animations/Elements/ANNElementTransition.h +7 -5
  90. package/ios/Animations/Elements/ANNElementTransition.mm +17 -19
  91. package/ios/Animations/Elements/ANNSharedElementFlight.h +13 -8
  92. package/ios/Animations/Elements/ANNSharedElementFlight.mm +59 -24
  93. package/ios/CLAUDE.md +10 -2
  94. package/ios/CORE.md +3 -2
  95. package/ios/Contract/ANNCommandAnswer.h +4 -3
  96. package/ios/Contract/ANNCommandAnswer.mm +12 -2
  97. package/ios/Contract/ANNCommandRunner.h +2 -2
  98. package/ios/Contract/ANNCommandRunner.mm +1 -1
  99. package/ios/Contract/ANNErrorTexts.h +6 -2
  100. package/ios/Contract/ANNErrorTexts.mm +18 -10
  101. package/ios/Core/ANNDeepLinkQueue.h +20 -1
  102. package/ios/Core/ANNDeepLinkQueue.mm +107 -12
  103. package/ios/Core/ANNErrorHandler.h +8 -3
  104. package/ios/Core/ANNErrorHandler.mm +17 -1
  105. package/ios/Core/ANNEventContract.h +246 -0
  106. package/ios/Core/ANNEventContract.mm +225 -0
  107. package/ios/Core/ANNEventEmitter.h +5 -1
  108. package/ios/Core/ANNEventEmitter.mm +117 -80
  109. package/ios/Core/ANNHinge.mm +6 -4
  110. package/ios/Core/ANNSceneDelegate.mm +25 -15
  111. package/ios/Core/ANNSystemUI.h +3 -2
  112. package/ios/Core/ANNSystemUI.mm +7 -0
  113. package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +6 -4
  114. package/ios/Layouts/Modal/ANNModalCoordinator.mm +1 -1
  115. package/ios/Layouts/Modal/ANNModalPresentation.h +8 -5
  116. package/ios/Layouts/Modal/ANNModalPresentation.mm +12 -12
  117. package/ios/Layouts/Modal/ANNSheetPresenter.mm +83 -1
  118. package/ios/Layouts/Root/ANNRootCoordinator.mm +12 -3
  119. package/ios/Layouts/Screen/ANNComponentPresenter.h +11 -3
  120. package/ios/Layouts/Screen/ANNComponentPresenter.mm +34 -6
  121. package/ios/Layouts/Screen/ANNComponentViewController.mm +28 -7
  122. package/ios/Layouts/Screen/ANNNativeScreenContext.mm +7 -5
  123. package/ios/Layouts/Screen/ANNScreenInsets.mm +6 -4
  124. package/ios/Layouts/Screen/ANNScreenRegions.mm +9 -6
  125. package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -7
  126. package/ios/Options/ANNSheetOptions.h +4 -1
  127. package/ios/Public/ApexNavigation/ANNSceneDelegate.h +3 -0
  128. package/ios/RECIPES.md +2 -1
  129. package/ios/TurboModules/ANNTurboEventEmitter.mm +4 -35
  130. package/ios/TurboModules/ANNTurboModule.mm +5 -2
  131. package/lib/module/ARCHITECTURE.md +7 -4
  132. package/lib/module/api/app/createNavigation.js +2 -0
  133. package/lib/module/api/app/createNavigation.js.map +1 -1
  134. package/lib/module/api/hooks/useNavigation.js +8 -3
  135. package/lib/module/api/hooks/useNavigation.js.map +1 -1
  136. package/lib/module/api/linking/dispatchedLinks.js +23 -0
  137. package/lib/module/api/linking/dispatchedLinks.js.map +1 -0
  138. package/lib/module/interfaces/contract/errors.json +275 -0
  139. package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
  140. package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
  141. package/lib/typescript/api/linking/dispatchedLinks.d.ts +7 -0
  142. package/lib/typescript/api/linking/dispatchedLinks.d.ts.map +1 -0
  143. package/lib/typescript/interfaces/Events.d.ts +9 -0
  144. package/lib/typescript/interfaces/Events.d.ts.map +1 -1
  145. package/lib/typescript/interfaces/options/animations.d.ts +27 -18
  146. package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
  147. package/lib/typescript/interfaces/options/modals.d.ts +33 -24
  148. package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
  149. package/lib/typescript/interfaces/options/sideMenu.d.ts +7 -6
  150. package/lib/typescript/interfaces/options/sideMenu.d.ts.map +1 -1
  151. package/lib/typescript/interfaces/options/system.d.ts +10 -6
  152. package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
  153. package/package.json +1 -1
  154. package/src/ARCHITECTURE.md +7 -4
  155. package/src/api/app/createNavigation.ts +2 -0
  156. package/src/api/hooks/useNavigation.ts +8 -3
  157. package/src/api/linking/dispatchedLinks.ts +18 -0
  158. package/src/interfaces/Events.ts +10 -0
  159. package/src/interfaces/contract/errors.json +275 -0
  160. package/src/interfaces/options/animations.ts +27 -18
  161. package/src/interfaces/options/modals.ts +33 -24
  162. package/src/interfaces/options/sideMenu.ts +7 -6
  163. package/src/interfaces/options/system.ts +10 -6
@@ -1,5 +1,6 @@
1
1
  #import "ANNHinge.h"
2
2
  #import "ANNAssert.h"
3
+ #import "ANNEventContract.h"
3
4
  #import "ANNUtils.h"
4
5
 
5
6
  NSNotificationName const ANNHingeDidChangeNotification = @"ANNHingeDidChange";
@@ -34,11 +35,12 @@ static NSDictionary<NSString *, id> *ANNHingeValue(UIHinge *hinge) API_AVAILABLE
34
35
  if (!hinge) {
35
36
  return @{};
36
37
  }
38
+ const auto &keys = ANNEventContract.HingeChanged;
37
39
  NSString *state = ANNHingeStateName(hinge.status);
38
40
  if (hinge.status == UIHingeStatusUnknown) {
39
- return @{@"state" : state};
41
+ return @{keys.state : state};
40
42
  }
41
- return @{@"state" : state, @"angle" : @(round(hinge.angle * 1800 / M_PI) / 10)};
43
+ return @{keys.state : state, keys.angle : @(round(hinge.angle * 1800 / M_PI) / 10)};
42
44
  }
43
45
  #endif
44
46
 
@@ -94,13 +96,13 @@ static NSDictionary<NSString *, id> *ANNHingeValue(UIHinge *hinge) API_AVAILABLE
94
96
  if ([ANNLastHinge isEqualToDictionary:value]) {
95
97
  return;
96
98
  }
97
- previousState = ANNLastHinge[@"state"];
99
+ previousState = ANNLastHinge[ANNEventContract.HingeChanged.state];
98
100
  ANNLastHinge = value;
99
101
  }
100
102
  if (ANNHingeUpdatesEnabled) {
101
103
  [ANNHingeEventEmitter sendHingeChanged:value];
102
104
  }
103
- id state = value[@"state"];
105
+ id state = value[ANNEventContract.HingeChanged.state];
104
106
  if (previousState != state && ![previousState isEqual:state]) {
105
107
  [NSNotificationCenter.defaultCenter postNotificationName:ANNHingeDidChangeNotification
106
108
  object:nil];
@@ -1,9 +1,10 @@
1
1
  #import "ANNSceneDelegate.h"
2
+ #import "ANNDeepLinkQueue.h"
2
3
  #import "ApexNavigation.h"
3
- #import <React/RCTLinkingManager.h>
4
4
 
5
5
  // On the scene life cycle UIKit stops calling the app delegate's URL, user activity and foreground/background
6
- // methods. The scene forwards them, so AppDelegate overrides (and the deep links of ANNDeepLinkQueue) keep working.
6
+ // methods. The scene forwards them, so AppDelegate overrides (and the deep links of ANNDeepLinkQueue) keep working; the
7
+ // links keep their scene, through which React Native 0.88 and newer take them.
7
8
  @implementation ANNSceneDelegate
8
9
 
9
10
  - (void)scene:(UIScene *)scene
@@ -15,21 +16,21 @@
15
16
  self.window = [ApexNavigation attachToWindowScene:(UIWindowScene *)scene];
16
17
 
17
18
  for (UIOpenURLContext *context in connectionOptions.URLContexts) {
18
- [self ann_openURLContext:context];
19
+ [self ann_openURLContext:context ofScene:scene];
19
20
  }
20
21
  for (NSUserActivity *activity in connectionOptions.userActivities) {
21
- [self ann_continueUserActivity:activity];
22
+ [self ann_continueUserActivity:activity ofScene:scene];
22
23
  }
23
24
  }
24
25
 
25
26
  - (void)scene:(UIScene *)scene openURLContexts:(NSSet<UIOpenURLContext *> *)URLContexts {
26
27
  for (UIOpenURLContext *context in URLContexts) {
27
- [self ann_openURLContext:context];
28
+ [self ann_openURLContext:context ofScene:scene];
28
29
  }
29
30
  }
30
31
 
31
32
  - (void)scene:(UIScene *)scene continueUserActivity:(NSUserActivity *)userActivity {
32
- [self ann_continueUserActivity:userActivity];
33
+ [self ann_continueUserActivity:userActivity ofScene:scene];
33
34
  }
34
35
 
35
36
  - (void)sceneDidBecomeActive:(UIScene *)scene {
@@ -62,7 +63,7 @@
62
63
 
63
64
  #pragma mark - private
64
65
 
65
- - (void)ann_openURLContext:(UIOpenURLContext *)context {
66
+ - (void)ann_openURLContext:(UIOpenURLContext *)context ofScene:(UIScene *)scene {
66
67
  UIApplication *application = UIApplication.sharedApplication;
67
68
  id<UIApplicationDelegate> appDelegate = application.delegate;
68
69
  if ([appDelegate respondsToSelector:@selector(application:openURL:options:)]) {
@@ -70,22 +71,31 @@
70
71
  options[UIApplicationOpenURLOptionsSourceApplicationKey] = context.options.sourceApplication;
71
72
  options[UIApplicationOpenURLOptionsAnnotationKey] = context.options.annotation;
72
73
  options[UIApplicationOpenURLOptionsOpenInPlaceKey] = @(context.options.openInPlace);
73
- [appDelegate application:application openURL:context.URL options:options];
74
+ [ANNDeepLinkQueue forwardURLContext:context
75
+ ofScene:scene
76
+ through:^{
77
+ [appDelegate application:application openURL:context.URL options:options];
78
+ }];
74
79
  } else {
75
- [RCTLinkingManager application:application openURL:context.URL options:@{}];
80
+ [ANNDeepLinkQueue openURLContext:context ofScene:scene];
76
81
  }
77
82
  }
78
83
 
79
- - (void)ann_continueUserActivity:(NSUserActivity *)activity {
84
+ - (void)ann_continueUserActivity:(NSUserActivity *)activity ofScene:(UIScene *)scene {
80
85
  UIApplication *application = UIApplication.sharedApplication;
81
86
  id<UIApplicationDelegate> appDelegate = application.delegate;
82
87
  if ([appDelegate respondsToSelector:@selector(application:continueUserActivity:restorationHandler:)]) {
83
- [appDelegate application:application
84
- continueUserActivity:activity
85
- restorationHandler:^(NSArray<id<UIUserActivityRestoring>> *restorableObjects){
86
- }];
88
+ [ANNDeepLinkQueue forwardUserActivity:activity
89
+ ofScene:scene
90
+ through:^{
91
+ [appDelegate application:application
92
+ continueUserActivity:activity
93
+ restorationHandler:^(
94
+ NSArray<id<UIUserActivityRestoring>> *restorableObjects){
95
+ }];
96
+ }];
87
97
  } else if ([activity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
88
- [RCTLinkingManager application:application openURL:activity.webpageURL options:@{}];
98
+ [ANNDeepLinkQueue openUserActivity:activity ofScene:scene];
89
99
  }
90
100
  }
91
101
 
@@ -36,8 +36,9 @@ typedef NS_ENUM(NSInteger, ANNSystemUIQuestion) {
36
36
  /// The owner adds the asks UIKit doesn't make — a merge, a side menu at rest, an overlay, a root replaced, the bar of a
37
37
  /// stack shown or hidden, the orientation after an appearance — and makes them once per turn of the main queue: a
38
38
  /// trigger marks groups, the marks of a turn are coalesced, and at its end UIKit is asked again for each marked group
39
- /// in the main window and every overlay window. It keeps no values but the last status bar of each window, which only
40
- /// chooses whether a change of the style animates. Never a wait. Main queue.
39
+ /// in the main window and every overlay window. Each ask of the status bar also lays out the screens' blurs under it
40
+ /// (`statusBar.blur`), then and on the next turn. It keeps no values but the last status bar of each window, which
41
+ /// only chooses whether a change of the style animates. Never a wait. Main queue.
41
42
  @interface ANNSystemUI : NSObject
42
43
 
43
44
  - (instancetype)init NS_UNAVAILABLE;
@@ -1,5 +1,6 @@
1
1
  #import "ANNSystemUI.h"
2
2
  #import "ANNAssert.h"
3
+ #import "ANNComponentPresenter.h"
3
4
  #import "ANNComponentViewController.h"
4
5
  #import "ANNDefaultOptions.h"
5
6
  #import "ANNOverlayCoordinator.h"
@@ -280,6 +281,12 @@ static NSArray<UIViewController *> *ANNChainOf(UIWindow *window) {
280
281
  ask();
281
282
  }
282
283
  }
284
+ // The status bar hidden or shown moves no view where the safe area stays (a device with a notch): the blurs under
285
+ // it take its frame now, and once more on the next turn of the main queue, in case UIKit settles it after the ask.
286
+ [ANNComponentPresenter statusBarDidChange];
287
+ dispatch_async(dispatch_get_main_queue(), ^{
288
+ [ANNComponentPresenter statusBarDidChange];
289
+ });
283
290
  }
284
291
 
285
292
  // A controller that is no node of the layout (React Native's `<Modal>`, an alert, the splash) animates as UIKit's
@@ -4,6 +4,7 @@
4
4
 
5
5
  #import "ANNArrangementState.h"
6
6
  #import "ANNComponentViewController.h"
7
+ #import "ANNEventContract.h"
7
8
  #import "ANNExternalViewController.h"
8
9
  #import "ANNShownScreens.h"
9
10
  #import "ANNUtils.h"
@@ -130,11 +131,12 @@ static BOOL ANNKeepsState(UIViewController *screen) {
130
131
 
131
132
  static NSDictionary<NSString *, id> *ANNStateOf(NSString *placement, NSString *layout, BOOL front,
132
133
  BOOL hidden) {
134
+ const auto &keys = ANNEventContract.ArrangementChanged;
133
135
  return @{
134
- @"placement" : placement,
135
- @"layout" : layout,
136
- @"front" : @(front),
137
- @"hidden" : @(hidden),
136
+ keys.placement : placement,
137
+ keys.layout : layout,
138
+ keys.front : @(front),
139
+ keys.hidden : @(hidden),
138
140
  };
139
141
  }
140
142
 
@@ -407,7 +407,7 @@ static void ANNCollectIds(UIViewController *node, NSMutableSet<NSString *> *ids)
407
407
  ANNNavigationOptions *resolved = [root resolveOptionsWithDefault];
408
408
  ANNModalPresentation *presentation =
409
409
  [[ANNModalPresentation alloc] initWithOptions:resolved animated:modal.animatedOverride];
410
- NSString *problem = [presentation problemPresentingFrom:presenting windowRoot:windowRoot];
410
+ NSString *problem = [presentation fitToPresenting:presenting windowRoot:windowRoot];
411
411
  if (problem) {
412
412
  [self failShowOf:modal reason:problem];
413
413
  done();
@@ -20,7 +20,8 @@ NS_ASSUME_NONNULL_BEGIN
20
20
 
21
21
  /// `modalPresentationStyle`: one to one; unset is UIKit's automatic style; an unknown string full screen.
22
22
  @property(nonatomic, readonly) UIModalPresentationStyle style;
23
- /// `modalTransitionStyle`: `coverVertical` unless one of the others is given.
23
+ /// `modalTransitionStyle`: `coverVertical` unless one of the others is given; `partialCurl` only where UIKit curls
24
+ /// (`fitToPresenting:windowRoot:`).
24
25
  @property(nonatomic, readonly) UIModalTransitionStyle transitionStyle;
25
26
  /// The present is animated: `enter.enabled` is not `false`.
26
27
  @property(nonatomic, readonly) BOOL animated;
@@ -47,10 +48,12 @@ NS_ASSUME_NONNULL_BEGIN
47
48
  animation:(nullable ANNModalAnimationOptions *)animation
48
49
  under:(nullable UIViewController *)under;
49
50
 
50
- /// Why UIKit can't make this show from `presenting` (`partialCurl` only full screen, to and from), as the reason of the
51
- /// rejection; nil when it can. `windowRoot` is the main window's root.
52
- - (nullable NSString *)problemPresentingFrom:(UIViewController *)presenting
53
- windowRoot:(nullable UIViewController *)windowRoot;
51
+ /// Fits the show to `presenting`, before `prepare:…`: `partialCurl` stays only for a full-screen modal over a
52
+ /// controller that fills the screen (`presenting` is `windowRoot`, the main window's root, or a full-screen modal);
53
+ /// otherwise `transitionStyle` becomes `coverVertical`. Returns why UIKit can't make the show (`presenting` was shown
54
+ /// with `partialCurl`), as the reason of the rejection; nil when it can.
55
+ - (nullable NSString *)fitToPresenting:(UIViewController *)presenting
56
+ windowRoot:(nullable UIViewController *)windowRoot;
54
57
 
55
58
  /// Before `presentViewController:`: the style and the transition, the presentation's delegate, the popover kept a
56
59
  /// popover (`adaptive: false`), the sheet, the size, the popover's source on `presentingLayout`, the hold, the
@@ -118,21 +118,21 @@ static NSDictionary<NSString *, NSNumber *> *ANNTransitionStyles(void) {
118
118
  return nil;
119
119
  }
120
120
 
121
- // UIKit takes `partialCurl` only for a full-screen modal over a controller that fills the screen, and a controller
122
- // shown with `partialCurl` presents nothing more: the command rejects before it asks UIKit.
123
- - (NSString *)problemPresentingFrom:(UIViewController *)presenting
124
- windowRoot:(UIViewController *)windowRoot {
121
+ // UIKit curls only a full-screen modal over a controller that fills the screen: elsewhere `partialCurl` is
122
+ // `coverVertical`, so the modal still shows. A controller shown with `partialCurl` presents nothing more: the command
123
+ // rejects before it asks UIKit.
124
+ - (NSString *)fitToPresenting:(UIViewController *)presenting
125
+ windowRoot:(UIViewController *)windowRoot {
125
126
  if (presenting.presentingViewController &&
126
127
  presenting.modalTransitionStyle == UIModalTransitionStylePartialCurl) {
127
- return @"partialCurl needs a full screen modal";
128
+ return @"a modal shown with partialCurl can't show another modal";
128
129
  }
129
- if (_transitionStyle != UIModalTransitionStylePartialCurl) {
130
- return nil;
131
- }
132
- BOOL presentingFills = presenting == windowRoot ||
133
- presenting.modalPresentationStyle == UIModalPresentationFullScreen;
134
- if (_style != UIModalPresentationFullScreen || !presentingFills) {
135
- return @"partialCurl needs a full screen modal";
130
+ if (_transitionStyle == UIModalTransitionStylePartialCurl) {
131
+ BOOL presentingFills = presenting == windowRoot ||
132
+ presenting.modalPresentationStyle == UIModalPresentationFullScreen;
133
+ if (_style != UIModalPresentationFullScreen || !presentingFills) {
134
+ _transitionStyle = UIModalTransitionStyleCoverVertical;
135
+ }
136
136
  }
137
137
  return nil;
138
138
  }
@@ -1,6 +1,7 @@
1
1
  #import "ANNSheetPresenter.h"
2
2
  #import "ANNModalSizePresenter.h"
3
3
  #import "ANNReactView.h"
4
+ #import "ANNUtils.h"
4
5
  #import "UIViewController+LayoutProtocol.h"
5
6
  #import <React/RCTLog.h>
6
7
  #import <objc/runtime.h>
@@ -53,7 +54,7 @@ static NSString *const ANNContentDetent = @"content";
53
54
  + (void)applyOptions:(ANNSheetOptions *)options toSheet:(UISheetPresentationController *)sheet {
54
55
  if (options.detents) {
55
56
  NSMutableArray<UISheetPresentationControllerDetent *> *detents = [NSMutableArray new];
56
- for (id value in options.detents) {
57
+ for (id value in [self sortedDetents:options.detents ofSheet:sheet]) {
57
58
  UISheetPresentationControllerDetent *detent = [self detentFor:value ofSheet:sheet];
58
59
  if (detent) {
59
60
  [detents addObject:detent];
@@ -81,6 +82,85 @@ static NSString *const ANNContentDetent = @"content";
81
82
  }
82
83
  }
83
84
 
85
+ // UIKit wants the detents once each, from the lowest to the highest: it takes the last for the largest and closes the
86
+ // sheet on a drag down from the first. The options may give them in any order and more than once; the first of a
87
+ // repeat stays, and detents of the same height keep their order.
88
+ + (NSArray *)sortedDetents:(NSArray *)values ofSheet:(UISheetPresentationController *)sheet {
89
+ NSMutableArray *detents = [NSMutableArray new];
90
+ NSMutableSet<NSString *> *identifiers = [NSMutableSet new];
91
+ for (id value in values) {
92
+ NSString *identifier = [self identifierFor:value];
93
+ if (identifier && ![identifiers containsObject:identifier]) {
94
+ [identifiers addObject:identifier];
95
+ [detents addObject:value];
96
+ }
97
+ }
98
+ CGFloat maximum = [self maximumDetentHeightOf:sheet];
99
+ return [detents sortedArrayWithOptions:NSSortStable
100
+ usingComparator:^NSComparisonResult(id a, id b) {
101
+ return [self compareHeightOf:a
102
+ with:b
103
+ maximum:maximum
104
+ ofSheet:sheet];
105
+ }];
106
+ }
107
+
108
+ // The content of a sheet sized to it changed its height, and with it maybe its place among the other detents.
109
+ + (void)sortDetentsOfSheet:(UISheetPresentationController *)sheet API_AVAILABLE(ios(16.0)) {
110
+ CGFloat maximum = [self maximumDetentHeightOf:sheet];
111
+ NSArray<UISheetPresentationControllerDetent *> *detents = sheet.detents;
112
+ NSArray<UISheetPresentationControllerDetent *> *sorted = [detents
113
+ sortedArrayWithOptions:NSSortStable
114
+ usingComparator:^NSComparisonResult(UISheetPresentationControllerDetent *a,
115
+ UISheetPresentationControllerDetent *b) {
116
+ return [self compareHeightOf:[self detentForIdentifier:a.identifier]
117
+ with:[self detentForIdentifier:b.identifier]
118
+ maximum:maximum
119
+ ofSheet:sheet];
120
+ }];
121
+ if (![sorted isEqualToArray:detents]) {
122
+ sheet.detents = sorted;
123
+ }
124
+ }
125
+
126
+ + (NSComparisonResult)compareHeightOf:(id)a
127
+ with:(id)b
128
+ maximum:(CGFloat)maximum
129
+ ofSheet:(UISheetPresentationController *)sheet {
130
+ CGFloat heightA = [self heightOf:a maximum:maximum ofSheet:sheet];
131
+ CGFloat heightB = [self heightOf:b maximum:maximum ofSheet:sheet];
132
+ return heightA < heightB ? NSOrderedAscending
133
+ : heightA > heightB ? NSOrderedDescending : NSOrderedSame;
134
+ }
135
+
136
+ // The height a detent of the options stands at, to sort them, as its resolver gives it: 'medium' half of the largest
137
+ // height (UIKit's is about half), 'large' all of it, a height capped at it, 'content' the content's height now, all of
138
+ // it while the content isn't measured.
139
+ + (CGFloat)heightOf:(id)value
140
+ maximum:(CGFloat)maximum
141
+ ofSheet:(UISheetPresentationController *)sheet {
142
+ if ([value isEqual:@"medium"]) {
143
+ return maximum / 2;
144
+ }
145
+ if ([value isKindOfClass:NSNumber.class]) {
146
+ return MIN([value doubleValue], maximum);
147
+ }
148
+ if ([value isEqual:ANNContentDetent]) {
149
+ CGFloat height = [self contentHeightOf:sheet.presentedViewController];
150
+ return height > 0 ? MIN(height, maximum) : maximum;
151
+ }
152
+ return maximum;
153
+ }
154
+
155
+ // About the largest height of a sheet: its container, or the window before it is shown, below the top safe area.
156
+ + (CGFloat)maximumDetentHeightOf:(UISheetPresentationController *)sheet {
157
+ UIView *view = sheet.containerView ?: ANNUtils.mainWindow;
158
+ if (!view) {
159
+ return ANNUtils.mainScreen.bounds.size.height;
160
+ }
161
+ return view.bounds.size.height - view.safeAreaInsets.top;
162
+ }
163
+
84
164
  + (UISheetPresentationControllerDetent *)detentFor:(id)value
85
165
  ofSheet:(UISheetPresentationController *)sheet {
86
166
  if ([value isEqual:@"medium"]) {
@@ -180,6 +260,7 @@ static NSString *const ANNContentDetent = @"content";
180
260
  }
181
261
  height = newHeight;
182
262
  [strongSheet animateChanges:^{
263
+ [self sortDetentsOfSheet:strongSheet];
183
264
  [strongSheet invalidateDetents];
184
265
  }];
185
266
  }
@@ -201,6 +282,7 @@ static NSString *const ANNContentDetent = @"content";
201
282
  // The new screen's content mounts may come later: its mounts resolve the detents again too.
202
283
  [self followContentOfSheet:sheet when:YES];
203
284
  [sheet animateChanges:^{
285
+ [self sortDetentsOfSheet:sheet];
204
286
  [sheet invalidateDetents];
205
287
  }];
206
288
  }
@@ -69,7 +69,10 @@ typedef NS_ENUM(NSInteger, ANNRootShowState) {
69
69
  // The nodes whose appearance the transition reports itself (`driveAppearanceOf:`), while `driving`.
70
70
  @property(nonatomic, copy) NSArray<UIViewController *> *driven;
71
71
  @property(nonatomic) BOOL driving;
72
+ // The snapshot of the window as it was: it dissolves over the new root (`dissolves`), plays its `exit` (`custom`), or
73
+ // only covers the old root's modals while they go, and leaves as the new root is the window's.
72
74
  @property(nonatomic, strong, nullable) UIView *snapshot;
75
+ @property(nonatomic) BOOL dissolves;
73
76
  // The root is the window's root: the old root's modals went before it (`startTransitionOf:done:`).
74
77
  @property(nonatomic) BOOL installed;
75
78
  // The root's view waits in the window under the current root (`park:`).
@@ -415,9 +418,14 @@ static __weak ANNRootShow *ANNAnimatingShow;
415
418
  BOOL seen = !window.hidden && scene != nil &&
416
419
  scene.activationState != UISceneActivationStateBackground;
417
420
  BOOL dissolves = !overSplash && !enterDisabled && !custom && seen;
421
+ show.dissolves = dissolves;
418
422
  show.custom = custom && seen;
419
- UIView *snapshot =
420
- dissolves || show.custom ? [window snapshotViewAfterScreenUpdates:NO] : nil;
423
+ // UIKit draws the dismissal of the old root's modals, without animation too (a sheet's dim goes a frame before
424
+ // its card): with nothing to dissolve, the snapshot covers them until the new root is in place.
425
+ BOOL covers = seen && old.presentedViewController != nil;
426
+ UIView *snapshot = dissolves || show.custom || covers
427
+ ? [window snapshotViewAfterScreenUpdates:NO]
428
+ : nil;
421
429
 
422
430
  show.state = ANNRootShowStateTransition;
423
431
  // It shows from here: a stack in it counts as on screen.
@@ -480,7 +488,8 @@ static __weak ANNRootShow *ANNAnimatingShow;
480
488
  [self endTransitionOf:show];
481
489
  return;
482
490
  }
483
- if (!snapshot) {
491
+ // A snapshot that only covered the old root's modals leaves in this turn, with the new root in place under it.
492
+ if (!snapshot || !show.dissolves) {
484
493
  [self endTransitionOf:show];
485
494
  return;
486
495
  }
@@ -19,11 +19,19 @@ NS_ASSUME_NONNULL_BEGIN
19
19
  /// it. Nothing before the screen's view is loaded.
20
20
  - (void)applyBackground:(ANNNavigationOptions *)resolvedOptions;
21
21
 
22
- /// `statusBar.blur` (iOS): UIKit's regular blur over the screen's own view in the frame of the scene's status bar at
23
- /// this moment (x 0, the view's width; it doesn't follow a rotation); `false` or none removes it. At each appearance
24
- /// and at a merge that names it; nothing before the screen's view is loaded.
22
+ /// `statusBar.blur` (iOS): UIKit's regular blur over the screen's own view in the frame of the scene's status bar (x 0,
23
+ /// the view's width), laid out at once; `false` or none removes it. At each appearance and at a merge that names it;
24
+ /// nothing before the screen's view is loaded.
25
25
  - (void)applyStatusBarBlur:(ANNNavigationOptions *)resolvedOptions;
26
26
 
27
+ /// The blur takes the status bar's frame as it is now; nothing without a blur. The single place of its frame: the blur
28
+ /// as it is applied, the screen's view laid out (a rotation, a new window size, a new safe area), a size transition
29
+ /// along its animation and at its end, the turn after new traits, and `statusBarDidChange`.
30
+ - (void)layOutStatusBarBlur;
31
+
32
+ /// The status bar was asked again (hidden or shown, a new style): every blur takes its frame.
33
+ + (void)statusBarDidChange;
34
+
27
35
  /// The blur stays the last subview of the screen's view: the content added after it goes under it.
28
36
  - (void)keepStatusBarBlurOnTop;
29
37
 
@@ -5,6 +5,10 @@
5
5
  #import "ANNTopBarPresenter.h"
6
6
  #import "ANNUtils.h"
7
7
 
8
+ // The presenters whose screen shows `statusBar.blur`: the status bar hidden or shown lays out no view, so they hear of
9
+ // it from the system UI (`statusBarDidChange`).
10
+ static NSHashTable<ANNComponentPresenter *> *ANNBlurringPresenters;
11
+
8
12
  @implementation ANNComponentPresenter {
9
13
  // `statusBar.blur` while it is on.
10
14
  UIVisualEffectView *_statusBarBlur;
@@ -95,9 +99,7 @@
95
99
 
96
100
  #pragma mark - The status bar's blur (`statusBar.blur`)
97
101
 
98
- // Over the screen's own view, never over a React view; no touches, nothing for VoiceOver. The frame is the scene's
99
- // status bar's as it is now, in the view's own coordinates without a conversion: a pushed screen's view may still be
100
- // out of place while it appears.
102
+ // Over the screen's own view, never over a React view; no touches, nothing for VoiceOver.
101
103
  - (void)applyStatusBarBlur:(ANNNavigationOptions *)resolvedOptions {
102
104
  ANNComponentViewController *screen = self.screen;
103
105
  if (!screen.isViewLoaded) {
@@ -106,6 +108,7 @@
106
108
  if (![resolvedOptions.statusBar.blur withDefault:NO]) {
107
109
  [_statusBarBlur removeFromSuperview];
108
110
  _statusBarBlur = nil;
111
+ [ANNBlurringPresenters removeObject:self];
109
112
  return;
110
113
  }
111
114
  UIView *view = screen.view;
@@ -115,15 +118,40 @@
115
118
  _statusBarBlur.userInteractionEnabled = NO;
116
119
  _statusBarBlur.isAccessibilityElement = NO;
117
120
  _statusBarBlur.accessibilityElementsHidden = YES;
121
+ if (!ANNBlurringPresenters) {
122
+ ANNBlurringPresenters = [NSHashTable weakObjectsHashTable];
123
+ }
124
+ [ANNBlurringPresenters addObject:self];
118
125
  }
119
- CGRect statusBar = ANNUtils.statusBarFrame;
120
- _statusBarBlur.frame = CGRectMake(0, CGRectGetMinY(statusBar), CGRectGetWidth(view.bounds),
121
- CGRectGetHeight(statusBar));
122
126
  if (_statusBarBlur.superview != view) {
123
127
  [view addSubview:_statusBarBlur];
124
128
  } else {
125
129
  [view bringSubviewToFront:_statusBarBlur];
126
130
  }
131
+ [self layOutStatusBarBlur];
132
+ }
133
+
134
+ // The status bar's frame in the scene of the screen's window (the main window's before the screen is in one), in the
135
+ // view's own coordinates without a conversion: a pushed screen's view may still be out of place while it appears. A
136
+ // hidden status bar has no frame, and the blur no height.
137
+ - (void)layOutStatusBarBlur {
138
+ UIView *view = _statusBarBlur.superview;
139
+ if (!view) {
140
+ return;
141
+ }
142
+ UIWindowScene *scene = view.window.windowScene;
143
+ CGRect statusBar = scene ? scene.statusBarManager.statusBarFrame : ANNUtils.statusBarFrame;
144
+ CGRect frame = CGRectMake(0, CGRectGetMinY(statusBar), CGRectGetWidth(view.bounds),
145
+ CGRectGetHeight(statusBar));
146
+ if (!CGRectEqualToRect(_statusBarBlur.frame, frame)) {
147
+ _statusBarBlur.frame = frame;
148
+ }
149
+ }
150
+
151
+ + (void)statusBarDidChange {
152
+ for (ANNComponentPresenter *presenter in ANNBlurringPresenters.allObjects) {
153
+ [presenter layOutStatusBarBlur];
154
+ }
127
155
  }
128
156
 
129
157
  - (void)keepStatusBarBlurOnTop {
@@ -3,6 +3,7 @@
3
3
  #import "ANNArrangementState.h"
4
4
  #import "ANNBottomTabsController.h"
5
5
  #import "ANNComponentPresenter.h"
6
+ #import "ANNEventContract.h"
6
7
  #import "ANNHinge.h"
7
8
  #import "ANNKeyCommands.h"
8
9
  #import "ANNNodeRegistry.h"
@@ -66,6 +67,12 @@
66
67
 
67
68
  #pragma mark - The view
68
69
 
70
+ - (nullable ANNComponentPresenter *)componentPresenter {
71
+ ANNNodePresenter *presenter = self.presenter;
72
+ return [presenter isKindOfClass:ANNComponentPresenter.class] ? (ANNComponentPresenter *)presenter
73
+ : nil;
74
+ }
75
+
69
76
  // The size of the screen of the navigation window's scene, not the deprecated main screen: a surface made before the
70
77
  // screen is in a window starts with the width it will have.
71
78
  - (void)loadView {
@@ -94,6 +101,22 @@
94
101
  }
95
102
  // The pair the screen is in may lie otherwise now.
96
103
  [self.arrangement screenDidChangeLayout:self];
104
+ [self.componentPresenter layOutStatusBarBlur];
105
+ }
106
+
107
+ // UIKit names the size transition as the moment the status bar's frame changes (a rotation, a new window size): the
108
+ // blur follows it along the animation and takes the final frame at its end.
109
+ - (void)viewWillTransitionToSize:(CGSize)size
110
+ withTransitionCoordinator:(id<UIViewControllerTransitionCoordinator>)coordinator {
111
+ [super viewWillTransitionToSize:size withTransitionCoordinator:coordinator];
112
+ __weak ANNComponentViewController *weakSelf = self;
113
+ [coordinator
114
+ animateAlongsideTransition:^(id<UIViewControllerTransitionCoordinatorContext> context) {
115
+ [weakSelf.componentPresenter layOutStatusBarBlur];
116
+ }
117
+ completion:^(id<UIViewControllerTransitionCoordinatorContext> context) {
118
+ [weakSelf.componentPresenter layOutStatusBarBlur];
119
+ }];
97
120
  }
98
121
 
99
122
  - (void)viewSafeAreaInsetsDidChange {
@@ -110,6 +133,7 @@
110
133
  __weak ANNComponentViewController *weakSelf = self;
111
134
  dispatch_async(dispatch_get_main_queue(), ^{
112
135
  [weakSelf updateRootLayout];
136
+ [weakSelf.componentPresenter layOutStatusBarBlur];
113
137
  });
114
138
  }
115
139
 
@@ -200,9 +224,7 @@
200
224
  #pragma mark - The root frame and the safe area
201
225
 
202
226
  - (void)applyBackground {
203
- if ([self.presenter isKindOfClass:ANNComponentPresenter.class]) {
204
- [(ANNComponentPresenter *)self.presenter applyBackground:[self resolveOptionsWithDefault]];
205
- }
227
+ [self.componentPresenter applyBackground:[self resolveOptionsWithDefault]];
206
228
  }
207
229
 
208
230
  // The navigation controller that shows the screen's stack: the stack, or UIKit's More navigation while a tab of More
@@ -434,7 +456,8 @@
434
456
  if (!regions) {
435
457
  return;
436
458
  }
437
- BOOL empty = [regions[@"division"] count] == 0 && [regions[@"occlusion"] count] == 0;
459
+ const auto &keys = ANNEventContract.ScreenRegionsChanged;
460
+ BOOL empty = [regions[keys.division] count] == 0 && [regions[keys.occlusion] count] == 0;
438
461
  if (empty && ![ANNScreenRegions regionsForScreenId:componentId]) {
439
462
  return;
440
463
  }
@@ -606,9 +629,7 @@
606
629
  content.autoresizingMask = UIViewAutoresizingNone;
607
630
  content.frame = [self rootFrameForOptions:[self resolveOptionsWithDefault]];
608
631
  [view addSubview:content];
609
- if ([self.presenter isKindOfClass:ANNComponentPresenter.class]) {
610
- [(ANNComponentPresenter *)self.presenter keepStatusBarBlurOnTop];
611
- }
632
+ [self.componentPresenter keepStatusBarBlurOnTop];
612
633
  [self applyBackground];
613
634
  [self updateRootLayout];
614
635
  }
@@ -1,5 +1,6 @@
1
1
  #import "ANNNativeScreenContext+Internal.h"
2
2
  #import "ANNErrorTexts.h"
3
+ #import "ANNEventContract.h"
3
4
  #import "ANNEventEmitter.h"
4
5
  #import "ANNNativeScreenCalls.h"
5
6
  #import <React/RCTUtils.h>
@@ -71,12 +72,13 @@
71
72
  componentId:self->_componentId]];
72
73
  return;
73
74
  }
75
+ const auto &keys = ANNEventContract.NativeScreenCall;
74
76
  [eventEmitter sendNativeScreenCall:@{
75
- @"componentId" : self->_componentId,
76
- @"callId" : callId,
77
- @"kind" : kind,
78
- @"name" : name,
79
- @"args" : arguments ?: @[],
77
+ keys.componentId : self->_componentId,
78
+ keys.callId : callId,
79
+ keys.kind : kind,
80
+ keys.name : name,
81
+ keys.args : arguments ?: @[],
80
82
  }];
81
83
  });
82
84
  }
@@ -1,4 +1,5 @@
1
1
  #import "ANNScreenInsets.h"
2
+ #import "ANNEventContract.h"
2
3
 
3
4
  NSNotificationName const ANNScreenSafeAreaPaddingDidChangeNotification = @"ANNScreenSafeAreaPaddingDidChange";
4
5
 
@@ -26,11 +27,12 @@ static NSMutableDictionary<NSString *, NSValue *> *ANNScreenPaddingById;
26
27
  object:screenId];
27
28
  }
28
29
  // JavaScript gets what still covers the content: the edges the safe area view keeps it off are 0.
30
+ const auto &keys = ANNEventContract.ScreenInsetsChanged;
29
31
  NSDictionary<NSString *, NSNumber *> *value = @{
30
- @"top" : @(MAX(insets.top - padding.top, 0)),
31
- @"right" : @(MAX(insets.right - padding.right, 0)),
32
- @"bottom" : @(MAX(insets.bottom - padding.bottom, 0)),
33
- @"left" : @(MAX(insets.left - padding.left, 0)),
32
+ keys.top : @(MAX(insets.top - padding.top, 0)),
33
+ keys.right : @(MAX(insets.right - padding.right, 0)),
34
+ keys.bottom : @(MAX(insets.bottom - padding.bottom, 0)),
35
+ keys.left : @(MAX(insets.left - padding.left, 0)),
34
36
  };
35
37
  @synchronized(ANNScreenInsetsById) {
36
38
  if ([ANNScreenInsetsById[screenId] isEqualToDictionary:value]) {