@granite-js/image 0.1.34 → 1.0.0
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 +3 -277
- package/GraniteImage.podspec +72 -0
- package/android/build.gradle +178 -0
- package/android/gradle.properties +5 -0
- package/android/src/coil/java/run/granite/image/providers/CoilImageProvider.kt +156 -0
- package/android/src/glide/java/run/granite/image/providers/GlideImageProvider.kt +168 -0
- package/android/src/main/AndroidManifest.xml +2 -0
- package/android/src/main/java/run/granite/image/GraniteImage.kt +277 -0
- package/android/src/main/java/run/granite/image/GraniteImageEvents.kt +83 -0
- package/android/src/main/java/run/granite/image/GraniteImageManager.kt +100 -0
- package/android/src/main/java/run/granite/image/GraniteImageModule.kt +131 -0
- package/android/src/main/java/run/granite/image/GraniteImagePackage.kt +59 -0
- package/android/src/main/java/run/granite/image/GraniteImageProvider.kt +105 -0
- package/android/src/main/java/run/granite/image/GraniteImageRegistry.kt +29 -0
- package/android/src/okhttp/java/run/granite/image/providers/OkHttpImageProvider.kt +228 -0
- package/dist/module/GraniteImage.js +127 -0
- package/dist/module/GraniteImage.js.map +1 -0
- package/dist/module/GraniteImageNativeComponent.ts +56 -0
- package/dist/module/NativeGraniteImageModule.js +5 -0
- package/dist/module/NativeGraniteImageModule.js.map +1 -0
- package/dist/module/index.js +6 -0
- package/dist/module/index.js.map +1 -0
- package/dist/module/package.json +1 -0
- package/dist/typescript/GraniteImage.d.ts +35 -0
- package/dist/typescript/GraniteImageNativeComponent.d.ts +37 -0
- package/dist/typescript/NativeGraniteImageModule.d.ts +16 -0
- package/dist/typescript/index.d.ts +4 -0
- package/example/react-native.config.js +21 -0
- package/ios/GraniteImageComponentView.h +14 -0
- package/ios/GraniteImageComponentView.mm +388 -0
- package/ios/GraniteImageModule.swift +107 -0
- package/ios/GraniteImageModuleBridge.m +15 -0
- package/ios/GraniteImageProvider.swift +70 -0
- package/ios/GraniteImageRegistry.swift +30 -0
- package/ios/Providers/SDWebImageProvider.swift +175 -0
- package/package.json +71 -32
- package/src/GraniteImage.tsx +215 -0
- package/src/GraniteImageNativeComponent.ts +56 -0
- package/src/NativeGraniteImageModule.ts +16 -0
- package/src/index.ts +21 -0
- package/dist/index.d.mts +0 -70
- package/dist/index.d.ts +0 -70
- package/dist/index.js +0 -204
- package/dist/index.mjs +0 -180
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import { type ViewProps, type ColorValue, codegenNativeComponent } from 'react-native';
|
|
2
|
+
import type { WithDefault, Int32, DirectEventHandler } from 'react-native/Libraries/Types/CodegenTypes';
|
|
3
|
+
|
|
4
|
+
// Event payload types
|
|
5
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
6
|
+
export type OnLoadStartEvent = Readonly<{}>;
|
|
7
|
+
|
|
8
|
+
export type OnProgressEvent = Readonly<{
|
|
9
|
+
loaded: Int32;
|
|
10
|
+
total: Int32;
|
|
11
|
+
}>;
|
|
12
|
+
|
|
13
|
+
export type OnLoadEvent = Readonly<{
|
|
14
|
+
width: Int32;
|
|
15
|
+
height: Int32;
|
|
16
|
+
}>;
|
|
17
|
+
|
|
18
|
+
export type OnErrorEvent = Readonly<{
|
|
19
|
+
error: string;
|
|
20
|
+
}>;
|
|
21
|
+
|
|
22
|
+
// eslint-disable-next-line @typescript-eslint/no-empty-object-type
|
|
23
|
+
export type OnLoadEndEvent = Readonly<{}>;
|
|
24
|
+
|
|
25
|
+
export type OnGraniteLoadStartEvent = DirectEventHandler<OnLoadStartEvent>;
|
|
26
|
+
export type OnGraniteProgressEvent = DirectEventHandler<OnProgressEvent>;
|
|
27
|
+
export type OnGraniteLoadEvent = DirectEventHandler<OnLoadEvent>;
|
|
28
|
+
export type OnGraniteErrorEvent = DirectEventHandler<OnErrorEvent>;
|
|
29
|
+
export type OnGraniteLoadEndEvent = DirectEventHandler<OnLoadEndEvent>;
|
|
30
|
+
|
|
31
|
+
interface GraniteImageProps extends ViewProps {
|
|
32
|
+
// Source
|
|
33
|
+
uri?: string;
|
|
34
|
+
headers?: string; // JSON string of headers object
|
|
35
|
+
|
|
36
|
+
// Display
|
|
37
|
+
contentMode?: WithDefault<'cover' | 'contain' | 'stretch' | 'center', 'cover'>;
|
|
38
|
+
tintColor?: ColorValue;
|
|
39
|
+
|
|
40
|
+
// Placeholder
|
|
41
|
+
defaultSource?: string; // Local asset name or URI
|
|
42
|
+
fallbackSource?: string; // Fallback image to show on error
|
|
43
|
+
|
|
44
|
+
// Priority & Cache
|
|
45
|
+
priority?: WithDefault<'low' | 'normal' | 'high', 'normal'>;
|
|
46
|
+
cachePolicy?: WithDefault<'memory' | 'disk' | 'none', 'disk'>;
|
|
47
|
+
|
|
48
|
+
// Callbacks
|
|
49
|
+
onGraniteLoadStart?: DirectEventHandler<OnLoadStartEvent>;
|
|
50
|
+
onGraniteProgress?: DirectEventHandler<OnProgressEvent>;
|
|
51
|
+
onGraniteLoad?: DirectEventHandler<OnLoadEvent>;
|
|
52
|
+
onGraniteError?: DirectEventHandler<OnErrorEvent>;
|
|
53
|
+
onGraniteLoadEnd?: DirectEventHandler<OnLoadEndEvent>;
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
export default codegenNativeComponent<GraniteImageProps>('GraniteImage');
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["TurboModuleRegistry","getEnforcing"],"sourceRoot":"../../src","sources":["NativeGraniteImageModule.ts"],"mappings":";;AAAA,SAA2BA,mBAAmB,QAAQ,cAAc;AAepE,eAAeA,mBAAmB,CAACC,YAAY,CAAO,oBAAoB,CAAC","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"names":["GraniteImage"],"sourceRoot":"../../src","sources":["index.ts"],"mappings":";;AAAA,OAAOA,YAAY,MAAM,mBAAgB;AAEzC,SACEA,YAAY,QAOP,mBAAgB;AAUvB,eAAeA,YAAY","ignoreList":[]}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"type":"module"}
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
import React from 'react';
|
|
2
|
+
import { type StyleProp, type ViewStyle, type ColorValue, type ImageRequireSource, ViewProps, NativeSyntheticEvent } from 'react-native';
|
|
3
|
+
import { type OnLoadStartEvent, type OnProgressEvent, type OnLoadEvent, type OnErrorEvent, type OnLoadEndEvent } from './GraniteImageNativeComponent';
|
|
4
|
+
export interface GraniteImageSource {
|
|
5
|
+
uri: string;
|
|
6
|
+
headers?: Record<string, string>;
|
|
7
|
+
priority?: 'low' | 'normal' | 'high';
|
|
8
|
+
cache?: 'immutable' | 'web' | 'cacheOnly';
|
|
9
|
+
}
|
|
10
|
+
export type ResizeMode = 'cover' | 'contain' | 'stretch' | 'center';
|
|
11
|
+
export type CachePolicy = 'memory' | 'disk' | 'none';
|
|
12
|
+
export type Priority = 'low' | 'normal' | 'high';
|
|
13
|
+
export interface GraniteImageProps extends ViewProps {
|
|
14
|
+
source: GraniteImageSource | string;
|
|
15
|
+
resizeMode?: ResizeMode;
|
|
16
|
+
tintColor?: ColorValue;
|
|
17
|
+
style?: StyleProp<ViewStyle>;
|
|
18
|
+
defaultSource?: ImageRequireSource | string;
|
|
19
|
+
fallbackSource?: ImageRequireSource | string;
|
|
20
|
+
priority?: Priority;
|
|
21
|
+
cachePolicy?: CachePolicy;
|
|
22
|
+
onLoadStart?: (event: NativeSyntheticEvent<OnLoadStartEvent>) => void;
|
|
23
|
+
onProgress?: (event: NativeSyntheticEvent<OnProgressEvent>) => void;
|
|
24
|
+
onLoad?: (event: NativeSyntheticEvent<OnLoadEvent>) => void;
|
|
25
|
+
onError?: (error: NativeSyntheticEvent<OnErrorEvent>) => void;
|
|
26
|
+
onLoadEnd?: (event: NativeSyntheticEvent<OnLoadEndEvent>) => void;
|
|
27
|
+
}
|
|
28
|
+
export interface GraniteImageStatic {
|
|
29
|
+
clearMemoryCache: () => Promise<void>;
|
|
30
|
+
clearDiskCache: () => Promise<void>;
|
|
31
|
+
preload: (sources: GraniteImageSource[]) => Promise<void>;
|
|
32
|
+
}
|
|
33
|
+
type GraniteImageComponent = React.FC<GraniteImageProps> & GraniteImageStatic;
|
|
34
|
+
export declare const GraniteImage: GraniteImageComponent;
|
|
35
|
+
export default GraniteImage;
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import { type ViewProps, type ColorValue } from 'react-native';
|
|
2
|
+
import type { WithDefault, Int32, DirectEventHandler } from 'react-native/Libraries/Types/CodegenTypes';
|
|
3
|
+
export type OnLoadStartEvent = Readonly<{}>;
|
|
4
|
+
export type OnProgressEvent = Readonly<{
|
|
5
|
+
loaded: Int32;
|
|
6
|
+
total: Int32;
|
|
7
|
+
}>;
|
|
8
|
+
export type OnLoadEvent = Readonly<{
|
|
9
|
+
width: Int32;
|
|
10
|
+
height: Int32;
|
|
11
|
+
}>;
|
|
12
|
+
export type OnErrorEvent = Readonly<{
|
|
13
|
+
error: string;
|
|
14
|
+
}>;
|
|
15
|
+
export type OnLoadEndEvent = Readonly<{}>;
|
|
16
|
+
export type OnGraniteLoadStartEvent = DirectEventHandler<OnLoadStartEvent>;
|
|
17
|
+
export type OnGraniteProgressEvent = DirectEventHandler<OnProgressEvent>;
|
|
18
|
+
export type OnGraniteLoadEvent = DirectEventHandler<OnLoadEvent>;
|
|
19
|
+
export type OnGraniteErrorEvent = DirectEventHandler<OnErrorEvent>;
|
|
20
|
+
export type OnGraniteLoadEndEvent = DirectEventHandler<OnLoadEndEvent>;
|
|
21
|
+
interface GraniteImageProps extends ViewProps {
|
|
22
|
+
uri?: string;
|
|
23
|
+
headers?: string;
|
|
24
|
+
contentMode?: WithDefault<'cover' | 'contain' | 'stretch' | 'center', 'cover'>;
|
|
25
|
+
tintColor?: ColorValue;
|
|
26
|
+
defaultSource?: string;
|
|
27
|
+
fallbackSource?: string;
|
|
28
|
+
priority?: WithDefault<'low' | 'normal' | 'high', 'normal'>;
|
|
29
|
+
cachePolicy?: WithDefault<'memory' | 'disk' | 'none', 'disk'>;
|
|
30
|
+
onGraniteLoadStart?: DirectEventHandler<OnLoadStartEvent>;
|
|
31
|
+
onGraniteProgress?: DirectEventHandler<OnProgressEvent>;
|
|
32
|
+
onGraniteLoad?: DirectEventHandler<OnLoadEvent>;
|
|
33
|
+
onGraniteError?: DirectEventHandler<OnErrorEvent>;
|
|
34
|
+
onGraniteLoadEnd?: DirectEventHandler<OnLoadEndEvent>;
|
|
35
|
+
}
|
|
36
|
+
declare const _default: import("react-native").HostComponent<GraniteImageProps>;
|
|
37
|
+
export default _default;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { type TurboModule } from 'react-native';
|
|
2
|
+
export interface ImageSource {
|
|
3
|
+
uri: string;
|
|
4
|
+
headers?: {
|
|
5
|
+
[key: string]: string;
|
|
6
|
+
};
|
|
7
|
+
priority?: string;
|
|
8
|
+
cache?: string;
|
|
9
|
+
}
|
|
10
|
+
export interface Spec extends TurboModule {
|
|
11
|
+
preload(sources: string): Promise<void>;
|
|
12
|
+
clearMemoryCache(): Promise<void>;
|
|
13
|
+
clearDiskCache(): Promise<void>;
|
|
14
|
+
}
|
|
15
|
+
declare const _default: Spec;
|
|
16
|
+
export default _default;
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
import GraniteImage from './GraniteImage';
|
|
2
|
+
export { GraniteImage, type GraniteImageProps, type GraniteImageSource, type GraniteImageStatic, type Priority, type ResizeMode, type CachePolicy, } from './GraniteImage';
|
|
3
|
+
export { type OnLoadEvent, type OnProgressEvent, type OnLoadEndEvent, type OnErrorEvent, type OnLoadStartEvent, } from './GraniteImageNativeComponent';
|
|
4
|
+
export default GraniteImage;
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
const path = require('path');
|
|
2
|
+
const pkg = require('../package.json');
|
|
3
|
+
|
|
4
|
+
module.exports = {
|
|
5
|
+
project: {
|
|
6
|
+
ios: {
|
|
7
|
+
automaticPodsInstallation: true,
|
|
8
|
+
},
|
|
9
|
+
},
|
|
10
|
+
dependencies: {
|
|
11
|
+
[pkg.name]: {
|
|
12
|
+
root: path.join(__dirname, '..'),
|
|
13
|
+
platforms: {
|
|
14
|
+
// Codegen script incorrectly fails without this
|
|
15
|
+
// So we explicitly specify the platforms with empty object
|
|
16
|
+
ios: {},
|
|
17
|
+
android: {},
|
|
18
|
+
},
|
|
19
|
+
},
|
|
20
|
+
},
|
|
21
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
//
|
|
2
|
+
// GraniteImageComponentView.h
|
|
3
|
+
// GraniteImagePOC
|
|
4
|
+
//
|
|
5
|
+
|
|
6
|
+
#import <UIKit/UIKit.h>
|
|
7
|
+
|
|
8
|
+
NS_ASSUME_NONNULL_BEGIN
|
|
9
|
+
|
|
10
|
+
// Forward declare to avoid pulling in C++ headers
|
|
11
|
+
@interface GraniteImageComponentView : UIView
|
|
12
|
+
@end
|
|
13
|
+
|
|
14
|
+
NS_ASSUME_NONNULL_END
|
|
@@ -0,0 +1,388 @@
|
|
|
1
|
+
#import <UIKit/UIKit.h>
|
|
2
|
+
#import <os/log.h>
|
|
3
|
+
|
|
4
|
+
#import <React/RCTViewComponentView.h>
|
|
5
|
+
#import <React/RCTConversions.h>
|
|
6
|
+
#import <react/renderer/components/GraniteImageSpec/ComponentDescriptors.h>
|
|
7
|
+
#import <react/renderer/components/GraniteImageSpec/EventEmitters.h>
|
|
8
|
+
#import <react/renderer/components/GraniteImageSpec/Props.h>
|
|
9
|
+
#import <react/renderer/components/GraniteImageSpec/RCTComponentViewHelpers.h>
|
|
10
|
+
|
|
11
|
+
// Import Swift module - the header is generated during build
|
|
12
|
+
// For CocoaPods: GraniteImage-Swift.h (based on pod name)
|
|
13
|
+
// For frameworks: <GraniteImage/GraniteImage-Swift.h>
|
|
14
|
+
#if __has_include(<GraniteImage/GraniteImage-Swift.h>)
|
|
15
|
+
#import <GraniteImage/GraniteImage-Swift.h>
|
|
16
|
+
#elif __has_include(<react_native_granite_image/react_native_granite_image-Swift.h>)
|
|
17
|
+
#import <react_native_granite_image/react_native_granite_image-Swift.h>
|
|
18
|
+
#else
|
|
19
|
+
// This will be found in the build directory - CocoaPods generates it there
|
|
20
|
+
#import "GraniteImage-Swift.h"
|
|
21
|
+
#endif
|
|
22
|
+
|
|
23
|
+
using namespace facebook::react;
|
|
24
|
+
|
|
25
|
+
static os_log_t graniteimage_log() {
|
|
26
|
+
static os_log_t log = os_log_create("com.graniteimage", "GraniteImage");
|
|
27
|
+
return log;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
@interface GraniteImageComponentView : RCTViewComponentView <RCTGraniteImageViewProtocol>
|
|
31
|
+
@end
|
|
32
|
+
|
|
33
|
+
// Constructor to verify the code is being loaded
|
|
34
|
+
__attribute__((constructor)) static void _graniteimage_constructor(void) {
|
|
35
|
+
os_log_error(graniteimage_log(), "Constructor called - class is being loaded");
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
// Force the linker to include this class
|
|
39
|
+
__attribute__((used)) static void _forceIncludeGraniteImageComponentView(void) {
|
|
40
|
+
[GraniteImageComponentView class];
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
@implementation GraniteImageComponentView {
|
|
44
|
+
UIView *_containerView;
|
|
45
|
+
NSString *_currentUri;
|
|
46
|
+
UIViewContentMode _currentContentMode;
|
|
47
|
+
BOOL _needsInitialLoad;
|
|
48
|
+
|
|
49
|
+
// New props
|
|
50
|
+
NSDictionary<NSString *, NSString *> *_currentHeaders;
|
|
51
|
+
GraniteProviderPriority _currentPriority;
|
|
52
|
+
GraniteProviderCachePolicy _currentCachePolicy;
|
|
53
|
+
UIColor *_currentTintColor;
|
|
54
|
+
NSString *_currentDefaultSource;
|
|
55
|
+
NSString *_currentFallbackSource;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
+ (void)load
|
|
59
|
+
{
|
|
60
|
+
NSLog(@"[GraniteImage] +load called - registering component");
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
+ (ComponentDescriptorProvider)componentDescriptorProvider
|
|
64
|
+
{
|
|
65
|
+
return concreteComponentDescriptorProvider<GraniteImageComponentDescriptor>();
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
- (instancetype)initWithFrame:(CGRect)frame
|
|
69
|
+
{
|
|
70
|
+
if (self = [super initWithFrame:frame]) {
|
|
71
|
+
static const auto defaultProps = std::make_shared<const GraniteImageProps>();
|
|
72
|
+
_props = defaultProps;
|
|
73
|
+
self.clipsToBounds = YES;
|
|
74
|
+
_currentContentMode = UIViewContentModeScaleAspectFill;
|
|
75
|
+
_currentPriority = GraniteProviderPriorityNormal;
|
|
76
|
+
_currentCachePolicy = GraniteProviderCachePolicyDisk;
|
|
77
|
+
_needsInitialLoad = YES;
|
|
78
|
+
}
|
|
79
|
+
return self;
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
- (void)updateProps:(const Props::Shared &)props oldProps:(const Props::Shared &)oldProps
|
|
83
|
+
{
|
|
84
|
+
const auto &newViewProps = *std::static_pointer_cast<const GraniteImageProps>(props);
|
|
85
|
+
|
|
86
|
+
// Handle uri changes
|
|
87
|
+
NSString *newUri = [NSString stringWithUTF8String:newViewProps.uri.c_str()];
|
|
88
|
+
|
|
89
|
+
// Handle headers (JSON string)
|
|
90
|
+
NSDictionary<NSString *, NSString *> *newHeaders = nil;
|
|
91
|
+
if (!newViewProps.headers.empty()) {
|
|
92
|
+
NSString *headersJson = [NSString stringWithUTF8String:newViewProps.headers.c_str()];
|
|
93
|
+
NSData *data = [headersJson dataUsingEncoding:NSUTF8StringEncoding];
|
|
94
|
+
if (data) {
|
|
95
|
+
newHeaders = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
// Handle contentMode changes
|
|
100
|
+
UIViewContentMode newContentMode = UIViewContentModeScaleAspectFill;
|
|
101
|
+
switch (newViewProps.contentMode) {
|
|
102
|
+
case GraniteImageContentMode::Cover:
|
|
103
|
+
newContentMode = UIViewContentModeScaleAspectFill;
|
|
104
|
+
break;
|
|
105
|
+
case GraniteImageContentMode::Contain:
|
|
106
|
+
newContentMode = UIViewContentModeScaleAspectFit;
|
|
107
|
+
break;
|
|
108
|
+
case GraniteImageContentMode::Stretch:
|
|
109
|
+
newContentMode = UIViewContentModeScaleToFill;
|
|
110
|
+
break;
|
|
111
|
+
case GraniteImageContentMode::Center:
|
|
112
|
+
newContentMode = UIViewContentModeCenter;
|
|
113
|
+
break;
|
|
114
|
+
}
|
|
115
|
+
|
|
116
|
+
// Handle priority
|
|
117
|
+
GraniteProviderPriority newPriority = GraniteProviderPriorityNormal;
|
|
118
|
+
switch (newViewProps.priority) {
|
|
119
|
+
case GraniteImagePriority::Low:
|
|
120
|
+
newPriority = GraniteProviderPriorityLow;
|
|
121
|
+
break;
|
|
122
|
+
case GraniteImagePriority::Normal:
|
|
123
|
+
newPriority = GraniteProviderPriorityNormal;
|
|
124
|
+
break;
|
|
125
|
+
case GraniteImagePriority::High:
|
|
126
|
+
newPriority = GraniteProviderPriorityHigh;
|
|
127
|
+
break;
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
// Handle cache policy
|
|
131
|
+
GraniteProviderCachePolicy newCachePolicy = GraniteProviderCachePolicyDisk;
|
|
132
|
+
switch (newViewProps.cachePolicy) {
|
|
133
|
+
case GraniteImageCachePolicy::Memory:
|
|
134
|
+
newCachePolicy = GraniteProviderCachePolicyMemory;
|
|
135
|
+
break;
|
|
136
|
+
case GraniteImageCachePolicy::Disk:
|
|
137
|
+
newCachePolicy = GraniteProviderCachePolicyDisk;
|
|
138
|
+
break;
|
|
139
|
+
case GraniteImageCachePolicy::None:
|
|
140
|
+
newCachePolicy = GraniteProviderCachePolicyNone;
|
|
141
|
+
break;
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
// Handle tintColor
|
|
145
|
+
UIColor *newTintColor = nil;
|
|
146
|
+
if (newViewProps.tintColor) {
|
|
147
|
+
newTintColor = RCTUIColorFromSharedColor(newViewProps.tintColor);
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
// Handle defaultSource
|
|
151
|
+
NSString *newDefaultSource = nil;
|
|
152
|
+
if (!newViewProps.defaultSource.empty()) {
|
|
153
|
+
newDefaultSource = [NSString stringWithUTF8String:newViewProps.defaultSource.c_str()];
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
// Handle fallbackSource
|
|
157
|
+
NSString *newFallbackSource = nil;
|
|
158
|
+
if (!newViewProps.fallbackSource.empty()) {
|
|
159
|
+
newFallbackSource = [NSString stringWithUTF8String:newViewProps.fallbackSource.c_str()];
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
BOOL shouldReload = _needsInitialLoad;
|
|
163
|
+
_needsInitialLoad = NO;
|
|
164
|
+
|
|
165
|
+
// Only URI change triggers image reload
|
|
166
|
+
if (![newUri isEqualToString:_currentUri ?: @""]) {
|
|
167
|
+
_currentUri = newUri;
|
|
168
|
+
shouldReload = YES;
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// ContentMode: update in-place without reloading
|
|
172
|
+
if (newContentMode != _currentContentMode) {
|
|
173
|
+
_currentContentMode = newContentMode;
|
|
174
|
+
if (_containerView && [_containerView isKindOfClass:[UIImageView class]]) {
|
|
175
|
+
((UIImageView *)_containerView).contentMode = newContentMode;
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
// TintColor: update in-place without reloading
|
|
180
|
+
BOOL tintColorChanged = (newTintColor != _currentTintColor) &&
|
|
181
|
+
(newTintColor == nil || ![newTintColor isEqual:_currentTintColor]);
|
|
182
|
+
if (tintColorChanged) {
|
|
183
|
+
_currentTintColor = newTintColor;
|
|
184
|
+
if (_containerView) {
|
|
185
|
+
id<GraniteImageProvidable> provider = [[GraniteImageRegistry shared] provider];
|
|
186
|
+
if (provider && [(NSObject *)provider respondsToSelector:@selector(applyTintColor:to:)]) {
|
|
187
|
+
[provider applyTintColor:newTintColor to:_containerView];
|
|
188
|
+
}
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
// Update stored values (these don't require reload, they affect next load)
|
|
193
|
+
_currentHeaders = newHeaders;
|
|
194
|
+
_currentPriority = newPriority;
|
|
195
|
+
_currentCachePolicy = newCachePolicy;
|
|
196
|
+
_currentDefaultSource = newDefaultSource;
|
|
197
|
+
_currentFallbackSource = newFallbackSource;
|
|
198
|
+
|
|
199
|
+
if (shouldReload) {
|
|
200
|
+
if (_currentUri.length > 0) {
|
|
201
|
+
[self loadImageWithProvider];
|
|
202
|
+
} else {
|
|
203
|
+
// URI가 비어있거나 nil인 경우 에러 발생
|
|
204
|
+
[self showErrorViewWithMessage:@"No URI provided"];
|
|
205
|
+
[self emitOnError:@"No URI provided"];
|
|
206
|
+
[self emitOnLoadEnd];
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
[super updateProps:props oldProps:oldProps];
|
|
211
|
+
}
|
|
212
|
+
|
|
213
|
+
- (void)emitOnLoadStart
|
|
214
|
+
{
|
|
215
|
+
if (_eventEmitter) {
|
|
216
|
+
std::dynamic_pointer_cast<const GraniteImageEventEmitter>(_eventEmitter)->onGraniteLoadStart({});
|
|
217
|
+
}
|
|
218
|
+
}
|
|
219
|
+
|
|
220
|
+
- (void)emitOnProgress:(int64_t)loaded total:(int64_t)total
|
|
221
|
+
{
|
|
222
|
+
if (_eventEmitter) {
|
|
223
|
+
GraniteImageEventEmitter::OnGraniteProgress event;
|
|
224
|
+
event.loaded = (int)loaded;
|
|
225
|
+
event.total = (int)total;
|
|
226
|
+
std::dynamic_pointer_cast<const GraniteImageEventEmitter>(_eventEmitter)->onGraniteProgress(event);
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
|
|
230
|
+
- (void)emitOnLoad:(CGSize)imageSize
|
|
231
|
+
{
|
|
232
|
+
if (_eventEmitter) {
|
|
233
|
+
GraniteImageEventEmitter::OnGraniteLoad event;
|
|
234
|
+
event.width = (int)imageSize.width;
|
|
235
|
+
event.height = (int)imageSize.height;
|
|
236
|
+
std::dynamic_pointer_cast<const GraniteImageEventEmitter>(_eventEmitter)->onGraniteLoad(event);
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
- (void)emitOnError:(NSString *)errorMessage
|
|
241
|
+
{
|
|
242
|
+
if (_eventEmitter) {
|
|
243
|
+
GraniteImageEventEmitter::OnGraniteError event;
|
|
244
|
+
event.error = std::string([errorMessage UTF8String]);
|
|
245
|
+
std::dynamic_pointer_cast<const GraniteImageEventEmitter>(_eventEmitter)->onGraniteError(event);
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
- (void)emitOnLoadEnd
|
|
250
|
+
{
|
|
251
|
+
if (_eventEmitter) {
|
|
252
|
+
std::dynamic_pointer_cast<const GraniteImageEventEmitter>(_eventEmitter)->onGraniteLoadEnd({});
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
- (void)loadImageWithProvider
|
|
257
|
+
{
|
|
258
|
+
// Remove existing container view
|
|
259
|
+
[_containerView removeFromSuperview];
|
|
260
|
+
_containerView = nil;
|
|
261
|
+
|
|
262
|
+
id<GraniteImageProvidable> provider = [[GraniteImageRegistry shared] provider];
|
|
263
|
+
|
|
264
|
+
if (!provider) {
|
|
265
|
+
[self showErrorViewWithMessage:@"No GraniteImageProvidable registered"];
|
|
266
|
+
[self emitOnError:@"No GraniteImageProvidable registered"];
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
|
|
270
|
+
if (!_currentUri || _currentUri.length == 0) {
|
|
271
|
+
[self showErrorViewWithMessage:@"No URI provided"];
|
|
272
|
+
[self emitOnError:@"No URI provided"];
|
|
273
|
+
return;
|
|
274
|
+
}
|
|
275
|
+
|
|
276
|
+
// Emit load start event
|
|
277
|
+
[self emitOnLoadStart];
|
|
278
|
+
|
|
279
|
+
// Create new image view from provider
|
|
280
|
+
UIView *imageView = [provider createImageView];
|
|
281
|
+
imageView.frame = self.bounds;
|
|
282
|
+
imageView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
283
|
+
[self addSubview:imageView];
|
|
284
|
+
_containerView = imageView;
|
|
285
|
+
|
|
286
|
+
// Check if provider supports extended loading with callbacks
|
|
287
|
+
__weak GraniteImageComponentView *weakSelf = self;
|
|
288
|
+
if ([(NSObject *)provider respondsToSelector:@selector(loadImageWithURL:into:contentMode:headers:priority:cachePolicy:defaultSource:progress:completion:)]) {
|
|
289
|
+
[provider loadImageWithURL:_currentUri
|
|
290
|
+
into:imageView
|
|
291
|
+
contentMode:_currentContentMode
|
|
292
|
+
headers:_currentHeaders
|
|
293
|
+
priority:_currentPriority
|
|
294
|
+
cachePolicy:_currentCachePolicy
|
|
295
|
+
defaultSource:_currentDefaultSource
|
|
296
|
+
progress:^(int64_t loaded, int64_t total) {
|
|
297
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
298
|
+
[weakSelf emitOnProgress:loaded total:total];
|
|
299
|
+
});
|
|
300
|
+
}
|
|
301
|
+
completion:^(UIImage *image, NSError *error, CGSize imageSize) {
|
|
302
|
+
dispatch_async(dispatch_get_main_queue(), ^{
|
|
303
|
+
GraniteImageComponentView *strongSelf = weakSelf;
|
|
304
|
+
if (!strongSelf) return;
|
|
305
|
+
|
|
306
|
+
if (error) {
|
|
307
|
+
[strongSelf emitOnError:error.localizedDescription];
|
|
308
|
+
|
|
309
|
+
// Load fallback image if available
|
|
310
|
+
if (strongSelf->_currentFallbackSource.length > 0) {
|
|
311
|
+
[provider loadImageWithURL:strongSelf->_currentFallbackSource
|
|
312
|
+
into:imageView
|
|
313
|
+
contentMode:strongSelf->_currentContentMode
|
|
314
|
+
headers:nil
|
|
315
|
+
priority:GraniteProviderPriorityHigh
|
|
316
|
+
cachePolicy:GraniteProviderCachePolicyDisk
|
|
317
|
+
defaultSource:nil
|
|
318
|
+
progress:nil
|
|
319
|
+
completion:nil];
|
|
320
|
+
}
|
|
321
|
+
} else {
|
|
322
|
+
[strongSelf emitOnLoad:imageSize];
|
|
323
|
+
}
|
|
324
|
+
[strongSelf emitOnLoadEnd];
|
|
325
|
+
|
|
326
|
+
// Apply tint color if set
|
|
327
|
+
if (strongSelf->_currentTintColor && [(NSObject *)provider respondsToSelector:@selector(applyTintColor:to:)]) {
|
|
328
|
+
[provider applyTintColor:strongSelf->_currentTintColor to:imageView];
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
}];
|
|
332
|
+
} else {
|
|
333
|
+
// Fall back to simple loading
|
|
334
|
+
[provider loadImageWithURL:_currentUri into:imageView contentMode:_currentContentMode];
|
|
335
|
+
|
|
336
|
+
// For simple providers, emit load success after a short delay (since we don't know when it finishes)
|
|
337
|
+
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
|
338
|
+
GraniteImageComponentView *strongSelf = weakSelf;
|
|
339
|
+
if (strongSelf) {
|
|
340
|
+
// We don't have real image size, so use view bounds
|
|
341
|
+
[strongSelf emitOnLoad:strongSelf.bounds.size];
|
|
342
|
+
[strongSelf emitOnLoadEnd];
|
|
343
|
+
}
|
|
344
|
+
});
|
|
345
|
+
}
|
|
346
|
+
}
|
|
347
|
+
|
|
348
|
+
- (void)showErrorViewWithMessage:(NSString *)message
|
|
349
|
+
{
|
|
350
|
+
UIView *errorView = [[UIView alloc] initWithFrame:self.bounds];
|
|
351
|
+
errorView.backgroundColor = [UIColor redColor];
|
|
352
|
+
errorView.autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight;
|
|
353
|
+
|
|
354
|
+
UILabel *label = [[UILabel alloc] init];
|
|
355
|
+
label.text = message;
|
|
356
|
+
label.textColor = [UIColor whiteColor];
|
|
357
|
+
label.textAlignment = NSTextAlignmentCenter;
|
|
358
|
+
label.font = [UIFont systemFontOfSize:12];
|
|
359
|
+
label.numberOfLines = 0;
|
|
360
|
+
label.translatesAutoresizingMaskIntoConstraints = NO;
|
|
361
|
+
|
|
362
|
+
[errorView addSubview:label];
|
|
363
|
+
[NSLayoutConstraint activateConstraints:@[
|
|
364
|
+
[label.centerXAnchor constraintEqualToAnchor:errorView.centerXAnchor],
|
|
365
|
+
[label.centerYAnchor constraintEqualToAnchor:errorView.centerYAnchor],
|
|
366
|
+
[label.leadingAnchor constraintGreaterThanOrEqualToAnchor:errorView.leadingAnchor constant:8],
|
|
367
|
+
[label.trailingAnchor constraintLessThanOrEqualToAnchor:errorView.trailingAnchor constant:-8]
|
|
368
|
+
]];
|
|
369
|
+
|
|
370
|
+
[self addSubview:errorView];
|
|
371
|
+
_containerView = errorView;
|
|
372
|
+
}
|
|
373
|
+
|
|
374
|
+
- (void)prepareForRecycle
|
|
375
|
+
{
|
|
376
|
+
[super prepareForRecycle];
|
|
377
|
+
|
|
378
|
+
id<GraniteImageProvidable> provider = [[GraniteImageRegistry shared] provider];
|
|
379
|
+
if (_containerView && provider) {
|
|
380
|
+
[provider cancelLoadWith:_containerView];
|
|
381
|
+
}
|
|
382
|
+
|
|
383
|
+
[_containerView removeFromSuperview];
|
|
384
|
+
_containerView = nil;
|
|
385
|
+
_currentUri = nil;
|
|
386
|
+
}
|
|
387
|
+
|
|
388
|
+
@end
|