react-native-fast-image 10.4.0 → 10.6.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/README.md +17 -6
- package/android/src/main/java/com/fastimage/FastImageViewConverter.java +1 -1
- package/android/src/main/java/com/fastimage/FastImageViewManager.java +18 -12
- package/android/src/main/java/com/fastimage/FastImageViewWithUrl.java +231 -32
- package/dist/index.cjs.js +15 -27
- package/dist/index.cjs.js.flow +9 -2
- package/dist/index.cjs.web.js +41 -8
- package/dist/index.cjs.web.js.flow +9 -2
- package/dist/index.d.ts +15 -5
- package/dist/index.js +15 -27
- package/dist/index.js.flow +9 -2
- package/dist/index.web.js +41 -8
- package/dist/index.web.js.flow +9 -2
- package/dist/placeholders.d.ts +2 -1
- package/dist/specs/FastImageViewNativeComponent.d.ts +0 -2
- package/ios/FastImage/FFFThumbHash.h +3 -0
- package/ios/FastImage/FFFThumbHash.m +5 -1
- package/ios/FastImage/FFFastImageView.h +13 -7
- package/ios/FastImage/FFFastImageView.m +187 -33
- package/ios/FastImage/FFFastImageViewComponentView.mm +78 -13
- package/package.json +1 -1
- package/src/index.js.flow +9 -2
- package/src/index.tsx +59 -71
- package/src/index.web.tsx +78 -15
- package/src/placeholders.ts +26 -9
- package/src/specs/FastImageViewNativeComponent.ts +10 -11
|
@@ -44,9 +44,9 @@
|
|
|
44
44
|
// downsample, or several sources: a load waits for the view's size (see
|
|
45
45
|
// didSetProps).
|
|
46
46
|
@property(nonatomic, assign) BOOL waitsForSize;
|
|
47
|
-
// The `source` prop
|
|
48
|
-
// the view's size
|
|
49
|
-
@property(nonatomic, strong) FFFastImageSource*
|
|
47
|
+
// The source that loads: the `source` prop's, or with several the one
|
|
48
|
+
// picked for the view's size.
|
|
49
|
+
@property(nonatomic, strong) FFFastImageSource* source;
|
|
50
50
|
// Several sources: the next load switches to another one for a new view
|
|
51
51
|
// size, and the one after it is loading. It's the same picture at another
|
|
52
52
|
// size, so it doesn't fade in.
|
|
@@ -70,8 +70,18 @@
|
|
|
70
70
|
// shownPlaceholder).
|
|
71
71
|
@property(nonatomic, strong) UIImage* hashImage;
|
|
72
72
|
@property(nonatomic, copy) NSString* hashImageKey;
|
|
73
|
-
//
|
|
74
|
-
//
|
|
73
|
+
// placeholderSource's image once it has loaded, or nil: it shows instead of
|
|
74
|
+
// the other placeholders (see shownPlaceholder). Its load, and which it is.
|
|
75
|
+
@property(nonatomic, strong) UIImage* urlPlaceholderImage;
|
|
76
|
+
@property(nonatomic, strong) id<SDWebImageOperation> urlPlaceholderOperation;
|
|
77
|
+
@property(nonatomic, assign) NSUInteger urlPlaceholderLoad;
|
|
78
|
+
// placeholderSource changed: its load starts with the image's, or after the
|
|
79
|
+
// props if the image is still loading (see startUrlPlaceholderLoad).
|
|
80
|
+
@property(nonatomic, assign) BOOL needsUrlPlaceholderLoad;
|
|
81
|
+
// Whether the view shows a placeholder (the placeholder prop, the source's,
|
|
82
|
+
// or placeholderSource's), and whether it stands for the image (the source's
|
|
83
|
+
// or placeholderSource's): drawn as with contain for scale-down and none, as
|
|
84
|
+
// the image's size isn't known yet, scaled smoothly and not blurred.
|
|
75
85
|
@property(nonatomic, assign) BOOL showsPlaceholder;
|
|
76
86
|
@property(nonatomic, assign) BOOL showsHash;
|
|
77
87
|
// dynamicRangeLimit switched between HDR and SDR: the image is decoded again,
|
|
@@ -444,7 +454,7 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
444
454
|
|
|
445
455
|
- (void) setImage: (UIImage*)image {
|
|
446
456
|
self.imageCount++;
|
|
447
|
-
self.showsHash = image != nil && image == self.hashImage;
|
|
457
|
+
self.showsHash = image != nil && (image == self.hashImage || image == self.urlPlaceholderImage);
|
|
448
458
|
self.showsPlaceholder = self.showsHash || (image != nil && image == _placeholder);
|
|
449
459
|
if ([_imageRendering isEqualToString: @"pixelated"]) {
|
|
450
460
|
// A source's placeholder is a blurred preview, scaled up smoothly
|
|
@@ -540,7 +550,7 @@ static UIImage* FFFBlurredImage(UIImage* image, CGFloat scale, CGFloat radius, B
|
|
|
540
550
|
// Whether the image is blurred: a loaded image, not the placeholder prop (as
|
|
541
551
|
// with React Native's Image, and on Android).
|
|
542
552
|
- (BOOL) blurs: (UIImage*)image {
|
|
543
|
-
return _blurRadius > 0 && image && image != _placeholder && image != self.hashImage;
|
|
553
|
+
return _blurRadius > 0 && image && image != _placeholder && image != self.hashImage && image != self.urlPlaceholderImage;
|
|
544
554
|
}
|
|
545
555
|
|
|
546
556
|
// Blurs the image off the main thread, and shows it (tinted as any image) unless
|
|
@@ -739,21 +749,11 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
739
749
|
return event;
|
|
740
750
|
}
|
|
741
751
|
|
|
742
|
-
- (void) setSource: (FFFastImageSource*)source {
|
|
743
|
-
if (self.propSource != source) {
|
|
744
|
-
self.propSource = source;
|
|
745
|
-
if (![self picksSource]) {
|
|
746
|
-
_source = source;
|
|
747
|
-
}
|
|
748
|
-
_needsReload = YES;
|
|
749
|
-
}
|
|
750
|
-
}
|
|
751
|
-
|
|
752
752
|
- (void) setSources: (NSArray<FFFastImageSource*>*)sources {
|
|
753
753
|
if (_sources != sources) {
|
|
754
754
|
_sources = [sources copy];
|
|
755
755
|
if (![self picksSource]) {
|
|
756
|
-
_source =
|
|
756
|
+
_source = _sources.firstObject;
|
|
757
757
|
}
|
|
758
758
|
_needsReload = YES;
|
|
759
759
|
}
|
|
@@ -837,11 +837,16 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
837
837
|
return [_dynamicRangeLimit isEqualToString: @"constrained"] || [_dynamicRangeLimit isEqualToString: @"no-limit"];
|
|
838
838
|
}
|
|
839
839
|
|
|
840
|
+
// Shows at once, as a new source placeholder does (see updateHashImage),
|
|
841
|
+
// without loading the image again.
|
|
840
842
|
- (void) setPlaceholder: (UIImage*)placeholder {
|
|
841
|
-
if (_placeholder
|
|
842
|
-
|
|
843
|
-
_needsReload = YES;
|
|
843
|
+
if (_placeholder == placeholder) {
|
|
844
|
+
return;
|
|
844
845
|
}
|
|
846
|
+
UIImage* previous = [self shownPlaceholder];
|
|
847
|
+
BOOL showing = self.showsPlaceholder;
|
|
848
|
+
_placeholder = placeholder;
|
|
849
|
+
[self showChangedPlaceholder: previous showing: showing];
|
|
845
850
|
}
|
|
846
851
|
|
|
847
852
|
- (void) setSourcePlaceholders: (NSArray<NSString*>*)sourcePlaceholders {
|
|
@@ -849,20 +854,146 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
849
854
|
[self updateHashImage];
|
|
850
855
|
}
|
|
851
856
|
|
|
852
|
-
|
|
853
|
-
|
|
857
|
+
- (void) setPlaceholderHash: (NSString*)placeholderHash {
|
|
858
|
+
_placeholderHash = [placeholderHash copy];
|
|
859
|
+
[self updateHashImage];
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
// What shows while the image loads and if it fails: placeholderSource's
|
|
863
|
+
// image once it has loaded, the picked source's placeholder (its ThumbHash or
|
|
864
|
+
// blurDataUri), or the placeholder prop.
|
|
854
865
|
- (UIImage*) shownPlaceholder {
|
|
855
|
-
return self.hashImage ?: _placeholder;
|
|
866
|
+
return self.urlPlaceholderImage ?: self.hashImage ?: _placeholder;
|
|
867
|
+
}
|
|
868
|
+
|
|
869
|
+
// Shows the placeholder that shows now (shownPlaceholder) in place of
|
|
870
|
+
// `previous` if the view showed it, or if it showed nothing (one that arrived
|
|
871
|
+
// while nothing showed): never over a loaded image, or the image kept while
|
|
872
|
+
// a new source loads.
|
|
873
|
+
- (void) showChangedPlaceholder: (UIImage*)previous showing: (BOOL)showing {
|
|
874
|
+
UIImage* placeholder = [self shownPlaceholder];
|
|
875
|
+
if (placeholder == previous) {
|
|
876
|
+
return;
|
|
877
|
+
}
|
|
878
|
+
BOOL showsNothing = super.image == nil && !self.showsLoadedImage && !self.blursOverNothing;
|
|
879
|
+
if (showing || (placeholder && showsNothing)) {
|
|
880
|
+
[self setImage: placeholder];
|
|
881
|
+
}
|
|
882
|
+
}
|
|
883
|
+
|
|
884
|
+
// Images that views showed, by their source's cacheKey or url, while they're
|
|
885
|
+
// still in memory (held by a view or SDWebImage's memory cache), so
|
|
886
|
+
// placeholderSource finds one at once whatever size it was decoded at (the
|
|
887
|
+
// memory cache's key includes the size).
|
|
888
|
+
+ (NSMapTable<NSString*, UIImage*>*) shownImages {
|
|
889
|
+
static NSMapTable* images;
|
|
890
|
+
static dispatch_once_t once;
|
|
891
|
+
dispatch_once(&once, ^{
|
|
892
|
+
images = [NSMapTable strongToWeakObjectsMapTable];
|
|
893
|
+
});
|
|
894
|
+
return images;
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
// The key a remote image (http or https) is recorded under: its cacheKey or
|
|
898
|
+
// url. nil for others (data uris, files), which aren't recorded.
|
|
899
|
+
+ (NSString*) shownImageKey: (FFFastImageSource*)source {
|
|
900
|
+
NSString* scheme = source.url.scheme.lowercaseString;
|
|
901
|
+
if (![scheme isEqualToString: @"http"] && ![scheme isEqualToString: @"https"]) {
|
|
902
|
+
return nil;
|
|
903
|
+
}
|
|
904
|
+
return source.cacheKey.length > 0 ? source.cacheKey : source.url.absoluteString;
|
|
905
|
+
}
|
|
906
|
+
|
|
907
|
+
+ (void) rememberShownImage: (UIImage*)image source: (FFFastImageSource*)source {
|
|
908
|
+
NSString* key = [self shownImageKey: source];
|
|
909
|
+
if (image && key) {
|
|
910
|
+
[[self shownImages] setObject: image forKey: key];
|
|
911
|
+
}
|
|
912
|
+
}
|
|
913
|
+
|
|
914
|
+
- (void) setPlaceholderSource: (FFFastImageSource*)placeholderSource {
|
|
915
|
+
if (!placeholderSource && !_placeholderSource) {
|
|
916
|
+
return;
|
|
917
|
+
}
|
|
918
|
+
_placeholderSource = placeholderSource;
|
|
919
|
+
[self cancelUrlPlaceholderLoad];
|
|
920
|
+
[self showUrlPlaceholderImage: nil];
|
|
921
|
+
self.needsUrlPlaceholderLoad = YES;
|
|
856
922
|
}
|
|
857
923
|
|
|
858
|
-
|
|
924
|
+
- (void) cancelUrlPlaceholderLoad {
|
|
925
|
+
self.urlPlaceholderLoad++;
|
|
926
|
+
[self.urlPlaceholderOperation cancel];
|
|
927
|
+
self.urlPlaceholderOperation = nil;
|
|
928
|
+
}
|
|
929
|
+
|
|
930
|
+
// Loads placeholderSource's image with the image's load (after layout, so at
|
|
931
|
+
// the view's size, with the props applied), or after the image failed: not
|
|
932
|
+
// if the image shows at once (from the memory cache, or a data uri), or the
|
|
933
|
+
// placeholder is already loaded or loading.
|
|
934
|
+
- (void) startUrlPlaceholderLoad {
|
|
935
|
+
self.needsUrlPlaceholderLoad = NO;
|
|
936
|
+
if (!_placeholderSource.url || self.urlPlaceholderImage || self.urlPlaceholderOperation) {
|
|
937
|
+
return;
|
|
938
|
+
}
|
|
939
|
+
if (_source && !self.hasErrored && [self loadsDuringLayout]) {
|
|
940
|
+
return;
|
|
941
|
+
}
|
|
942
|
+
[self loadUrlPlaceholder];
|
|
943
|
+
}
|
|
944
|
+
|
|
945
|
+
// Loads placeholderSource's image, as a source would load (its cache,
|
|
946
|
+
// headers and cacheKey), at a high priority, as it only helps if it comes
|
|
947
|
+
// before the image. One a view still shows (or that's still in memory) shows
|
|
948
|
+
// at once. A failure shows nothing more.
|
|
949
|
+
- (void) loadUrlPlaceholder {
|
|
950
|
+
[self cancelUrlPlaceholderLoad];
|
|
951
|
+
NSUInteger load = self.urlPlaceholderLoad;
|
|
952
|
+
FFFastImageSource* source = _placeholderSource;
|
|
953
|
+
NSString* key = [FFFastImageView shownImageKey: source];
|
|
954
|
+
UIImage* shown = key ? [[FFFastImageView shownImages] objectForKey: key] : nil;
|
|
955
|
+
if (shown) {
|
|
956
|
+
[self showUrlPlaceholderImage: shown];
|
|
957
|
+
return;
|
|
958
|
+
}
|
|
959
|
+
SDWebImageOptions options = [source cacheOptions] | SDWebImageHighPriority | SDWebImageRetryFailed | SDWebImageHandleCookies;
|
|
960
|
+
SDWebImageContext* context = [self contextForSource: source box: [self decodeBox] cover: NO];
|
|
961
|
+
__weak typeof(self) weakSelf = self;
|
|
962
|
+
self.urlPlaceholderOperation = [[SDWebImageManager sharedManager] loadImageWithURL: source.url options: options context: context progress: nil completed: ^(UIImage* _Nullable image, NSData* _Nullable data, NSError* _Nullable error, SDImageCacheType cacheType, BOOL finished, NSURL* _Nullable imageURL) {
|
|
963
|
+
if (!finished || weakSelf.urlPlaceholderLoad != load) {
|
|
964
|
+
return;
|
|
965
|
+
}
|
|
966
|
+
weakSelf.urlPlaceholderOperation = nil;
|
|
967
|
+
if (!image) {
|
|
968
|
+
return;
|
|
969
|
+
}
|
|
970
|
+
[FFFastImageView rememberShownImage: image source: source];
|
|
971
|
+
[weakSelf showUrlPlaceholderImage: image];
|
|
972
|
+
}];
|
|
973
|
+
}
|
|
974
|
+
|
|
975
|
+
- (void) showUrlPlaceholderImage: (UIImage*)image {
|
|
976
|
+
if (self.urlPlaceholderImage == image) {
|
|
977
|
+
return;
|
|
978
|
+
}
|
|
979
|
+
UIImage* previous = [self shownPlaceholder];
|
|
980
|
+
BOOL showing = self.showsPlaceholder;
|
|
981
|
+
self.urlPlaceholderImage = image;
|
|
982
|
+
[self showChangedPlaceholder: previous showing: showing];
|
|
983
|
+
}
|
|
984
|
+
|
|
985
|
+
// The picked source's ThumbHash or blurDataUri, or placeholderHash, or nil.
|
|
859
986
|
- (NSString*) pickedSourcePlaceholder {
|
|
860
987
|
NSUInteger index = 0;
|
|
861
988
|
if ([self picksSource]) {
|
|
862
989
|
index = _source ? [_sources indexOfObjectIdenticalTo: _source] : NSNotFound;
|
|
990
|
+
// None until one is picked.
|
|
991
|
+
if (index == NSNotFound) {
|
|
992
|
+
return nil;
|
|
993
|
+
}
|
|
863
994
|
}
|
|
864
995
|
NSString* hash = index < _sourcePlaceholders.count ? _sourcePlaceholders[index] : nil;
|
|
865
|
-
return hash.length > 0 ? hash : nil;
|
|
996
|
+
return hash.length > 0 ? hash : _placeholderHash.length > 0 ? _placeholderHash : nil;
|
|
866
997
|
}
|
|
867
998
|
|
|
868
999
|
// Decodes the picked source's placeholder. A view showing the previous
|
|
@@ -876,10 +1007,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
876
1007
|
BOOL showing = self.showsPlaceholder;
|
|
877
1008
|
self.hashImageKey = hash;
|
|
878
1009
|
self.hashImage = [FFFThumbHash imageForPlaceholder: hash];
|
|
879
|
-
|
|
880
|
-
if (showing && placeholder != previous) {
|
|
881
|
-
[self setImage: placeholder];
|
|
882
|
-
}
|
|
1010
|
+
[self showChangedPlaceholder: previous showing: showing];
|
|
883
1011
|
}
|
|
884
1012
|
|
|
885
1013
|
- (void) didSetProps {
|
|
@@ -909,6 +1037,14 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
909
1037
|
} else {
|
|
910
1038
|
[self reloadIfResized];
|
|
911
1039
|
}
|
|
1040
|
+
// A new placeholderSource while the image loads, or after it failed (a
|
|
1041
|
+
// load waiting for the view's size starts it with the image's).
|
|
1042
|
+
if (self.needsUrlPlaceholderLoad && !_needsReload && !self.waitsForSize) {
|
|
1043
|
+
if (!self.hasCompleted || self.hasErrored) {
|
|
1044
|
+
[self startUrlPlaceholderLoad];
|
|
1045
|
+
}
|
|
1046
|
+
self.needsUrlPlaceholderLoad = NO;
|
|
1047
|
+
}
|
|
912
1048
|
}
|
|
913
1049
|
|
|
914
1050
|
// The same image decoded again for a dynamicRangeLimit between HDR and SDR,
|
|
@@ -1003,6 +1139,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1003
1139
|
- (void) reloadImage {
|
|
1004
1140
|
_needsReload = NO;
|
|
1005
1141
|
self.needsDynamicRangeReload = NO;
|
|
1142
|
+
self.needsUrlPlaceholderLoad = NO;
|
|
1006
1143
|
self.waitsForSize = NO;
|
|
1007
1144
|
self.loadsSwitchedSource = self.switchesSource;
|
|
1008
1145
|
self.switchesSource = NO;
|
|
@@ -1038,6 +1175,7 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1038
1175
|
[self setImage: [self shownPlaceholder]];
|
|
1039
1176
|
self.showsLoadedImage = NO;
|
|
1040
1177
|
[self sendOnError: @"Failed to decode the image"];
|
|
1178
|
+
[self startUrlPlaceholderLoad];
|
|
1041
1179
|
if (self.onFastImageLoadEnd) {
|
|
1042
1180
|
self.onFastImageLoadEnd([self loadEndEvent: NO]);
|
|
1043
1181
|
}
|
|
@@ -1080,10 +1218,15 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1080
1218
|
self.retriedAfterBackground = NO;
|
|
1081
1219
|
[self stopWaitingForActive];
|
|
1082
1220
|
|
|
1221
|
+
[self startUrlPlaceholderLoad];
|
|
1083
1222
|
[self downloadImage: _source options: options context: [self loadContext] events: YES];
|
|
1084
|
-
} else
|
|
1085
|
-
|
|
1086
|
-
|
|
1223
|
+
} else {
|
|
1224
|
+
if ([self shownPlaceholder]) {
|
|
1225
|
+
[self setImage: [self shownPlaceholder]];
|
|
1226
|
+
self.showsLoadedImage = NO;
|
|
1227
|
+
}
|
|
1228
|
+
// No image: only placeholders show.
|
|
1229
|
+
[self startUrlPlaceholderLoad];
|
|
1087
1230
|
}
|
|
1088
1231
|
}
|
|
1089
1232
|
|
|
@@ -1319,6 +1462,8 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1319
1462
|
[weakSelf setImage: current];
|
|
1320
1463
|
}
|
|
1321
1464
|
weakSelf.showsLoadedImage = NO;
|
|
1465
|
+
// Shows instead of the other placeholders once it loads.
|
|
1466
|
+
[weakSelf startUrlPlaceholderLoad];
|
|
1322
1467
|
[weakSelf sendOnError: [source errorMessage: error]];
|
|
1323
1468
|
if (weakSelf.onFastImageLoadEnd) {
|
|
1324
1469
|
weakSelf.onFastImageLoadEnd([weakSelf loadEndEvent: NO]);
|
|
@@ -1326,6 +1471,12 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1326
1471
|
} else {
|
|
1327
1472
|
weakSelf.hasCompleted = YES;
|
|
1328
1473
|
weakSelf.showsLoadedImage = YES;
|
|
1474
|
+
// For other views' placeholderSource.
|
|
1475
|
+
[FFFastImageView rememberShownImage: image source: source];
|
|
1476
|
+
// The image came first: its placeholder isn't needed.
|
|
1477
|
+
if (!weakSelf.urlPlaceholderImage) {
|
|
1478
|
+
[weakSelf cancelUrlPlaceholderLoad];
|
|
1479
|
+
}
|
|
1329
1480
|
[weakSelf sendOnLoad: image];
|
|
1330
1481
|
if (weakSelf.onFastImageLoadEnd) {
|
|
1331
1482
|
weakSelf.onFastImageLoadEnd([weakSelf loadEndEvent: YES]);
|
|
@@ -1375,11 +1526,14 @@ NSString *FFFErrorMessage(NSError *error)
|
|
|
1375
1526
|
self.waitsForSize = NO;
|
|
1376
1527
|
[self stopWaitingForActive];
|
|
1377
1528
|
[self sd_cancelLatestImageLoad];
|
|
1529
|
+
[self cancelUrlPlaceholderLoad];
|
|
1378
1530
|
}
|
|
1379
1531
|
|
|
1380
1532
|
- (void) dealloc {
|
|
1381
1533
|
[self stopWaitingForActive];
|
|
1382
1534
|
[self sd_cancelLatestImageLoad];
|
|
1535
|
+
// Fabric frees views it doesn't recycle without stop.
|
|
1536
|
+
[_urlPlaceholderOperation cancel];
|
|
1383
1537
|
}
|
|
1384
1538
|
|
|
1385
1539
|
@end
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
#import <react/renderer/components/RNFastImageSpec/RCTComponentViewHelpers.h>
|
|
9
9
|
|
|
10
10
|
#import "FFFastImageView.h"
|
|
11
|
+
#import "FFFThumbHash.h"
|
|
11
12
|
#import "RCTConvert+FFFastImage.h"
|
|
12
13
|
|
|
13
14
|
using namespace facebook::react;
|
|
@@ -102,6 +103,57 @@ static id _Nullable FFFJSON(const folly::dynamic &value)
|
|
|
102
103
|
return value.isNull() ? nil : FFFObjectFromDynamic(value);
|
|
103
104
|
}
|
|
104
105
|
|
|
106
|
+
// A source (or `placeholder`) without its thumbhash and blurDataUri, to
|
|
107
|
+
// compare: one source, or each of several.
|
|
108
|
+
static folly::dynamic FFFWithoutPlaceholders(const folly::dynamic &value)
|
|
109
|
+
{
|
|
110
|
+
if (value.isArray()) {
|
|
111
|
+
folly::dynamic result = folly::dynamic::array;
|
|
112
|
+
for (const auto &entry : value) {
|
|
113
|
+
result.push_back(FFFWithoutPlaceholders(entry));
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
116
|
+
}
|
|
117
|
+
if (!value.isObject()) {
|
|
118
|
+
return value;
|
|
119
|
+
}
|
|
120
|
+
folly::dynamic result = value;
|
|
121
|
+
result.erase("thumbhash");
|
|
122
|
+
result.erase("blurDataUri");
|
|
123
|
+
return result;
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
// A source's (or `placeholder`'s) ThumbHash, or its blurDataUri if it's a data
|
|
127
|
+
// uri (JS warns about another), or nil.
|
|
128
|
+
static NSString *_Nullable FFFSourcePlaceholder(NSDictionary *source)
|
|
129
|
+
{
|
|
130
|
+
id thumbhash = source[@"thumbhash"];
|
|
131
|
+
if ([thumbhash isKindOfClass:[NSString class]] && [thumbhash length] > 0) {
|
|
132
|
+
return thumbhash;
|
|
133
|
+
}
|
|
134
|
+
id blurDataUri = source[@"blurDataUri"];
|
|
135
|
+
if ([blurDataUri isKindOfClass:[NSString class]] && [FFFThumbHash isDataUri:blurDataUri]) {
|
|
136
|
+
return blurDataUri;
|
|
137
|
+
}
|
|
138
|
+
return nil;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
// Whether the placeholder prop is a url to load apart from the image (a
|
|
142
|
+
// source with an http or https uri), rather than an image to show at once: a
|
|
143
|
+
// bundled image (served by the packager in development), a file or a data uri.
|
|
144
|
+
static BOOL FFFIsUrlPlaceholder(id _Nullable placeholder)
|
|
145
|
+
{
|
|
146
|
+
if (![placeholder isKindOfClass:[NSDictionary class]] || placeholder[@"__packager_asset"]) {
|
|
147
|
+
return NO;
|
|
148
|
+
}
|
|
149
|
+
id uri = placeholder[@"uri"];
|
|
150
|
+
if (![uri isKindOfClass:[NSString class]]) {
|
|
151
|
+
return NO;
|
|
152
|
+
}
|
|
153
|
+
NSString *scheme = [NSURL URLWithString:uri].scheme.lowercaseString;
|
|
154
|
+
return [scheme isEqualToString:@"http"] || [scheme isEqualToString:@"https"];
|
|
155
|
+
}
|
|
156
|
+
|
|
105
157
|
// The events JS has a handler for: the handledEvents prop's bits (see
|
|
106
158
|
// src/specs/FastImageViewNativeComponent.ts).
|
|
107
159
|
typedef NS_OPTIONS(int32_t, FFFEvent) {
|
|
@@ -226,22 +278,35 @@ typedef NS_OPTIONS(int32_t, FFFEvent) {
|
|
|
226
278
|
FFFastImageView *view = _imageView;
|
|
227
279
|
|
|
228
280
|
if (all || oldViewProps.source != newViewProps.source) {
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
281
|
+
// One source, or several (an array). Only a change besides their
|
|
282
|
+
// thumbhashes and blurDataUris loads again: a new one for the same
|
|
283
|
+
// image (e.g. one that arrives later) shows at once.
|
|
284
|
+
BOOL loads = all || FFFWithoutPlaceholders(oldViewProps.source) != FFFWithoutPlaceholders(newViewProps.source);
|
|
285
|
+
id json = FFFJSON(newViewProps.source);
|
|
286
|
+
NSArray *entries = [json isKindOfClass:[NSArray class]] ? json : json ? @[ json ] : @[];
|
|
287
|
+
NSMutableArray<FFFastImageSource *> *sources = [NSMutableArray arrayWithCapacity:entries.count];
|
|
288
|
+
NSMutableArray<NSString *> *placeholders = [NSMutableArray arrayWithCapacity:entries.count];
|
|
289
|
+
for (id entry in entries) {
|
|
290
|
+
if ([entry isKindOfClass:[NSDictionary class]]) {
|
|
291
|
+
if (loads) {
|
|
292
|
+
[sources addObject:[RCTConvert FFFastImageSource:entry]];
|
|
293
|
+
}
|
|
294
|
+
[placeholders addObject:FFFSourcePlaceholder(entry) ?: @""];
|
|
295
|
+
}
|
|
296
|
+
}
|
|
297
|
+
if (loads) {
|
|
298
|
+
view.sources = sources.count > 0 ? sources : nil;
|
|
299
|
+
}
|
|
300
|
+
view.sourcePlaceholders = placeholders;
|
|
234
301
|
}
|
|
235
302
|
if (all || oldViewProps.placeholder != newViewProps.placeholder) {
|
|
303
|
+
// A thumbhash or blurDataUri, a url to load, or an image to show.
|
|
236
304
|
id placeholder = FFFJSON(newViewProps.placeholder);
|
|
237
|
-
|
|
238
|
-
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
[placeholders addObject:[NSString stringWithUTF8String:placeholder.c_str()]];
|
|
243
|
-
}
|
|
244
|
-
view.sourcePlaceholders = placeholders.count > 0 ? placeholders : nil;
|
|
305
|
+
BOOL hash = [placeholder isKindOfClass:[NSDictionary class]] && (placeholder[@"thumbhash"] || placeholder[@"blurDataUri"]);
|
|
306
|
+
BOOL loads = !hash && FFFIsUrlPlaceholder(placeholder);
|
|
307
|
+
view.placeholderHash = hash ? FFFSourcePlaceholder(placeholder) : nil;
|
|
308
|
+
view.placeholder = placeholder && !hash && !loads ? [RCTConvert UIImage:placeholder] : nil;
|
|
309
|
+
view.placeholderSource = loads ? [RCTConvert FFFastImageSource:placeholder] : nil;
|
|
245
310
|
}
|
|
246
311
|
if (all || oldViewProps.objectFit != newViewProps.objectFit) {
|
|
247
312
|
view.objectFit = [RCTConvert FFFObjectFit:FFFString(newViewProps.objectFit)];
|
package/package.json
CHANGED
package/src/index.js.flow
CHANGED
|
@@ -110,6 +110,13 @@ export type PreloadResult = PreloadSuccess | PreloadFailure
|
|
|
110
110
|
export type PreloadFn = (
|
|
111
111
|
sources: Array<FastImageSource>,
|
|
112
112
|
) => Promise<Array<PreloadResult>>
|
|
113
|
+
export type PlaceholderSource = {
|
|
114
|
+
uri?: string,
|
|
115
|
+
headers?: Object,
|
|
116
|
+
cacheKey?: string,
|
|
117
|
+
cache?: CacheControls,
|
|
118
|
+
}
|
|
119
|
+
|
|
113
120
|
export type SourcePlaceholder = {
|
|
114
121
|
thumbhash?: string,
|
|
115
122
|
blurDataUri?: string,
|
|
@@ -138,8 +145,8 @@ export type FastImageProps = $ReadOnly<{|
|
|
|
138
145
|
onLoadStart?: ?() => void,
|
|
139
146
|
onProgress?: ?(event: OnProgressEvent) => void,
|
|
140
147
|
|
|
141
|
-
source?: ?(FastImageSource | Array<FastImageSource> | number),
|
|
142
|
-
placeholder?: ?(number | string | SourcePlaceholder),
|
|
148
|
+
source?: ?(FastImageSource | Array<FastImageSource | number> | number),
|
|
149
|
+
placeholder?: ?(number | string | SourcePlaceholder | PlaceholderSource),
|
|
143
150
|
// Deprecated: use placeholder, which is the same.
|
|
144
151
|
defaultSource?: ?number,
|
|
145
152
|
|