@apexnative/navigation 1.0.0-alpha.4 → 1.0.0-alpha.6
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/CHANGELOG.md +38 -0
- package/README.md +1 -1
- package/android/CLAUDE.md +23 -14
- package/android/CORE.md +32 -19
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +60 -13
- package/android/src/main/java/dev/apexnative/navigation/options/AnimationsOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
- package/android/src/main/java/dev/apexnative/navigation/options/HardwareBackButtonOptions.kt +9 -1
- package/android/src/main/java/dev/apexnative/navigation/options/StackAnimationOptions.kt +40 -2
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +28 -13
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +7 -4
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReactView.kt +39 -20
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +7 -6
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +19 -51
- package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +30 -39
- package/android/src/main/java/dev/apexnative/navigation/react/events/EventEmitter.kt +34 -7
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationState.kt +154 -0
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +12 -10
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotScreenView.kt +12 -12
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewExtensions.kt +0 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/arrangement/ArrangementController.kt +10 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsCollapseBehavior.kt +5 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +22 -21
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowGeometry.kt +22 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/TabsRoom.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentViewController.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenReadiness.kt +103 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ScreenSafeArea.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/externalcomponent/ExternalComponentViewController.kt +2 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +20 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +119 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +7 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/SheetBack.kt +42 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackCheck.kt +50 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +45 -14
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +25 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/PreparedFrame.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +13 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/ScreenSearch.kt +3 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +2 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/BarComponents.kt +5 -51
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +4 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ScrollEventListener.kt +6 -9
- package/android/src/main/java/dev/apexnative/navigation/views/animations/AnimationLayer.kt +73 -0
- package/android/src/main/java/dev/apexnative/navigation/views/animations/ValueInterpolation.kt +14 -0
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabsLayout.kt +61 -2
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomBottomTabItemView.kt +0 -5
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/CustomTabRow.kt +72 -10
- package/android/src/main/java/dev/apexnative/navigation/views/element/ElementTransitions.kt +9 -2
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ElementPictures.kt +11 -0
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/FlightGeometry.kt +318 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ImageMatrixAnimator.kt +18 -20
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/PlainViewAnimator.kt +17 -25
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ReactImageAnimator.kt +15 -22
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/SharedAnimatorFactory.kt +12 -9
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/TextChangeAnimator.kt +84 -35
- package/android/src/main/java/dev/apexnative/navigation/views/element/animators/ViewChangeAnimator.kt +49 -40
- package/cli/doctor/checks/splash.js +4 -22
- package/cli/splash/index.js +3 -1
- package/docs-md/architecture/transitions.md +4 -2
- package/docs-md/containers/modals.md +4 -2
- package/docs-md/containers/overlays.md +1 -1
- package/docs-md/containers/side-menu.md +1 -1
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/bottom-sheets.md +1 -1
- package/docs-md/fundamentals/starting-the-app.md +1 -1
- package/docs-md/guides/cli.md +1 -2
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +23 -2
- package/docs-md/index.json +12 -0
- package/docs-md/migration/from-expo-router.md +1 -1
- package/docs-md/migration/from-react-native-navigation.md +1 -1
- package/docs-md/migration/from-react-navigation.md +2 -2
- package/docs-md/screen-ui/back-and-gestures.md +36 -0
- package/docs-md/screen-ui/bar-buttons.md +4 -4
- package/docs-md/screen-ui/search.md +1 -1
- package/docs-md/screen-ui/system-ui.md +16 -11
- package/docs-md/screen-ui/toolbar-and-fab.md +2 -2
- package/docs-md/screen-ui/top-bar.md +8 -4
- package/docs-md/start/installation.md +1 -1
- package/ios/Animations/ANNFrameDriver.mm +2 -0
- package/ios/Animations/Elements/ANNSharedElementFlight.mm +70 -4
- package/ios/Animations/Interpolators/ANNInterpolate.h +2 -30
- package/ios/Animations/Interpolators/ANNTransformParts.h +48 -0
- package/ios/Animations/Interpolators/ANNTransformParts.mm +507 -0
- package/ios/Animations/Interpolators/ANNViewTracks.h +6 -4
- package/ios/Animations/Interpolators/ANNViewTracks.mm +49 -9
- package/ios/CLAUDE.md +17 -15
- package/ios/CORE.md +30 -26
- package/ios/Contract/ANNCommandAnswer.h +9 -2
- package/ios/Contract/ANNCommandAnswer.mm +25 -12
- package/ios/Contract/ANNCommandRunner.h +8 -7
- package/ios/Contract/ANNCommandRunner.mm +21 -27
- package/ios/Contract/ANNLayoutNode.h +3 -3
- package/ios/Contract/ANNLayoutReader.h +3 -3
- package/ios/Core/ANNAppearanceDispatcher.h +5 -4
- package/ios/Core/ANNAppearanceDispatcher.mm +12 -1
- package/ios/Core/ANNJavaScriptCalls.h +11 -12
- package/ios/Core/ANNJavaScriptCalls.mm +7 -16
- package/ios/Core/ANNNavigationState.h +4 -6
- package/ios/Core/ANNNavigationState.mm +0 -4
- package/ios/Core/ANNReloadCoordinator.mm +4 -8
- package/ios/Core/ApexNavigation+Internal.h +1 -1
- package/ios/Layouts/Arrangement/ANNArrangementState.h +2 -17
- package/ios/Layouts/Arrangement/ANNArrangementState.mm +0 -38
- package/ios/Layouts/Arrangement/ANNArrangementViewController.mm +4 -4
- package/ios/Layouts/Root/ANNSplashViewController.h +5 -3
- package/ios/Layouts/Root/ANNSplashViewController.mm +147 -0
- package/ios/Layouts/Screen/ANNComponentViewController.h +2 -2
- package/ios/Layouts/Screen/ANNComponentViewController.mm +9 -22
- package/ios/Layouts/Screen/ANNPreviewInteraction.h +7 -4
- package/ios/Layouts/Screen/ANNPreviewInteraction.mm +72 -10
- package/ios/Layouts/Screen/ANNScreenEnvironment.h +63 -0
- package/ios/Layouts/Screen/ANNScreenEnvironment.mm +181 -0
- package/ios/Layouts/Screen/ANNScreenRegions.h +3 -18
- package/ios/Layouts/Screen/ANNScreenRegions.mm +0 -38
- package/ios/Layouts/Screen/ANNScreenSafeArea.h +4 -9
- package/ios/Layouts/Screen/ANNScreenSafeArea.mm +14 -16
- package/ios/Layouts/Stack/ANNPreviewPush.h +7 -6
- package/ios/Layouts/Stack/ANNPreviewPush.mm +142 -27
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.h +12 -1
- package/ios/Layouts/Tabs/ANNBottomTabsAccessoryView.mm +23 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +2 -1
- package/ios/Layouts/Tabs/ANNBottomTabsCustomRow.mm +8 -3
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +123 -36
- package/ios/Layouts/Tabs/ANNTabsDesign.h +6 -5
- package/ios/Layouts/Tabs/ANNTabsDesign.mm +9 -1
- package/ios/Layouts/Tabs/UITabBar+utils.h +5 -5
- package/ios/Layouts/Tabs/UITabBar+utils.mm +32 -13
- package/ios/Options/ANNBottomTabsOptions.h +2 -0
- package/ios/Options/ANNBottomTabsOptions.mm +1 -0
- package/ios/Options/ANNOptions.h +18 -0
- package/ios/Options/ANNOptions.mm +112 -1
- package/ios/Options/ANNOptionsReader.mm +110 -0
- package/ios/Options/Params/ANNParam.h +15 -0
- package/ios/Options/Params/ANNParam.mm +61 -0
- package/ios/Options/Params/Color.h +3 -0
- package/ios/Options/Params/Color.mm +21 -0
- package/ios/Options/Params/Image.h +4 -0
- package/ios/Options/Params/Image.mm +20 -0
- package/ios/React/ANNReactView.mm +6 -25
- package/ios/React/ANNSafeAreaViewComponentView.mm +26 -15
- package/ios/React/ANNSurfaceHostingView.h +6 -0
- package/ios/React/ANNSurfaceHostingView.mm +116 -0
- package/ios/TopBar/ANNBackButtonPresenter.mm +6 -5
- package/ios/TopBar/ANNBarAppearance.h +2 -1
- package/ios/TopBar/ANNBarAppearance.mm +111 -30
- package/ios/TopBar/ANNBarBackgroundComponent.mm +16 -0
- package/ios/TopBar/ANNBarButtonComponentView.h +4 -4
- package/ios/TopBar/ANNBarButtonComponentView.mm +1 -1
- package/ios/TopBar/ANNBarButtons.h +5 -3
- package/ios/TopBar/ANNBarButtons.mm +186 -17
- package/ios/TopBar/ANNLargeTitlePresenter.mm +24 -4
- package/ios/TopBar/ANNMainList.h +3 -0
- package/ios/TopBar/ANNMainList.mm +4 -0
- package/ios/TopBar/ANNScrollEdgeEffects.h +22 -6
- package/ios/TopBar/ANNScrollEdgeEffects.mm +123 -16
- package/ios/TopBar/ANNSearchPresenter.mm +2 -2
- package/ios/TopBar/ANNSearchResultsViewController.mm +5 -5
- package/ios/TopBar/ANNTopBarPresenter.h +3 -2
- package/ios/TopBar/ANNTopBarPresenter.mm +95 -22
- package/ios/TurboModules/ANNTurboModule.mm +20 -16
- package/lib/module/ARCHITECTURE.md +8 -4
- package/lib/module/api/commands/commands.js +54 -6
- package/lib/module/api/commands/commands.js.map +1 -1
- package/lib/module/api/hooks/useSafeAreaInsets.js +4 -3
- package/lib/module/api/hooks/useSafeAreaInsets.js.map +1 -1
- package/lib/module/api/options/OptionsChain.js +2 -2
- package/lib/module/engine/commands.js +21 -1
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/places.js +8 -0
- package/lib/module/engine/places.js.map +1 -1
- package/lib/module/engine/screens.js +29 -4
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/typescript/api/app/createNavigation.d.ts +3 -3
- package/lib/typescript/api/commands/commands.d.ts.map +1 -1
- package/lib/typescript/api/commands/presentation.d.ts +4 -3
- package/lib/typescript/api/commands/presentation.d.ts.map +1 -1
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts +4 -3
- package/lib/typescript/api/hooks/useSafeAreaInsets.d.ts.map +1 -1
- package/lib/typescript/api/options/OptionsChain.d.ts +2 -2
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/places.d.ts +2 -0
- package/lib/typescript/engine/places.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts +20 -6
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/Options.d.ts +1 -1
- package/lib/typescript/interfaces/Options.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +30 -9
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/bottomTabs.d.ts +18 -5
- package/lib/typescript/interfaces/options/bottomTabs.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +8 -8
- package/lib/typescript/interfaces/options/stack.d.ts +9 -0
- package/lib/typescript/interfaces/options/stack.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/system.d.ts +8 -5
- package/lib/typescript/interfaces/options/system.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/toolbar.d.ts +2 -2
- package/lib/typescript/interfaces/options/topBar.d.ts +32 -29
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +8 -4
- package/src/api/app/createNavigation.ts +3 -3
- package/src/api/commands/commands.ts +48 -7
- package/src/api/commands/presentation.ts +4 -3
- package/src/api/hooks/useSafeAreaInsets.ts +4 -3
- package/src/api/options/OptionsChain.ts +2 -2
- package/src/engine/commands.ts +30 -6
- package/src/engine/places.ts +9 -0
- package/src/engine/screens.tsx +29 -8
- package/src/interfaces/Options.ts +1 -0
- package/src/interfaces/options/animations.ts +31 -9
- package/src/interfaces/options/bottomTabs.ts +18 -5
- package/src/interfaces/options/buttons.ts +8 -8
- package/src/interfaces/options/stack.ts +9 -0
- package/src/interfaces/options/system.ts +8 -5
- package/src/interfaces/options/toolbar.ts +2 -2
- package/src/interfaces/options/topBar.ts +32 -29
- package/android/src/main/java/dev/apexnative/navigation/react/ArrangementStates.kt +0 -42
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenInsets.kt +0 -70
- package/android/src/main/java/dev/apexnative/navigation/react/events/NavigationStateReporter.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/react/events/StateHold.kt +0 -62
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
- package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
- package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
|
@@ -1,12 +1,121 @@
|
|
|
1
1
|
#import "ANNOptionsReader.h"
|
|
2
2
|
#import "ANNAssert.h"
|
|
3
3
|
#import "ANNNavigationOptions.h"
|
|
4
|
+
#if DEBUG
|
|
5
|
+
#import <React/RCTLog.h>
|
|
6
|
+
#endif
|
|
4
7
|
|
|
5
8
|
// The parse runs on the main queue, one tree at a time: the path and the first failure are its state. A group read
|
|
6
9
|
// outside a parse (a module that reads its own JSON) finds an empty path and keeps no failure.
|
|
7
10
|
static NSMutableArray<NSString *> *ANNPathComponents;
|
|
8
11
|
static NSString *ANNFirstFailure;
|
|
9
12
|
|
|
13
|
+
#if DEBUG
|
|
14
|
+
// A deep mutable copy of `json` with `value` at `path` (keys, and indexes of arrays); nil `value` removes the key.
|
|
15
|
+
static id ANNJSONWith(id json, NSArray *path, id value) {
|
|
16
|
+
if (path.count == 0) {
|
|
17
|
+
return value;
|
|
18
|
+
}
|
|
19
|
+
NSArray *rest = [path subarrayWithRange:NSMakeRange(1, path.count - 1)];
|
|
20
|
+
if ([json isKindOfClass:NSArray.class]) {
|
|
21
|
+
NSMutableArray *items = [json mutableCopy];
|
|
22
|
+
NSUInteger index = [path[0] unsignedIntegerValue];
|
|
23
|
+
items[index] = ANNJSONWith(items[index], rest, value);
|
|
24
|
+
return items;
|
|
25
|
+
}
|
|
26
|
+
NSMutableDictionary *fields = [json mutableCopy];
|
|
27
|
+
id inner = ANNJSONWith(fields[path[0]], rest, value);
|
|
28
|
+
if (inner) {
|
|
29
|
+
fields[path[0]] = inner;
|
|
30
|
+
} else {
|
|
31
|
+
[fields removeObjectForKey:path[0]];
|
|
32
|
+
}
|
|
33
|
+
return fields;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
// There is no iOS test target: in development the value equality of the options (`isSameValueAs:`) checks itself
|
|
37
|
+
// once on a tree with a value of each kind: two reads, a copy and a merge give the same options and the same hash, and
|
|
38
|
+
// a change of one value at any depth gives other options. A module's group (the menu) compares by its properties.
|
|
39
|
+
static void ANNCheckSameValues(void) {
|
|
40
|
+
NSDictionary *button = @{
|
|
41
|
+
@"id" : @"save",
|
|
42
|
+
@"text" : @"Save",
|
|
43
|
+
@"icon" : @{@"system" : @"star"},
|
|
44
|
+
@"enabled" : @YES,
|
|
45
|
+
@"badge" : @1,
|
|
46
|
+
@"fontSize" : @15,
|
|
47
|
+
@"menu" : @{
|
|
48
|
+
@"title" : @"More",
|
|
49
|
+
@"items" : @[ @{@"id" : @"details", @"title" : @"Details", @"checked" : @YES} ],
|
|
50
|
+
},
|
|
51
|
+
};
|
|
52
|
+
NSDictionary *json = @{
|
|
53
|
+
@"topBar" : @{
|
|
54
|
+
@"title" : @{@"text" : @"Home", @"color" : @(0xFF112233)},
|
|
55
|
+
@"rightButtonColor" :
|
|
56
|
+
@{@"dynamic" : @{@"light" : @(0xFF000000), @"dark" : @(0xFFFFFFFF)}},
|
|
57
|
+
@"leftButtonColor" : NSNull.null,
|
|
58
|
+
@"rightButtons" : @[ button ],
|
|
59
|
+
@"leftButtons" : @[],
|
|
60
|
+
@"height" : @44,
|
|
61
|
+
},
|
|
62
|
+
@"keyCommands" : @[ @{@"input" : @"k", @"modifiers" : @[ @"command" ]} ],
|
|
63
|
+
};
|
|
64
|
+
ANNNavigationOptions *options = [[ANNNavigationOptions alloc] initWithDict:json];
|
|
65
|
+
ANNNavigationOptions *again = [[ANNNavigationOptions alloc] initWithDict:json];
|
|
66
|
+
ANNNavigationOptions *copy = [options copy];
|
|
67
|
+
ANNNavigationOptions *overEmpty = [options withDefault:[ANNNavigationOptions new]];
|
|
68
|
+
for (ANNNavigationOptions *same in @[ again, copy, overEmpty ]) {
|
|
69
|
+
if (![options isSameValueAs:same] || options.valueHash != same.valueHash) {
|
|
70
|
+
RCTLogError(@"[ApexNavigation] options read, copied or resolved from the same JSON are "
|
|
71
|
+
@"not the same value");
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
// Each change: the path of one value and its new value; an unset key is not the same as `null` or `[]`.
|
|
75
|
+
NSDictionary<NSString *, NSArray *> *changes = @{
|
|
76
|
+
@"a text" : @[ @[ @"topBar", @"title", @"text" ], @"Profile" ],
|
|
77
|
+
@"a color" : @[ @[ @"topBar", @"title", @"color" ], @(0xFF112234) ],
|
|
78
|
+
@"a side of a color pair" :
|
|
79
|
+
@[ @[ @"topBar", @"rightButtonColor", @"dynamic", @"dark" ], @(0xFFEEEEEE) ],
|
|
80
|
+
@"a number" : @[ @[ @"topBar", @"height" ], @45 ],
|
|
81
|
+
@"an image" : @[ @[ @"topBar", @"rightButtons", @0, @"icon", @"system" ], @"heart" ],
|
|
82
|
+
@"a boolean" : @[ @[ @"topBar", @"rightButtons", @0, @"enabled" ], @NO ],
|
|
83
|
+
@"a boolean for a number" : @[ @[ @"topBar", @"rightButtons", @0, @"badge" ], @YES ],
|
|
84
|
+
@"a font" : @[ @[ @"topBar", @"rightButtons", @0, @"fontSize" ], @16 ],
|
|
85
|
+
@"a module's group" :
|
|
86
|
+
@[ @[ @"topBar", @"rightButtons", @0, @"menu", @"items", @0, @"checked" ], @NO ],
|
|
87
|
+
@"an object of an array" : @[ @[ @"keyCommands", @0, @"modifiers", @0 ], @"shift" ],
|
|
88
|
+
};
|
|
89
|
+
NSDictionary<NSString *, NSArray *> *removals = @{
|
|
90
|
+
@"a color of `null` and no color" : @[ @"topBar", @"leftButtonColor" ],
|
|
91
|
+
@"an empty array and no array" : @[ @"topBar", @"leftButtons" ],
|
|
92
|
+
};
|
|
93
|
+
NSMutableDictionary<NSString *, id> *variants = [NSMutableDictionary new];
|
|
94
|
+
[changes enumerateKeysAndObjectsUsingBlock:^(NSString *what, NSArray *change, BOOL *stop) {
|
|
95
|
+
variants[what] = ANNJSONWith(json, change[0], change[1]);
|
|
96
|
+
}];
|
|
97
|
+
[removals enumerateKeysAndObjectsUsingBlock:^(NSString *what, NSArray *path, BOOL *stop) {
|
|
98
|
+
variants[what] = ANNJSONWith(json, path, nil);
|
|
99
|
+
}];
|
|
100
|
+
[variants enumerateKeysAndObjectsUsingBlock:^(NSString *what, NSDictionary *variant,
|
|
101
|
+
BOOL *stop) {
|
|
102
|
+
ANNNavigationOptions *changed = [[ANNNavigationOptions alloc] initWithDict:variant];
|
|
103
|
+
if ([options isSameValueAs:changed] || [changed isSameValueAs:options]) {
|
|
104
|
+
RCTLogError(@"[ApexNavigation] options that differ in %@ are the same value", what);
|
|
105
|
+
}
|
|
106
|
+
}];
|
|
107
|
+
ANNNavigationOptions *merged = [options copy];
|
|
108
|
+
[merged mergeOptions:[[ANNNavigationOptions alloc]
|
|
109
|
+
initWithDict:@{@"topBar" : @{@"title" : @{@"text" : @"Profile"}}}]];
|
|
110
|
+
ANNNavigationOptions *read = [[ANNNavigationOptions alloc]
|
|
111
|
+
initWithDict:ANNJSONWith(json, @[ @"topBar", @"title", @"text" ], @"Profile")];
|
|
112
|
+
if (![merged isSameValueAs:read] || merged.valueHash != read.valueHash) {
|
|
113
|
+
RCTLogError(@"[ApexNavigation] merged options are not the same value as the same options "
|
|
114
|
+
@"read at once");
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
#endif
|
|
118
|
+
|
|
10
119
|
@implementation ANNOptionsReader
|
|
11
120
|
|
|
12
121
|
+ (ANNNavigationOptions *)optionsFrom:(NSDictionary *)json unreadablePath:(NSString **)unreadablePath {
|
|
@@ -15,6 +124,7 @@ static NSString *ANNFirstFailure;
|
|
|
15
124
|
static dispatch_once_t checkOnce;
|
|
16
125
|
dispatch_once(&checkOnce, ^{
|
|
17
126
|
ANNCheckOptionClassesFrom(ANNNavigationOptions.class);
|
|
127
|
+
ANNCheckSameValues();
|
|
18
128
|
});
|
|
19
129
|
#endif
|
|
20
130
|
NSMutableArray<NSString *> *outerPath = ANNPathComponents;
|
|
@@ -8,6 +8,14 @@ BOOL ANNJSONIsBoolean(id _Nullable value);
|
|
|
8
8
|
/// A JSON number that is finite: never a boolean, `NaN` or an infinity.
|
|
9
9
|
BOOL ANNJSONIsNumber(id _Nullable value);
|
|
10
10
|
|
|
11
|
+
/// Two values as the options keep them are the same: a boolean is never the same as a number, arrays and objects
|
|
12
|
+
/// compare item by item, anything else (a string, a number, `null`, a color) by `isEqual:`. nil is the same only as
|
|
13
|
+
/// nil.
|
|
14
|
+
BOOL ANNJSONValuesSame(id _Nullable a, id _Nullable b);
|
|
15
|
+
|
|
16
|
+
/// A hash of a value `ANNJSONValuesSame` compares: values that are the same have the same hash.
|
|
17
|
+
NSUInteger ANNJSONValueHash(id _Nullable value);
|
|
18
|
+
|
|
11
19
|
/// One value of the options as the JSON gave it. Immutable: trees of options may share it. `null` is a value too where
|
|
12
20
|
/// the kind has an explicit "none" (a text, a color): it takes the place of what the layers under it gave.
|
|
13
21
|
@interface ANNParam : NSObject
|
|
@@ -23,6 +31,13 @@ BOOL ANNJSONIsNumber(id _Nullable value);
|
|
|
23
31
|
/// The options gave `null`.
|
|
24
32
|
- (BOOL)isNull;
|
|
25
33
|
|
|
34
|
+
/// A param of the same class with the same value: two reads of the same JSON are the same, two params without a value
|
|
35
|
+
/// are the same. `==` and `isEqual:` stay identity.
|
|
36
|
+
- (BOOL)isSameValueAs:(nullable ANNParam *)other;
|
|
37
|
+
|
|
38
|
+
/// A hash consistent with `isSameValueAs:`.
|
|
39
|
+
- (NSUInteger)valueHash;
|
|
40
|
+
|
|
26
41
|
@end
|
|
27
42
|
|
|
28
43
|
NS_ASSUME_NONNULL_END
|
|
@@ -9,6 +9,56 @@ BOOL ANNJSONIsNumber(id value) {
|
|
|
9
9
|
return [value isKindOfClass:NSNumber.class] && !ANNJSONIsBoolean(value) && isfinite([value doubleValue]);
|
|
10
10
|
}
|
|
11
11
|
|
|
12
|
+
BOOL ANNJSONValuesSame(id a, id b) {
|
|
13
|
+
if (a == b) {
|
|
14
|
+
return YES;
|
|
15
|
+
}
|
|
16
|
+
if (!a || !b) {
|
|
17
|
+
return NO;
|
|
18
|
+
}
|
|
19
|
+
if ([a isKindOfClass:NSArray.class]) {
|
|
20
|
+
if (![b isKindOfClass:NSArray.class] || [a count] != [b count]) {
|
|
21
|
+
return NO;
|
|
22
|
+
}
|
|
23
|
+
NSArray *items = b;
|
|
24
|
+
__block BOOL same = YES;
|
|
25
|
+
[(NSArray *)a enumerateObjectsUsingBlock:^(id item, NSUInteger index, BOOL *stop) {
|
|
26
|
+
same = ANNJSONValuesSame(item, items[index]);
|
|
27
|
+
*stop = !same;
|
|
28
|
+
}];
|
|
29
|
+
return same;
|
|
30
|
+
}
|
|
31
|
+
if ([a isKindOfClass:NSDictionary.class]) {
|
|
32
|
+
if (![b isKindOfClass:NSDictionary.class] || [a count] != [b count]) {
|
|
33
|
+
return NO;
|
|
34
|
+
}
|
|
35
|
+
NSDictionary *fields = b;
|
|
36
|
+
__block BOOL same = YES;
|
|
37
|
+
[(NSDictionary *)a enumerateKeysAndObjectsUsingBlock:^(id key, id value, BOOL *stop) {
|
|
38
|
+
same = ANNJSONValuesSame(value, fields[key]);
|
|
39
|
+
*stop = !same;
|
|
40
|
+
}];
|
|
41
|
+
return same;
|
|
42
|
+
}
|
|
43
|
+
// `isEqual:` of NSNumber takes `true` for 1: a badge of `true` (a dot) and of `1` (a number) are not the same.
|
|
44
|
+
if (ANNJSONIsBoolean(a) != ANNJSONIsBoolean(b)) {
|
|
45
|
+
return NO;
|
|
46
|
+
}
|
|
47
|
+
return [a isEqual:b];
|
|
48
|
+
}
|
|
49
|
+
|
|
50
|
+
// An object's hash is its count only (Foundation's); an array mixes its items in, so lists of other values differ.
|
|
51
|
+
NSUInteger ANNJSONValueHash(id value) {
|
|
52
|
+
if ([value isKindOfClass:NSArray.class]) {
|
|
53
|
+
NSUInteger hash = [value count];
|
|
54
|
+
for (id item in value) {
|
|
55
|
+
hash = hash * 31 + ANNJSONValueHash(item);
|
|
56
|
+
}
|
|
57
|
+
return hash;
|
|
58
|
+
}
|
|
59
|
+
return [value hash];
|
|
60
|
+
}
|
|
61
|
+
|
|
12
62
|
@implementation ANNParam
|
|
13
63
|
|
|
14
64
|
- (instancetype)initWithValue:(id)value {
|
|
@@ -26,4 +76,15 @@ BOOL ANNJSONIsNumber(id value) {
|
|
|
26
76
|
return _value == NSNull.null;
|
|
27
77
|
}
|
|
28
78
|
|
|
79
|
+
- (BOOL)isSameValueAs:(ANNParam *)other {
|
|
80
|
+
if (other == self) {
|
|
81
|
+
return YES;
|
|
82
|
+
}
|
|
83
|
+
return other.class == self.class && ANNJSONValuesSame(_value, other.value);
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
- (NSUInteger)valueHash {
|
|
87
|
+
return ANNJSONValueHash(_value);
|
|
88
|
+
}
|
|
89
|
+
|
|
29
90
|
@end
|
|
@@ -38,6 +38,9 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
38
38
|
/// A pair that has only one of its sides.
|
|
39
39
|
- (BOOL)isPartial;
|
|
40
40
|
|
|
41
|
+
/// The same color: a pair by its sides (its dynamic color is made anew at each read), anything else by its value.
|
|
42
|
+
- (BOOL)isSameValueAs:(nullable ANNParam *)other;
|
|
43
|
+
|
|
41
44
|
@end
|
|
42
45
|
|
|
43
46
|
NS_ASSUME_NONNULL_END
|
|
@@ -40,6 +40,27 @@
|
|
|
40
40
|
return [super get];
|
|
41
41
|
}
|
|
42
42
|
|
|
43
|
+
- (BOOL)isSameValueAs:(ANNParam *)other {
|
|
44
|
+
if (other == self) {
|
|
45
|
+
return YES;
|
|
46
|
+
}
|
|
47
|
+
if (other.class != self.class) {
|
|
48
|
+
return NO;
|
|
49
|
+
}
|
|
50
|
+
Color *color = (Color *)other;
|
|
51
|
+
if (_light || _dark || color.light || color.dark) {
|
|
52
|
+
return ANNJSONValuesSame(_light, color.light) && ANNJSONValuesSame(_dark, color.dark);
|
|
53
|
+
}
|
|
54
|
+
return [super isSameValueAs:other];
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
- (NSUInteger)valueHash {
|
|
58
|
+
if (_light || _dark) {
|
|
59
|
+
return _light.hash * 31 + _dark.hash;
|
|
60
|
+
}
|
|
61
|
+
return [super valueHash];
|
|
62
|
+
}
|
|
63
|
+
|
|
43
64
|
- (UIColor *)withDefault:(UIColor *)defaultValue {
|
|
44
65
|
if (!self.isPartial) {
|
|
45
66
|
return self.hasValue ? self.get : defaultValue;
|
|
@@ -19,6 +19,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
19
19
|
/// What the options name; nil without a value.
|
|
20
20
|
@property(nonatomic, strong, readonly, nullable) ANNImageSource *source;
|
|
21
21
|
|
|
22
|
+
/// The same image the options name: a source by its `sourceKey` (what a file holds now isn't compared, so a file the
|
|
23
|
+
/// app writes again at the same path is the same value), an image that came already by identity.
|
|
24
|
+
- (BOOL)isSameValueAs:(nullable ANNParam *)other;
|
|
25
|
+
|
|
22
26
|
@end
|
|
23
27
|
|
|
24
28
|
NS_ASSUME_NONNULL_END
|
|
@@ -17,6 +17,26 @@
|
|
|
17
17
|
return source ? [ANNOptionImages imageOf:source] : nil;
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
+
- (BOOL)isSameValueAs:(ANNParam *)other {
|
|
21
|
+
if (other == self) {
|
|
22
|
+
return YES;
|
|
23
|
+
}
|
|
24
|
+
if (other.class != self.class) {
|
|
25
|
+
return NO;
|
|
26
|
+
}
|
|
27
|
+
ANNImageSource *source = self.source;
|
|
28
|
+
ANNImageSource *otherSource = ((Image *)other).source;
|
|
29
|
+
if (source || otherSource) {
|
|
30
|
+
return [source.sourceKey isEqualToString:otherSource.sourceKey];
|
|
31
|
+
}
|
|
32
|
+
return self.value == other.value;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
- (NSUInteger)valueHash {
|
|
36
|
+
ANNImageSource *source = self.source;
|
|
37
|
+
return source ? source.sourceKey.hash : (NSUInteger)(__bridge void *)self.value;
|
|
38
|
+
}
|
|
39
|
+
|
|
20
40
|
// A source that doesn't load is no image: the default then, as without a value.
|
|
21
41
|
- (UIImage *)withDefault:(UIImage *)defaultValue {
|
|
22
42
|
UIImage *image = self.hasValue ? self.get : nil;
|
|
@@ -8,29 +8,9 @@
|
|
|
8
8
|
#import "ANNSurfaceHostingView.h"
|
|
9
9
|
#import <React/RCTFabricSurface.h>
|
|
10
10
|
#import <React/RCTSurfacePresenter.h>
|
|
11
|
-
#import <React/RCTSurfaceStage.h>
|
|
12
11
|
#import <React/RCTSurfaceView.h>
|
|
13
12
|
#import <ReactCommon/RCTHost.h>
|
|
14
13
|
|
|
15
|
-
// A surface that still starts can't be stopped yet: it is checked this often, at most this many times (5 s), and then
|
|
16
|
-
// stopped anyway.
|
|
17
|
-
static const NSTimeInterval ANNSurfaceStopCheckInterval = 0.05;
|
|
18
|
-
static const NSUInteger ANNSurfaceStopCheckLimit = 100;
|
|
19
|
-
|
|
20
|
-
// Stops `surface` once it runs. The block holds the surface and the view that hosts it until then: a hosting view
|
|
21
|
-
// released earlier would stop the surface in its `dealloc`, maybe off the main queue.
|
|
22
|
-
static void ANNStopSurfaceWhenRunning(RCTFabricSurface *surface, UIView *hostingView,
|
|
23
|
-
NSUInteger checksLeft) {
|
|
24
|
-
if (RCTSurfaceStageIsRunning(surface.stage) || checksLeft == 0) {
|
|
25
|
-
[surface stop];
|
|
26
|
-
return;
|
|
27
|
-
}
|
|
28
|
-
int64_t delay = (int64_t)(ANNSurfaceStopCheckInterval * NSEC_PER_SEC);
|
|
29
|
-
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{
|
|
30
|
-
ANNStopSurfaceWhenRunning(surface, hostingView, checksLeft - 1);
|
|
31
|
-
});
|
|
32
|
-
}
|
|
33
|
-
|
|
34
14
|
@interface ANNReactView () <RCTSurfacePresenterObserver, ANNScreenReadinessListener>
|
|
35
15
|
@end
|
|
36
16
|
|
|
@@ -359,14 +339,15 @@ static void ANNStopSurfaceWhenRunning(RCTFabricSurface *surface, UIView *hosting
|
|
|
359
339
|
_reactViewReadyBlock = nil;
|
|
360
340
|
_mountHandlers = nil;
|
|
361
341
|
[_contentDidMountHandlers removeAllObjects];
|
|
362
|
-
|
|
363
|
-
if (
|
|
342
|
+
ANNSurfaceHostingView *hostingView = _hostingView;
|
|
343
|
+
if (_surface && hostingView) {
|
|
364
344
|
// On the next turn of the main queue: a destroy that runs inside React Native's mounting (a wait that ends in
|
|
365
345
|
// a mount, a root that replaces at once) would stop the surface there, React Native would drop its last
|
|
366
|
-
// transaction and recycle its root view with the old content still in it, to show in a later screen.
|
|
367
|
-
|
|
346
|
+
// transaction and recycle its root view with the old content still in it, to show in a later screen. The
|
|
347
|
+
// hosting view stops the surface then, or at its start when it doesn't run yet; the block keeps the hosting
|
|
348
|
+
// view, and with it the surface, until it asks.
|
|
368
349
|
dispatch_async(dispatch_get_main_queue(), ^{
|
|
369
|
-
|
|
350
|
+
[hostingView stopSurfaceWhenRunning];
|
|
370
351
|
});
|
|
371
352
|
}
|
|
372
353
|
}
|
|
@@ -1,11 +1,14 @@
|
|
|
1
1
|
#import "ANNSafeAreaViewComponentView.h"
|
|
2
|
-
#import "
|
|
2
|
+
#import "ANNScreenEnvironment.h"
|
|
3
3
|
#import "ANNScreenReadiness.h"
|
|
4
4
|
#import "../cpp/react/renderer/components/ApexNavigationSpec/ANNSafeAreaViewComponentDescriptor.h"
|
|
5
5
|
#import <React/RCTConversions.h>
|
|
6
6
|
|
|
7
7
|
using namespace facebook::react;
|
|
8
8
|
|
|
9
|
+
@interface ANNSafeAreaViewComponentView () <ANNScreenPaddingListener>
|
|
10
|
+
@end
|
|
11
|
+
|
|
9
12
|
@implementation ANNSafeAreaViewComponentView {
|
|
10
13
|
ANNSafeAreaViewShadowNode::ConcreteState::Shared _state;
|
|
11
14
|
NSString *_screenId;
|
|
@@ -22,21 +25,13 @@ using namespace facebook::react;
|
|
|
22
25
|
if (self = [super initWithFrame:frame]) {
|
|
23
26
|
static const auto defaultProps = std::make_shared<const ANNSafeAreaViewProps>();
|
|
24
27
|
_props = defaultProps;
|
|
25
|
-
[NSNotificationCenter.defaultCenter addObserver:self
|
|
26
|
-
selector:@selector(paddingDidChange:)
|
|
27
|
-
name:ANNScreenSafeAreaPaddingDidChangeNotification
|
|
28
|
-
object:nil];
|
|
29
28
|
}
|
|
30
29
|
return self;
|
|
31
30
|
}
|
|
32
31
|
|
|
33
|
-
- (void)dealloc {
|
|
34
|
-
[NSNotificationCenter.defaultCenter removeObserver:self];
|
|
35
|
-
}
|
|
36
|
-
|
|
37
32
|
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps {
|
|
38
33
|
const auto &newProps = static_cast<const ANNSafeAreaViewProps &>(*props);
|
|
39
|
-
|
|
34
|
+
[self listenToScreenId:RCTNSStringFromStringNilIfEmpty(newProps.screenId)];
|
|
40
35
|
// The screen's readiness comes from this view, never from its root's children.
|
|
41
36
|
if (_screenId != nil) {
|
|
42
37
|
[ANNScreenReadiness screenHasSafeAreaView:_screenId];
|
|
@@ -70,18 +65,33 @@ using namespace facebook::react;
|
|
|
70
65
|
[self applyPadding];
|
|
71
66
|
}
|
|
72
67
|
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
68
|
+
// The view hears the padding changes of its screen only. It listens before it applies the padding it has, at each
|
|
69
|
+
// props, so a change between the two can't be missed.
|
|
70
|
+
- (void)listenToScreenId:(nullable NSString *)screenId {
|
|
71
|
+
if ((screenId == nil && _screenId == nil) || [screenId isEqualToString:_screenId]) {
|
|
72
|
+
return;
|
|
73
|
+
}
|
|
74
|
+
if (_screenId != nil) {
|
|
75
|
+
[ANNScreenEnvironment removePaddingListener:self forScreenId:_screenId];
|
|
76
|
+
}
|
|
77
|
+
_screenId = screenId;
|
|
78
|
+
if (screenId != nil) {
|
|
79
|
+
[ANNScreenEnvironment addPaddingListener:self forScreenId:screenId];
|
|
76
80
|
}
|
|
77
81
|
}
|
|
78
82
|
|
|
83
|
+
#pragma mark - ANNScreenPaddingListener
|
|
84
|
+
|
|
85
|
+
- (void)screenPaddingDidChange {
|
|
86
|
+
[self applyPadding];
|
|
87
|
+
}
|
|
88
|
+
|
|
79
89
|
// The padding of the screen goes to the state, which lays the view out with it.
|
|
80
90
|
- (void)applyPadding {
|
|
81
91
|
if (_state == nullptr || _screenId == nil) {
|
|
82
92
|
return;
|
|
83
93
|
}
|
|
84
|
-
UIEdgeInsets padding = [
|
|
94
|
+
UIEdgeInsets padding = [ANNScreenEnvironment paddingForScreenId:_screenId];
|
|
85
95
|
EdgeInsets insets{.left = (Float)padding.left,
|
|
86
96
|
.top = (Float)padding.top,
|
|
87
97
|
.right = (Float)padding.right,
|
|
@@ -144,7 +154,8 @@ using namespace facebook::react;
|
|
|
144
154
|
- (void)prepareForRecycle {
|
|
145
155
|
[super prepareForRecycle];
|
|
146
156
|
_state.reset();
|
|
147
|
-
|
|
157
|
+
// A recycled view gets its next screen's id with its next props.
|
|
158
|
+
[self listenToScreenId:nil];
|
|
148
159
|
_readySent = NO;
|
|
149
160
|
_contentRenderedSent = NO;
|
|
150
161
|
_contentRendered = NO;
|
|
@@ -12,6 +12,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
12
12
|
/// out again, so a render that was on its way during the change doesn't keep the old size's layout.
|
|
13
13
|
- (void)surfaceDidMount;
|
|
14
14
|
|
|
15
|
+
/// The screen went: the hosted surface stops as soon as it runs, on the main queue. A surface that runs already stops
|
|
16
|
+
/// at once; one that doesn't run yet stops at its change to running (`surface:didChangeStage:`). Until then this view
|
|
17
|
+
/// and its surface are kept, so the core gives them up on the main queue. A surface that hasn't started after 5 s is
|
|
18
|
+
/// let go, and stops if it ever starts later. Once; main queue.
|
|
19
|
+
- (void)stopSurfaceWhenRunning;
|
|
20
|
+
|
|
15
21
|
@end
|
|
16
22
|
|
|
17
23
|
NS_ASSUME_NONNULL_END
|
|
@@ -1,9 +1,49 @@
|
|
|
1
1
|
#import "ANNSurfaceHostingView.h"
|
|
2
|
+
#import "ANNAssert.h"
|
|
2
3
|
#import <React/RCTFabricSurface.h>
|
|
4
|
+
#import <objc/runtime.h>
|
|
5
|
+
|
|
6
|
+
// How long a gone screen's surface that doesn't run yet is kept for its stop. The host starts a surface through a weak
|
|
7
|
+
// reference once its JavaScript ran, so a surface let go before that, which nothing else holds, goes away unstarted.
|
|
8
|
+
static const NSTimeInterval ANNSurfaceStartWaitLimit = 5.0;
|
|
9
|
+
static const void *ANNSurfaceStopperKey = &ANNSurfaceStopperKey;
|
|
10
|
+
|
|
11
|
+
// The hosting views whose surface stops at its start; each keeps its surface. Main queue.
|
|
12
|
+
static NSMutableSet<ANNSurfaceHostingView *> *ANNHostingViewsStoppingAtStart;
|
|
13
|
+
|
|
14
|
+
// The delegate of a surface its hosting view let go at the limit: the surface keeps it, and it keeps nothing, so
|
|
15
|
+
// neither holds the other alive. A surface that something else still starts (a start already on its way holds its
|
|
16
|
+
// surface; React Native restarts every surface it knows after a reload, holding it) stops at once, instead of running
|
|
17
|
+
// with no screen and going away running, which leaves its React tree registered.
|
|
18
|
+
@interface ANNSurfaceStopper : NSObject <RCTSurfaceDelegate>
|
|
19
|
+
@end
|
|
20
|
+
|
|
21
|
+
@implementation ANNSurfaceStopper
|
|
22
|
+
|
|
23
|
+
// React Native's delegate method names the deprecated `RCTSurface`; the surface is an `RCTFabricSurface`.
|
|
24
|
+
#pragma clang diagnostic push
|
|
25
|
+
#pragma clang diagnostic ignored "-Wdeprecated"
|
|
26
|
+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
27
|
+
- (void)surface:(RCTSurface *)surface didChangeStage:(RCTSurfaceStage)stage {
|
|
28
|
+
if (!RCTSurfaceStageIsRunning(stage)) {
|
|
29
|
+
return;
|
|
30
|
+
}
|
|
31
|
+
// The start tells this off the main queue, before it ends: the stop goes on the main queue, the block keeping the
|
|
32
|
+
// surface until then.
|
|
33
|
+
id<RCTSurfaceProtocol> startedSurface = (id<RCTSurfaceProtocol>)surface;
|
|
34
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
35
|
+
[startedSurface stop];
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
#pragma clang diagnostic pop
|
|
39
|
+
|
|
40
|
+
@end
|
|
3
41
|
|
|
4
42
|
@implementation ANNSurfaceHostingView {
|
|
5
43
|
// The surface was given a new size since its last mount.
|
|
6
44
|
BOOL _surfaceSizeChanged;
|
|
45
|
+
// The screen went: the surface stops each time it runs. Main queue.
|
|
46
|
+
BOOL _stopsWhenRunning;
|
|
7
47
|
}
|
|
8
48
|
|
|
9
49
|
// React Native 0.87 posts the hosting view's traits as the app's scheme from here (`RCTSurfaceHostingView.mm`). On
|
|
@@ -60,6 +100,82 @@
|
|
|
60
100
|
[surface setMinimumSize:minimumSize maximumSize:maximumSize];
|
|
61
101
|
}
|
|
62
102
|
|
|
103
|
+
#pragma mark - The stop
|
|
104
|
+
|
|
105
|
+
// `RCTFabricSurface stop` does nothing while the surface doesn't run, and the host starts a surface later, off the main
|
|
106
|
+
// queue: a screen can go before that. Its surface then stops at its change to running. This view is kept until then,
|
|
107
|
+
// with its surface: released earlier, it would stop the surface in its `dealloc`, maybe off the main queue.
|
|
108
|
+
- (void)stopSurfaceWhenRunning {
|
|
109
|
+
ANNAssertMainQueue();
|
|
110
|
+
if (_stopsWhenRunning) {
|
|
111
|
+
return;
|
|
112
|
+
}
|
|
113
|
+
_stopsWhenRunning = YES;
|
|
114
|
+
RCTFabricSurface *surface = [self fabricSurface];
|
|
115
|
+
if (!surface) {
|
|
116
|
+
return;
|
|
117
|
+
}
|
|
118
|
+
if (RCTSurfaceStageIsRunning(surface.stage)) {
|
|
119
|
+
[surface stop];
|
|
120
|
+
return;
|
|
121
|
+
}
|
|
122
|
+
if (!ANNHostingViewsStoppingAtStart) {
|
|
123
|
+
ANNHostingViewsStoppingAtStart = [NSMutableSet new];
|
|
124
|
+
}
|
|
125
|
+
[ANNHostingViewsStoppingAtStart addObject:self];
|
|
126
|
+
__weak ANNSurfaceHostingView *weakSelf = self;
|
|
127
|
+
int64_t delay = (int64_t)(ANNSurfaceStartWaitLimit * NSEC_PER_SEC);
|
|
128
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, delay), dispatch_get_main_queue(), ^{
|
|
129
|
+
[weakSelf letSurfaceGo];
|
|
130
|
+
});
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
// The surface tells its stage off the main queue, from its start and its stop. React Native's hosting view follows it
|
|
134
|
+
// as before; a gone screen's surface that starts stops on the next turn of the main queue, once its start ended. The
|
|
135
|
+
// block keeps this view, and with it the surface, until then, so their last release is on the main queue.
|
|
136
|
+
#pragma clang diagnostic push
|
|
137
|
+
#pragma clang diagnostic ignored "-Wdeprecated"
|
|
138
|
+
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
|
139
|
+
- (void)surface:(RCTSurface *)surface didChangeStage:(RCTSurfaceStage)stage {
|
|
140
|
+
[super surface:surface didChangeStage:stage];
|
|
141
|
+
if (!RCTSurfaceStageIsRunning(stage)) {
|
|
142
|
+
return;
|
|
143
|
+
}
|
|
144
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
145
|
+
[self stopSurfaceIfAsked];
|
|
146
|
+
});
|
|
147
|
+
}
|
|
148
|
+
#pragma clang diagnostic pop
|
|
149
|
+
|
|
150
|
+
- (void)stopSurfaceIfAsked {
|
|
151
|
+
if (!_stopsWhenRunning) {
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
RCTFabricSurface *surface = [self fabricSurface];
|
|
155
|
+
if (surface && RCTSurfaceStageIsRunning(surface.stage)) {
|
|
156
|
+
[surface stop];
|
|
157
|
+
}
|
|
158
|
+
[ANNHostingViewsStoppingAtStart removeObject:self];
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// The limit: the surface hasn't started. It gets a delegate of its own that stops it at any later start, and this
|
|
162
|
+
// view lets it go. The delegate changes before the check: a start told before the change reaches this view, which
|
|
163
|
+
// stops the surface (its block keeps the view); one told after it reaches the new delegate.
|
|
164
|
+
- (void)letSurfaceGo {
|
|
165
|
+
if (![ANNHostingViewsStoppingAtStart containsObject:self]) {
|
|
166
|
+
return;
|
|
167
|
+
}
|
|
168
|
+
RCTFabricSurface *surface = [self fabricSurface];
|
|
169
|
+
ANNSurfaceStopper *stopper = [ANNSurfaceStopper new];
|
|
170
|
+
objc_setAssociatedObject(surface, ANNSurfaceStopperKey, stopper,
|
|
171
|
+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
172
|
+
surface.delegate = stopper;
|
|
173
|
+
if (RCTSurfaceStageIsRunning(surface.stage)) {
|
|
174
|
+
[surface stop];
|
|
175
|
+
}
|
|
176
|
+
[ANNHostingViewsStoppingAtStart removeObject:self];
|
|
177
|
+
}
|
|
178
|
+
|
|
63
179
|
- (nullable RCTFabricSurface *)fabricSurface {
|
|
64
180
|
id surface = self.surface;
|
|
65
181
|
return [surface isKindOfClass:RCTFabricSurface.class] ? surface : nil;
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
#import "ANNExternalViewController.h"
|
|
3
3
|
#import "ANNIconButtonImage.h"
|
|
4
4
|
#import "ANNStackController.h"
|
|
5
|
+
#import "ANNTabsDesign.h"
|
|
5
6
|
#import <objc/runtime.h>
|
|
6
7
|
|
|
7
8
|
// The item below that the core configured: what it set may go again.
|
|
@@ -141,10 +142,8 @@ static const void *ANNBackButtonConfiguredKey = &ANNBackButtonConfiguredKey;
|
|
|
141
142
|
ANNBackBarButtonItem *item = [ANNBackBarButtonItem new];
|
|
142
143
|
item.allowsMenu = [backButton.enableMenu withDefault:YES];
|
|
143
144
|
item.style = UIBarButtonItemStylePlain;
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
before26 = NO;
|
|
147
|
-
}
|
|
145
|
+
// The compatibility mode of iOS 26 shows the title below too, as the design before it.
|
|
146
|
+
BOOL before26 = !ANNTabsDesign.isGlass;
|
|
148
147
|
item.title = title ?: (before26 ? belowTitle : nil);
|
|
149
148
|
UIColor *background = [self iconBackgroundColorOf:backButton];
|
|
150
149
|
if (background) {
|
|
@@ -165,7 +164,9 @@ static const void *ANNBackButtonConfiguredKey = &ANNBackButtonConfiguredKey;
|
|
|
165
164
|
[iconBackground.height withDefault:0])
|
|
166
165
|
insets:UIEdgeInsetsZero];
|
|
167
166
|
if (@available(iOS 26.0, *)) {
|
|
168
|
-
|
|
167
|
+
if (ANNTabsDesign.isGlass) {
|
|
168
|
+
item.hidesSharedBackground = [backButton.hideSharedBackground withDefault:YES];
|
|
169
|
+
}
|
|
169
170
|
}
|
|
170
171
|
}
|
|
171
172
|
return item;
|
|
@@ -45,7 +45,8 @@ typedef struct {
|
|
|
45
45
|
/// (a clear color is a clear bar). The one rule for the bar, its color in the list and the top edge effect.
|
|
46
46
|
+ (nullable UIColor *)solidColorOf:(nullable Color *)color traits:(UITraitCollection *)traits;
|
|
47
47
|
|
|
48
|
-
/// A 1×1 image of `color` resolved for `traits
|
|
48
|
+
/// A 1×1 image of `color` resolved for `traits`, with a variant for each style: the same image for the same resolved
|
|
49
|
+
/// colors, so a look filled again equals the one before (as the back button's tinted arrow).
|
|
49
50
|
+ (UIImage *)imageOfColor:(UIColor *)color traits:(UITraitCollection *)traits;
|
|
50
51
|
|
|
51
52
|
@end
|