@apexnative/navigation 1.0.0-alpha.5 → 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 +25 -0
- package/README.md +1 -1
- package/android/CLAUDE.md +14 -12
- package/android/CORE.md +9 -6
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +36 -5
- package/android/src/main/java/dev/apexnative/navigation/options/BottomTabsOptions.kt +13 -8
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/react/ReloadCoordinator.kt +0 -2
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenEnvironment.kt +177 -0
- package/android/src/main/java/dev/apexnative/navigation/react/ScreenRegions.kt +12 -49
- package/android/src/main/java/dev/apexnative/navigation/react/ScrollEvents.kt +49 -128
- package/android/src/main/java/dev/apexnative/navigation/safearea/SafeAreaView.kt +6 -6
- 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 +9 -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 +13 -10
- 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 +7 -8
- 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/Navigator.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -6
- 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 +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +3 -0
- 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/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/docs-md/architecture/transitions.md +4 -2
- package/docs-md/containers/modals.md +2 -0
- 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/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +21 -0
- 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 +14 -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/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 +24 -20
- 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/ANNJavaScriptCalls.h +11 -12
- package/ios/Core/ANNJavaScriptCalls.mm +7 -16
- 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/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 +57 -5
- 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/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/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/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/screens.d.ts +20 -6
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +12 -7
- 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/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/hooks/useSafeAreaInsets.ts +4 -3
- package/src/api/options/OptionsChain.ts +2 -2
- package/src/engine/commands.ts +30 -6
- package/src/engine/screens.tsx +29 -8
- package/src/interfaces/options/animations.ts +12 -7
- package/src/interfaces/options/bottomTabs.ts +18 -5
- package/src/interfaces/options/buttons.ts +8 -8
- 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/viewcontrollers/bottomtabs/CustomTabRowPlacer.kt +0 -223
- package/ios/Layouts/Screen/ANNScreenInsets.h +0 -30
- package/ios/Layouts/Screen/ANNScreenInsets.mm +0 -70
|
@@ -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
|
|
@@ -1,5 +1,12 @@
|
|
|
1
1
|
#import "ANNBarAppearance.h"
|
|
2
2
|
#import "ANNBackButtonPresenter.h"
|
|
3
|
+
#import "ANNTabsDesign.h"
|
|
4
|
+
#import <objc/runtime.h>
|
|
5
|
+
|
|
6
|
+
// The icon an indicator was tinted from: an entry found by the icon's address counts only for that icon. An indicator
|
|
7
|
+
// that is the icon itself (already an original image) carries nothing: the icon must not hold itself, and it may be
|
|
8
|
+
// the image the options' cache keeps.
|
|
9
|
+
static const void *ANNIndicatorIconKey = &ANNIndicatorIconKey;
|
|
3
10
|
|
|
4
11
|
// UIKit's fonts of the places the options build over: the title of the row, the large title, the text of the back
|
|
5
12
|
// button.
|
|
@@ -28,6 +35,27 @@ static BOOL ANNIsClear(UIColor *color, UITraitCollection *traits) {
|
|
|
28
35
|
return CGColorGetAlpha([color resolvedColorWithTraitCollection:traits].CGColor) == 0;
|
|
29
36
|
}
|
|
30
37
|
|
|
38
|
+
// The images the looks are made of, by what they are drawn from, so a look filled again from the same options is
|
|
39
|
+
// equal to the one before (UIKit compares appearances by value, images by identity) and nothing is set again. Main
|
|
40
|
+
// queue; a memory warning empties it.
|
|
41
|
+
static NSCache<id, UIImage *> *ANNLookImages(void) {
|
|
42
|
+
static NSCache<id, UIImage *> *images;
|
|
43
|
+
static dispatch_once_t once;
|
|
44
|
+
dispatch_once(&once, ^{
|
|
45
|
+
images = [NSCache new];
|
|
46
|
+
images.countLimit = 64;
|
|
47
|
+
});
|
|
48
|
+
return images;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// A color of the options as a key: a pair by its sides (its dynamic color is made anew), else its value.
|
|
52
|
+
static NSArray *ANNColorKeyOf(Color *color) {
|
|
53
|
+
if (color.light || color.dark) {
|
|
54
|
+
return @[ color.light ?: NSNull.null, color.dark ?: NSNull.null ];
|
|
55
|
+
}
|
|
56
|
+
return @[ color.value ?: NSNull.null ];
|
|
57
|
+
}
|
|
58
|
+
|
|
31
59
|
static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions *font,
|
|
32
60
|
Color *color,
|
|
33
61
|
UIFont *placeFont) {
|
|
@@ -104,16 +132,14 @@ static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions
|
|
|
104
132
|
return resolved && !ANNIsClear(resolved, traits) ? resolved : nil;
|
|
105
133
|
}
|
|
106
134
|
|
|
135
|
+
// The glass bar is translucent by default; the design before iOS 26 (and its compatibility mode) opaque.
|
|
107
136
|
+ (BOOL)translucentByDefault {
|
|
108
|
-
|
|
109
|
-
return YES;
|
|
110
|
-
}
|
|
111
|
-
return NO;
|
|
137
|
+
return ANNTabsDesign.isGlass;
|
|
112
138
|
}
|
|
113
139
|
|
|
114
|
-
// `color` beats `blur` and `translucent`; a clear color is a clear bar. On iOS 26 a color is a solid
|
|
115
|
-
// with the color and a 1×1 image of it, without the system effect. `blur` is the system's chrome
|
|
116
|
-
// base, with no view of its own in the bar.
|
|
140
|
+
// `color` beats `blur` and `translucent`; a clear color is a clear bar. On the glass of iOS 26 a color is a solid
|
|
141
|
+
// bar: a clear base with the color and a 1×1 image of it, without the system effect. `blur` is the system's chrome
|
|
142
|
+
// material on a clear base, with no view of its own in the bar.
|
|
117
143
|
+ (void)configure:(UINavigationBarAppearance *)appearance
|
|
118
144
|
color:(UIColor *)color
|
|
119
145
|
blur:(BOOL)blur
|
|
@@ -124,7 +150,7 @@ static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions
|
|
|
124
150
|
[appearance configureWithTransparentBackground];
|
|
125
151
|
return;
|
|
126
152
|
}
|
|
127
|
-
if (
|
|
153
|
+
if (ANNTabsDesign.isGlass) {
|
|
128
154
|
[appearance configureWithTransparentBackground];
|
|
129
155
|
appearance.backgroundEffect = nil;
|
|
130
156
|
appearance.backgroundColor = color;
|
|
@@ -219,23 +245,51 @@ static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions
|
|
|
219
245
|
if (!color) {
|
|
220
246
|
return nil;
|
|
221
247
|
}
|
|
222
|
-
|
|
248
|
+
symbol = @"chevron.backward";
|
|
249
|
+
icon = [UIImage systemImageNamed:symbol];
|
|
223
250
|
isSymbol = YES;
|
|
224
251
|
}
|
|
252
|
+
// A symbol by its name; an image by its object, which the entry keeps, so its address is never another's.
|
|
253
|
+
id iconKey = isSymbol ? symbol : [NSValue valueWithNonretainedObject:icon];
|
|
254
|
+
NSArray *key = @[
|
|
255
|
+
@"back", iconKey, color ? ANNColorKeyOf(backButton.color) : NSNull.null
|
|
256
|
+
];
|
|
257
|
+
NSCache<id, UIImage *> *images = ANNLookImages();
|
|
258
|
+
UIImage *cached = [images objectForKey:key];
|
|
259
|
+
if (cached && (isSymbol || cached == icon ||
|
|
260
|
+
objc_getAssociatedObject(cached, ANNIndicatorIconKey) == icon)) {
|
|
261
|
+
return cached;
|
|
262
|
+
}
|
|
263
|
+
UIImage *indicator = nil;
|
|
225
264
|
if (color) {
|
|
226
|
-
|
|
265
|
+
indicator = [icon imageWithTintColor:color
|
|
266
|
+
renderingMode:UIImageRenderingModeAlwaysOriginal];
|
|
267
|
+
} else {
|
|
268
|
+
indicator =
|
|
269
|
+
isSymbol ? icon : [icon imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
|
270
|
+
}
|
|
271
|
+
if (!isSymbol && indicator != icon) {
|
|
272
|
+
objc_setAssociatedObject(indicator, ANNIndicatorIconKey, icon,
|
|
273
|
+
OBJC_ASSOCIATION_RETAIN_NONATOMIC);
|
|
227
274
|
}
|
|
228
|
-
|
|
275
|
+
[images setObject:indicator forKey:key];
|
|
276
|
+
return indicator;
|
|
229
277
|
}
|
|
230
278
|
|
|
279
|
+
// One for the app: every look that hides the arrow holds the same image.
|
|
231
280
|
+ (UIImage *)emptyIndicator {
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
|
|
238
|
-
|
|
281
|
+
static UIImage *empty;
|
|
282
|
+
static dispatch_once_t once;
|
|
283
|
+
dispatch_once(&once, ^{
|
|
284
|
+
UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat];
|
|
285
|
+
format.opaque = NO;
|
|
286
|
+
UIGraphicsImageRenderer *renderer =
|
|
287
|
+
[[UIGraphicsImageRenderer alloc] initWithSize:CGSizeMake(1, 1) format:format];
|
|
288
|
+
UIImage *drawn = [renderer imageWithActions:^(UIGraphicsImageRendererContext *context){
|
|
289
|
+
}];
|
|
290
|
+
empty = [drawn imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal];
|
|
291
|
+
});
|
|
292
|
+
return empty;
|
|
239
293
|
}
|
|
240
294
|
|
|
241
295
|
+ (UIColor *)pinnedTitleColor:(Color *)color traits:(UITraitCollection *)traits {
|
|
@@ -244,8 +298,44 @@ static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions
|
|
|
244
298
|
}
|
|
245
299
|
|
|
246
300
|
// The 1×1 picture of the color in both styles: the bar's image view picks the variant of its own style, so a `{ light,
|
|
247
|
-
// dark }` color follows a switch of the style without a new appearance.
|
|
301
|
+
// dark }` color follows a switch of the style without a new appearance. Made once for the two resolved colors and the
|
|
302
|
+
// style picked.
|
|
248
303
|
+ (UIImage *)imageOfColor:(UIColor *)color traits:(UITraitCollection *)traits {
|
|
304
|
+
UITraitCollection *light =
|
|
305
|
+
[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleLight];
|
|
306
|
+
UITraitCollection *dark =
|
|
307
|
+
[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
|
|
308
|
+
// Each variant is drawn with the window's other traits (contrast, elevation) and registered by its style only.
|
|
309
|
+
NSArray<UITraitCollection *> *base = traits ? @[ traits ] : @[];
|
|
310
|
+
UITraitCollection *lightTraits = [UITraitCollection
|
|
311
|
+
traitCollectionWithTraitsFromCollections:[base arrayByAddingObject:light]];
|
|
312
|
+
UITraitCollection *darkTraits = [UITraitCollection
|
|
313
|
+
traitCollectionWithTraitsFromCollections:[base arrayByAddingObject:dark]];
|
|
314
|
+
UIColor *lightColor = [color resolvedColorWithTraitCollection:lightTraits];
|
|
315
|
+
UIColor *darkColor = [color resolvedColorWithTraitCollection:darkTraits];
|
|
316
|
+
BOOL isDark = traits.userInterfaceStyle == UIUserInterfaceStyleDark;
|
|
317
|
+
NSArray *key = @[ @"color", lightColor, darkColor, @(isDark) ];
|
|
318
|
+
NSCache<id, UIImage *> *images = ANNLookImages();
|
|
319
|
+
UIImage *cached = [images objectForKey:key];
|
|
320
|
+
if (cached) {
|
|
321
|
+
return cached;
|
|
322
|
+
}
|
|
323
|
+
UIImage *image = [self drawImageOf:color
|
|
324
|
+
light:light
|
|
325
|
+
dark:dark
|
|
326
|
+
lightTraits:lightTraits
|
|
327
|
+
darkTraits:darkTraits
|
|
328
|
+
isDark:isDark];
|
|
329
|
+
[images setObject:image forKey:key];
|
|
330
|
+
return image;
|
|
331
|
+
}
|
|
332
|
+
|
|
333
|
+
+ (UIImage *)drawImageOf:(UIColor *)color
|
|
334
|
+
light:(UITraitCollection *)light
|
|
335
|
+
dark:(UITraitCollection *)dark
|
|
336
|
+
lightTraits:(UITraitCollection *)lightTraits
|
|
337
|
+
darkTraits:(UITraitCollection *)darkTraits
|
|
338
|
+
isDark:(BOOL)isDark {
|
|
249
339
|
UIImage * (^draw)(UITraitCollection *) = ^UIImage *(UITraitCollection *variant) {
|
|
250
340
|
UIColor *resolved = [color resolvedColorWithTraitCollection:variant];
|
|
251
341
|
UIGraphicsImageRendererFormat *format = [UIGraphicsImageRendererFormat preferredFormat];
|
|
@@ -257,20 +347,11 @@ static NSDictionary<NSAttributedStringKey, id> *ANNTextAttributes(ANNFontOptions
|
|
|
257
347
|
[context fillRect:CGRectMake(0, 0, 1, 1)];
|
|
258
348
|
}];
|
|
259
349
|
};
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
UITraitCollection *dark =
|
|
263
|
-
[UITraitCollection traitCollectionWithUserInterfaceStyle:UIUserInterfaceStyleDark];
|
|
264
|
-
// Each variant is drawn with the window's other traits (contrast, elevation) and registered by its style only.
|
|
265
|
-
NSArray<UITraitCollection *> *base = traits ? @[ traits ] : @[];
|
|
266
|
-
UIImage *lightImage =
|
|
267
|
-
draw([UITraitCollection traitCollectionWithTraitsFromCollections:[base arrayByAddingObject:light]]);
|
|
268
|
-
UIImage *darkImage =
|
|
269
|
-
draw([UITraitCollection traitCollectionWithTraitsFromCollections:[base arrayByAddingObject:dark]]);
|
|
350
|
+
UIImage *lightImage = draw(lightTraits);
|
|
351
|
+
UIImage *darkImage = draw(darkTraits);
|
|
270
352
|
UIImageAsset *asset = [UIImageAsset new];
|
|
271
353
|
[asset registerImage:lightImage withTraitCollection:light];
|
|
272
354
|
[asset registerImage:darkImage withTraitCollection:dark];
|
|
273
|
-
BOOL isDark = traits.userInterfaceStyle == UIUserInterfaceStyleDark;
|
|
274
355
|
UIImage *picked = [asset imageWithTraitCollection:isDark ? dark : light];
|
|
275
356
|
return picked ?: (isDark ? darkImage : lightImage);
|
|
276
357
|
}
|
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
#import "ANNBarBackgroundComponent.h"
|
|
2
|
+
#import <React/RCTLog.h>
|
|
2
3
|
#import <objc/runtime.h>
|
|
3
4
|
|
|
4
5
|
// The background a bar shows now.
|
|
@@ -13,6 +14,21 @@ static UIView *ANNBackgroundHostOf(UINavigationBar *bar) {
|
|
|
13
14
|
return subview;
|
|
14
15
|
}
|
|
15
16
|
}
|
|
17
|
+
#if DEBUG
|
|
18
|
+
// The name is UIKit's private one: a system that renames the view puts the components on the bar itself. Told
|
|
19
|
+
// once, in development, and only for a bar that has its views already (one not laid out yet has none).
|
|
20
|
+
static BOOL told = NO;
|
|
21
|
+
if (!told && bar.subviews.count > 0) {
|
|
22
|
+
told = YES;
|
|
23
|
+
NSMutableArray<NSString *> *names = [NSMutableArray new];
|
|
24
|
+
for (UIView *subview in bar.subviews) {
|
|
25
|
+
[names addObject:NSStringFromClass(subview.class)];
|
|
26
|
+
}
|
|
27
|
+
RCTLogWarn(@"[ApexNavigation] the bar has no view named …BarBackground, so a background "
|
|
28
|
+
@"component goes on the bar itself at index 1; its views: %@",
|
|
29
|
+
[names componentsJoinedByString:@", "]);
|
|
30
|
+
}
|
|
31
|
+
#endif
|
|
16
32
|
return bar;
|
|
17
33
|
}
|
|
18
34
|
|
|
@@ -4,10 +4,10 @@
|
|
|
4
4
|
NS_ASSUME_NONNULL_BEGIN
|
|
5
5
|
|
|
6
6
|
/// The slot of a React button (`topBar.leftButtons[].component`) as the custom view of its bar item. It takes a content
|
|
7
|
-
/// view (`UIView<ANNComponentProtocol>`), never a class of the roots.
|
|
8
|
-
///
|
|
9
|
-
///
|
|
10
|
-
///
|
|
7
|
+
/// view (`UIView<ANNComponentProtocol>`), never a class of the roots. In the glass design of iOS 26 (`ANNTabsDesign`)
|
|
8
|
+
/// the slot is 44 pt high and as wide as the content, at least 44 pt; the width follows the content both ways
|
|
9
|
+
/// (measured after every mount; a change over 0.5 pt only), and the slot's constraints live with the view, so a new
|
|
10
|
+
/// item built around it reuses them. In the compatibility mode and before iOS 26: the content's size, no
|
|
11
11
|
/// constraints. A button rendered before the screen shows enters at its width. The core measures only the surface and
|
|
12
12
|
/// never moves the views inside it: the JS wrapper centres them.
|
|
13
13
|
@interface ANNBarButtonComponentView : UIView
|
|
@@ -7,7 +7,7 @@ static const CGFloat ANNBarButtonSlotHeight = 44;
|
|
|
7
7
|
static const CGFloat ANNBarButtonSlotMinimumWidth = 44;
|
|
8
8
|
static const CGFloat ANNBarButtonWidthTolerance = 0.5;
|
|
9
9
|
|
|
10
|
-
// The slot of iOS 26's bar: unless
|
|
10
|
+
// The slot of iOS 26's bar: unless UIKit keeps the earlier design for the app (`ANNTabsDesign`).
|
|
11
11
|
static BOOL ANNUsesSlotConstraints(void) {
|
|
12
12
|
return ANNTabsDesign.isGlass;
|
|
13
13
|
}
|
|
@@ -14,8 +14,9 @@ typedef NS_ENUM(NSInteger, ANNBarButtonsSide) {
|
|
|
14
14
|
|
|
15
15
|
/// The buttons of one screen's bar and toolbar (`topBar.leftButtons[].*`, the side options, `overflowMenu`,
|
|
16
16
|
/// `toolbar.buttons`): built from the resolved options, whole, at the screen's creation, at each appearance and at a
|
|
17
|
-
/// merge of a side
|
|
18
|
-
///
|
|
17
|
+
/// merge of a side, when anything the side's build reads changed or the item no longer holds what it put there.
|
|
18
|
+
/// Each button is an `ANNUIBarButtonItem` of the first kind its fields give (React view → icon and text → image
|
|
19
|
+
/// button → SF Symbol → image → text → system item); a button without `id` is not placed. Side colors go
|
|
19
20
|
/// onto what is built, never into the options; what the user chose (a toggle, a menu's check) goes into the screen's
|
|
20
21
|
/// user layer. The React buttons' slots are kept by component id, so a button built again reuses its slot. Main queue.
|
|
21
22
|
@interface ANNBarButtons : NSObject
|
|
@@ -27,7 +28,8 @@ typedef NS_ENUM(NSInteger, ANNBarButtonsSide) {
|
|
|
27
28
|
+ (instancetype)new NS_UNAVAILABLE;
|
|
28
29
|
|
|
29
30
|
/// Sets `side` of the screen's item (left, right, center) from `options`; `animated` fades new buttons in (a side set
|
|
30
|
-
/// as groups changes at once).
|
|
31
|
+
/// as groups changes at once). Nothing when the side's buttons, colors, loaded images, React roots and traits are the
|
|
32
|
+
/// same as at its last build and the item still holds what that build set: iOS 26 animates even equal new items.
|
|
31
33
|
- (void)applySide:(ANNBarButtonsSide)side
|
|
32
34
|
options:(ANNNavigationOptions *)options
|
|
33
35
|
animated:(BOOL)animated;
|