@apexnative/navigation 1.0.0-alpha.3 → 1.0.0-alpha.4
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 +9 -0
- package/android/CLAUDE.md +17 -7
- package/android/CORE.md +8 -3
- package/android/RECIPES.md +9 -3
- package/android/src/main/java/dev/apexnative/navigation/NavigationApplication.kt +34 -13
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutChecks.kt +5 -1
- package/android/src/main/java/dev/apexnative/navigation/layout/LayoutNode.kt +6 -2
- package/android/src/main/java/dev/apexnative/navigation/nativescreen/NativeScreenContext.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonMenuOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/ButtonOptions.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/options/OptionsCache.kt +8 -4
- package/android/src/main/java/dev/apexnative/navigation/options/UserChoices.kt +159 -0
- package/android/src/main/java/dev/apexnative/navigation/react/CommandAnswer.kt +2 -24
- package/android/src/main/java/dev/apexnative/navigation/react/CommandContract.kt +59 -0
- package/android/src/main/java/dev/apexnative/navigation/react/Hinge.kt +27 -19
- package/android/src/main/java/dev/apexnative/navigation/react/NavigationTurboModule.kt +5 -4
- package/android/src/main/java/dev/apexnative/navigation/react/PendingCalls.kt +48 -0
- package/android/src/main/java/dev/apexnative/navigation/react/events/AppearanceDispatcher.kt +17 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotContainers.kt +10 -7
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotView.kt +4 -3
- package/android/src/main/java/dev/apexnative/navigation/slots/SlotViews.kt +40 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageKey.kt +66 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageLoader.kt +7 -89
- package/android/src/main/java/dev/apexnative/navigation/utils/ImageRequests.kt +76 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/OptionImages.kt +262 -0
- package/android/src/main/java/dev/apexnative/navigation/utils/ViewController.kt +3 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/AppliedTabCalls.kt +49 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsController.kt +16 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ContentReadiness.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/RenderWait.kt +27 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +5 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +4 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +6 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +8 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitPanes.kt +23 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/splitview/SplitViewController.kt +58 -15
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackCommands.kt +28 -18
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackController.kt +16 -7
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +2 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +85 -29
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +2 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/fab/FabPresenter.kt +7 -5
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/search/StackSearchPresenter.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/MenuChecks.kt +13 -56
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/topbar/TopBarPresenter.kt +10 -2
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +56 -21
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
- package/cli/doctor/checks/app.js +61 -13
- package/cli/migrate/native.js +18 -0
- package/cli/setup/source.js +2 -1
- package/cli/setup/steps/mainApplication.js +134 -19
- package/docs-md/architecture/transitions.md +6 -7
- package/docs-md/help/glossary.md +1 -1
- package/docs-md/help/troubleshooting.md +20 -3
- package/docs-md/index.json +4 -0
- package/docs-md/migration/from-react-native-navigation.md +8 -2
- package/docs-md/screen-ui/back-and-gestures.md +2 -2
- package/docs-md/start/installation.md +3 -1
- package/ios/CLAUDE.md +17 -5
- package/ios/CORE.md +13 -8
- package/ios/Contract/ANNCommandAnswer.mm +5 -20
- package/ios/Contract/ANNCommandContract.h +15 -0
- package/ios/Contract/ANNCommandContract.mm +78 -0
- package/ios/Core/ANNAppearanceDispatcher.mm +1 -1
- package/ios/Core/ANNErrorHandler.h +1 -1
- package/ios/Core/ANNErrorHandler.mm +3 -1
- package/ios/Core/ANNEventEmitter.h +1 -1
- package/ios/Core/ANNEventEmitter.mm +2 -2
- package/ios/Core/ANNJavaScriptCalls.h +34 -0
- package/ios/Core/ANNJavaScriptCalls.mm +61 -0
- package/ios/Core/ANNNavigationState.h +7 -3
- package/ios/Core/ANNNavigationState.mm +10 -5
- package/ios/Core/ANNNodeHelper.h +32 -12
- package/ios/Core/ANNNodeHelper.mm +100 -3
- package/ios/Core/ANNRenderWait.h +15 -1
- package/ios/Core/ANNRenderWait.mm +119 -17
- package/ios/Core/UIViewController+LayoutProtocol.h +9 -2
- package/ios/Core/UIViewController+LayoutProtocol.mm +8 -0
- package/ios/Layouts/Modal/ANNModalCoordinator.h +1 -1
- package/ios/Layouts/Modal/ANNModalCoordinator.mm +55 -14
- package/ios/Layouts/Screen/ANNComponentViewController.h +8 -0
- package/ios/Layouts/Screen/ANNComponentViewController.mm +32 -5
- package/ios/Layouts/Screen/ANNScreenPlace.h +41 -0
- package/ios/Layouts/Screen/ANNScreenPlace.mm +175 -0
- package/ios/Layouts/SplitView/ANNSplitViewCommands.h +3 -2
- package/ios/Layouts/SplitView/ANNSplitViewCommands.mm +105 -39
- package/ios/Layouts/SplitView/ANNSplitViewController.mm +4 -2
- package/ios/Layouts/Stack/ANNStackCommands.mm +1 -0
- package/ios/Layouts/Stack/ANNStackController.mm +26 -3
- package/ios/Layouts/Stack/ANNStackQueue.h +3 -1
- package/ios/Layouts/Stack/ANNStackQueue.mm +56 -43
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.h +4 -2
- package/ios/Layouts/Stack/UINavigationController+ANNCommands.mm +2 -3
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.h +4 -0
- package/ios/Layouts/Tabs/ANNBottomTabItemBuilder.mm +11 -0
- package/ios/Layouts/Tabs/ANNBottomTabsController.mm +13 -5
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.h +2 -2
- package/ios/Layouts/Tabs/ANNBottomTabsPresenter.mm +109 -7
- package/ios/Options/ANNOptionsCache.h +7 -6
- package/ios/Options/ANNSearchBarOptions.h +4 -3
- package/ios/Options/ANNUserChoice.h +57 -0
- package/ios/Options/ANNUserChoice.mm +44 -0
- package/ios/Options/Params/ANNImageSource.h +61 -0
- package/ios/Options/Params/ANNImageSource.mm +101 -0
- package/ios/Options/Params/ANNOptionImages.h +37 -0
- package/ios/Options/Params/ANNOptionImages.mm +284 -0
- package/ios/Options/Params/Image.h +11 -2
- package/ios/Options/Params/Image.mm +15 -2
- package/ios/Options/Params/ImageParser.h +5 -5
- package/ios/Options/Params/ImageParser.mm +28 -46
- package/ios/Public/ApexNavigation/ApexNavigation.h +1 -1
- package/ios/RECIPES.md +11 -5
- package/ios/React/Slots/ANNSlotComponentView.mm +24 -17
- package/ios/React/Slots/ANNSlotContainers.h +14 -6
- package/ios/React/Slots/ANNSlotContainers.mm +75 -2
- package/ios/TopBar/ANNBarButtons.h +9 -5
- package/ios/TopBar/ANNBarButtons.mm +211 -63
- package/ios/TopBar/ANNIconButtonImage.h +1 -0
- package/ios/TopBar/ANNIconButtonImage.mm +34 -1
- package/ios/TopBar/ANNLargeTitlePresenter.h +13 -0
- package/ios/TopBar/ANNLargeTitlePresenter.mm +26 -3
- package/ios/TopBar/ANNSearchPresenter.mm +170 -19
- package/ios/TopBar/ANNTopBarPresenter.h +5 -0
- package/ios/TopBar/ANNTopBarPresenter.mm +60 -11
- package/ios/TurboModules/ANNTurboModule.mm +39 -21
- package/lib/module/ARCHITECTURE.md +18 -18
- package/lib/module/adapters/NativeANNTurboModule.js.map +1 -1
- package/lib/module/api/navigation.js.map +1 -1
- package/lib/module/engine/commands.js +9 -7
- package/lib/module/engine/commands.js.map +1 -1
- package/lib/module/engine/layout.js +13 -9
- package/lib/module/engine/layout.js.map +1 -1
- package/lib/module/engine/options.js +44 -11
- package/lib/module/engine/options.js.map +1 -1
- package/lib/module/engine/places.js +44 -7
- package/lib/module/engine/places.js.map +1 -1
- package/lib/module/engine/props.js +30 -1
- package/lib/module/engine/props.js.map +1 -1
- package/lib/module/engine/rememberedOptions.js +1 -1
- package/lib/module/engine/rememberedOptions.js.map +1 -1
- package/lib/module/engine/screens.js +22 -12
- package/lib/module/engine/screens.js.map +1 -1
- package/lib/module/interfaces/contract/commands.json +101 -0
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts +5 -2
- package/lib/typescript/adapters/NativeANNTurboModule.d.ts.map +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts +1 -1
- package/lib/typescript/api/events/navigationEvents.d.ts.map +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts +1 -1
- package/lib/typescript/api/hooks/useNavigation.d.ts.map +1 -1
- package/lib/typescript/api/navigation.d.ts +4 -1
- package/lib/typescript/api/navigation.d.ts.map +1 -1
- package/lib/typescript/engine/commands.d.ts.map +1 -1
- package/lib/typescript/engine/options.d.ts +5 -2
- package/lib/typescript/engine/options.d.ts.map +1 -1
- package/lib/typescript/engine/places.d.ts +5 -2
- package/lib/typescript/engine/places.d.ts.map +1 -1
- package/lib/typescript/engine/props.d.ts +8 -1
- package/lib/typescript/engine/props.d.ts.map +1 -1
- package/lib/typescript/engine/rememberedOptions.d.ts.map +1 -1
- package/lib/typescript/engine/screens.d.ts.map +1 -1
- package/lib/typescript/interfaces/ComponentEvents.d.ts +2 -2
- package/lib/typescript/interfaces/ComponentEvents.d.ts.map +1 -1
- package/lib/typescript/interfaces/Events.d.ts +19 -10
- package/lib/typescript/interfaces/Events.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/animations.d.ts +5 -2
- package/lib/typescript/interfaces/options/animations.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/buttons.d.ts +13 -5
- package/lib/typescript/interfaces/options/buttons.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/topBar.d.ts +22 -9
- package/lib/typescript/interfaces/options/topBar.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/ARCHITECTURE.md +18 -18
- package/src/adapters/NativeANNTurboModule.ts +5 -2
- package/src/api/events/navigationEvents.ts +1 -1
- package/src/api/hooks/useNavigation.ts +1 -1
- package/src/api/navigation.ts +4 -1
- package/src/engine/commands.ts +19 -7
- package/src/engine/layout.ts +16 -9
- package/src/engine/options.ts +39 -11
- package/src/engine/places.ts +56 -7
- package/src/engine/props.ts +30 -1
- package/src/engine/rememberedOptions.ts +4 -1
- package/src/engine/screens.tsx +23 -13
- package/src/interfaces/ComponentEvents.ts +2 -2
- package/src/interfaces/Events.ts +19 -10
- package/src/interfaces/contract/commands.json +101 -0
- package/src/interfaces/options/animations.ts +5 -2
- package/src/interfaces/options/buttons.ts +13 -5
- package/src/interfaces/options/topBar.ts +22 -9
|
@@ -16,7 +16,11 @@
|
|
|
16
16
|
// How long the new top of a `setStackRoot` waits for its first content when `waitForRender` is not set.
|
|
17
17
|
static const NSTimeInterval ANNStackRootRenderLimit = 0.5;
|
|
18
18
|
|
|
19
|
-
//
|
|
19
|
+
// How long a push waits for its new top's first content when `waitForRender` is not set.
|
|
20
|
+
static const NSTimeInterval ANNPushRenderLimit = 0.5;
|
|
21
|
+
|
|
22
|
+
// The limits of the bar components of a push: title and background 1 s, buttons 0.5 s. With `waitForRender` unset they
|
|
23
|
+
// count from the wait's start, the content's time coming off them; with `true`, from the content's end.
|
|
20
24
|
static const ANNBarWaitLimits ANNPushBarLimits = {1, 0.5};
|
|
21
25
|
|
|
22
26
|
// The bar components of a `setStackRoot` within its 0.5 s.
|
|
@@ -53,8 +57,8 @@ typedef NS_ENUM(NSInteger, ANNStackOperationKind) {
|
|
|
53
57
|
@property(nonatomic) BOOL animated;
|
|
54
58
|
@property(nonatomic) BOOL waits;
|
|
55
59
|
@property(nonatomic) BOOL atOnce;
|
|
56
|
-
// When the replace's own wait for its new top started (`waitForRender` unset): the custom transition's
|
|
57
|
-
// is left of the 0.5 s. 0 when it didn't wait so.
|
|
60
|
+
// When the push's or the replace's own wait for its new top started (`waitForRender` unset): the custom transition's
|
|
61
|
+
// wait takes what is left of the 0.5 s. 0 when it didn't wait so.
|
|
58
62
|
@property(nonatomic) CFTimeInterval waitStarted;
|
|
59
63
|
@property(nonatomic, copy, nullable) dispatch_block_t completion;
|
|
60
64
|
@property(nonatomic, copy, nullable) ANNTransitionRejectionBlock rejection;
|
|
@@ -371,12 +375,13 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
|
|
|
371
375
|
|
|
372
376
|
- (void)noneWithValue:(id)value
|
|
373
377
|
answer:(ANNCommandAnswer *)answer
|
|
378
|
+
what:(NSString *)what
|
|
374
379
|
componentId:(NSString *)componentId
|
|
375
380
|
source:(UIViewController *)source {
|
|
376
381
|
ANNAssertMainQueue();
|
|
377
382
|
ANNStackOperation *operation = [self operationOfKind:ANNStackOperationNone
|
|
378
383
|
answer:answer
|
|
379
|
-
what
|
|
384
|
+
what:what
|
|
380
385
|
componentId:componentId
|
|
381
386
|
source:source];
|
|
382
387
|
operation.value = value;
|
|
@@ -559,41 +564,35 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
|
|
|
559
564
|
[self scheduleRunNext];
|
|
560
565
|
return;
|
|
561
566
|
}
|
|
562
|
-
// `waitForRender: true` waits for the content as long as it takes, then for the bar components
|
|
563
|
-
// the content's end
|
|
564
|
-
//
|
|
565
|
-
// transition.
|
|
567
|
+
// `waitForRender: true` waits for the content as long as it takes, then for the bar components, each kind to its own
|
|
568
|
+
// limit from the content's end; unset (also with `enabled: false`), for its first content at most 0.5 s, then for
|
|
569
|
+
// the bar components, each kind to its own limit from the wait's start (as when the push waited for them alone);
|
|
570
|
+
// `false`, for nothing, the content coming during the transition. Either wait's content ends after the calls
|
|
571
|
+
// JavaScript made in the commit that mounted the content (its `useScreenOptions`), content there already too, so
|
|
572
|
+
// that their options are in the bar on the transition's first frame. A native screen ends the wait at its start. A
|
|
573
|
+
// stack that isn't seen never waits.
|
|
566
574
|
Bool *waitForRender = [node resolveOptionsWithDefault].animations.push.waitForRender;
|
|
567
575
|
// A push whose caller waited already (a preview, `showInColumn`) waits for nothing here.
|
|
568
576
|
BOOL onScreen = operation.waits && [_display stackIsOnScreen];
|
|
569
577
|
__weak ANNStackQueue *weakSelf = self;
|
|
570
578
|
__weak ANNStackOperation *weakOperation = operation;
|
|
571
|
-
if (onScreen && waitForRender.hasValue
|
|
572
|
-
|
|
573
|
-
|
|
574
|
-
|
|
575
|
-
|
|
576
|
-
unknownLimit:YES
|
|
577
|
-
bar:ANNRenderWaitBarAfterContent
|
|
578
|
-
barLimits:ANNPushBarLimits
|
|
579
|
-
then:^{
|
|
580
|
-
[weakSelf startPushTransition:weakOperation];
|
|
581
|
-
}];
|
|
582
|
-
return;
|
|
583
|
-
}
|
|
584
|
-
if (onScreen && !waitForRender.hasValue) {
|
|
579
|
+
if (onScreen && (!waitForRender.hasValue || waitForRender.get)) {
|
|
580
|
+
BOOL bounded = !waitForRender.hasValue;
|
|
581
|
+
if (bounded) {
|
|
582
|
+
operation.waitStarted = CACurrentMediaTime();
|
|
583
|
+
}
|
|
585
584
|
[self waitFor:operation
|
|
586
|
-
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
585
|
+
entry:node
|
|
586
|
+
content:ANNRenderWaitContentNode
|
|
587
|
+
limit:bounded ? ANNPushRenderLimit : 0
|
|
588
|
+
unknownLimit:!bounded
|
|
589
|
+
bar:ANNRenderWaitBarAfterContent
|
|
590
|
+
barLimits:ANNPushBarLimits
|
|
591
|
+
afterJavaScript:YES
|
|
592
|
+
barFromStart:bounded
|
|
593
|
+
then:^{
|
|
594
|
+
[weakSelf startPushTransition:weakOperation];
|
|
595
|
+
}];
|
|
597
596
|
return;
|
|
598
597
|
}
|
|
599
598
|
// UIKit sizes the screen inside the push: it makes its content right before.
|
|
@@ -616,10 +615,14 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
|
|
|
616
615
|
operation.covered = covered;
|
|
617
616
|
ANNTransitionPlan *plan = nil;
|
|
618
617
|
if (animated) {
|
|
618
|
+
// Unset `waitForRender`: the custom transition's wait takes what is left of the push's own 0.5 s.
|
|
619
|
+
NSTimeInterval limit =
|
|
620
|
+
[ANNAnimationRules customWaitLimitAfterWaitStartedAt:operation.waitStarted
|
|
621
|
+
commandLimit:ANNPushRenderLimit];
|
|
619
622
|
plan = [ANNTransitionPlan stackPlanOfKind:ANNTransitionKindPush
|
|
620
623
|
animation:push
|
|
621
624
|
waitForRender:push.waitForRender
|
|
622
|
-
waitLimit:
|
|
625
|
+
waitLimit:limit
|
|
623
626
|
from:covered
|
|
624
627
|
to:node];
|
|
625
628
|
}
|
|
@@ -746,6 +749,8 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
|
|
|
746
749
|
unknownLimit:NO
|
|
747
750
|
bar:ANNRenderWaitBarWithContent
|
|
748
751
|
barLimits:ANNStackRootBarLimits
|
|
752
|
+
afterJavaScript:NO
|
|
753
|
+
barFromStart:NO
|
|
749
754
|
then:^{
|
|
750
755
|
[weakSelf startReplaceTransition:weakOperation];
|
|
751
756
|
}];
|
|
@@ -759,6 +764,8 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
|
|
|
759
764
|
unknownLimit:YES
|
|
760
765
|
bar:ANNRenderWaitBarAfterContent
|
|
761
766
|
barLimits:ANNStackRootBarLimits
|
|
767
|
+
afterJavaScript:NO
|
|
768
|
+
barFromStart:NO
|
|
762
769
|
then:^{
|
|
763
770
|
[weakSelf startReplaceTransition:weakOperation];
|
|
764
771
|
}];
|
|
@@ -1046,16 +1053,20 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
|
|
|
1046
1053
|
// for it in the stack's view, its appearance driven: the wait ends at the entry's ready handler (set first, then the
|
|
1047
1054
|
// park and `render`, then the test of 16.1), at `limit` with `markReady`, or with `unknownLimit` for a screen whose
|
|
1048
1055
|
// rendered state stays unknown after 1 s. Without content, only the bar components, without parking (bar roots render
|
|
1049
|
-
// out of a window).
|
|
1050
|
-
//
|
|
1056
|
+
// out of a window). With `afterJavaScript` the content's end waits for the calls JavaScript made by then
|
|
1057
|
+
// (`settlesThroughJavaScript`); with `barFromStart` the bar phase's limits count from the wait's start
|
|
1058
|
+
// (`barLimitsFromStart`). A wait that would end does nothing once its operation ended or belongs to an old
|
|
1059
|
+
// JavaScript; its end runs protected (the end steps of the operation, `then`).
|
|
1051
1060
|
- (void)waitFor:(ANNStackOperation *)operation
|
|
1052
|
-
|
|
1053
|
-
|
|
1054
|
-
|
|
1055
|
-
|
|
1056
|
-
|
|
1057
|
-
|
|
1058
|
-
|
|
1061
|
+
entry:(UIViewController *)entry
|
|
1062
|
+
content:(ANNRenderWaitContent)content
|
|
1063
|
+
limit:(NSTimeInterval)limit
|
|
1064
|
+
unknownLimit:(BOOL)unknownLimit
|
|
1065
|
+
bar:(ANNRenderWaitBar)bar
|
|
1066
|
+
barLimits:(ANNBarWaitLimits)barLimits
|
|
1067
|
+
afterJavaScript:(BOOL)afterJavaScript
|
|
1068
|
+
barFromStart:(BOOL)barFromStart
|
|
1069
|
+
then:(dispatch_block_t)then {
|
|
1059
1070
|
__weak ANNStackQueue *weakSelf = self;
|
|
1060
1071
|
__weak ANNStackOperation *weakOperation = operation;
|
|
1061
1072
|
// The screens the wait is about are those the entry waits for (a pair: the ones it puts in the window).
|
|
@@ -1069,6 +1080,8 @@ static NSArray<UIViewController *> *ANNTopFirst(NSArray<UIViewController *> *ent
|
|
|
1069
1080
|
bar:bar
|
|
1070
1081
|
barLimits:barLimits];
|
|
1071
1082
|
wait.unknownContentRule = unknownLimit;
|
|
1083
|
+
wait.settlesThroughJavaScript = afterJavaScript;
|
|
1084
|
+
wait.barLimitsFromStart = barFromStart;
|
|
1072
1085
|
wait.canEnd = ^BOOL {
|
|
1073
1086
|
ANNStackQueue *queue = weakSelf;
|
|
1074
1087
|
ANNStackOperation *waiting = weakOperation;
|
|
@@ -6,9 +6,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
6
6
|
/// The commands of a stack.
|
|
7
7
|
@interface UINavigationController (ANNCommands)
|
|
8
8
|
|
|
9
|
-
/// `
|
|
10
|
-
/// `fromViewController`.
|
|
9
|
+
/// `showInColumn` into the split view's column `column` that is a stack: `children` in place of the stack's screens, the
|
|
10
|
+
/// last one on top, from the screen `fromViewController`. A navigation controller that isn't a stack of the core
|
|
11
|
+
/// rejects `[ANNErrorTexts columnNotStack:column:]`.
|
|
11
12
|
- (void)setStackChildren:(NSArray<UIViewController *> *)children
|
|
13
|
+
inColumn:(NSString *)column
|
|
12
14
|
fromViewController:(nullable UIViewController *)fromViewController
|
|
13
15
|
animated:(BOOL)animated
|
|
14
16
|
completion:(ANNTransitionCompletionBlock)completion
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
// A stack of the core takes the screens through its queue, without a wait. A navigation controller of UIKit's own (the
|
|
9
9
|
// one UIKit puts around a column that is a screen alone) has no queue: it rejects.
|
|
10
10
|
- (void)setStackChildren:(NSArray<UIViewController *> *)children
|
|
11
|
+
inColumn:(NSString *)column
|
|
11
12
|
fromViewController:(UIViewController *)fromViewController
|
|
12
13
|
animated:(BOOL)animated
|
|
13
14
|
completion:(ANNTransitionCompletionBlock)completion
|
|
@@ -21,9 +22,7 @@
|
|
|
21
22
|
return;
|
|
22
23
|
}
|
|
23
24
|
NSString *fromId = fromViewController.layoutInfo.componentId ?: @"";
|
|
24
|
-
|
|
25
|
-
NSString *reason = @"its column is not a stack of the navigation";
|
|
26
|
-
rejection([ANNErrorTexts couldNot:what reason:reason], nil);
|
|
25
|
+
rejection([ANNErrorTexts columnNotStack:fromId column:column], nil);
|
|
27
26
|
}
|
|
28
27
|
|
|
29
28
|
@end
|
|
@@ -23,6 +23,10 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
23
23
|
/// (default `iconColor`) unless `disableSelectedIconTint`. Nil without any.
|
|
24
24
|
+ (nullable UIImage *)selectedIconOf:(ANNBottomTabOptions *)tab;
|
|
25
25
|
|
|
26
|
+
/// The images of the options `iconOf:` and `selectedIconOf:` draw from (`bottomTab.icon`, `bottomTab.selectedIcon`):
|
|
27
|
+
/// each one an SF Symbol doesn't cover. Their sources, not their images: nothing loads.
|
|
28
|
+
+ (NSArray<Image *> *)rastersOf:(ANNBottomTabOptions *)tab;
|
|
29
|
+
|
|
26
30
|
/// `selectedIconColor`, else `iconColor` (`bottomTab.selectedIconColor`); nil without both.
|
|
27
31
|
+ (nullable UIColor *)selectedIconColorOf:(ANNBottomTabOptions *)tab;
|
|
28
32
|
|
|
@@ -67,6 +67,17 @@ static void ANNSetTitleAttributes(UITabBarItemStateAppearance *state, UIColor *c
|
|
|
67
67
|
return icon ? ANNRaster(icon, color) : nil;
|
|
68
68
|
}
|
|
69
69
|
|
|
70
|
+
+ (NSArray<Image *> *)rastersOf:(ANNBottomTabOptions *)tab {
|
|
71
|
+
NSMutableArray<Image *> *rasters = [NSMutableArray new];
|
|
72
|
+
if (!ANNSymbol(tab.sfSymbol) && tab.icon.source) {
|
|
73
|
+
[rasters addObject:tab.icon];
|
|
74
|
+
}
|
|
75
|
+
if (!ANNSymbol(tab.sfSelectedSymbol) && tab.selectedIcon.source) {
|
|
76
|
+
[rasters addObject:tab.selectedIcon];
|
|
77
|
+
}
|
|
78
|
+
return rasters;
|
|
79
|
+
}
|
|
80
|
+
|
|
70
81
|
+ (UIColor *)selectedIconColorOf:(ANNBottomTabOptions *)tab {
|
|
71
82
|
return [tab.selectedIconColor withDefault:nil] ?: [tab.iconColor withDefault:nil];
|
|
72
83
|
}
|
|
@@ -817,7 +817,7 @@ static void ANNStacksOfNode(UIViewController *node, NSMutableArray<ANNStackContr
|
|
|
817
817
|
if (index >= _tabs.count || self.nodeHelper.destroyed) {
|
|
818
818
|
return NO;
|
|
819
819
|
}
|
|
820
|
-
[self
|
|
820
|
+
[self finishEarlierSelection];
|
|
821
821
|
_pressedIndex = NSNotFound;
|
|
822
822
|
// The release of a long press: UIKit would select the tab under the finger.
|
|
823
823
|
if (source == ANNTabPressSourceBar && index == _longPressedIndex) {
|
|
@@ -957,6 +957,14 @@ static void ANNStacksOfNode(UIViewController *node, NSMutableArray<ANNStackContr
|
|
|
957
957
|
[self sendPendingSelected];
|
|
958
958
|
}
|
|
959
959
|
|
|
960
|
+
// Before anything newer on these tabs (a press, a long press, a More pick, a choice from code): a switch still running
|
|
961
|
+
// ends with its appearances and schedules its `Selected`, and a `Selected` still waiting goes, as Android's
|
|
962
|
+
// `flushPendingSelected` does.
|
|
963
|
+
- (void)finishEarlierSelection {
|
|
964
|
+
[self endSwitch];
|
|
965
|
+
[self sendPendingSelected];
|
|
966
|
+
}
|
|
967
|
+
|
|
960
968
|
- (void)sendPendingSelected {
|
|
961
969
|
if (!_hasPendingSelected) {
|
|
962
970
|
return;
|
|
@@ -980,8 +988,8 @@ static void ANNStacksOfNode(UIViewController *node, NSMutableArray<ANNStackContr
|
|
|
980
988
|
if (index >= _tabs.count || self.nodeHelper.destroyed) {
|
|
981
989
|
return;
|
|
982
990
|
}
|
|
983
|
-
// A pending `Selected`
|
|
984
|
-
[self
|
|
991
|
+
// A running switch and a pending `Selected` go before the event of a later press.
|
|
992
|
+
[self finishEarlierSelection];
|
|
985
993
|
if (marksSelection) {
|
|
986
994
|
_longPressedIndex = index;
|
|
987
995
|
}
|
|
@@ -1024,7 +1032,7 @@ static void ANNStacksOfNode(UIViewController *node, NSMutableArray<ANNStackContr
|
|
|
1024
1032
|
// first, then UIKit's, then the state at once.
|
|
1025
1033
|
- (void)selectTabFromCode:(NSUInteger)index {
|
|
1026
1034
|
ANNAssertMainQueue();
|
|
1027
|
-
[self
|
|
1035
|
+
[self finishEarlierSelection];
|
|
1028
1036
|
_pressedIndex = NSNotFound;
|
|
1029
1037
|
if (index == _selectedTabIndex || index >= _tabs.count || self.nodeHelper.destroyed) {
|
|
1030
1038
|
return;
|
|
@@ -1235,7 +1243,7 @@ static void ANNStacksOfNode(UIViewController *node, NSMutableArray<ANNStackContr
|
|
|
1235
1243
|
if (pick && index == _selectedTabIndex) {
|
|
1236
1244
|
return;
|
|
1237
1245
|
}
|
|
1238
|
-
[self
|
|
1246
|
+
[self finishEarlierSelection];
|
|
1239
1247
|
_pressedIndex = NSNotFound;
|
|
1240
1248
|
if (pick) {
|
|
1241
1249
|
[self sendPressedOfTabAtIndex:index wasSelected:NO];
|
|
@@ -10,8 +10,8 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
10
10
|
/// `ANNBottomTabsController`.
|
|
11
11
|
@interface ANNBottomTabsPresenter : ANNNodePresenter
|
|
12
12
|
|
|
13
|
-
/// The repaint of a node the repaint pass left (a tab not in a window)
|
|
14
|
-
/// resolved options.
|
|
13
|
+
/// The repaint of a node the repaint pass left (a tab not in a window), as it is about to show: the nodes of its
|
|
14
|
+
/// subtree whose views are in its view (what shows with it), children first, with their resolved options.
|
|
15
15
|
+ (void)repaintNode:(UIViewController *)node;
|
|
16
16
|
|
|
17
17
|
#pragma mark - Creation, in this order
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#import "ANNBottomTabsAccessoryView.h"
|
|
4
4
|
#import "ANNBottomTabsController.h"
|
|
5
5
|
#import "ANNBottomTabsCustomRow.h"
|
|
6
|
+
#import "ANNOptionImages.h"
|
|
6
7
|
#import "ANNTabDotView.h"
|
|
7
8
|
#import "ANNTabsDesign.h"
|
|
8
9
|
#import "UITabBar+utils.h"
|
|
@@ -73,6 +74,8 @@ static id ANNValue(NSArray *list, NSUInteger index) {
|
|
|
73
74
|
NSMutableDictionary<NSString *, ANNBottomTabsAccessoryView *> *_accessoryViews;
|
|
74
75
|
// The pictures of the bar's colors by color and size.
|
|
75
76
|
NSMutableDictionary<NSArray *, UIImage *> *_colorImages;
|
|
77
|
+
// The icons an item waits for, by layout index: a token per wait, which a later build of the item replaces.
|
|
78
|
+
NSMutableDictionary<NSNumber *, NSObject *> *_iconLoads;
|
|
76
79
|
}
|
|
77
80
|
|
|
78
81
|
- (nullable ANNBottomTabsController *)tabs {
|
|
@@ -82,10 +85,20 @@ static id ANNValue(NSArray *list, NSUInteger index) {
|
|
|
82
85
|
}
|
|
83
86
|
|
|
84
87
|
+ (void)repaintNode:(UIViewController *)node {
|
|
88
|
+
[self repaintNode:node showingIn:node.viewIfLoaded];
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
// Children first. Only what shows with the tab repaints: a node whose view is out of the tab's views (a stack's lower
|
|
92
|
+
// screens, a hidden tab of tabs inside) takes the new options at its own appearance, as the repaint of what shows
|
|
93
|
+
// leaves the nodes out of a window (`ANNOptionsRepaint`).
|
|
94
|
+
+ (void)repaintNode:(UIViewController *)node showingIn:(nullable UIView *)tabView {
|
|
85
95
|
for (UIViewController *child in node.layoutChildren) {
|
|
86
|
-
[self repaintNode:child];
|
|
96
|
+
[self repaintNode:child showingIn:tabView];
|
|
97
|
+
}
|
|
98
|
+
UIView *view = node.viewIfLoaded;
|
|
99
|
+
if (tabView && view && [view isDescendantOfView:tabView]) {
|
|
100
|
+
[node.presenter applyDefaultOptionsChange:[node resolveOptionsWithDefault]];
|
|
87
101
|
}
|
|
88
|
-
[node.presenter applyDefaultOptionsChange:[node resolveOptionsWithDefault]];
|
|
89
102
|
}
|
|
90
103
|
|
|
91
104
|
#pragma mark - Creation
|
|
@@ -112,6 +125,7 @@ static id ANNValue(NSArray *list, NSUInteger index) {
|
|
|
112
125
|
_buttonIds = [NSMapTable weakToStrongObjectsMapTable];
|
|
113
126
|
_accessoryViews = [NSMutableDictionary new];
|
|
114
127
|
_colorImages = [NSMutableDictionary new];
|
|
128
|
+
_iconLoads = [NSMutableDictionary new];
|
|
115
129
|
for (NSUInteger index = 0; index < count; index++) {
|
|
116
130
|
[_badges addObject:NSNull.null];
|
|
117
131
|
[_dots addObject:[ANNBottomTabDotIndicatorOptions new]];
|
|
@@ -326,8 +340,12 @@ static id ANNValue(NSArray *list, NSUInteger index) {
|
|
|
326
340
|
// The row's items show nothing: no image, no title, only the accessibility.
|
|
327
341
|
NSString *text = [ANNBottomTabItemBuilder titleOf:bottomTab];
|
|
328
342
|
NSString *title = row || _titlesHidden ? nil : text;
|
|
329
|
-
|
|
330
|
-
|
|
343
|
+
// While new icons load off the main queue the item keeps the ones it shows; a wait of an earlier build is over.
|
|
344
|
+
[_iconLoads removeObjectForKey:@(index)];
|
|
345
|
+
BOOL keepsIcons = !row && [self loadsIconsLaterOf:bottomTab atIndex:index];
|
|
346
|
+
UIImage *icon = row || keepsIcons ? nil : [ANNBottomTabItemBuilder iconOf:bottomTab];
|
|
347
|
+
UIImage *selectedIcon =
|
|
348
|
+
row || keepsIcons ? nil : [ANNBottomTabItemBuilder selectedIconOf:bottomTab];
|
|
331
349
|
if (@available(iOS 18.0, *)) {
|
|
332
350
|
if (uiTab) {
|
|
333
351
|
UITab *systemTab = uiTab;
|
|
@@ -339,12 +357,13 @@ static id ANNValue(NSArray *list, NSUInteger index) {
|
|
|
339
357
|
systemTab.title = tabTitle;
|
|
340
358
|
}
|
|
341
359
|
}
|
|
342
|
-
if ((!search || icon || row) && systemTab.image != icon) {
|
|
360
|
+
if (!keepsIcons && (!search || icon || row) && systemTab.image != icon) {
|
|
343
361
|
systemTab.image = icon;
|
|
344
362
|
}
|
|
345
363
|
#if __IPHONE_OS_VERSION_MAX_ALLOWED >= 270000
|
|
346
364
|
if (@available(iOS 26.1, *)) {
|
|
347
|
-
if ((!search || selectedIcon || row) &&
|
|
365
|
+
if (!keepsIcons && (!search || selectedIcon || row) &&
|
|
366
|
+
systemTab.selectedImage != selectedIcon) {
|
|
348
367
|
systemTab.selectedImage = selectedIcon;
|
|
349
368
|
}
|
|
350
369
|
}
|
|
@@ -365,7 +384,7 @@ static id ANNValue(NSArray *list, NSUInteger index) {
|
|
|
365
384
|
item.accessibilityLabel = text;
|
|
366
385
|
}
|
|
367
386
|
}
|
|
368
|
-
if (!roleItem || icon) {
|
|
387
|
+
if (!keepsIcons && (!roleItem || icon)) {
|
|
369
388
|
if (item.image != icon) {
|
|
370
389
|
item.image = icon;
|
|
371
390
|
}
|
|
@@ -427,6 +446,89 @@ static id ANNValue(NSArray *list, NSUInteger index) {
|
|
|
427
446
|
}
|
|
428
447
|
}
|
|
429
448
|
|
|
449
|
+
// `bottomTab` with the images that came in place of the rasters they came for: a build from it asks the cache for
|
|
450
|
+
// none of them. A raster that failed stays as it was (its failure answers for a moment).
|
|
451
|
+
static ANNBottomTabOptions *ANNWithIcons(ANNBottomTabOptions *bottomTab, NSArray<Image *> *rasters,
|
|
452
|
+
NSArray *came) {
|
|
453
|
+
ANNBottomTabOptions *built = [bottomTab copy];
|
|
454
|
+
for (NSUInteger place = 0; place < rasters.count; place++) {
|
|
455
|
+
UIImage *image = [came[place] isKindOfClass:UIImage.class] ? came[place] : nil;
|
|
456
|
+
if (!image) {
|
|
457
|
+
continue;
|
|
458
|
+
}
|
|
459
|
+
Image *raster = rasters[place];
|
|
460
|
+
if (raster == bottomTab.icon) {
|
|
461
|
+
built.icon = [[Image alloc] initWithValue:image];
|
|
462
|
+
}
|
|
463
|
+
if (raster == bottomTab.selectedIcon) {
|
|
464
|
+
built.selectedIcon = [[Image alloc] initWithValue:image];
|
|
465
|
+
}
|
|
466
|
+
}
|
|
467
|
+
return built;
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
// New raster icons of an item on the bar that shows load off the main queue when the cache doesn't have them: the item
|
|
471
|
+
// takes them when they came, built again from the same options with the images that came (the cache may have let them
|
|
472
|
+
// go by then: icons larger than it), unless it was built again first (the last build wins). The first icons, and every
|
|
473
|
+
// icon while the bar is out of a window, load at once: the tab's first frame shows them.
|
|
474
|
+
- (BOOL)loadsIconsLaterOf:(ANNBottomTabOptions *)bottomTab atIndex:(NSUInteger)index {
|
|
475
|
+
if (!_tabsAreSet || !self.tabs.viewIfLoaded.window) {
|
|
476
|
+
return NO;
|
|
477
|
+
}
|
|
478
|
+
NSArray<Image *> *rasters = [ANNBottomTabItemBuilder rastersOf:bottomTab];
|
|
479
|
+
BOOL later = NO;
|
|
480
|
+
for (Image *raster in rasters) {
|
|
481
|
+
if ([ANNOptionImages loadsLater:raster.source]) {
|
|
482
|
+
later = YES;
|
|
483
|
+
}
|
|
484
|
+
}
|
|
485
|
+
if (!later) {
|
|
486
|
+
return NO;
|
|
487
|
+
}
|
|
488
|
+
NSObject *load = [NSObject new];
|
|
489
|
+
_iconLoads[@(index)] = load;
|
|
490
|
+
// Every raster of the item, those the cache has too: each comes with its image, by its place in `rasters`.
|
|
491
|
+
NSMutableArray *came = [NSMutableArray arrayWithCapacity:rasters.count];
|
|
492
|
+
for (NSUInteger each = 0; each < rasters.count; each++) {
|
|
493
|
+
[came addObject:NSNull.null];
|
|
494
|
+
}
|
|
495
|
+
__block NSUInteger pending = rasters.count;
|
|
496
|
+
__block BOOL asking = YES;
|
|
497
|
+
__weak ANNBottomTabsPresenter *weakSelf = self;
|
|
498
|
+
[rasters enumerateObjectsUsingBlock:^(Image *raster, NSUInteger place, BOOL *stop) {
|
|
499
|
+
[ANNOptionImages loadLater:raster.source
|
|
500
|
+
done:^(UIImage *image) {
|
|
501
|
+
if (image) {
|
|
502
|
+
came[place] = image;
|
|
503
|
+
}
|
|
504
|
+
pending--;
|
|
505
|
+
ANNBottomTabsPresenter *presenter = weakSelf;
|
|
506
|
+
if (pending == 0 && !asking) {
|
|
507
|
+
[presenter iconsCame:load
|
|
508
|
+
atIndex:index
|
|
509
|
+
bottomTab:ANNWithIcons(bottomTab, rasters, came)];
|
|
510
|
+
}
|
|
511
|
+
}];
|
|
512
|
+
}];
|
|
513
|
+
asking = NO;
|
|
514
|
+
if (pending == 0) {
|
|
515
|
+
// A load ended while the others were asked for: the cache has them all now.
|
|
516
|
+
[_iconLoads removeObjectForKey:@(index)];
|
|
517
|
+
return NO;
|
|
518
|
+
}
|
|
519
|
+
return YES;
|
|
520
|
+
}
|
|
521
|
+
|
|
522
|
+
- (void)iconsCame:(NSObject *)load
|
|
523
|
+
atIndex:(NSUInteger)index
|
|
524
|
+
bottomTab:(ANNBottomTabOptions *)bottomTab {
|
|
525
|
+
if (_iconLoads[@(index)] != load || index >= self.tabs.layoutTabs.count) {
|
|
526
|
+
return;
|
|
527
|
+
}
|
|
528
|
+
[_iconLoads removeObjectForKey:@(index)];
|
|
529
|
+
[self buildItemAtIndex:index bottomTab:bottomTab updatesBadgeAndDot:NO];
|
|
530
|
+
}
|
|
531
|
+
|
|
430
532
|
- (void)updateDotAtIndex:(NSUInteger)index
|
|
431
533
|
shown:(BOOL)shown
|
|
432
534
|
options:(ANNBottomTabDotIndicatorOptions *)dot {
|
|
@@ -2,13 +2,14 @@
|
|
|
2
2
|
|
|
3
3
|
NS_ASSUME_NONNULL_BEGIN
|
|
4
4
|
|
|
5
|
-
/// The one point where options change: a number for the whole process that grows each time a node's own options
|
|
6
|
-
/// default options change. A node's resolved options are cached with the number they were built at,
|
|
7
|
-
/// every cache miss. Main queue.
|
|
5
|
+
/// The one point where options change: a number for the whole process that grows each time a node's own options, its
|
|
6
|
+
/// user layer or the default options change. A node's resolved options are cached with the number they were built at,
|
|
7
|
+
/// so any change makes every cache miss. Main queue.
|
|
8
8
|
///
|
|
9
|
-
/// Exactly
|
|
10
|
-
/// `mergeOwnOptions:` and `changeOwnOptions:` go through)
|
|
11
|
-
///
|
|
9
|
+
/// Exactly three places call `optionsChanged`: the setter of a node's own options (`ANNNodeHelper.options`, which
|
|
10
|
+
/// `mergeOwnOptions:` and `changeOwnOptions:` go through), the setter of its user layer
|
|
11
|
+
/// (`ANNNodeHelper setUserChoice:forKey:`) and the store of new default options (`ANNDefaultOptions`). Nothing else may
|
|
12
|
+
/// change own options, choices or defaults: a write past them would leave caches stale.
|
|
12
13
|
@interface ANNOptionsCache : NSObject
|
|
13
14
|
|
|
14
15
|
/// The generation of the options now.
|
|
@@ -15,9 +15,10 @@ typedef NS_ENUM(NSInteger, SearchBarPlacement) {
|
|
|
15
15
|
SearchBarPlacementIntegratedCentered,
|
|
16
16
|
};
|
|
17
17
|
|
|
18
|
-
/// `topBar.searchBar` (`OptionsSearchBar`): the search of a screen. `ANNSearchPresenter` applies it. The scope
|
|
19
|
-
/// tokens
|
|
20
|
-
/// stay when the options apply again
|
|
18
|
+
/// `topBar.searchBar` (`OptionsSearchBar`): the search of a screen. `ANNSearchPresenter` applies it. The scope the user
|
|
19
|
+
/// picks, the tokens the user deletes or brings back and the suggestions UIKit removes go into the screen's user layer
|
|
20
|
+
/// (`setUserChoice:forKey:`), not into these options: they stay when the options apply again and through a
|
|
21
|
+
/// `mergeOptions` that sends the same values, and give way when a merge changes the field.
|
|
21
22
|
@interface ANNSearchBarOptions : ANNOptions
|
|
22
23
|
|
|
23
24
|
@property(nonatomic, strong) Bool *visible;
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
#import "ANNNavigationOptions.h"
|
|
2
|
+
#import <Foundation/Foundation.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
/// Writes a choice into options being resolved: `options` is a copy the block may change, but its lists and their
|
|
7
|
+
/// items are shared with other trees, so the block puts in a new list with a copy of what it changes, never a change in
|
|
8
|
+
/// place. It finds what it changes by identity (a button by its id, a menu item by its id, a scope by its id) and
|
|
9
|
+
/// leaves the options as they are when that is not there.
|
|
10
|
+
typedef void (^ANNUserChoiceApply)(ANNNavigationOptions *options, id value);
|
|
11
|
+
|
|
12
|
+
/// The value of a choice after the app's `mergeOptions` into the node, by Android's rules (`UserChoices.yieldTo`):
|
|
13
|
+
/// `merge` is what the app sent, `own` the node's own options before it, `before` and `after` the node's resolution
|
|
14
|
+
/// with the defaults and without any user layer (the app's options alone) before and after the merge. The same `value`
|
|
15
|
+
/// keeps the choice, a smaller value keeps part of it, nil drops it. A part leaves when what it was made in is gone
|
|
16
|
+
/// from `after` (a button, a menu item, a scope), or when `merge` gives its field a value other than the one the app
|
|
17
|
+
/// had there: `own`'s, or `before`'s where `own` has no such field (a list the node takes from its stack or the
|
|
18
|
+
/// defaults). The same value sent again (a re-render) keeps it.
|
|
19
|
+
typedef id _Nullable (^ANNUserChoiceMerged)(id value, ANNNavigationOptions *merge,
|
|
20
|
+
ANNNavigationOptions *own,
|
|
21
|
+
ANNNavigationOptions *before,
|
|
22
|
+
ANNNavigationOptions *after);
|
|
23
|
+
|
|
24
|
+
/// One choice the user made in native UI (a toggled button, the check of a single-selection menu, the search scope, the
|
|
25
|
+
/// tokens and the suggestions UIKit removed, a renamed title), kept in the node's user layer
|
|
26
|
+
/// (`ANNNodeHelper setUserChoice:forKey:`) apart from its own options. The node's resolution writes it over the own
|
|
27
|
+
/// options (`applyTo:`); the app's real change of the same field, or a merge after which what it is about is gone,
|
|
28
|
+
/// takes it away (`choiceAfterMerge:own:before:after:`). The blocks capture only plain data (a side, an id, a key),
|
|
29
|
+
/// never a presenter: the node keeps them as long as the choice.
|
|
30
|
+
@interface ANNUserChoice : NSObject
|
|
31
|
+
|
|
32
|
+
/// What the user chose, never changed: a number, a string, an array of ids or of option copies.
|
|
33
|
+
@property(nonatomic, strong, readonly) id value;
|
|
34
|
+
|
|
35
|
+
+ (instancetype)choiceWithValue:(id)value
|
|
36
|
+
apply:(ANNUserChoiceApply)apply
|
|
37
|
+
merged:(ANNUserChoiceMerged)merged;
|
|
38
|
+
|
|
39
|
+
- (instancetype)init NS_UNAVAILABLE;
|
|
40
|
+
+ (instancetype)new NS_UNAVAILABLE;
|
|
41
|
+
|
|
42
|
+
/// The same choice with another value.
|
|
43
|
+
- (instancetype)choiceWithValue:(id)value;
|
|
44
|
+
|
|
45
|
+
/// The choice written into `options` (a copy being resolved).
|
|
46
|
+
- (void)applyTo:(ANNNavigationOptions *)options;
|
|
47
|
+
|
|
48
|
+
/// The choice after the app's `mergeOptions` (`ANNUserChoiceMerged`): itself, the choice with a smaller value, or nil
|
|
49
|
+
/// when it goes.
|
|
50
|
+
- (nullable ANNUserChoice *)choiceAfterMerge:(ANNNavigationOptions *)merge
|
|
51
|
+
own:(ANNNavigationOptions *)own
|
|
52
|
+
before:(ANNNavigationOptions *)before
|
|
53
|
+
after:(ANNNavigationOptions *)after;
|
|
54
|
+
|
|
55
|
+
@end
|
|
56
|
+
|
|
57
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,44 @@
|
|
|
1
|
+
#import "ANNUserChoice.h"
|
|
2
|
+
|
|
3
|
+
@implementation ANNUserChoice {
|
|
4
|
+
ANNUserChoiceApply _apply;
|
|
5
|
+
ANNUserChoiceMerged _merged;
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
+ (instancetype)choiceWithValue:(id)value
|
|
9
|
+
apply:(ANNUserChoiceApply)apply
|
|
10
|
+
merged:(ANNUserChoiceMerged)merged {
|
|
11
|
+
return [[self alloc] initWithValue:value apply:apply merged:merged];
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
- (instancetype)initWithValue:(id)value
|
|
15
|
+
apply:(ANNUserChoiceApply)apply
|
|
16
|
+
merged:(ANNUserChoiceMerged)merged {
|
|
17
|
+
if (self = [super init]) {
|
|
18
|
+
_value = value;
|
|
19
|
+
_apply = [apply copy];
|
|
20
|
+
_merged = [merged copy];
|
|
21
|
+
}
|
|
22
|
+
return self;
|
|
23
|
+
}
|
|
24
|
+
|
|
25
|
+
- (instancetype)choiceWithValue:(id)value {
|
|
26
|
+
return [[ANNUserChoice alloc] initWithValue:value apply:_apply merged:_merged];
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
- (void)applyTo:(ANNNavigationOptions *)options {
|
|
30
|
+
_apply(options, _value);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
- (ANNUserChoice *)choiceAfterMerge:(ANNNavigationOptions *)merge
|
|
34
|
+
own:(ANNNavigationOptions *)own
|
|
35
|
+
before:(ANNNavigationOptions *)before
|
|
36
|
+
after:(ANNNavigationOptions *)after {
|
|
37
|
+
id value = _merged(_value, merge, own, before, after);
|
|
38
|
+
if (!value) {
|
|
39
|
+
return nil;
|
|
40
|
+
}
|
|
41
|
+
return value == _value ? self : [self choiceWithValue:value];
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
@end
|