@apexnative/navigation 1.0.0-alpha.2 → 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 +31 -0
- package/android/CLAUDE.md +38 -9
- package/android/CORE.md +11 -4
- package/android/RECIPES.md +9 -3
- package/android/src/main/java/dev/apexnative/navigation/NavigationActivity.kt +29 -1
- 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/StatusBarOptions.kt +7 -3
- 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/arrangement/SecondaryScreenPresenter.kt +13 -5
- 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 +92 -19
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/bottomtabs/BottomTabsPresenter.kt +25 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/component/ComponentPresenter.kt +28 -7
- 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/component/ScreenFrame.kt +1 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalAnimations.kt +22 -24
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalCoordinator.kt +79 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalRecord.kt +21 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalSheet.kt +29 -12
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/modal/ModalViews.kt +22 -18
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/BackCallbackState.kt +4 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/Navigator.kt +45 -6
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/navigator/RootCoordinator.kt +35 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/overlay/OverlayCoordinator.kt +10 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/Capabilities.kt +11 -4
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ContainerBackground.kt +3 -3
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/parent/ParentController.kt +3 -0
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/sidemenu/SideMenuController.kt +5 -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 +72 -16
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/BackPreview.kt +287 -0
- 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 +164 -13
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackDisplay.kt +26 -1
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackQueue.kt +136 -40
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackTransitions.kt +26 -8
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/stack/StackViews.kt +107 -4
- 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 +131 -9
- package/android/src/main/java/dev/apexnative/navigation/viewcontrollers/viewcontroller/ViewController.kt +75 -21
- package/android/src/main/java/dev/apexnative/navigation/views/animations/Curves.kt +29 -11
- package/android/src/main/java/dev/apexnative/navigation/views/bottomtabs/BottomTabs.kt +4 -2
- package/android/src/main/java/dev/apexnative/navigation/views/stack/ScreenEdge.kt +15 -0
- package/android/src/main/java/dev/apexnative/navigation/views/stack/StackLayout.kt +137 -1
- package/android/src/main/java/dev/apexnative/navigation/views/stack/fab/Fab.kt +14 -10
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TitleBar.kt +8 -1
- package/android/src/main/java/dev/apexnative/navigation/views/stack/topbar/TopBar.kt +26 -4
- 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 +12 -7
- package/docs-md/containers/modals.md +4 -2
- package/docs-md/containers/overlays.md +3 -1
- package/docs-md/containers/side-menu.md +4 -2
- package/docs-md/containers/tabs.md +2 -0
- package/docs-md/flows/forms-in-a-modal.md +1 -1
- package/docs-md/help/glossary.md +2 -2
- package/docs-md/help/troubleshooting.md +20 -3
- package/docs-md/index.json +12 -0
- package/docs-md/migration/from-react-native-navigation.md +8 -2
- package/docs-md/screen-ui/back-and-gestures.md +8 -2
- package/docs-md/screen-ui/system-ui.md +1 -1
- 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/hooks/useNavigation.js +8 -3
- package/lib/module/api/hooks/useNavigation.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 +15 -7
- 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/modals.d.ts +18 -13
- package/lib/typescript/interfaces/options/modals.d.ts.map +1 -1
- package/lib/typescript/interfaces/options/system.d.ts +6 -3
- package/lib/typescript/interfaces/options/system.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 +9 -4
- 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 +15 -7
- package/src/interfaces/options/buttons.ts +13 -5
- package/src/interfaces/options/modals.ts +18 -13
- package/src/interfaces/options/system.ts +6 -3
- package/src/interfaces/options/topBar.ts +22 -9
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
|
|
3
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
4
|
+
|
|
5
|
+
/// Where an image of the options comes from.
|
|
6
|
+
typedef NS_ENUM(NSInteger, ANNImageSourceKind) {
|
|
7
|
+
/// An SF Symbol (`{ system, fallback? }`); a name the system doesn't have takes the fallback.
|
|
8
|
+
ANNImageSourceKindSymbol,
|
|
9
|
+
/// A `file:` address: a file, or an asset of the app's bundle that React Native resolved.
|
|
10
|
+
ANNImageSourceKindFile,
|
|
11
|
+
/// A `data:` address.
|
|
12
|
+
ANNImageSourceKindData,
|
|
13
|
+
/// Metro's asset in development, read from the dev server.
|
|
14
|
+
ANNImageSourceKindMetro,
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
/// An image the options name, as they name it: read when the options are read, loaded when an element shows it
|
|
18
|
+
/// (`ANNOptionImages`). Immutable; any thread.
|
|
19
|
+
@interface ANNImageSource : NSObject
|
|
20
|
+
|
|
21
|
+
- (instancetype)init NS_UNAVAILABLE;
|
|
22
|
+
+ (instancetype)new NS_UNAVAILABLE;
|
|
23
|
+
|
|
24
|
+
/// An SF Symbol, with the source a name the system doesn't have takes.
|
|
25
|
+
+ (instancetype)symbolNamed:(NSString *)name fallback:(nullable ANNImageSource *)fallback;
|
|
26
|
+
|
|
27
|
+
/// An address of `kind` (not a symbol) with the source's `scale`, `width` and `height` (0 without them).
|
|
28
|
+
+ (instancetype)sourceOfKind:(ANNImageSourceKind)kind
|
|
29
|
+
url:(NSURL *)url
|
|
30
|
+
address:(NSString *)address
|
|
31
|
+
scale:(CGFloat)scale
|
|
32
|
+
width:(CGFloat)width
|
|
33
|
+
height:(CGFloat)height;
|
|
34
|
+
|
|
35
|
+
@property(nonatomic, readonly) ANNImageSourceKind kind;
|
|
36
|
+
@property(nonatomic, copy, readonly, nullable) NSString *symbolName;
|
|
37
|
+
@property(nonatomic, strong, readonly, nullable) ANNImageSource *fallback;
|
|
38
|
+
@property(nonatomic, strong, readonly, nullable) NSURL *url;
|
|
39
|
+
/// The address as the options gave it (with its bundle); the symbol's name for a symbol.
|
|
40
|
+
@property(nonatomic, copy, readonly) NSString *address;
|
|
41
|
+
@property(nonatomic, readonly) CGFloat scale;
|
|
42
|
+
@property(nonatomic, readonly) CGFloat width;
|
|
43
|
+
@property(nonatomic, readonly) CGFloat height;
|
|
44
|
+
|
|
45
|
+
/// What the decoded image depends on: the address, the scale and the width (the scale without one), and for a `file:`
|
|
46
|
+
/// source outside the app's bundle the file's modification date and size, read at each ask, so a file the app writes
|
|
47
|
+
/// again at the same path shows its new content. The cache's key.
|
|
48
|
+
@property(nonatomic, copy, readonly) NSString *key;
|
|
49
|
+
|
|
50
|
+
/// What the options name, as text: the kind, the address, the scale and the width, and a symbol's fallback, without the
|
|
51
|
+
/// file's stamp, so what the file holds now never changes it. Reads nothing from the disk. For telling whether two
|
|
52
|
+
/// options name the same image (`key` is for the decoded one).
|
|
53
|
+
@property(nonatomic, copy, readonly) NSString *sourceKey;
|
|
54
|
+
|
|
55
|
+
/// The image reads from the device alone (a file, an asset of the app, a `data:` address), so it may load off the main
|
|
56
|
+
/// queue; Metro's asset reads from the network and a symbol is the system's.
|
|
57
|
+
@property(nonatomic, readonly) BOOL loadsLater;
|
|
58
|
+
|
|
59
|
+
@end
|
|
60
|
+
|
|
61
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
#import "ANNImageSource.h"
|
|
2
|
+
|
|
3
|
+
// The path of a `file:` source the app may write again: a file outside the app's bundle (documents, caches, a temporary
|
|
4
|
+
// folder); nil for any other source. The bundle's files and assets don't change while the app runs.
|
|
5
|
+
static NSString *ANNWritablePathOf(ANNImageSourceKind kind, NSURL *url) {
|
|
6
|
+
if (kind != ANNImageSourceKindFile || !url.isFileURL) {
|
|
7
|
+
return nil;
|
|
8
|
+
}
|
|
9
|
+
NSString *path = url.path;
|
|
10
|
+
NSString *bundle = NSBundle.mainBundle.bundlePath;
|
|
11
|
+
if (!path.isAbsolutePath || [path isEqualToString:bundle] ||
|
|
12
|
+
[path hasPrefix:[bundle stringByAppendingString:@"/"]]) {
|
|
13
|
+
return nil;
|
|
14
|
+
}
|
|
15
|
+
return path;
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// The modification date and size of the file at `path` (else of its `.png`, as the loader looks for it), or nil.
|
|
19
|
+
static NSString *ANNFileStampOf(NSString *path) {
|
|
20
|
+
NSFileManager *files = NSFileManager.defaultManager;
|
|
21
|
+
NSDictionary<NSFileAttributeKey, id> *attributes = [files attributesOfItemAtPath:path
|
|
22
|
+
error:nil];
|
|
23
|
+
if (!attributes && path.pathExtension.length == 0) {
|
|
24
|
+
attributes = [files attributesOfItemAtPath:[path stringByAppendingPathExtension:@"png"]
|
|
25
|
+
error:nil];
|
|
26
|
+
}
|
|
27
|
+
NSDate *modified = attributes.fileModificationDate;
|
|
28
|
+
if (!modified) {
|
|
29
|
+
return nil;
|
|
30
|
+
}
|
|
31
|
+
return [NSString stringWithFormat:@"%.6f|%llu", modified.timeIntervalSinceReferenceDate,
|
|
32
|
+
attributes.fileSize];
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
@implementation ANNImageSource {
|
|
36
|
+
// The key without the file's stamp, and the path whose stamp joins it.
|
|
37
|
+
NSString *_baseKey;
|
|
38
|
+
NSString *_writablePath;
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
+ (instancetype)symbolNamed:(NSString *)name fallback:(ANNImageSource *)fallback {
|
|
42
|
+
ANNImageSource *source = [[self alloc] initWithKind:ANNImageSourceKindSymbol
|
|
43
|
+
url:nil
|
|
44
|
+
address:name
|
|
45
|
+
scale:0
|
|
46
|
+
width:0
|
|
47
|
+
height:0];
|
|
48
|
+
source->_symbolName = [name copy];
|
|
49
|
+
source->_fallback = fallback;
|
|
50
|
+
return source;
|
|
51
|
+
}
|
|
52
|
+
|
|
53
|
+
+ (instancetype)sourceOfKind:(ANNImageSourceKind)kind
|
|
54
|
+
url:(NSURL *)url
|
|
55
|
+
address:(NSString *)address
|
|
56
|
+
scale:(CGFloat)scale
|
|
57
|
+
width:(CGFloat)width
|
|
58
|
+
height:(CGFloat)height {
|
|
59
|
+
return [[self alloc] initWithKind:kind
|
|
60
|
+
url:url
|
|
61
|
+
address:address
|
|
62
|
+
scale:scale
|
|
63
|
+
width:width
|
|
64
|
+
height:height];
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
- (instancetype)initWithKind:(ANNImageSourceKind)kind
|
|
68
|
+
url:(NSURL *)url
|
|
69
|
+
address:(NSString *)address
|
|
70
|
+
scale:(CGFloat)scale
|
|
71
|
+
width:(CGFloat)width
|
|
72
|
+
height:(CGFloat)height {
|
|
73
|
+
if (self = [super init]) {
|
|
74
|
+
_kind = kind;
|
|
75
|
+
_url = url;
|
|
76
|
+
_address = [address copy];
|
|
77
|
+
_scale = scale;
|
|
78
|
+
_width = width;
|
|
79
|
+
_height = height;
|
|
80
|
+
_baseKey = [NSString stringWithFormat:@"%g|%g|%@", scale, width, _address];
|
|
81
|
+
_writablePath = ANNWritablePathOf(kind, url);
|
|
82
|
+
}
|
|
83
|
+
return self;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
// Read at each ask: a file the app writes again at the same path is another image.
|
|
87
|
+
- (NSString *)key {
|
|
88
|
+
NSString *stamp = _writablePath ? ANNFileStampOf(_writablePath) : nil;
|
|
89
|
+
return stamp ? [NSString stringWithFormat:@"%@|%@", _baseKey, stamp] : _baseKey;
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
- (NSString *)sourceKey {
|
|
93
|
+
NSString *own = [NSString stringWithFormat:@"%ld|%@", (long)_kind, _baseKey];
|
|
94
|
+
return _fallback ? [NSString stringWithFormat:@"%@|%@", own, _fallback.sourceKey] : own;
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
- (BOOL)loadsLater {
|
|
98
|
+
return _kind == ANNImageSourceKindFile || _kind == ANNImageSourceKindData;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
@end
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
#import "ANNImageSource.h"
|
|
2
|
+
#import <UIKit/UIKit.h>
|
|
3
|
+
|
|
4
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
5
|
+
|
|
6
|
+
/// The images of the options, every one of them (bar buttons and their menus, the back button, tab icons, search
|
|
7
|
+
/// tokens and suggestions): one cache for the app, by the source's key (`NSCache`, its cost the image's bytes, let go
|
|
8
|
+
/// at a memory warning). The images don't depend on the JavaScript, so a reload keeps them.
|
|
9
|
+
///
|
|
10
|
+
/// Every decode runs on one serial queue of the loader: `imageOf:` waits for it, `loadLater:done:` doesn't. A failure
|
|
11
|
+
/// (a missing file, data that isn't an image or doesn't decode, an exception while decoding) never throws: no image,
|
|
12
|
+
/// and one warning per address in the log. It is not cached, as on Android: it answers for a moment (1 s, the reads of
|
|
13
|
+
/// one apply), then the next read tries again, so a file written later or Metro's server back shows.
|
|
14
|
+
@interface ANNOptionImages : NSObject
|
|
15
|
+
|
|
16
|
+
- (instancetype)init NS_UNAVAILABLE;
|
|
17
|
+
+ (instancetype)new NS_UNAVAILABLE;
|
|
18
|
+
|
|
19
|
+
/// The image of `source`: a symbol, what the cache has, or else loaded before this returns; nil on a failure. Any
|
|
20
|
+
/// thread.
|
|
21
|
+
+ (nullable UIImage *)imageOf:(ANNImageSource *)source;
|
|
22
|
+
|
|
23
|
+
/// The cache doesn't have the image of `source`, it didn't fail a moment ago, and it loads off the main queue
|
|
24
|
+
/// (`loadsLater` of the source): what `loadLater:done:` doesn't give at once. Any thread.
|
|
25
|
+
+ (BOOL)loadsLater:(ANNImageSource *)source;
|
|
26
|
+
|
|
27
|
+
/// Loads `source` on the loader's queue and gives `done` the image, or nil on a failure, on the main queue; one load
|
|
28
|
+
/// for every caller that asks for the same image meanwhile. What the cache has, or what doesn't load off the main
|
|
29
|
+
/// queue, comes at once, before this returns. Main queue.
|
|
30
|
+
+ (void)loadLater:(ANNImageSource *)source done:(void (^)(UIImage *_Nullable image))done;
|
|
31
|
+
|
|
32
|
+
/// Lets every image of the cache go. Any thread.
|
|
33
|
+
+ (void)clear;
|
|
34
|
+
|
|
35
|
+
@end
|
|
36
|
+
|
|
37
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,284 @@
|
|
|
1
|
+
#import "ANNOptionImages.h"
|
|
2
|
+
#import <React/RCTAssert.h>
|
|
3
|
+
#import <React/RCTLog.h>
|
|
4
|
+
#import <React/RCTUtils.h>
|
|
5
|
+
|
|
6
|
+
// The options' icons are small: 16 MB of decoded pixels holds hundreds of them.
|
|
7
|
+
static const NSUInteger ANNOptionImagesCostLimit = 16 * 1024 * 1024;
|
|
8
|
+
|
|
9
|
+
// The longest piece of an address a warning names (a `data:` address is the whole image).
|
|
10
|
+
static const NSUInteger ANNOptionImagesLoggedAddressLength = 80;
|
|
11
|
+
|
|
12
|
+
// How long a failure answers for its source without a new try: the many reads of one apply ask once, a later apply
|
|
13
|
+
// tries again (a file written since, Metro's server back).
|
|
14
|
+
static const NSTimeInterval ANNOptionImagesFailureMoment = 1;
|
|
15
|
+
|
|
16
|
+
static const void *ANNOptionImagesQueueKey = &ANNOptionImagesQueueKey;
|
|
17
|
+
|
|
18
|
+
static NSCache<NSString *, UIImage *> *ANNCache;
|
|
19
|
+
static dispatch_queue_t ANNQueue;
|
|
20
|
+
// The addresses already named in a warning: one each.
|
|
21
|
+
static NSMutableSet<NSString *> *ANNWarned;
|
|
22
|
+
// The keys whose last try failed, with the system's uptime then; guarded by itself. Failures never go into the cache.
|
|
23
|
+
static NSMutableDictionary<NSString *, NSNumber *> *ANNFailures;
|
|
24
|
+
// The loads off the main queue under way, with who waits for each. Main queue.
|
|
25
|
+
static NSMutableDictionary<NSString *, NSMutableArray *> *ANNLoading;
|
|
26
|
+
|
|
27
|
+
static void ANNSetUp(void) {
|
|
28
|
+
static dispatch_once_t once;
|
|
29
|
+
dispatch_once(&once, ^{
|
|
30
|
+
ANNCache = [NSCache new];
|
|
31
|
+
ANNCache.name = @"ApexNavigation option images";
|
|
32
|
+
ANNCache.totalCostLimit = ANNOptionImagesCostLimit;
|
|
33
|
+
dispatch_queue_attr_t attributes = dispatch_queue_attr_make_with_qos_class(
|
|
34
|
+
DISPATCH_QUEUE_SERIAL, QOS_CLASS_USER_INITIATED, 0);
|
|
35
|
+
ANNQueue = dispatch_queue_create("dev.apexnative.navigation.images", attributes);
|
|
36
|
+
dispatch_queue_set_specific(ANNQueue, ANNOptionImagesQueueKey,
|
|
37
|
+
(void *)ANNOptionImagesQueueKey, NULL);
|
|
38
|
+
ANNWarned = [NSMutableSet new];
|
|
39
|
+
ANNFailures = [NSMutableDictionary new];
|
|
40
|
+
ANNLoading = [NSMutableDictionary new];
|
|
41
|
+
NSNotificationCenter *center = NSNotificationCenter.defaultCenter;
|
|
42
|
+
[center addObserverForName:UIApplicationDidReceiveMemoryWarningNotification
|
|
43
|
+
object:nil
|
|
44
|
+
queue:nil
|
|
45
|
+
usingBlock:^(NSNotification *notification) {
|
|
46
|
+
[ANNCache removeAllObjects];
|
|
47
|
+
}];
|
|
48
|
+
});
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// The bytes of the decoded image, the cost the cache weighs it with.
|
|
52
|
+
static NSUInteger ANNBytesOf(UIImage *image) {
|
|
53
|
+
CGImageRef cgImage = image.CGImage;
|
|
54
|
+
if (cgImage) {
|
|
55
|
+
return CGImageGetBytesPerRow(cgImage) * CGImageGetHeight(cgImage);
|
|
56
|
+
}
|
|
57
|
+
CGFloat width = image.size.width * image.scale;
|
|
58
|
+
CGFloat height = image.size.height * image.scale;
|
|
59
|
+
return (NSUInteger)MAX(1, width * height * 4);
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
static void ANNWarnOnce(ANNImageSource *source, NSString *why) {
|
|
63
|
+
@synchronized(ANNWarned) {
|
|
64
|
+
if ([ANNWarned containsObject:source.address]) {
|
|
65
|
+
return;
|
|
66
|
+
}
|
|
67
|
+
[ANNWarned addObject:source.address];
|
|
68
|
+
}
|
|
69
|
+
NSString *address = source.address;
|
|
70
|
+
if (address.length > ANNOptionImagesLoggedAddressLength) {
|
|
71
|
+
address = [[address substringToIndex:ANNOptionImagesLoggedAddressLength]
|
|
72
|
+
stringByAppendingString:@"…"];
|
|
73
|
+
}
|
|
74
|
+
RCTLogWarn(@"[ApexNavigation] could not load the image %@: %@", address, why);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// The last try of `key` failed a moment ago.
|
|
78
|
+
static BOOL ANNFailedJustNow(NSString *key) {
|
|
79
|
+
@synchronized(ANNFailures) {
|
|
80
|
+
NSNumber *at = ANNFailures[key];
|
|
81
|
+
if (!at) {
|
|
82
|
+
return NO;
|
|
83
|
+
}
|
|
84
|
+
NSTimeInterval since = NSProcessInfo.processInfo.systemUptime - at.doubleValue;
|
|
85
|
+
if (since < ANNOptionImagesFailureMoment) {
|
|
86
|
+
return YES;
|
|
87
|
+
}
|
|
88
|
+
[ANNFailures removeObjectForKey:key];
|
|
89
|
+
return NO;
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
static void ANNSetFailed(NSString *key, BOOL failed) {
|
|
94
|
+
@synchronized(ANNFailures) {
|
|
95
|
+
if (failed) {
|
|
96
|
+
ANNFailures[key] = @(NSProcessInfo.processInfo.systemUptime);
|
|
97
|
+
} else {
|
|
98
|
+
[ANNFailures removeObjectForKey:key];
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
// A file at the address: an image with nothing but its pixels, which can be decoded ahead. An asset of a catalog
|
|
104
|
+
// (found by name) may have a dark variant, which a decoded copy would lose.
|
|
105
|
+
static BOOL ANNIsFileAt(NSURL *url) {
|
|
106
|
+
NSString *path = url.path;
|
|
107
|
+
if (path.length == 0) {
|
|
108
|
+
return NO;
|
|
109
|
+
}
|
|
110
|
+
NSFileManager *files = NSFileManager.defaultManager;
|
|
111
|
+
if ([files fileExistsAtPath:path]) {
|
|
112
|
+
return YES;
|
|
113
|
+
}
|
|
114
|
+
return path.pathExtension.length == 0 &&
|
|
115
|
+
[files fileExistsAtPath:[path stringByAppendingPathExtension:@"png"]];
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
@implementation ANNOptionImages
|
|
119
|
+
|
|
120
|
+
+ (UIImage *)imageOf:(ANNImageSource *)source {
|
|
121
|
+
if (source.kind == ANNImageSourceKindSymbol) {
|
|
122
|
+
UIImage *symbol = [UIImage systemImageNamed:source.symbolName];
|
|
123
|
+
return symbol ?: (source.fallback ? [self imageOf:source.fallback] : nil);
|
|
124
|
+
}
|
|
125
|
+
ANNSetUp();
|
|
126
|
+
NSString *key = source.key;
|
|
127
|
+
UIImage *cached = [ANNCache objectForKey:key];
|
|
128
|
+
if (cached || ANNFailedJustNow(key)) {
|
|
129
|
+
return cached;
|
|
130
|
+
}
|
|
131
|
+
if (dispatch_get_specific(ANNOptionImagesQueueKey)) {
|
|
132
|
+
return [self loadOnQueue:source];
|
|
133
|
+
}
|
|
134
|
+
__block UIImage *image = nil;
|
|
135
|
+
dispatch_sync(ANNQueue, ^{
|
|
136
|
+
image = [self loadOnQueue:source];
|
|
137
|
+
});
|
|
138
|
+
return image;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// A failure of a moment ago answers at once, so an item built again when its load failed doesn't load again.
|
|
142
|
+
+ (BOOL)loadsLater:(ANNImageSource *)source {
|
|
143
|
+
if (!source.loadsLater) {
|
|
144
|
+
return NO;
|
|
145
|
+
}
|
|
146
|
+
ANNSetUp();
|
|
147
|
+
NSString *key = source.key;
|
|
148
|
+
return [ANNCache objectForKey:key] == nil && !ANNFailedJustNow(key);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
+ (void)loadLater:(ANNImageSource *)source done:(void (^)(UIImage *))done {
|
|
152
|
+
RCTAssertMainQueue();
|
|
153
|
+
if (![self loadsLater:source]) {
|
|
154
|
+
done([self imageOf:source]);
|
|
155
|
+
return;
|
|
156
|
+
}
|
|
157
|
+
NSString *key = source.key;
|
|
158
|
+
NSMutableArray *waiters = ANNLoading[key];
|
|
159
|
+
if (waiters) {
|
|
160
|
+
[waiters addObject:[done copy]];
|
|
161
|
+
return;
|
|
162
|
+
}
|
|
163
|
+
ANNLoading[key] = [NSMutableArray arrayWithObject:[done copy]];
|
|
164
|
+
dispatch_async(ANNQueue, ^{
|
|
165
|
+
UIImage *image = [self loadOnQueue:source];
|
|
166
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
167
|
+
NSArray *waiting = ANNLoading[key];
|
|
168
|
+
[ANNLoading removeObjectForKey:key];
|
|
169
|
+
for (void (^waiter)(UIImage *) in waiting) {
|
|
170
|
+
waiter(image);
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
});
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
+ (void)clear {
|
|
177
|
+
ANNSetUp();
|
|
178
|
+
[ANNCache removeAllObjects];
|
|
179
|
+
@synchronized(ANNFailures) {
|
|
180
|
+
[ANNFailures removeAllObjects];
|
|
181
|
+
}
|
|
182
|
+
}
|
|
183
|
+
|
|
184
|
+
#pragma mark - The loader's queue
|
|
185
|
+
|
|
186
|
+
// The cache again (a load that came first), else the decode. An image goes into the cache; a failure doesn't, as on
|
|
187
|
+
// Android: it answers only for a moment (`ANNFailedJustNow`), and the next apply after that tries again.
|
|
188
|
+
+ (UIImage *)loadOnQueue:(ANNImageSource *)source {
|
|
189
|
+
NSString *key = source.key;
|
|
190
|
+
UIImage *cached = [ANNCache objectForKey:key];
|
|
191
|
+
if (cached || ANNFailedJustNow(key)) {
|
|
192
|
+
return cached;
|
|
193
|
+
}
|
|
194
|
+
NSString *failure = nil;
|
|
195
|
+
UIImage *image = nil;
|
|
196
|
+
@try {
|
|
197
|
+
image = [self decode:source failure:&failure];
|
|
198
|
+
} @catch (NSException *exception) {
|
|
199
|
+
image = nil;
|
|
200
|
+
failure = [NSString
|
|
201
|
+
stringWithFormat:@"decoding it threw %@ (%@)", exception.name, exception.reason];
|
|
202
|
+
}
|
|
203
|
+
if (!image) {
|
|
204
|
+
ANNSetFailed(key, YES);
|
|
205
|
+
ANNWarnOnce(source, failure ?: @"it can't be decoded");
|
|
206
|
+
return nil;
|
|
207
|
+
}
|
|
208
|
+
ANNSetFailed(key, NO);
|
|
209
|
+
[ANNCache setObject:image forKey:key cost:ANNBytesOf(image)];
|
|
210
|
+
return image;
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
// The image of an address, as React Native's converter of images reads it, with the scale of the source; nil with
|
|
214
|
+
// what failed. A plain file and data are decoded here, so the first frame that shows them doesn't.
|
|
215
|
+
+ (UIImage *)decode:(ANNImageSource *)source failure:(NSString **)failure {
|
|
216
|
+
NSURL *url = source.url;
|
|
217
|
+
UIImage *image = nil;
|
|
218
|
+
BOOL decodesAhead = YES;
|
|
219
|
+
switch (source.kind) {
|
|
220
|
+
case ANNImageSourceKindFile:
|
|
221
|
+
image = RCTImageFromLocalAssetURL(url) ?: RCTImageFromLocalBundleAssetURL(url);
|
|
222
|
+
if (!image) {
|
|
223
|
+
*failure = @"there is no image at that address";
|
|
224
|
+
return nil;
|
|
225
|
+
}
|
|
226
|
+
decodesAhead = ANNIsFileAt(url);
|
|
227
|
+
break;
|
|
228
|
+
case ANNImageSourceKindData:
|
|
229
|
+
case ANNImageSourceKindMetro: {
|
|
230
|
+
// Metro's assets in development: read from the dev server, as before the cache.
|
|
231
|
+
NSError *error = nil;
|
|
232
|
+
NSData *data = url ? [NSData dataWithContentsOfURL:url options:0 error:&error] : nil;
|
|
233
|
+
if (data.length == 0) {
|
|
234
|
+
*failure = error.localizedDescription ?: @"it has no data";
|
|
235
|
+
return nil;
|
|
236
|
+
}
|
|
237
|
+
image = [UIImage imageWithData:data];
|
|
238
|
+
if (!image) {
|
|
239
|
+
*failure = @"its data is not an image";
|
|
240
|
+
return nil;
|
|
241
|
+
}
|
|
242
|
+
break;
|
|
243
|
+
}
|
|
244
|
+
case ANNImageSourceKindSymbol:
|
|
245
|
+
return nil;
|
|
246
|
+
}
|
|
247
|
+
// An image UIKit can't prepare (16 bits a channel, CMYK) it still draws: it stays as it is then, decoded when it
|
|
248
|
+
// first shows.
|
|
249
|
+
UIImage *decoded = decodesAhead ? [image imageByPreparingForDisplay] : nil;
|
|
250
|
+
if (decoded) {
|
|
251
|
+
image = decoded.CGImage ? [UIImage imageWithCGImage:decoded.CGImage
|
|
252
|
+
scale:image.scale
|
|
253
|
+
orientation:image.imageOrientation]
|
|
254
|
+
: decoded;
|
|
255
|
+
}
|
|
256
|
+
if (!(image.size.width > 0) || !(image.size.height > 0)) {
|
|
257
|
+
*failure = @"it is empty";
|
|
258
|
+
return nil;
|
|
259
|
+
}
|
|
260
|
+
return [self image:image scaledBy:source];
|
|
261
|
+
}
|
|
262
|
+
|
|
263
|
+
// The scale of the source; without it the pixel width over `width`; without both, the file's own. A size that
|
|
264
|
+
// doesn't match is only logged.
|
|
265
|
+
+ (UIImage *)image:(UIImage *)image scaledBy:(ANNImageSource *)source {
|
|
266
|
+
CGFloat scale = source.scale;
|
|
267
|
+
CGFloat width = source.width;
|
|
268
|
+
CGFloat height = source.height;
|
|
269
|
+
if (scale <= 0 && width > 0 && image.CGImage) {
|
|
270
|
+
scale = CGImageGetWidth(image.CGImage) / width;
|
|
271
|
+
}
|
|
272
|
+
if (scale > 0 && image.CGImage && scale != image.scale) {
|
|
273
|
+
image = [UIImage imageWithCGImage:image.CGImage
|
|
274
|
+
scale:scale
|
|
275
|
+
orientation:image.imageOrientation];
|
|
276
|
+
}
|
|
277
|
+
if (width > 0 && height > 0 && !CGSizeEqualToSize(image.size, CGSizeMake(width, height))) {
|
|
278
|
+
RCTLogInfo(@"[ApexNavigation] an image of the options is %@, its source says %@",
|
|
279
|
+
NSStringFromCGSize(image.size), NSStringFromCGSize(CGSizeMake(width, height)));
|
|
280
|
+
}
|
|
281
|
+
return image;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
@end
|
|
@@ -1,15 +1,24 @@
|
|
|
1
|
+
#import "ANNImageSource.h"
|
|
1
2
|
#import "Param.h"
|
|
2
3
|
#import <UIKit/UIKit.h>
|
|
3
4
|
|
|
4
5
|
NS_ASSUME_NONNULL_BEGIN
|
|
5
6
|
|
|
6
|
-
/// An image of the options,
|
|
7
|
-
/// without it.
|
|
7
|
+
/// An image of the options: its source, read with the options, and the image, loaded when an element asks for it
|
|
8
|
+
/// through the shared cache (`ANNOptionImages`). A source that doesn't load is no image: the element shows without it.
|
|
9
|
+
/// A value that is a `UIImage` is that image, with no source: an element made again from an image that came already
|
|
10
|
+
/// (a tab's icon loaded off the main queue) doesn't ask the cache again.
|
|
8
11
|
@interface Image : Param
|
|
9
12
|
|
|
13
|
+
/// The image, loaded at once when the cache doesn't have it; nil without a source and when it doesn't load.
|
|
10
14
|
- (nullable UIImage *)get;
|
|
15
|
+
/// The image, or `defaultValue` without a source and when it doesn't load. `hasValue` says only that the options name
|
|
16
|
+
/// an image: a choice between an image and what shows without one reads the image itself.
|
|
11
17
|
- (nullable UIImage *)withDefault:(nullable UIImage *)defaultValue;
|
|
12
18
|
|
|
19
|
+
/// What the options name; nil without a value.
|
|
20
|
+
@property(nonatomic, strong, readonly, nullable) ANNImageSource *source;
|
|
21
|
+
|
|
13
22
|
@end
|
|
14
23
|
|
|
15
24
|
NS_ASSUME_NONNULL_END
|
|
@@ -1,13 +1,26 @@
|
|
|
1
1
|
#import "Image.h"
|
|
2
|
+
#import "ANNOptionImages.h"
|
|
2
3
|
|
|
3
4
|
@implementation Image
|
|
4
5
|
|
|
6
|
+
- (ANNImageSource *)source {
|
|
7
|
+
id value = self.value;
|
|
8
|
+
return [value isKindOfClass:ANNImageSource.class] ? value : nil;
|
|
9
|
+
}
|
|
10
|
+
|
|
5
11
|
- (UIImage *)get {
|
|
6
|
-
|
|
12
|
+
id value = self.value;
|
|
13
|
+
if ([value isKindOfClass:UIImage.class]) {
|
|
14
|
+
return value;
|
|
15
|
+
}
|
|
16
|
+
ANNImageSource *source = self.source;
|
|
17
|
+
return source ? [ANNOptionImages imageOf:source] : nil;
|
|
7
18
|
}
|
|
8
19
|
|
|
20
|
+
// A source that doesn't load is no image: the default then, as without a value.
|
|
9
21
|
- (UIImage *)withDefault:(UIImage *)defaultValue {
|
|
10
|
-
|
|
22
|
+
UIImage *image = self.hasValue ? self.get : nil;
|
|
23
|
+
return image ?: defaultValue;
|
|
11
24
|
}
|
|
12
25
|
|
|
13
26
|
@end
|
|
@@ -2,11 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
NS_ASSUME_NONNULL_BEGIN
|
|
4
4
|
|
|
5
|
-
/// Reads `key` of the options' JSON as
|
|
6
|
-
/// SF Symbol, else the fallback source; a source (a string, or an object with `uri`,
|
|
7
|
-
/// `height` and Metro's asset flag)
|
|
8
|
-
/// Metro asset. Remote addresses and arrays of sources are
|
|
9
|
-
/// Native log; it never throws.
|
|
5
|
+
/// Reads `key` of the options' JSON as the source of an image, which loads when an element shows it (`Image`):
|
|
6
|
+
/// `{ system, fallback? }` is an SF Symbol, else the fallback source; a source (a string, or an object with `uri`,
|
|
7
|
+
/// `bundle`, `scale`, `width`, `height` and Metro's asset flag) is a file, an asset of the app's bundle, a `data:`
|
|
8
|
+
/// address, or in development a Metro asset. Remote addresses and arrays of sources are no image, with an error in the
|
|
9
|
+
/// React Native log; it never throws.
|
|
10
10
|
@interface ImageParser : NSObject
|
|
11
11
|
|
|
12
12
|
+ (Image *)parse:(nullable NSDictionary *)json key:(NSString *)key;
|