@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
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
#import "ANNIconButtonImage.h"
|
|
4
4
|
#import "ANNShowTextButton.h"
|
|
5
5
|
#import "ANNUIBarButtonItem.h"
|
|
6
|
+
#import "ANNUserChoice.h"
|
|
6
7
|
#import "UIViewController+LayoutProtocol.h"
|
|
7
8
|
#import <React/RCTLog.h>
|
|
8
9
|
|
|
@@ -54,6 +55,187 @@ static void ANNSetButtonsOfSide(ANNNavigationOptions *options, ANNBarButtonsSide
|
|
|
54
55
|
}
|
|
55
56
|
}
|
|
56
57
|
|
|
58
|
+
static ANNButtonOptions *ANNButtonWithId(NSArray<ANNButtonOptions *> *buttons, NSString *buttonId) {
|
|
59
|
+
for (ANNButtonOptions *button in buttons) {
|
|
60
|
+
if ([[button.identifier withDefault:nil] isEqualToString:buttonId]) {
|
|
61
|
+
return button;
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
return nil;
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// The button `buttonId` of `side` in `options` changed on a copy, in a new list; nothing when the side has no such
|
|
68
|
+
// button. The other buttons stay the objects they are: nothing changes them.
|
|
69
|
+
static void ANNChangeButton(ANNNavigationOptions *options,
|
|
70
|
+
ANNBarButtonsSide side,
|
|
71
|
+
NSString *buttonId,
|
|
72
|
+
void (^change)(ANNButtonOptions *changed)) {
|
|
73
|
+
NSArray<ANNButtonOptions *> *list = ANNButtonsOfSide(options, side);
|
|
74
|
+
if (!ANNButtonWithId(list, buttonId)) {
|
|
75
|
+
return;
|
|
76
|
+
}
|
|
77
|
+
NSMutableArray<ANNButtonOptions *> *changedList = [NSMutableArray arrayWithCapacity:list.count];
|
|
78
|
+
for (ANNButtonOptions *button in list) {
|
|
79
|
+
if ([[button.identifier withDefault:nil] isEqualToString:buttonId]) {
|
|
80
|
+
ANNButtonOptions *copy = [button copy];
|
|
81
|
+
change(copy);
|
|
82
|
+
[changedList addObject:copy];
|
|
83
|
+
} else {
|
|
84
|
+
[changedList addObject:button];
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
ANNSetButtonsOfSide(options, side, changedList);
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
#pragma mark - The user's choices
|
|
91
|
+
|
|
92
|
+
static NSString *ANNToggleChoiceKey(ANNBarButtonsSide side, NSString *buttonId) {
|
|
93
|
+
return [NSString stringWithFormat:@"buttons.%ld.selected.%@", (long)side, buttonId];
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
static NSString *ANNButtonMenuChoiceKey(ANNBarButtonsSide side, NSString *buttonId) {
|
|
97
|
+
return [NSString stringWithFormat:@"buttons.%ld.menu.%@", (long)side, buttonId];
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
// The button `buttonId` of `side` in `options`, or nil.
|
|
101
|
+
static ANNButtonOptions *ANNButtonIn(ANNNavigationOptions *options, ANNBarButtonsSide side,
|
|
102
|
+
NSString *buttonId) {
|
|
103
|
+
return ANNButtonWithId(ANNButtonsOfSide(options, side), buttonId);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// A toggle the user flipped: `selected` of the button with its id, wherever its list comes from. It goes when the
|
|
107
|
+
// node's resolution after a merge has no such button, or when the merge gives the button a `selected` other than the
|
|
108
|
+
// app had (the own button's, else the button the node resolved to before the merge), by the rules of a menu's check.
|
|
109
|
+
static ANNUserChoice *ANNToggleChoice(ANNBarButtonsSide side, NSString *buttonId, BOOL selected) {
|
|
110
|
+
return [ANNUserChoice choiceWithValue:@(selected)
|
|
111
|
+
apply:^(ANNNavigationOptions *options, id value) {
|
|
112
|
+
BOOL on = [(NSNumber *)value boolValue];
|
|
113
|
+
ANNChangeButton(options, side, buttonId, ^(ANNButtonOptions *changed) {
|
|
114
|
+
changed.selected = [Bool withValue:on];
|
|
115
|
+
});
|
|
116
|
+
}
|
|
117
|
+
merged:^id(id value, ANNNavigationOptions *merge, ANNNavigationOptions *own,
|
|
118
|
+
ANNNavigationOptions *before, ANNNavigationOptions *after) {
|
|
119
|
+
if (!ANNButtonIn(after, side, buttonId)) {
|
|
120
|
+
return nil;
|
|
121
|
+
}
|
|
122
|
+
ANNButtonOptions *given = ANNButtonIn(merge, side, buttonId);
|
|
123
|
+
if (!given.selected.hasValue) {
|
|
124
|
+
return value;
|
|
125
|
+
}
|
|
126
|
+
ANNButtonOptions *was = ANNButtonIn(own, side, buttonId);
|
|
127
|
+
if (!was.selected.hasValue) {
|
|
128
|
+
was = ANNButtonIn(before, side, buttonId);
|
|
129
|
+
}
|
|
130
|
+
BOOL same = [given.selected withDefault:NO] == [was.selected withDefault:NO];
|
|
131
|
+
return same ? value : nil;
|
|
132
|
+
}];
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
// The items of the level (a menu's or a submenu's own list, without its submenus) that holds `itemId`, at any depth;
|
|
136
|
+
// nil when no level holds it.
|
|
137
|
+
static NSArray<ANNMenuItemOptions *> *ANNLevelOf(NSArray<ANNMenuItemOptions *> *items,
|
|
138
|
+
NSString *itemId) {
|
|
139
|
+
NSMutableArray<ANNMenuItemOptions *> *level = [NSMutableArray new];
|
|
140
|
+
BOOL holds = NO;
|
|
141
|
+
for (ANNMenuItemOptions *item in items) {
|
|
142
|
+
NSString *leafId = item.items.count ? nil : [item.identifier withDefault:nil];
|
|
143
|
+
if (leafId) {
|
|
144
|
+
[level addObject:item];
|
|
145
|
+
holds = holds || [leafId isEqualToString:itemId];
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
if (holds) {
|
|
149
|
+
return level;
|
|
150
|
+
}
|
|
151
|
+
for (ANNMenuItemOptions *item in items) {
|
|
152
|
+
if (item.items.count) {
|
|
153
|
+
NSArray<ANNMenuItemOptions *> *found = ANNLevelOf(item.items, itemId);
|
|
154
|
+
if (found) {
|
|
155
|
+
return found;
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
}
|
|
159
|
+
return nil;
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
// The ids of the items in the level that holds `itemId`: the neighbors one pick unchecks.
|
|
163
|
+
static NSArray<NSString *> *ANNNeighborsOf(NSArray<ANNMenuItemOptions *> *items, NSString *itemId) {
|
|
164
|
+
NSMutableArray<NSString *> *ids = [NSMutableArray new];
|
|
165
|
+
for (ANNMenuItemOptions *item in ANNLevelOf(items, itemId)) {
|
|
166
|
+
[ids addObject:[item.identifier withDefault:@""]];
|
|
167
|
+
}
|
|
168
|
+
return ids;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// The checked items of the level that holds `itemId` in `menu`: the field a merge gives that level. Nil when `menu`
|
|
172
|
+
// doesn't hold the item.
|
|
173
|
+
static NSSet<NSString *> *ANNCheckedInLevel(ANNMenuOptions *menu, NSString *itemId) {
|
|
174
|
+
NSArray<ANNMenuItemOptions *> *level = menu ? ANNLevelOf(menu.items, itemId) : nil;
|
|
175
|
+
if (!level) {
|
|
176
|
+
return nil;
|
|
177
|
+
}
|
|
178
|
+
NSMutableSet<NSString *> *checked = [NSMutableSet new];
|
|
179
|
+
for (ANNMenuItemOptions *item in level) {
|
|
180
|
+
if ([item.checked withDefault:NO]) {
|
|
181
|
+
[checked addObject:[item.identifier withDefault:@""]];
|
|
182
|
+
}
|
|
183
|
+
}
|
|
184
|
+
return checked;
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
// A menu that has items, or nil: a merge without items gives no menu.
|
|
188
|
+
static ANNMenuOptions *ANNMenuWithItems(ANNMenuOptions *menu) {
|
|
189
|
+
return menu.items ? menu : nil;
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// The picks of single-selection levels in one menu, in the order made: the resolution plays them again on a copy of
|
|
193
|
+
// the menu it has, so a new menu from the app keeps the check where its items have the same ids. As on Android
|
|
194
|
+
// (`UserChoices.yieldTo`), at each merge into the node a pick goes when the node's resolution after it no longer has
|
|
195
|
+
// the item, or when the merge gives the menu with other checks in the pick's level than the app had there (the own
|
|
196
|
+
// options' menu, else the menu the node resolved to before the merge); the picks of the other levels stay.
|
|
197
|
+
typedef ANNMenuOptions *_Nullable (^ANNMenuOf)(ANNNavigationOptions *options);
|
|
198
|
+
typedef void (^ANNSetMenu)(ANNNavigationOptions *options, ANNMenuOptions *menu);
|
|
199
|
+
|
|
200
|
+
static ANNUserChoice *ANNMenuChoice(NSArray<NSString *> *picks,
|
|
201
|
+
ANNMenuOf menuOf,
|
|
202
|
+
ANNSetMenu setMenu) {
|
|
203
|
+
return [ANNUserChoice choiceWithValue:picks
|
|
204
|
+
apply:^(ANNNavigationOptions *options, id value) {
|
|
205
|
+
ANNMenuOptions *menu = menuOf(options);
|
|
206
|
+
if (menu.items.count == 0) {
|
|
207
|
+
return;
|
|
208
|
+
}
|
|
209
|
+
ANNMenuOptions *copy = [ANNBarButtons deepCopyOfMenu:menu];
|
|
210
|
+
for (NSString *itemId in (NSArray<NSString *> *)value) {
|
|
211
|
+
[copy itemPicked:itemId];
|
|
212
|
+
}
|
|
213
|
+
setMenu(options, copy);
|
|
214
|
+
}
|
|
215
|
+
merged:^id(id value, ANNNavigationOptions *merge, ANNNavigationOptions *own,
|
|
216
|
+
ANNNavigationOptions *before, ANNNavigationOptions *after) {
|
|
217
|
+
ANNMenuOptions *now = ANNMenuWithItems(menuOf(after));
|
|
218
|
+
ANNMenuOptions *given = ANNMenuWithItems(menuOf(merge));
|
|
219
|
+
ANNMenuOptions *was = ANNMenuWithItems(menuOf(own)) ?: ANNMenuWithItems(menuOf(before));
|
|
220
|
+
NSArray<NSString *> *made = value;
|
|
221
|
+
NSMutableArray<NSString *> *kept = [NSMutableArray new];
|
|
222
|
+
for (NSString *itemId in made) {
|
|
223
|
+
BOOL gone = !now || !ANNLevelOf(now.items, itemId);
|
|
224
|
+
NSSet<NSString *> *givenChecks = given ? ANNCheckedInLevel(given, itemId) : nil;
|
|
225
|
+
NSSet<NSString *> *wasChecks = ANNCheckedInLevel(was, itemId);
|
|
226
|
+
BOOL same = givenChecks == wasChecks || [givenChecks isEqualToSet:wasChecks];
|
|
227
|
+
BOOL changed = given && !same;
|
|
228
|
+
if (!gone && !changed) {
|
|
229
|
+
[kept addObject:itemId];
|
|
230
|
+
}
|
|
231
|
+
}
|
|
232
|
+
if (kept.count == 0) {
|
|
233
|
+
return nil;
|
|
234
|
+
}
|
|
235
|
+
return kept.count == made.count ? value : kept;
|
|
236
|
+
}];
|
|
237
|
+
}
|
|
238
|
+
|
|
57
239
|
static UIColor *ANNColorOf(Color *color) {
|
|
58
240
|
if (!color.hasValue || color.isNoColor) {
|
|
59
241
|
return nil;
|
|
@@ -438,11 +620,12 @@ static UIBarButtonSystemItem ANNSystemItemOf(NSString *name) {
|
|
|
438
620
|
}
|
|
439
621
|
UIMenu *built = [current menuWithPickHandler:^(NSString *itemId) {
|
|
440
622
|
[weakSelf menuItemPicked:itemId
|
|
441
|
-
|
|
442
|
-
|
|
623
|
+
choiceKey:@"topBar.overflowMenu"
|
|
624
|
+
menuOf:^ANNMenuOptions *(ANNNavigationOptions *options) {
|
|
625
|
+
return options.topBar.overflowMenu;
|
|
443
626
|
}
|
|
444
|
-
setMenu:^(ANNNavigationOptions *
|
|
445
|
-
|
|
627
|
+
setMenu:^(ANNNavigationOptions *options, ANNMenuOptions *copy) {
|
|
628
|
+
options.topBar.overflowMenu = copy;
|
|
446
629
|
}];
|
|
447
630
|
}];
|
|
448
631
|
UIMenu *section = [UIMenu menuWithTitle:built.title
|
|
@@ -880,16 +1063,14 @@ static UIBarButtonSystemItem ANNSystemItemOf(NSString *name) {
|
|
|
880
1063
|
__weak ANNBarButtons *weakSelf = self;
|
|
881
1064
|
item.menu = [menu menuWithPickHandler:^(NSString *itemId) {
|
|
882
1065
|
[weakSelf menuItemPicked:itemId
|
|
1066
|
+
choiceKey:ANNButtonMenuChoiceKey(side, buttonId)
|
|
883
1067
|
menuOf:^ANNMenuOptions *(ANNNavigationOptions *options) {
|
|
884
|
-
return
|
|
1068
|
+
return ANNButtonWithId(ANNButtonsOfSide(options, side), buttonId).menu;
|
|
885
1069
|
}
|
|
886
|
-
setMenu:^(ANNNavigationOptions *
|
|
887
|
-
|
|
888
|
-
|
|
889
|
-
|
|
890
|
-
change:^(ANNButtonOptions *changed) {
|
|
891
|
-
changed.menu = copy;
|
|
892
|
-
}];
|
|
1070
|
+
setMenu:^(ANNNavigationOptions *options, ANNMenuOptions *copy) {
|
|
1071
|
+
ANNChangeButton(options, side, buttonId, ^(ANNButtonOptions *changed) {
|
|
1072
|
+
changed.menu = copy;
|
|
1073
|
+
});
|
|
893
1074
|
}];
|
|
894
1075
|
}];
|
|
895
1076
|
if (![button.menuOnLongPress withDefault:NO]) {
|
|
@@ -948,22 +1129,15 @@ static UIBarButtonSystemItem ANNSystemItemOf(NSString *name) {
|
|
|
948
1129
|
[screen.eventEmitter sendOnNavigationButtonPressed:componentId buttonId:press.buttonId];
|
|
949
1130
|
return;
|
|
950
1131
|
}
|
|
951
|
-
// A toggle flips, the
|
|
1132
|
+
// A toggle flips, the user layer keeps the new state, then the event tells it.
|
|
952
1133
|
ANNUIBarButtonItem *item = press.item;
|
|
953
1134
|
BOOL selected = !item.selected;
|
|
954
1135
|
item.selected = selected;
|
|
955
1136
|
_selected[press.buttonId] = @(selected);
|
|
956
|
-
__weak ANNBarButtons *weakSelf = self;
|
|
957
1137
|
NSString *buttonId = press.buttonId;
|
|
958
1138
|
ANNBarButtonsSide side = press.side;
|
|
959
|
-
[screen
|
|
960
|
-
|
|
961
|
-
side:side
|
|
962
|
-
inOwn:own
|
|
963
|
-
change:^(ANNButtonOptions *changed) {
|
|
964
|
-
changed.selected = [Bool withValue:selected];
|
|
965
|
-
}];
|
|
966
|
-
}];
|
|
1139
|
+
[screen setUserChoice:ANNToggleChoice(side, buttonId, selected)
|
|
1140
|
+
forKey:ANNToggleChoiceKey(side, buttonId)];
|
|
967
1141
|
[screen.eventEmitter sendOnNavigationButtonPressed:componentId
|
|
968
1142
|
buttonId:buttonId
|
|
969
1143
|
selected:@(selected)];
|
|
@@ -971,46 +1145,15 @@ static UIBarButtonSystemItem ANNSystemItemOf(NSString *name) {
|
|
|
971
1145
|
|
|
972
1146
|
+ (nullable ANNButtonOptions *)buttonWithId:(NSString *)buttonId
|
|
973
1147
|
in:(NSArray<ANNButtonOptions *> *)buttons {
|
|
974
|
-
|
|
975
|
-
if ([[button.identifier withDefault:nil] isEqualToString:buttonId]) {
|
|
976
|
-
return button;
|
|
977
|
-
}
|
|
978
|
-
}
|
|
979
|
-
return nil;
|
|
980
|
-
}
|
|
981
|
-
|
|
982
|
-
// The button `buttonId` of `side` in the own options, changed on a copy: the own list when it has the button, else the
|
|
983
|
-
// list the screen shows (from its stack or the defaults), copied into the own options first. Lists are new arrays.
|
|
984
|
-
- (void)changeButton:(NSString *)buttonId
|
|
985
|
-
side:(ANNBarButtonsSide)side
|
|
986
|
-
inOwn:(ANNNavigationOptions *)own
|
|
987
|
-
change:(void (^)(ANNButtonOptions *changed))change {
|
|
988
|
-
NSArray<ANNButtonOptions *> *list = ANNButtonsOfSide(own, side);
|
|
989
|
-
if (![ANNBarButtons buttonWithId:buttonId in:list]) {
|
|
990
|
-
list = ANNButtonsOfSide([_screen resolveOptionsWithDefault], side);
|
|
991
|
-
}
|
|
992
|
-
if (![ANNBarButtons buttonWithId:buttonId in:list]) {
|
|
993
|
-
return;
|
|
994
|
-
}
|
|
995
|
-
NSMutableArray<ANNButtonOptions *> *changedList = [NSMutableArray arrayWithCapacity:list.count];
|
|
996
|
-
for (ANNButtonOptions *button in list) {
|
|
997
|
-
ANNButtonOptions *copy = [button copy];
|
|
998
|
-
if (button.menu.items.count > 0) {
|
|
999
|
-
copy.menu = [ANNBarButtons deepCopyOfMenu:button.menu];
|
|
1000
|
-
}
|
|
1001
|
-
if ([[button.identifier withDefault:nil] isEqualToString:buttonId]) {
|
|
1002
|
-
change(copy);
|
|
1003
|
-
}
|
|
1004
|
-
[changedList addObject:copy];
|
|
1005
|
-
}
|
|
1006
|
-
ANNSetButtonsOfSide(own, side, changedList);
|
|
1148
|
+
return ANNButtonWithId(buttons, buttonId);
|
|
1007
1149
|
}
|
|
1008
1150
|
|
|
1009
1151
|
#pragma mark - Menus
|
|
1010
1152
|
|
|
1011
1153
|
- (void)menuItemPicked:(NSString *)itemId
|
|
1154
|
+
choiceKey:(NSString *)choiceKey
|
|
1012
1155
|
menuOf:(ANNMenuOptions *_Nullable (^)(ANNNavigationOptions *options))menuOf
|
|
1013
|
-
setMenu:(void (^)(ANNNavigationOptions *
|
|
1156
|
+
setMenu:(void (^)(ANNNavigationOptions *options, ANNMenuOptions *menu))setMenu {
|
|
1014
1157
|
UIViewController *screen = _screen;
|
|
1015
1158
|
NSString *componentId = screen.layoutInfo.componentId;
|
|
1016
1159
|
if (!screen || componentId.length == 0) {
|
|
@@ -1021,13 +1164,18 @@ static UIBarButtonSystemItem ANNSystemItemOf(NSString *name) {
|
|
|
1021
1164
|
if (!menu || ![ANNBarButtons menu:menu hasSingleSelectionOf:itemId]) {
|
|
1022
1165
|
return;
|
|
1023
1166
|
}
|
|
1024
|
-
// The
|
|
1025
|
-
//
|
|
1026
|
-
|
|
1027
|
-
|
|
1028
|
-
|
|
1029
|
-
|
|
1030
|
-
|
|
1167
|
+
// The new pick goes last; the earlier picks of its own list go, so a pick the app takes away later doesn't bring
|
|
1168
|
+
// back one the user replaced.
|
|
1169
|
+
NSArray<NSString *> *made = [screen userChoiceForKey:choiceKey].value ?: @[];
|
|
1170
|
+
NSArray<NSString *> *neighbors = ANNNeighborsOf(menu.items, itemId);
|
|
1171
|
+
NSMutableArray<NSString *> *picks = [NSMutableArray new];
|
|
1172
|
+
for (NSString *pick in made) {
|
|
1173
|
+
if (![neighbors containsObject:pick]) {
|
|
1174
|
+
[picks addObject:pick];
|
|
1175
|
+
}
|
|
1176
|
+
}
|
|
1177
|
+
[picks addObject:itemId];
|
|
1178
|
+
[screen setUserChoice:ANNMenuChoice(picks, menuOf, setMenu) forKey:choiceKey];
|
|
1031
1179
|
}
|
|
1032
1180
|
|
|
1033
1181
|
+ (BOOL)menu:(ANNMenuOptions *)menu hasSingleSelectionOf:(NSString *)itemId {
|
|
@@ -13,6 +13,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|
|
13
13
|
|
|
14
14
|
/// `tint` colors the icon (nil: its own colors); `background` fills the rectangle of `size` (zero: the icon's size)
|
|
15
15
|
/// rounded by `cornerRadius`; `insets` pad the icon. Colors may be dynamic: each variant resolves them.
|
|
16
|
+
/// An icon that can't be drawn so (no room, an exception while drawing) comes back as it is.
|
|
16
17
|
+ (UIImage *)imageWithIcon:(UIImage *)icon
|
|
17
18
|
tint:(nullable UIColor *)tint
|
|
18
19
|
background:(nullable UIColor *)background
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
#import "ANNIconButtonImage.h"
|
|
2
2
|
#import "ANNUtils.h"
|
|
3
|
+
#import <React/RCTLog.h>
|
|
3
4
|
|
|
4
5
|
@implementation ANNIconButtonImage
|
|
5
6
|
|
|
@@ -9,6 +10,30 @@
|
|
|
9
10
|
cornerRadius:(CGFloat)cornerRadius
|
|
10
11
|
size:(CGSize)size
|
|
11
12
|
insets:(UIEdgeInsets)insets {
|
|
13
|
+
// An icon that can't be drawn so (no size, insets that leave no room, an exception of the renderer) shows as it is.
|
|
14
|
+
@try {
|
|
15
|
+
UIImage *image = [self variantsOfIcon:icon
|
|
16
|
+
tint:tint
|
|
17
|
+
background:background
|
|
18
|
+
cornerRadius:cornerRadius
|
|
19
|
+
size:size
|
|
20
|
+
insets:insets];
|
|
21
|
+
if (image) {
|
|
22
|
+
return image;
|
|
23
|
+
}
|
|
24
|
+
} @catch (NSException *exception) {
|
|
25
|
+
RCTLogWarn(@"[ApexNavigation] a button's image could not be drawn on its background: %@",
|
|
26
|
+
exception.reason);
|
|
27
|
+
}
|
|
28
|
+
return icon;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
+ (nullable UIImage *)variantsOfIcon:(UIImage *)icon
|
|
32
|
+
tint:(UIColor *)tint
|
|
33
|
+
background:(UIColor *)background
|
|
34
|
+
cornerRadius:(CGFloat)cornerRadius
|
|
35
|
+
size:(CGSize)size
|
|
36
|
+
insets:(UIEdgeInsets)insets {
|
|
12
37
|
// Each variant carries a scale with its style: an asset of style-only traits gives back an image of scale 1, its
|
|
13
38
|
// pixels taken as points (a 32 pt background drawn 96 pt wide). The current scale and the usual ones are drawn,
|
|
14
39
|
// so a screen of another scale still finds its style. Each variant is an original image, so that a variant UIKit
|
|
@@ -30,6 +55,9 @@
|
|
|
30
55
|
size:size
|
|
31
56
|
insets:insets
|
|
32
57
|
traits:traits];
|
|
58
|
+
if (!drawn) {
|
|
59
|
+
return nil;
|
|
60
|
+
}
|
|
33
61
|
[asset registerImage:[drawn imageWithRenderingMode:UIImageRenderingModeAlwaysOriginal]
|
|
34
62
|
withTraitCollection:traits];
|
|
35
63
|
}
|
|
@@ -54,7 +82,7 @@
|
|
|
54
82
|
]];
|
|
55
83
|
}
|
|
56
84
|
|
|
57
|
-
+ (UIImage *)drawIcon:(UIImage *)icon
|
|
85
|
+
+ (nullable UIImage *)drawIcon:(UIImage *)icon
|
|
58
86
|
tint:(UIColor *)tint
|
|
59
87
|
background:(UIColor *)background
|
|
60
88
|
cornerRadius:(CGFloat)cornerRadius
|
|
@@ -66,6 +94,11 @@
|
|
|
66
94
|
iconSize.height + insets.top + insets.bottom);
|
|
67
95
|
CGSize canvas = CGSizeMake(size.width > 0 ? size.width : padded.width,
|
|
68
96
|
size.height > 0 ? size.height : padded.height);
|
|
97
|
+
BOOL drawable = canvas.width > 0 && canvas.height > 0 && isfinite(canvas.width) &&
|
|
98
|
+
isfinite(canvas.height);
|
|
99
|
+
if (!drawable) {
|
|
100
|
+
return nil;
|
|
101
|
+
}
|
|
69
102
|
UIImage *drawn = icon;
|
|
70
103
|
if (tint) {
|
|
71
104
|
drawn = [icon imageWithTintColor:[tint resolvedColorWithTraitCollection:traits]
|
|
@@ -57,6 +57,19 @@ typedef struct {
|
|
|
57
57
|
/// goes, and the bar follows what it follows at rest.
|
|
58
58
|
- (void)screenDidAppear:(ANNNavigationOptions *)options list:(nullable UIScrollView *)list;
|
|
59
59
|
|
|
60
|
+
/// iOS 26: a push starts that brings the screen in with its content drawn already (it waited for it parked, out of the
|
|
61
|
+
/// stack). UIKit draws the large title, for the whole transition, inside the scroll view the bar follows, and takes
|
|
62
|
+
/// its place from that scroll view as the transition starts: a list below the bar clips it, a list laid out before the
|
|
63
|
+
/// push may give it the room of a small title, and a title that doesn't fold into the list has nothing to follow yet.
|
|
64
|
+
/// The screen's view gets the place of `screenInPop:list:inlineInRow:`, at rest at `top`, the top of the safe area the
|
|
65
|
+
/// screen takes on the stack (`stackTop` the stack's own), and the bar follows it until the screen appeared, also over
|
|
66
|
+
/// a main list. Called before UIKit's push.
|
|
67
|
+
- (void)screenInPush:(ANNNavigationOptions *)options
|
|
68
|
+
list:(nullable UIScrollView *)list
|
|
69
|
+
inlineInRow:(BOOL)inlineInRow
|
|
70
|
+
top:(CGFloat)top
|
|
71
|
+
stackTop:(CGFloat)stackTop;
|
|
72
|
+
|
|
60
73
|
/// The screen went without appearing (a swipe back given up): the place of the title of the pop goes, and the bar
|
|
61
74
|
/// follows what it followed before.
|
|
62
75
|
- (void)screenDidDisappear:(ANNNavigationOptions *)options list:(nullable UIScrollView *)list;
|
|
@@ -37,9 +37,11 @@ static UINavigationItemLargeTitleDisplayMode ANNItemModeOf(NSString *mode) {
|
|
|
37
37
|
// The push from a lower bar whose fill is pending: the old bar's bottom in the screen's coordinates.
|
|
38
38
|
BOOL _titlePlacePending;
|
|
39
39
|
CGFloat _oldBarBottom;
|
|
40
|
-
// The place of the title while a pop reveals the screen, and whether the bar follows it now
|
|
40
|
+
// The place of the title while a pop reveals the screen, and whether the bar follows it now; for a push, the bar
|
|
41
|
+
// follows it until the screen appeared, also with a main list.
|
|
41
42
|
UIScrollView *_titleHost;
|
|
42
43
|
BOOL _hostsTitle;
|
|
44
|
+
BOOL _hostsPushTitle;
|
|
43
45
|
// The largest room the bar took over the content while the screen showed at rest: that of an expanded title.
|
|
44
46
|
CGFloat _expandedRoom;
|
|
45
47
|
}
|
|
@@ -99,13 +101,15 @@ static UINavigationItemLargeTitleDisplayMode ANNItemModeOf(NSString *mode) {
|
|
|
99
101
|
if (followed != _emptyScrollView) {
|
|
100
102
|
_emptyScrollView = nil;
|
|
101
103
|
}
|
|
102
|
-
// The place of a pop's title holds while the title can show and no main list at the top takes it over
|
|
104
|
+
// The place of a pop's title holds while the title can show and no main list at the top takes it over; that of a
|
|
105
|
+
// push, while the title can show.
|
|
103
106
|
BOOL dropsHost = NO;
|
|
104
107
|
if (_hostsTitle) {
|
|
105
|
-
if (canShow && (!collapses || !list)) {
|
|
108
|
+
if (canShow && (_hostsPushTitle || !collapses || !list)) {
|
|
106
109
|
followed = _titleHost;
|
|
107
110
|
} else {
|
|
108
111
|
_hostsTitle = NO;
|
|
112
|
+
_hostsPushTitle = NO;
|
|
109
113
|
dropsHost = YES;
|
|
110
114
|
}
|
|
111
115
|
}
|
|
@@ -159,6 +163,23 @@ static UINavigationItemLargeTitleDisplayMode ANNItemModeOf(NSString *mode) {
|
|
|
159
163
|
}
|
|
160
164
|
}
|
|
161
165
|
|
|
166
|
+
- (void)screenInPush:(ANNNavigationOptions *)options
|
|
167
|
+
list:(UIScrollView *)list
|
|
168
|
+
inlineInRow:(BOOL)inlineInRow
|
|
169
|
+
top:(CGFloat)top
|
|
170
|
+
stackTop:(CGFloat)stackTop {
|
|
171
|
+
if (@available(iOS 26.0, *)) {
|
|
172
|
+
UIView *screenView = _screen.viewIfLoaded;
|
|
173
|
+
if (!screenView || inlineInRow || !ANNLargeTitleCanShow(options) || top - stackTop <= 0) {
|
|
174
|
+
return;
|
|
175
|
+
}
|
|
176
|
+
[self placeTitleHostIn:screenView top:top];
|
|
177
|
+
_hostsTitle = YES;
|
|
178
|
+
_hostsPushTitle = YES;
|
|
179
|
+
[self applyCollapse:options list:list];
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
|
|
162
183
|
// A plain scroll view over the whole screen, at rest at its top under the bar (its content starts at `top`, where the
|
|
163
184
|
// content of the screen started as it was covered): UIKit puts the title just above that start, where the bar's title
|
|
164
185
|
// is at rest. It takes no touches and draws nothing itself; the core's search of the screen's list skips it.
|
|
@@ -195,6 +216,7 @@ static UINavigationItemLargeTitleDisplayMode ANNItemModeOf(NSString *mode) {
|
|
|
195
216
|
- (void)handBackTitle:(ANNNavigationOptions *)options list:(UIScrollView *)list {
|
|
196
217
|
BOOL hosted = _hostsTitle;
|
|
197
218
|
_hostsTitle = NO;
|
|
219
|
+
_hostsPushTitle = NO;
|
|
198
220
|
[self applyCollapse:options list:list];
|
|
199
221
|
if (hosted) {
|
|
200
222
|
[_titleHost removeFromSuperview];
|
|
@@ -252,6 +274,7 @@ static UINavigationItemLargeTitleDisplayMode ANNItemModeOf(NSString *mode) {
|
|
|
252
274
|
|
|
253
275
|
- (void)removeTitleHost {
|
|
254
276
|
_hostsTitle = NO;
|
|
277
|
+
_hostsPushTitle = NO;
|
|
255
278
|
[_titleHost removeFromSuperview];
|
|
256
279
|
}
|
|
257
280
|
|