@apexnative/navigation 1.0.0-alpha.6 → 1.0.0-alpha.8
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.
- package/ApexNavigation.podspec +2 -1
- package/CHANGELOG.md +15 -0
- package/android/CLAUDE.md +19 -10
- package/android/CORE.md +3 -2
- package/android/consumer-rules.pro +6 -0
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/LargeTitleOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/LayoutAnimation.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/OptionKey.kt +18 -0
- package/android/src/main/java/dev/apexnative/navigation/options/OptionPaths.kt +91 -0
- package/android/src/main/java/dev/apexnative/navigation/options/SheetOptions.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/options/TitleOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/TopBarAppearanceOptions.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/options/TopBarOptions.kt +16 -7
- package/android/src/main/java/dev/apexnative/navigation/react/ContentHeights.kt +95 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ContentMounts.kt +66 -0
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/react/OptionsCommands.kt +41 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +16 -1
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +12 -1
- package/android/src/main/java/dev/apexnative/navigation/utils/MaterialTheme.kt +5 -6
- package/android/src/main/java/dev/apexnative/navigation/utils/SystemAnimations.kt +36 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +329 -55
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +26 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetStops.kt +60 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuMotion.kt +8 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +32 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +46 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +15 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabMotion.kt +142 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +215 -43
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +25 -26
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarRowTransition.kt +3 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/ButtonPlaces.kt +11 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +86 -39
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarVisibility.kt +9 -2
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +124 -181
- package/android/src/main/java/dev/apexnative/navigation/views/element/TargetsWait.kt +8 -1
- package/android/src/main/java/dev/apexnative/navigation/views/element/TransitionLayer.kt +37 -112
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +56 -3
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +170 -153
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMorph.kt +199 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +55 -45
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/{TextChangeAnimator.kt → TextMorph.kt} +79 -129
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewMorph.kt +108 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabCopy.kt +85 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/FabMenu.kt +13 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +5 -24
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +51 -34
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonComponentCell.kt +6 -6
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarButtonView.kt +113 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/BarMenu.kt +219 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonBar.kt +121 -175
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonMenus.kt +34 -64
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/titlebar/ButtonRow.kt +216 -0
- package/android/src/main/jni/ANNContentHeights.cpp +82 -0
- package/android/src/main/res/drawable/apex_navigation_ic_arrow_right.xml +14 -0
- package/android/src/main/res/layout/apex_navigation_menu_header.xml +19 -0
- package/android/src/main/res/layout/apex_navigation_menu_item.xml +68 -0
- package/android/src/main/res/values/styles.xml +1 -1
- package/android/src/main/res/values-night/styles.xml +1 -1
- package/cli/doctor/checks/app.js +16 -1
- package/cli/project.js +4 -0
- package/cli/setup/index.js +5 -0
- package/cli/setup/steps/metro.js +46 -0
- package/docs-md/architecture/native-screens.md +4 -0
- package/docs-md/architecture/theme-and-context.md +131 -12
- package/docs-md/architecture/transitions.md +43 -4
- package/docs-md/flows/bottom-sheets.md +4 -2
- package/docs-md/fundamentals/screens-and-props.md +9 -1
- package/docs-md/guides/cli.md +3 -1
- package/docs-md/help/troubleshooting.md +1 -1
- package/docs-md/index.json +29 -1
- package/docs-md/screen-ui/back-and-gestures.md +3 -1
- package/docs-md/screen-ui/bar-buttons.md +1 -1
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -0
- package/docs-md/start/installation.md +16 -1
- package/ios/Animations/ANNFrameDriver.h +55 -15
- package/ios/Animations/ANNFrameDriver.mm +214 -60
- package/ios/Animations/ANNTransitionAnimator.h +26 -5
- package/ios/Animations/ANNTransitionAnimator.mm +49 -3
- package/ios/Animations/ANNTransitionPlan.h +2 -2
- package/ios/Animations/ANNTransitionPlan.mm +10 -0
- package/ios/Animations/Elements/ANNElementFinder.h +2 -2
- package/ios/Animations/Elements/ANNElementFinder.mm +2 -2
- package/ios/Animations/Elements/ANNElementTransition.h +3 -3
- package/ios/Animations/Elements/ANNElementTransition.mm +2 -2
- package/ios/Animations/Elements/ANNSharedElementFlight.h +3 -3
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +181 -43
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -5
- package/ios/Animations/Interpolators/ANNViewTracks.mm +51 -21
- package/ios/CLAUDE.md +19 -8
- package/ios/CORE.md +1 -1
- package/ios/Core/ANNAppDelegate.mm +0 -2
- package/ios/Core/ANNDeepLinkQueue.h +6 -5
- package/ios/Core/ANNDeepLinkQueue.mm +22 -10
- package/ios/Core/ANNSceneDelegate.mm +2 -2
- package/ios/Core/ANNUtils.h +7 -0
- package/ios/Core/ANNUtils.mm +10 -0
- package/ios/Core/ApexNavigation.mm +6 -2
- package/ios/Layouts/Overlay/ANNOverlayCoordinator.mm +1 -7
- package/ios/Layouts/Root/ANNRootCoordinator.mm +45 -15
- package/ios/Layouts/Screen/ANNComponentViewController.mm +4 -1
- package/ios/Layouts/Stack/ANNStackAnimatingDelegate.h +11 -2
- package/ios/Layouts/Stack/ANNStackAnimatingDelegate.mm +14 -7
- package/ios/Layouts/Stack/ANNStackController.h +8 -0
- package/ios/Layouts/Stack/ANNStackController.mm +153 -11
- package/ios/Layouts/Stack/ANNSwipePopInteraction.h +26 -0
- package/ios/Layouts/Stack/ANNSwipePopInteraction.mm +185 -0
- package/ios/Layouts/Stack/InteractivePopGestureDelegate.mm +28 -4
- package/ios/Layouts/Tabs/ANNTabsPreloadWindow.mm +2 -4
- package/ios/Options/ANNAnimationOptions.h +2 -1
- package/ios/Options/ANNAnimationOptions.mm +1 -1
- package/ios/Options/ANNOptions.h +7 -0
- package/ios/Options/ANNOptions.mm +45 -0
- package/ios/Options/ANNOptionsReader.mm +2 -4
- package/ios/Options/ANNStackPopAnimationOptions.h +13 -0
- package/ios/Options/ANNStackPopAnimationOptions.mm +11 -0
- package/ios/Options/Params/ColorParser.h +4 -3
- package/ios/Options/Params/ColorParser.mm +22 -4
- package/ios/React/ANNSurfaceHostingView.h +8 -0
- package/ios/React/ANNSurfaceHostingView.mm +28 -1
- package/ios/React/Slots/ANNSlotHost.mm +1 -3
- package/ios/React/Slots/ANNSlotScreenView.mm +8 -2
- package/ios/TurboModules/ANNTurboModule.mm +46 -0
- package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.cpp +20 -0
- package/ios/cpp/react/renderer/components/ApexNavigationSpec/ANNSlotState.h +5 -14
- package/lib/module/ARCHITECTURE.md +9 -3
- package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
- package/lib/module/api/app/AppRegistry.js +14 -2
- package/lib/module/api/app/AppRegistry.js.map +1 -1
- package/lib/module/api/app/createNavigation.js +188 -66
- package/lib/module/api/app/createNavigation.js.map +1 -1
- package/lib/module/api/events/navigationEvents.js +6 -2
- package/lib/module/api/events/navigationEvents.js.map +1 -1
- package/lib/module/api/linking/linking.js +11 -0
- package/lib/module/api/linking/linking.js.map +1 -1
- package/lib/module/api/screens/defineScreen.js +45 -3
- package/lib/module/api/screens/defineScreen.js.map +1 -1
- package/lib/module/api/screens/nativeScreens.js +31 -6
- package/lib/module/api/screens/nativeScreens.js.map +1 -1
- package/lib/module/api/screens/registerScreens.js +193 -35
- package/lib/module/api/screens/registerScreens.js.map +1 -1
- package/lib/module/api/theme/theme.js +5 -0
- package/lib/module/api/theme/theme.js.map +1 -1
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/layout.js +7 -2
- package/lib/module/engine/layout.js.map +1 -1
- package/lib/module/engine/nativeScreens.js +9 -0
- package/lib/module/engine/nativeScreens.js.map +1 -1
- package/lib/module/engine/options.js +5 -4
- package/lib/module/engine/options.js.map +1 -1
- package/lib/module/engine/optionsChange.js +102 -0
- package/lib/module/engine/optionsChange.js.map +1 -0
- package/lib/module/engine/refresh.js +85 -0
- package/lib/module/engine/refresh.js.map +1 -0
- package/lib/module/engine/rememberedOptions.js +60 -0
- package/lib/module/engine/rememberedOptions.js.map +1 -1
- package/lib/module/engine/screens.js +26 -0
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts +7 -0
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
- package/lib/typescript/api/app/AppRegistry.d.ts.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +5 -0
- package/lib/typescript/api/app/createNavigation.d.ts.map +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts +2 -2
- package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
- package/lib/typescript/api/index.d.ts +1 -1
- package/lib/typescript/api/index.d.ts.map +1 -1
- package/lib/typescript/api/linking/linking.d.ts +8 -0
- package/lib/typescript/api/linking/linking.d.ts.map +1 -1
- package/lib/typescript/api/screens/defineScreen.d.ts +23 -4
- package/lib/typescript/api/screens/defineScreen.d.ts.map +1 -1
- package/lib/typescript/api/screens/nativeScreens.d.ts +8 -1
- package/lib/typescript/api/screens/nativeScreens.d.ts.map +1 -1
- package/lib/typescript/api/screens/registerScreens.d.ts.map +1 -1
- package/lib/typescript/api/theme/theme.d.ts +2 -0
- package/lib/typescript/api/theme/theme.d.ts.map +1 -1
- package/lib/typescript/engine/commands.d.ts +5 -0
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/layout.d.ts.map +1 -1
- package/lib/typescript/engine/nativeScreens.d.ts +5 -0
- package/lib/typescript/engine/nativeScreens.d.ts.map +1 -1
- package/lib/typescript/engine/optionsChange.d.ts +28 -0
- package/lib/typescript/engine/optionsChange.d.ts.map +1 -0
- package/lib/typescript/engine/refresh.d.ts +35 -0
- package/lib/typescript/engine/refresh.d.ts.map +1 -0
- package/lib/typescript/engine/rememberedOptions.d.ts +13 -0
- package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +7 -0
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
- package/lib/typescript/interfaces/options/animations.d.ts +40 -15
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +2 -2
- package/lib/typescript/interfaces/options/buttons.d.ts +2 -2
- package/lib/typescript/interfaces/options/modals.d.ts +6 -7
- package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/stack.d.ts +8 -3
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/topBar.d.ts +14 -0
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/metro/acceptPlugin.js +57 -0
- package/metro/babelTransformer.js +45 -0
- package/metro/index.js +36 -0
- package/package.json +3 -1
- package/src/ARCHITECTURE.md +9 -3
- package/src/adapters/NativeANNTurboModule.ts +7 -0
- package/src/api/app/AppRegistry.ts +16 -2
- package/src/api/app/createNavigation.ts +198 -66
- package/src/api/events/navigationEvents.ts +9 -3
- package/src/api/index.ts +1 -0
- package/src/api/linking/linking.ts +22 -1
- package/src/api/screens/defineScreen.ts +49 -5
- package/src/api/screens/nativeScreens.ts +40 -4
- package/src/api/screens/registerScreens.tsx +185 -42
- package/src/api/theme/theme.ts +5 -0
- package/src/engine/commands.ts +21 -0
- package/src/engine/layout.ts +11 -2
- package/src/engine/nativeScreens.ts +10 -1
- package/src/engine/options.ts +6 -8
- package/src/engine/optionsChange.ts +107 -0
- package/src/engine/refresh.ts +97 -0
- package/src/engine/rememberedOptions.ts +70 -0
- package/src/engine/screens.tsx +24 -0
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/options/animations.ts +40 -15
- package/src/interfaces/options/bottomTabs.ts +2 -2
- package/src/interfaces/options/buttons.ts +2 -2
- package/src/interfaces/options/modals.ts +6 -7
- package/src/interfaces/options/stack.ts +8 -3
- package/src/interfaces/options/topBar.ts +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementStub.kt +0 -31
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +0 -134
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +0 -78
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PropertyAnimatorCreator.kt +0 -33
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +0 -57
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +0 -146
|
@@ -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.
|
|
13
|
-
///
|
|
14
|
-
///
|
|
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
|
|
@@ -29,6 +29,21 @@ typedef struct {
|
|
|
29
29
|
BOOL clipped;
|
|
30
30
|
} ANNFlightEnd;
|
|
31
31
|
|
|
32
|
+
// What the copy shows at one time: its size, center, transform and corners, its two pictures' frames and alphas, and
|
|
33
|
+
// its visible part (in the copy's coordinates) with its corners.
|
|
34
|
+
typedef struct {
|
|
35
|
+
CGSize size;
|
|
36
|
+
CGPoint center;
|
|
37
|
+
CATransform3D transform;
|
|
38
|
+
CGFloat cornerRadius;
|
|
39
|
+
CGRect fromFrame;
|
|
40
|
+
CGRect toFrame;
|
|
41
|
+
CGFloat fromAlpha;
|
|
42
|
+
CGFloat toAlpha;
|
|
43
|
+
CGRect mask;
|
|
44
|
+
CGFloat maskRadius;
|
|
45
|
+
} ANNFlightState;
|
|
46
|
+
|
|
32
47
|
static BOOL ANNIsText(UIView *view) {
|
|
33
48
|
static Class paragraph;
|
|
34
49
|
static dispatch_once_t once;
|
|
@@ -200,6 +215,30 @@ static UIImage *ANNPictureOf(UIView *view) {
|
|
|
200
215
|
}];
|
|
201
216
|
}
|
|
202
217
|
|
|
218
|
+
// A layer at `frame` in its superlayer: its bounds and its position (its anchor is its center).
|
|
219
|
+
static void ANNPlaceLayer(CALayer *layer, CGRect frame) {
|
|
220
|
+
layer.bounds = CGRectMake(0, 0, frame.size.width, frame.size.height);
|
|
221
|
+
layer.position = CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame));
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
// The core's animation of `keyPath` on `layer` (`ANNKeyframes`), under its key.
|
|
225
|
+
static void ANNAddKeyframes(CALayer *layer, NSString *keyPath, NSArray *values,
|
|
226
|
+
NSArray<NSNumber *> *times, NSTimeInterval length) {
|
|
227
|
+
CAKeyframeAnimation *animation = ANNKeyframes(keyPath, values, times, length);
|
|
228
|
+
if (animation) {
|
|
229
|
+
[layer addAnimation:animation forKey:ANNAnimationKey(keyPath)];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
|
|
233
|
+
// The bounds and the position of a layer at `frame`, as values of its animations.
|
|
234
|
+
static NSValue *ANNBoundsValue(CGRect frame) {
|
|
235
|
+
return [NSValue valueWithCGRect:CGRectMake(0, 0, frame.size.width, frame.size.height)];
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
static NSValue *ANNPositionValue(CGRect frame) {
|
|
239
|
+
return [NSValue valueWithCGPoint:CGPointMake(CGRectGetMidX(frame), CGRectGetMidY(frame))];
|
|
240
|
+
}
|
|
241
|
+
|
|
203
242
|
@implementation ANNSharedElementFlight {
|
|
204
243
|
__weak UIView *_fromView;
|
|
205
244
|
__weak UIView *_toView;
|
|
@@ -208,7 +247,9 @@ static UIImage *ANNPictureOf(UIView *view) {
|
|
|
208
247
|
UIView *_copy;
|
|
209
248
|
UIImageView *_fromPicture;
|
|
210
249
|
UIImageView *_toPicture;
|
|
211
|
-
|
|
250
|
+
// The visible part of the copy: a plain layer with corners, as Core Animation can't animate a path whose corners
|
|
251
|
+
// change.
|
|
252
|
+
CALayer *_mask;
|
|
212
253
|
ANNFlightEnd _from;
|
|
213
254
|
ANNFlightEnd _to;
|
|
214
255
|
ANNTrack *_track;
|
|
@@ -343,7 +384,8 @@ static UIImage *ANNPictureOf(UIView *view) {
|
|
|
343
384
|
// A text's pictures are neither clipped nor masked.
|
|
344
385
|
_copy.clipsToBounds = !_text;
|
|
345
386
|
if (!_text && (_from.clipped || _to.clipped)) {
|
|
346
|
-
_mask = [
|
|
387
|
+
_mask = [CALayer layer];
|
|
388
|
+
_mask.backgroundColor = UIColor.blackColor.CGColor;
|
|
347
389
|
_copy.layer.mask = _mask;
|
|
348
390
|
}
|
|
349
391
|
[_copy addSubview:_fromPicture];
|
|
@@ -360,72 +402,168 @@ static UIImage *ANNPictureOf(UIView *view) {
|
|
|
360
402
|
[container addSubview:_copy];
|
|
361
403
|
from.hidden = YES;
|
|
362
404
|
to.hidden = YES;
|
|
363
|
-
[self
|
|
405
|
+
[self placeAtTime:0];
|
|
364
406
|
}
|
|
365
407
|
|
|
366
408
|
- (UIView *)copyView {
|
|
367
409
|
return _copy;
|
|
368
410
|
}
|
|
369
411
|
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
return _track.endTime;
|
|
374
|
-
}
|
|
375
|
-
|
|
376
|
-
- (void)applyAtTime:(NSTimeInterval)time {
|
|
377
|
-
if (_restored) {
|
|
378
|
-
return;
|
|
379
|
-
}
|
|
412
|
+
// The copy's whole state at `time`: its own geometry, its pictures' and its visible part's.
|
|
413
|
+
- (ANNFlightState)stateAtTime:(NSTimeInterval)time {
|
|
414
|
+
ANNFlightState state;
|
|
380
415
|
double f = [_track fractionAt:time];
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
|
|
385
|
-
|
|
386
|
-
|
|
416
|
+
state.size = ANNInterpolateSize(_from.size, _to.size, f);
|
|
417
|
+
state.center = ANNInterpolatePoint(_from.center, _to.center, f);
|
|
418
|
+
state.transform = ANNTransformOfParts(ANNInterpolateTransformParts(_from.parts, _to.parts, f));
|
|
419
|
+
state.cornerRadius = 0;
|
|
420
|
+
state.mask = CGRectZero;
|
|
421
|
+
state.maskRadius = 0;
|
|
387
422
|
if (_text) {
|
|
388
|
-
[self
|
|
389
|
-
return;
|
|
423
|
+
[self placeTextPicturesOf:&state atTime:time];
|
|
424
|
+
return state;
|
|
390
425
|
}
|
|
391
|
-
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
|
|
395
|
-
|
|
426
|
+
state.cornerRadius = ANNInterpolateNumber(_from.cornerRadius, _to.cornerRadius, f);
|
|
427
|
+
CGRect whole = CGRectMake(0, 0, state.size.width, state.size.height);
|
|
428
|
+
state.fromFrame = whole;
|
|
429
|
+
state.toFrame = whole;
|
|
430
|
+
state.fromAlpha = 1;
|
|
431
|
+
state.toAlpha = (CGFloat)f;
|
|
396
432
|
if (_mask) {
|
|
397
433
|
CGRect fromWhole = CGRectMake(0, 0, _from.size.width, _from.size.height);
|
|
398
434
|
CGRect toWhole = CGRectMake(0, 0, _to.size.width, _to.size.height);
|
|
399
435
|
CGRect fromVisible = _from.clipped ? _from.visible : fromWhole;
|
|
400
436
|
CGRect toVisible = _to.clipped ? _to.visible : toWhole;
|
|
401
|
-
|
|
437
|
+
state.mask = ANNInterpolateRect(fromVisible, toVisible, f);
|
|
438
|
+
// At most half the shorter side, as a rounded path has it.
|
|
402
439
|
CGFloat radius = ANNInterpolateNumber(_from.visibleRadius, _to.visibleRadius, f);
|
|
403
|
-
|
|
404
|
-
|
|
440
|
+
CGFloat half = MIN(state.mask.size.width, state.mask.size.height) / 2;
|
|
441
|
+
state.maskRadius = MAX(MIN(radius, half), 0);
|
|
405
442
|
}
|
|
443
|
+
return state;
|
|
406
444
|
}
|
|
407
445
|
|
|
408
446
|
// The text's pictures keep their aspect at the height of the copy, at the line's start; they swap by the clock,
|
|
409
447
|
// linearly, in the first quarter of the flight when the text grows, in the last when it shrinks.
|
|
410
|
-
- (void)
|
|
411
|
-
|
|
412
|
-
|
|
413
|
-
|
|
448
|
+
- (void)placeTextPicturesOf:(ANNFlightState *)state atTime:(NSTimeInterval)time {
|
|
449
|
+
CGRect frames[2];
|
|
450
|
+
UIImageView *pictures[2] = {_fromPicture, _toPicture};
|
|
451
|
+
for (int index = 0; index < 2; index++) {
|
|
452
|
+
CGSize imageSize = pictures[index].image.size;
|
|
453
|
+
CGFloat height = state->size.height;
|
|
414
454
|
CGFloat width = imageSize.height > 0 ? imageSize.width * height / imageSize.height : 0;
|
|
415
|
-
CGFloat x = _rightToLeft ? size.width - width : 0;
|
|
416
|
-
|
|
455
|
+
CGFloat x = _rightToLeft ? state->size.width - width : 0;
|
|
456
|
+
frames[index] = CGRectMake(x, 0, width, height);
|
|
417
457
|
}
|
|
458
|
+
state->fromFrame = frames[0];
|
|
459
|
+
state->toFrame = frames[1];
|
|
460
|
+
double swap = [self swapAtTime:time];
|
|
461
|
+
state->fromAlpha = (CGFloat)(1 - swap);
|
|
462
|
+
state->toAlpha = (CGFloat)swap;
|
|
463
|
+
}
|
|
464
|
+
|
|
465
|
+
// The quarter of the flight the text's pictures swap in.
|
|
466
|
+
- (NSTimeInterval)swapStart {
|
|
418
467
|
NSTimeInterval duration = _track.duration;
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
468
|
+
return _track.delay + (_grows ? 0 : duration - duration / 4);
|
|
469
|
+
}
|
|
470
|
+
|
|
471
|
+
- (double)swapAtTime:(NSTimeInterval)time {
|
|
472
|
+
NSTimeInterval quarter = _track.duration / 4;
|
|
422
473
|
if (quarter <= 0) {
|
|
423
|
-
|
|
424
|
-
}
|
|
425
|
-
|
|
474
|
+
return time >= _track.delay ? 1 : 0;
|
|
475
|
+
}
|
|
476
|
+
return MIN(MAX((time - [self swapStart]) / quarter, 0.0), 1.0);
|
|
477
|
+
}
|
|
478
|
+
|
|
479
|
+
// The copy shows its state at `time` (its own values, with no animation).
|
|
480
|
+
- (void)placeAtTime:(NSTimeInterval)time {
|
|
481
|
+
ANNFlightState state = [self stateAtTime:time];
|
|
482
|
+
[CATransaction begin];
|
|
483
|
+
[CATransaction setDisableActions:YES];
|
|
484
|
+
_copy.layer.transform = CATransform3DIdentity;
|
|
485
|
+
_copy.bounds = CGRectMake(0, 0, state.size.width, state.size.height);
|
|
486
|
+
_copy.center = state.center;
|
|
487
|
+
_copy.layer.transform = state.transform;
|
|
488
|
+
if (!_text) {
|
|
489
|
+
_copy.layer.cornerRadius = state.cornerRadius;
|
|
490
|
+
}
|
|
491
|
+
ANNPlaceLayer(_fromPicture.layer, state.fromFrame);
|
|
492
|
+
ANNPlaceLayer(_toPicture.layer, state.toFrame);
|
|
493
|
+
_fromPicture.alpha = state.fromAlpha;
|
|
494
|
+
_toPicture.alpha = state.toAlpha;
|
|
495
|
+
if (_mask) {
|
|
496
|
+
ANNPlaceLayer(_mask, state.mask);
|
|
497
|
+
_mask.cornerRadius = state.maskRadius;
|
|
498
|
+
}
|
|
499
|
+
[CATransaction commit];
|
|
500
|
+
}
|
|
501
|
+
|
|
502
|
+
#pragma mark - ANNFrameClient
|
|
503
|
+
|
|
504
|
+
- (NSTimeInterval)endTime {
|
|
505
|
+
return _track.endTime;
|
|
506
|
+
}
|
|
507
|
+
|
|
508
|
+
- (void)addAnimationsWithLength:(NSTimeInterval)length {
|
|
509
|
+
if (_restored) {
|
|
510
|
+
return;
|
|
511
|
+
}
|
|
512
|
+
NSMutableArray<NSNumber *> *marks =
|
|
513
|
+
[NSMutableArray arrayWithObjects:@(_track.delay), @(_track.endTime), nil];
|
|
514
|
+
if (_text) {
|
|
515
|
+
[marks addObject:@([self swapStart])];
|
|
516
|
+
[marks addObject:@([self swapStart] + _track.duration / 4)];
|
|
517
|
+
}
|
|
518
|
+
NSArray<NSNumber *> *times = ANNSampleTimes(_track.endTime, length, marks);
|
|
519
|
+
NSMutableArray *bounds = [NSMutableArray new];
|
|
520
|
+
NSMutableArray *positions = [NSMutableArray new];
|
|
521
|
+
NSMutableArray *transforms = [NSMutableArray new];
|
|
522
|
+
NSMutableArray *radii = [NSMutableArray new];
|
|
523
|
+
NSMutableArray *fromBounds = [NSMutableArray new];
|
|
524
|
+
NSMutableArray *fromPositions = [NSMutableArray new];
|
|
525
|
+
NSMutableArray *fromAlphas = [NSMutableArray new];
|
|
526
|
+
NSMutableArray *toBounds = [NSMutableArray new];
|
|
527
|
+
NSMutableArray *toPositions = [NSMutableArray new];
|
|
528
|
+
NSMutableArray *toAlphas = [NSMutableArray new];
|
|
529
|
+
NSMutableArray *maskBounds = [NSMutableArray new];
|
|
530
|
+
NSMutableArray *maskPositions = [NSMutableArray new];
|
|
531
|
+
NSMutableArray *maskRadii = [NSMutableArray new];
|
|
532
|
+
for (NSNumber *time in times) {
|
|
533
|
+
ANNFlightState state = [self stateAtTime:time.doubleValue];
|
|
534
|
+
CGRect whole = CGRectMake(0, 0, state.size.width, state.size.height);
|
|
535
|
+
[bounds addObject:ANNBoundsValue(whole)];
|
|
536
|
+
[positions addObject:[NSValue valueWithCGPoint:state.center]];
|
|
537
|
+
[transforms addObject:[NSValue valueWithCATransform3D:state.transform]];
|
|
538
|
+
[radii addObject:@(state.cornerRadius)];
|
|
539
|
+
[fromBounds addObject:ANNBoundsValue(state.fromFrame)];
|
|
540
|
+
[fromPositions addObject:ANNPositionValue(state.fromFrame)];
|
|
541
|
+
[fromAlphas addObject:@(state.fromAlpha)];
|
|
542
|
+
[toBounds addObject:ANNBoundsValue(state.toFrame)];
|
|
543
|
+
[toPositions addObject:ANNPositionValue(state.toFrame)];
|
|
544
|
+
[toAlphas addObject:@(state.toAlpha)];
|
|
545
|
+
[maskBounds addObject:ANNBoundsValue(state.mask)];
|
|
546
|
+
[maskPositions addObject:ANNPositionValue(state.mask)];
|
|
547
|
+
[maskRadii addObject:@(state.maskRadius)];
|
|
548
|
+
}
|
|
549
|
+
CALayer *copy = _copy.layer;
|
|
550
|
+
ANNAddKeyframes(copy, @"bounds", bounds, times, length);
|
|
551
|
+
ANNAddKeyframes(copy, @"position", positions, times, length);
|
|
552
|
+
ANNAddKeyframes(copy, @"transform", transforms, times, length);
|
|
553
|
+
if (!_text) {
|
|
554
|
+
ANNAddKeyframes(copy, @"cornerRadius", radii, times, length);
|
|
555
|
+
}
|
|
556
|
+
ANNAddKeyframes(_fromPicture.layer, @"bounds", fromBounds, times, length);
|
|
557
|
+
ANNAddKeyframes(_fromPicture.layer, @"position", fromPositions, times, length);
|
|
558
|
+
ANNAddKeyframes(_fromPicture.layer, @"opacity", fromAlphas, times, length);
|
|
559
|
+
ANNAddKeyframes(_toPicture.layer, @"bounds", toBounds, times, length);
|
|
560
|
+
ANNAddKeyframes(_toPicture.layer, @"position", toPositions, times, length);
|
|
561
|
+
ANNAddKeyframes(_toPicture.layer, @"opacity", toAlphas, times, length);
|
|
562
|
+
if (_mask) {
|
|
563
|
+
ANNAddKeyframes(_mask, @"bounds", maskBounds, times, length);
|
|
564
|
+
ANNAddKeyframes(_mask, @"position", maskPositions, times, length);
|
|
565
|
+
ANNAddKeyframes(_mask, @"cornerRadius", maskRadii, times, length);
|
|
426
566
|
}
|
|
427
|
-
_fromPicture.alpha = (CGFloat)(1 - swap);
|
|
428
|
-
_toPicture.alpha = (CGFloat)swap;
|
|
429
567
|
}
|
|
430
568
|
|
|
431
569
|
- (void)restore {
|
|
@@ -20,13 +20,14 @@ FOUNDATION_EXPORT CGFloat ANNCameraDistance(void);
|
|
|
20
20
|
/// position and bounds, never its frame) moved by its own translation, `alpha` its alpha before any hide, the
|
|
21
21
|
/// translations, scales and rotations those of its own transform (React Native's `transform`, split into its parts as
|
|
22
22
|
/// Android's views hold it: the shorter angle, −180…180). A property that doesn't play keeps the view's own value, its
|
|
23
|
-
/// skew and perspective stay.
|
|
24
|
-
/// only when a property of the transform plays; with `translationX` and `x` on one side the
|
|
25
|
-
///
|
|
23
|
+
/// skew and perspective stay. Only what plays animates, in Core Animation (`ANNKeyframes`): `opacity` only with an
|
|
24
|
+
/// `alpha` track, `transform` only when a property of the transform plays; with `translationX` and `x` on one side the
|
|
25
|
+
/// translation wins. The view's own values (its frame, alpha and transform) are never written: the animations show
|
|
26
|
+
/// over them, and `restore` removes the animations.
|
|
26
27
|
@interface ANNViewTracks : NSObject <ANNFrameClient>
|
|
27
28
|
|
|
28
29
|
/// The playing properties of `side` on `view`. `alphaBefore`: the view's alpha before the core hid it for the
|
|
29
|
-
/// transition (the arriving screen), else its alpha now
|
|
30
|
+
/// transition (the arriving screen), else its alpha now.
|
|
30
31
|
- (instancetype)initWithView:(UIView *)view
|
|
31
32
|
side:(ANNViewAnimationOptions *)side
|
|
32
33
|
alphaBefore:(CGFloat)alphaBefore;
|
|
@@ -35,7 +36,7 @@ FOUNDATION_EXPORT CGFloat ANNCameraDistance(void);
|
|
|
35
36
|
/// disappearing from it to `to`, or both ends given.
|
|
36
37
|
- (instancetype)initWithView:(UIView *)view element:(ANNElementTransitionOptions *)element;
|
|
37
38
|
|
|
38
|
-
/// A linear fade of `view`'s alpha (a dismissed popover with nothing to play
|
|
39
|
+
/// A linear fade of `view`'s alpha (a dismissed popover with nothing to play, a transition under Reduce Motion).
|
|
39
40
|
- (instancetype)initWithView:(UIView *)view
|
|
40
41
|
fadeFrom:(CGFloat)from
|
|
41
42
|
to:(CGFloat)to
|
|
@@ -93,8 +93,8 @@ static ANNTrack *ANNTrackOf(Double *from, Double *to, Double *duration, Double *
|
|
|
93
93
|
CGFloat _ownPerspective;
|
|
94
94
|
BOOL _animatesTransform;
|
|
95
95
|
BOOL _tilts;
|
|
96
|
-
BOOL
|
|
97
|
-
BOOL
|
|
96
|
+
BOOL _addedAlpha;
|
|
97
|
+
BOOL _addedTransform;
|
|
98
98
|
BOOL _restored;
|
|
99
99
|
NSTimeInterval _endTime;
|
|
100
100
|
}
|
|
@@ -235,17 +235,9 @@ static ANNTrack *ANNTrackOf(Double *from, Double *to, Double *duration, Double *
|
|
|
235
235
|
return track ? [track valueAt:time] : [self viewValueOf:property];
|
|
236
236
|
}
|
|
237
237
|
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
}
|
|
242
|
-
if (self.animatesAlpha) {
|
|
243
|
-
_view.alpha = (CGFloat)[_tracks[ANNViewPropertyAlpha] valueAt:time];
|
|
244
|
-
_wroteAlpha = YES;
|
|
245
|
-
}
|
|
246
|
-
if (!_animatesTransform) {
|
|
247
|
-
return;
|
|
248
|
-
}
|
|
238
|
+
// The view's transform at `time`: every property of the transform at its value then, the ones that don't play at the
|
|
239
|
+
// view's own.
|
|
240
|
+
- (CATransform3D)transformAtTime:(NSTimeInterval)time {
|
|
249
241
|
CGFloat scaleX = (CGFloat)[self valueOf:ANNViewPropertyScaleX at:time];
|
|
250
242
|
CGFloat scaleY = (CGFloat)[self valueOf:ANNViewPropertyScaleY at:time];
|
|
251
243
|
// The translation wins over the place when a side has both.
|
|
@@ -279,10 +271,48 @@ static ANNTrack *ANNTrackOf(Double *from, Double *to, Double *duration, Double *
|
|
|
279
271
|
perspective.m34 = _ownPerspective != 0 ? _ownPerspective : -1.0 / ANNCameraDistance();
|
|
280
272
|
transform = CATransform3DConcat(transform, perspective);
|
|
281
273
|
}
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
274
|
+
return CATransform3DConcat(transform,
|
|
275
|
+
CATransform3DMakeTranslation(translationX, translationY, 0));
|
|
276
|
+
}
|
|
277
|
+
|
|
278
|
+
- (void)addAnimationsWithLength:(NSTimeInterval)length {
|
|
279
|
+
if (_restored || !self.hasTracks) {
|
|
280
|
+
return;
|
|
281
|
+
}
|
|
282
|
+
// Every track's start and end is a sample, so a track that starts late or ends early does it on time.
|
|
283
|
+
NSMutableArray<NSNumber *> *marks = [NSMutableArray new];
|
|
284
|
+
for (NSInteger index = 0; index < ANNViewPropertyCount; index++) {
|
|
285
|
+
ANNTrack *track = _tracks[index];
|
|
286
|
+
if (track) {
|
|
287
|
+
[marks addObject:@(track.delay)];
|
|
288
|
+
[marks addObject:@(track.endTime)];
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
NSArray<NSNumber *> *times = ANNSampleTimes(_endTime, length, marks);
|
|
292
|
+
CALayer *layer = _view.layer;
|
|
293
|
+
if (self.animatesAlpha) {
|
|
294
|
+
NSMutableArray<NSNumber *> *values = [NSMutableArray arrayWithCapacity:times.count];
|
|
295
|
+
for (NSNumber *time in times) {
|
|
296
|
+
[values addObject:@([_tracks[ANNViewPropertyAlpha] valueAt:time.doubleValue])];
|
|
297
|
+
}
|
|
298
|
+
CAKeyframeAnimation *animation = ANNKeyframes(@"opacity", values, times, length);
|
|
299
|
+
if (animation) {
|
|
300
|
+
[layer addAnimation:animation forKey:ANNAnimationKey(@"opacity")];
|
|
301
|
+
_addedAlpha = YES;
|
|
302
|
+
}
|
|
303
|
+
}
|
|
304
|
+
if (_animatesTransform) {
|
|
305
|
+
NSMutableArray<NSValue *> *values = [NSMutableArray arrayWithCapacity:times.count];
|
|
306
|
+
for (NSNumber *time in times) {
|
|
307
|
+
CATransform3D transform = [self transformAtTime:time.doubleValue];
|
|
308
|
+
[values addObject:[NSValue valueWithCATransform3D:transform]];
|
|
309
|
+
}
|
|
310
|
+
CAKeyframeAnimation *animation = ANNKeyframes(@"transform", values, times, length);
|
|
311
|
+
if (animation) {
|
|
312
|
+
[layer addAnimation:animation forKey:ANNAnimationKey(@"transform")];
|
|
313
|
+
_addedTransform = YES;
|
|
314
|
+
}
|
|
315
|
+
}
|
|
286
316
|
}
|
|
287
317
|
|
|
288
318
|
- (void)restore {
|
|
@@ -290,11 +320,11 @@ static ANNTrack *ANNTrackOf(Double *from, Double *to, Double *duration, Double *
|
|
|
290
320
|
return;
|
|
291
321
|
}
|
|
292
322
|
_restored = YES;
|
|
293
|
-
if (
|
|
294
|
-
_view.
|
|
323
|
+
if (_addedAlpha) {
|
|
324
|
+
[_view.layer removeAnimationForKey:ANNAnimationKey(@"opacity")];
|
|
295
325
|
}
|
|
296
|
-
if (
|
|
297
|
-
_view.layer
|
|
326
|
+
if (_addedTransform) {
|
|
327
|
+
[_view.layer removeAnimationForKey:ANNAnimationKey(@"transform")];
|
|
298
328
|
}
|
|
299
329
|
}
|
|
300
330
|
|
package/ios/CLAUDE.md
CHANGED
|
@@ -44,7 +44,7 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
|
|
|
44
44
|
- Screen: React root, slot, native screen — `Layouts/Screen/`: `ANNComponentViewController`; `React/`: `ANNReactView`, `ANNReactViewCreator`, `Slots/`; `Layouts/External/`
|
|
45
45
|
- Screen: insets, background, keyboard focus — `Layouts/Screen/`: `ANNScreenEnvironment`, `ANNScreenSafeArea`, `ANNScreenBars`, `ANNScreenPlace` (the safe area of a parked screen), `ANNComponentPresenter` (background, status bar blur), `ANNComponentViewController` (key commands)
|
|
46
46
|
- Safe area view — `React/`: `ANNSafeAreaViewComponentView`; `Layouts/Screen/`: `ANNScreenReadiness`; `cpp/`
|
|
47
|
-
- Stack: queue, commands, back, gestures — `Layouts/Stack/`: `ANNStackQueue`, `ANNStackCommands`, `ANNStackController`, `InteractivePopGestureDelegate`, `ANNStackAnimatingDelegate`
|
|
47
|
+
- Stack: queue, commands, back, gestures — `Layouts/Stack/`: `ANNStackQueue`, `ANNStackCommands`, `ANNStackController`, `InteractivePopGestureDelegate`, `ANNStackAnimatingDelegate`, `ANNSwipePopInteraction`
|
|
48
48
|
- Top bar: title, buttons, menus, large title — `TopBar/`: `ANNTopBarPresenter`, `ANNBarButtons`, `ANNBarAppearance`, `ANNLargeTitlePresenter`, `ANNBackButtonPresenter`, `ANNTitleView`, `ANNTitleGlassView`
|
|
49
49
|
- Top bar: components, waits — `TopBar/`: `ANNBarComponentsWait`, `ANNBarBackgroundComponent`, `ANNTitleComponentView`, `ANNBarButtonComponentView`; `React/`: `ANNReactComponentRegistry`
|
|
50
50
|
- Search — `TopBar/`: `ANNSearchPresenter`, `ANNSearchResultsViewController`
|
|
@@ -133,16 +133,26 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
|
|
|
133
133
|
- Use an `@available` check for newer OS APIs, and `#if __IPHONE_OS_VERSION_MAX_ALLOWED >= …` for names of a newer SDK.
|
|
134
134
|
- React Native hears of the app's light or dark style only from its hosting views in the window. A view that stands in
|
|
135
135
|
for one (`ANNSlotScreenView`, the screens of one tree) posts the trait change itself, with the window filter of
|
|
136
|
-
`ANNSurfaceHostingView`; without it `useColorScheme` keeps the old theme.
|
|
136
|
+
`ANNSurfaceHostingView`; without it `useColorScheme` keeps the old theme. `Appearance.setColorScheme` sets the style
|
|
137
|
+
only on the windows there are at that moment: every window the core makes but the main window comes from
|
|
138
|
+
`+[ANNUtils makeWindowOfClass:]`, which gives it the main window's style. A window of the system's style would draw
|
|
139
|
+
its screens in the other theme, and its hosting views would switch the app's theme in JS.
|
|
137
140
|
- A step that runs later in a command (a ready handler, a timer, a transition's end) goes through the owner's guard
|
|
138
141
|
(`protect:step:`), so an exception rejects the command instead of crashing the app. So does the start of a wait
|
|
139
142
|
(the root, a modal, an overlay: their record is listed and parked by then), and the modal's end steps inside UIKit's
|
|
140
143
|
completions and delegate calls (`protectEndOf:showing:step:`, `protectBatch:step:`); the overlay's guard is
|
|
141
144
|
`protectWaitOf:step:`.
|
|
142
|
-
- Each step of a transition's element (a pair's start and take-off, an element's start,
|
|
145
|
+
- Each step of a transition's element (a pair's start and take-off, an element's start, its animations and restore
|
|
143
146
|
through `ANNIsolatedElement`) runs in `ANNElementStep`: an exception skips that element only. A new step of an
|
|
144
|
-
element goes there too, or one bad element stops the transition
|
|
145
|
-
|
|
147
|
+
element goes there too, or one bad element stops the transition. The elements of a transition are found in one walk
|
|
148
|
+
per screen (`ANNTransitionViews`).
|
|
149
|
+
- A custom transition runs in Core Animation (`ANNFrameDriver`): each client samples its values once, at the start,
|
|
150
|
+
into keyframe animations under the core's keys (`ANNAnimationKey`), and `restore` removes them. It never writes a
|
|
151
|
+
view's own alpha or transform for a frame; a value the core writes for the transition (the arriving view's hide, a
|
|
152
|
+
hidden flag, a depth) goes back as before. A pop that follows the swipe back also stops the time of the transition's
|
|
153
|
+
container (`startPausedOn:done:`), and the container gets its time back before the clients restore. UIKit runs its
|
|
154
|
+
swipe back without asking the navigation's delegate for a transition (iOS 26 checked), so that pop follows the
|
|
155
|
+
stack's own swipe from the leading edge, and UIKit's swipes stand aside meanwhile (`swipeBackTakes:`).
|
|
146
156
|
- An `ANNCommandAnswer` that goes away unanswered rejects (`notAnswered:`), but only once nothing holds it: a record,
|
|
147
157
|
list or queue that keeps an answer still answers it on every exit, or the promise waits as long as the record lives.
|
|
148
158
|
- On iOS 26 UIKit draws a screen's large title, for a whole pop or swipe back, inside the scroll view the bar follows
|
|
@@ -152,9 +162,10 @@ Top-level files: `Package.swift` (SwiftPM; `headerFolders` lists every folder wh
|
|
|
152
162
|
- Under a hidden bar UIKit's swipe delegate refuses the swipes in questions of its own: `InteractivePopGestureDelegate`
|
|
153
163
|
stops passing those on while the bar is hidden (`barHidden`, set again on the recognizer at each change, from
|
|
154
164
|
`swipeBackFollowsBar`), and the core sorts the pans itself.
|
|
155
|
-
- UIKit works out the safe area of the window's root view from where it is
|
|
156
|
-
|
|
157
|
-
root out after a restore (`+[ANNRootCoordinator stopAnimationOf:]`).
|
|
165
|
+
- UIKit works out the safe area of the window's root view from where it is: a `transform` written on it that moves it
|
|
166
|
+
leaves it with no top inset, and putting the transform back doesn't work it out again. A root's own `enter` moves
|
|
167
|
+
only what Core Animation draws; the root is still laid out after a restore (`+[ANNRootCoordinator stopAnimationOf:]`).
|
|
168
|
+
While it animates, the new root takes no touches: it is drawn elsewhere than where it is.
|
|
158
169
|
- UIKit's `hidesBarsWhenKeyboardAppears`, once on, keeps hiding the bar after it is set back to `NO`: the stack hides
|
|
159
170
|
the bars for `topBar.hideOnKeyboard` itself (`keyboardWillShow:` / `keyboardDidHide:` of `ANNStackController`) and
|
|
160
171
|
leaves the flag off.
|
package/ios/CORE.md
CHANGED
|
@@ -139,7 +139,7 @@ Each of these owns one rule. Call it; do not copy it.
|
|
|
139
139
|
- `ANNReloadCoordinator`: The generation of the JavaScript; the reset after a reload. Called by: Runner, emitter, answer.
|
|
140
140
|
- 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`.
|
|
141
141
|
- 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`.
|
|
142
|
-
- Back: The stack: `ANNStackController` (`userGoBackTo:`, `backAction`, the hold) and `InteractivePopGestureDelegate`; UIKit's own pops are followed by `ANNStackQueue uikitShows:`. Called by: UIKit, the user.
|
|
142
|
+
- Back: The stack: `ANNStackController` (`userGoBackTo:`, `backAction`, the hold) and `InteractivePopGestureDelegate`; UIKit's own pops are followed by `ANNStackQueue uikitShows:`. A top whose own pop is `interactive` gets the stack's own swipe from the leading edge instead of UIKit's (UIKit runs its swipe without asking the delegate for a transition): `swipeBackTakes:` picks the swipe, `ownSwipeBack:` pops, and `swipeAnimatorFrom:to:` and `ANNSwipePopInteraction` play the pop under the finger. Called by: UIKit, the user.
|
|
143
143
|
|
|
144
144
|
## Modules
|
|
145
145
|
|
|
@@ -71,8 +71,6 @@ static BOOL ANNAppUsesScenes(void) {
|
|
|
71
71
|
rootViewFactory.reactHost = host;
|
|
72
72
|
[ApexNavigation bootstrapWithHost:host];
|
|
73
73
|
self.window = ApexNavigation.mainWindow;
|
|
74
|
-
// The links that come before React content wait for it; the launch link waits for the scene.
|
|
75
|
-
[ANNDeepLinkQueue start];
|
|
76
74
|
if (!ANNAppUsesScenes()) {
|
|
77
75
|
[self.window makeKeyAndVisible];
|
|
78
76
|
}
|
|
@@ -14,7 +14,8 @@ typedef void (^ANNLaunchLinkCompletion)(NSURL *_Nullable url);
|
|
|
14
14
|
*/
|
|
15
15
|
@interface ANNDeepLinkQueue : NSObject
|
|
16
16
|
|
|
17
|
-
/// Starts the queue as the app launches (
|
|
17
|
+
/// Starts the queue as the app launches (`+[ApexNavigation bootstrapWithHost:]`, so also under an app delegate of the
|
|
18
|
+
/// app's own). Once: a second call does nothing.
|
|
18
19
|
+ (void)start;
|
|
19
20
|
|
|
20
21
|
/// Hands `url` to `Linking`, or keeps it until React content first appears and no `takeLaunchLink:` waits.
|
|
@@ -33,11 +34,11 @@ typedef void (^ANNLaunchLinkCompletion)(NSURL *_Nullable url);
|
|
|
33
34
|
ofScene:(UIScene *)scene
|
|
34
35
|
through:(NS_NOESCAPE dispatch_block_t)forward;
|
|
35
36
|
|
|
36
|
-
///
|
|
37
|
-
+ (void)
|
|
37
|
+
/// `dispatchURL:` for the scene's link, with its scene and context: for an app delegate that doesn't take links.
|
|
38
|
+
+ (void)dispatchURLContext:(UIOpenURLContext *)context ofScene:(UIScene *)scene;
|
|
38
39
|
|
|
39
|
-
/// `
|
|
40
|
-
+ (void)
|
|
40
|
+
/// `dispatchURLContext:ofScene:` for a web link (`webpageURL`) of the scene; an activity without one does nothing.
|
|
41
|
+
+ (void)dispatchUserActivity:(NSUserActivity *)activity ofScene:(UIScene *)scene;
|
|
41
42
|
|
|
42
43
|
/// The app's window scene connected (`+[ApexNavigation attachToWindowScene:]`).
|
|
43
44
|
+ (void)windowSceneDidConnect;
|
|
@@ -71,19 +71,19 @@ static ANNLaunchLinkCompletion ANNWaitingLaunchLinkCompletion;
|
|
|
71
71
|
selector:@selector(applicationDidBecomeActive:)
|
|
72
72
|
name:UIApplicationDidBecomeActiveNotification
|
|
73
73
|
object:nil];
|
|
74
|
+
// Started once the app is active (an app that starts React Native later, on demand), the queue missed the scene's
|
|
75
|
+
// connection and the notification: the links that launched the app went elsewhere, and none waits for them.
|
|
76
|
+
if (UIApplication.sharedApplication.applicationState == UIApplicationStateActive) {
|
|
77
|
+
[self launchLinksKnown];
|
|
78
|
+
}
|
|
74
79
|
}
|
|
75
80
|
|
|
76
81
|
+ (void)dispatchURL:(NSURL *)url {
|
|
77
|
-
ANNAssertMainQueue();
|
|
78
82
|
// The URL of the scene's link keeps its context; one the app delegate changed is a URL of its own.
|
|
79
83
|
ANNDeepLink *link = [ANNForwardedSceneLink.url isEqual:url]
|
|
80
84
|
? ANNForwardedSceneLink
|
|
81
85
|
: [[ANNDeepLink alloc] initWithURL:url context:nil activity:nil scene:nil];
|
|
82
|
-
|
|
83
|
-
[self openLink:link];
|
|
84
|
-
return;
|
|
85
|
-
}
|
|
86
|
-
[ANNPendingDeepLinks addObject:link];
|
|
86
|
+
[self dispatchLink:link];
|
|
87
87
|
}
|
|
88
88
|
|
|
89
89
|
+ (void)forwardURLContext:(UIOpenURLContext *)context
|
|
@@ -104,15 +104,18 @@ static ANNLaunchLinkCompletion ANNWaitingLaunchLinkCompletion;
|
|
|
104
104
|
through:forward];
|
|
105
105
|
}
|
|
106
106
|
|
|
107
|
-
+ (void)
|
|
108
|
-
[self
|
|
107
|
+
+ (void)dispatchURLContext:(UIOpenURLContext *)context ofScene:(UIScene *)scene {
|
|
108
|
+
[self dispatchLink:[[ANNDeepLink alloc] initWithURL:context.URL context:context activity:nil scene:scene]];
|
|
109
109
|
}
|
|
110
110
|
|
|
111
|
-
+ (void)
|
|
111
|
+
+ (void)dispatchUserActivity:(NSUserActivity *)activity ofScene:(UIScene *)scene {
|
|
112
112
|
if (activity.webpageURL == nil) {
|
|
113
113
|
return;
|
|
114
114
|
}
|
|
115
|
-
[self
|
|
115
|
+
[self dispatchLink:[[ANNDeepLink alloc] initWithURL:activity.webpageURL
|
|
116
|
+
context:nil
|
|
117
|
+
activity:activity
|
|
118
|
+
scene:scene]];
|
|
116
119
|
}
|
|
117
120
|
|
|
118
121
|
+ (void)windowSceneDidConnect {
|
|
@@ -191,6 +194,15 @@ static ANNLaunchLinkCompletion ANNWaitingLaunchLinkCompletion;
|
|
|
191
194
|
completion(url);
|
|
192
195
|
}
|
|
193
196
|
|
|
197
|
+
+ (void)dispatchLink:(ANNDeepLink *)link {
|
|
198
|
+
ANNAssertMainQueue();
|
|
199
|
+
if (ANNReactContentAppeared && ANNWaitingLaunchLinkCompletion == nil) {
|
|
200
|
+
[self openLink:link];
|
|
201
|
+
return;
|
|
202
|
+
}
|
|
203
|
+
[ANNPendingDeepLinks addObject:link];
|
|
204
|
+
}
|
|
205
|
+
|
|
194
206
|
+ (void)forwardLink:(ANNDeepLink *)link through:(NS_NOESCAPE dispatch_block_t)forward {
|
|
195
207
|
ANNAssertMainQueue();
|
|
196
208
|
ANNDeepLink *outer = ANNForwardedSceneLink;
|
|
@@ -77,7 +77,7 @@
|
|
|
77
77
|
[appDelegate application:application openURL:context.URL options:options];
|
|
78
78
|
}];
|
|
79
79
|
} else {
|
|
80
|
-
[ANNDeepLinkQueue
|
|
80
|
+
[ANNDeepLinkQueue dispatchURLContext:context ofScene:scene];
|
|
81
81
|
}
|
|
82
82
|
}
|
|
83
83
|
|
|
@@ -95,7 +95,7 @@
|
|
|
95
95
|
}];
|
|
96
96
|
}];
|
|
97
97
|
} else if ([activity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
|
|
98
|
-
[ANNDeepLinkQueue
|
|
98
|
+
[ANNDeepLinkQueue dispatchUserActivity:activity ofScene:scene];
|
|
99
99
|
}
|
|
100
100
|
}
|
|
101
101
|
|
package/ios/Core/ANNUtils.h
CHANGED
|
@@ -23,6 +23,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
23
23
|
/// The key window of `scene`; nil when none of its windows is key.
|
|
24
24
|
+ (nullable UIWindow *)keyWindowOfScene:(nullable UIWindowScene *)scene;
|
|
25
25
|
|
|
26
|
+
/// A new window of `windowClass` (`UIWindow` or a subclass) for the core: in the navigation window's scene, or at the
|
|
27
|
+
/// size of `mainScreen` before that window has one. It takes the navigation window's `overrideUserInterfaceStyle`:
|
|
28
|
+
/// React Native's `Appearance.setColorScheme` sets the style once, on the windows there are at that moment, and a
|
|
29
|
+
/// window of the system's style would show its screens in the other theme and switch the app's theme in JS when its
|
|
30
|
+
/// hosting views move in. Every window the core makes but the main window, the style's source, comes from here.
|
|
31
|
+
+ (__kindof UIWindow *)makeWindowOfClass:(Class)windowClass;
|
|
32
|
+
|
|
26
33
|
/// The status bar's frame in the navigation window's scene (its status bar manager); `CGRectZero` without a scene.
|
|
27
34
|
@property(class, nonatomic, readonly) CGRect statusBarFrame;
|
|
28
35
|
|
package/ios/Core/ANNUtils.mm
CHANGED
|
@@ -26,6 +26,16 @@
|
|
|
26
26
|
return windows;
|
|
27
27
|
}
|
|
28
28
|
|
|
29
|
+
+ (UIWindow *)makeWindowOfClass:(Class)windowClass {
|
|
30
|
+
UIWindowScene *scene = self.mainWindow.windowScene;
|
|
31
|
+
// `initWithFrame:` is deprecated from iOS 26 on and warns only with a deployment target from 26: a window without a
|
|
32
|
+
// scene is made so, and joins the scene as the main window gets it.
|
|
33
|
+
UIWindow *window = scene ? [[windowClass alloc] initWithWindowScene:scene]
|
|
34
|
+
: [[windowClass alloc] initWithFrame:self.mainScreen.bounds];
|
|
35
|
+
window.overrideUserInterfaceStyle = self.mainWindow.overrideUserInterfaceStyle;
|
|
36
|
+
return window;
|
|
37
|
+
}
|
|
38
|
+
|
|
29
39
|
+ (UIWindow *)keyWindowOfScene:(UIWindowScene *)scene {
|
|
30
40
|
for (UIWindow *window in scene.windows) {
|
|
31
41
|
if (window.isKeyWindow) {
|