react-native-fast-image 8.6.40 → 8.6.42
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.
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
package com.dylanvann.fastimage;
|
|
2
|
+
|
|
3
|
+
import android.content.Context;
|
|
4
|
+
import android.os.Build;
|
|
5
|
+
import android.text.TextUtils;
|
|
6
|
+
import android.webkit.CookieManager;
|
|
7
|
+
import android.webkit.CookieSyncManager;
|
|
8
|
+
|
|
9
|
+
import androidx.annotation.Nullable;
|
|
10
|
+
|
|
11
|
+
import java.net.CookieHandler;
|
|
12
|
+
import java.net.URI;
|
|
13
|
+
import java.util.Collections;
|
|
14
|
+
import java.util.List;
|
|
15
|
+
import java.util.Map;
|
|
16
|
+
|
|
17
|
+
// Reads and stores cookies in Android's cookie store (the WebView's
|
|
18
|
+
// CookieManager), which React Native's networking (fetch) and its Image
|
|
19
|
+
// component use. Image requests then send the cookies the app's other requests
|
|
20
|
+
// got, as on iOS. Does what React Native's ForwardingCookieHandler does,
|
|
21
|
+
// without the ReactContext that one needs (up to at least 0.77). Used through
|
|
22
|
+
// OkHttp's JavaNetCookieJar, which parses the cookies.
|
|
23
|
+
final class FastImageCookieHandler extends CookieHandler {
|
|
24
|
+
private final Context context;
|
|
25
|
+
@Nullable
|
|
26
|
+
private CookieManager cookieManager;
|
|
27
|
+
|
|
28
|
+
FastImageCookieHandler(Context context) {
|
|
29
|
+
this.context = context.getApplicationContext();
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
@Override
|
|
33
|
+
public Map<String, List<String>> get(URI uri, Map<String, List<String>> requestHeaders) {
|
|
34
|
+
CookieManager cookieManager = cookieManager();
|
|
35
|
+
if (cookieManager == null) return Collections.emptyMap();
|
|
36
|
+
String cookies = cookieManager.getCookie(uri.toString());
|
|
37
|
+
if (TextUtils.isEmpty(cookies)) return Collections.emptyMap();
|
|
38
|
+
return Collections.singletonMap("Cookie", Collections.singletonList(cookies));
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
@Override
|
|
42
|
+
public void put(URI uri, Map<String, List<String>> responseHeaders) {
|
|
43
|
+
CookieManager cookieManager = cookieManager();
|
|
44
|
+
if (cookieManager == null) return;
|
|
45
|
+
String url = uri.toString();
|
|
46
|
+
boolean added = false;
|
|
47
|
+
for (Map.Entry<String, List<String>> header : responseHeaders.entrySet()) {
|
|
48
|
+
String name = header.getKey();
|
|
49
|
+
if (!"Set-Cookie".equalsIgnoreCase(name) && !"Set-Cookie2".equalsIgnoreCase(name)) continue;
|
|
50
|
+
for (String cookie : header.getValue()) {
|
|
51
|
+
cookieManager.setCookie(url, cookie);
|
|
52
|
+
added = true;
|
|
53
|
+
}
|
|
54
|
+
}
|
|
55
|
+
if (added) flush(cookieManager);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
@SuppressWarnings("deprecation")
|
|
59
|
+
private static void flush(CookieManager cookieManager) {
|
|
60
|
+
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
|
61
|
+
cookieManager.flush();
|
|
62
|
+
} else {
|
|
63
|
+
CookieSyncManager.getInstance().sync();
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
// null while the WebView isn't available (not installed, or being
|
|
68
|
+
// updated): requests go without cookies then, instead of failing.
|
|
69
|
+
@Nullable
|
|
70
|
+
@SuppressWarnings("deprecation")
|
|
71
|
+
private synchronized CookieManager cookieManager() {
|
|
72
|
+
if (cookieManager == null) {
|
|
73
|
+
try {
|
|
74
|
+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.LOLLIPOP) {
|
|
75
|
+
CookieSyncManager.createInstance(context);
|
|
76
|
+
}
|
|
77
|
+
cookieManager = CookieManager.getInstance();
|
|
78
|
+
} catch (Exception e) {
|
|
79
|
+
return null;
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return cookieManager;
|
|
83
|
+
}
|
|
84
|
+
}
|
package/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java
CHANGED
|
@@ -13,6 +13,7 @@ import com.bumptech.glide.load.model.ModelLoader;
|
|
|
13
13
|
import com.bumptech.glide.load.model.ModelLoaderFactory;
|
|
14
14
|
import com.bumptech.glide.load.model.MultiModelLoaderFactory;
|
|
15
15
|
import com.bumptech.glide.module.LibraryGlideModule;
|
|
16
|
+
import com.facebook.react.modules.network.CookieJarContainer;
|
|
16
17
|
import com.facebook.react.modules.network.OkHttpClientProvider;
|
|
17
18
|
|
|
18
19
|
import java.io.File;
|
|
@@ -24,6 +25,7 @@ import java.util.WeakHashMap;
|
|
|
24
25
|
|
|
25
26
|
import okhttp3.Cache;
|
|
26
27
|
import okhttp3.Interceptor;
|
|
28
|
+
import okhttp3.JavaNetCookieJar;
|
|
27
29
|
import okhttp3.MediaType;
|
|
28
30
|
import okhttp3.OkHttpClient;
|
|
29
31
|
import okhttp3.Request;
|
|
@@ -47,11 +49,18 @@ public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {
|
|
|
47
49
|
@NonNull Glide glide,
|
|
48
50
|
@NonNull Registry registry
|
|
49
51
|
) {
|
|
50
|
-
OkHttpClient
|
|
51
|
-
|
|
52
|
+
OkHttpClient sharedClient = OkHttpClientProvider.getOkHttpClient();
|
|
53
|
+
OkHttpClient.Builder builder = sharedClient
|
|
52
54
|
.newBuilder()
|
|
53
|
-
.addInterceptor(createInterceptor(progressListener))
|
|
54
|
-
|
|
55
|
+
.addInterceptor(createInterceptor(progressListener));
|
|
56
|
+
// React Native's shared client comes with an empty cookie jar (React
|
|
57
|
+
// Native only fills it in for its networking and Image clients), so
|
|
58
|
+
// images were loaded without the app's cookies, unlike on iOS. Use the
|
|
59
|
+
// same cookie store. A cookie jar the app set up itself is kept.
|
|
60
|
+
if (sharedClient.cookieJar() instanceof CookieJarContainer) {
|
|
61
|
+
builder.cookieJar(new JavaNetCookieJar(new FastImageCookieHandler(context)));
|
|
62
|
+
}
|
|
63
|
+
OkHttpClient client = builder.build();
|
|
55
64
|
OkHttpUrlLoader.Factory factory = new OkHttpUrlLoader.Factory(client);
|
|
56
65
|
registry.replace(GlideUrl.class, InputStream.class, factory);
|
|
57
66
|
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#import "FFFastImageView.h"
|
|
2
2
|
#import <SDWebImage/UIImage+MultiFormat.h>
|
|
3
3
|
#import <SDWebImage/UIView+WebCache.h>
|
|
4
|
+
#import <React/RCTUtils.h>
|
|
4
5
|
|
|
5
6
|
@interface FFFastImageView ()
|
|
6
7
|
|
|
@@ -14,11 +15,31 @@
|
|
|
14
15
|
// The image before tinting, kept while a tint is applied so the tint can be
|
|
15
16
|
// changed or removed. nil when there's no tint (super.image is untinted).
|
|
16
17
|
@property(nonatomic, strong) UIImage* untintedImage;
|
|
18
|
+
// Whether the current load was already restarted after the app came back
|
|
19
|
+
// from the background (see downloadImage:).
|
|
20
|
+
@property(nonatomic, assign) BOOL retriedAfterBackground;
|
|
21
|
+
// Waits for the app to be active again, to restart a load.
|
|
22
|
+
@property(nonatomic, strong) id activeObserver;
|
|
17
23
|
|
|
18
24
|
@end
|
|
19
25
|
|
|
26
|
+
// When the app last went to the background (CACurrentMediaTime), or 0.
|
|
27
|
+
static CFTimeInterval FFFEnteredBackgroundAt = 0;
|
|
28
|
+
|
|
20
29
|
@implementation FFFastImageView
|
|
21
30
|
|
|
31
|
+
+ (void) initialize {
|
|
32
|
+
if (self != [FFFastImageView class]) {
|
|
33
|
+
return;
|
|
34
|
+
}
|
|
35
|
+
[[NSNotificationCenter defaultCenter] addObserverForName: UIApplicationDidEnterBackgroundNotification
|
|
36
|
+
object: nil
|
|
37
|
+
queue: [NSOperationQueue mainQueue]
|
|
38
|
+
usingBlock: ^(NSNotification* notification) {
|
|
39
|
+
FFFEnteredBackgroundAt = CACurrentMediaTime();
|
|
40
|
+
}];
|
|
41
|
+
}
|
|
42
|
+
|
|
22
43
|
- (id) init {
|
|
23
44
|
self = [super init];
|
|
24
45
|
self.resizeMode = RCTResizeModeCover;
|
|
@@ -232,6 +253,8 @@
|
|
|
232
253
|
}
|
|
233
254
|
self.hasCompleted = NO;
|
|
234
255
|
self.hasErrored = NO;
|
|
256
|
+
self.retriedAfterBackground = NO;
|
|
257
|
+
[self stopWaitingForActive];
|
|
235
258
|
|
|
236
259
|
[self downloadImage: _source options: options context: context];
|
|
237
260
|
} else if (_defaultSource) {
|
|
@@ -267,6 +290,7 @@
|
|
|
267
290
|
});
|
|
268
291
|
};
|
|
269
292
|
}
|
|
293
|
+
CFTimeInterval startedAt = CACurrentMediaTime();
|
|
270
294
|
[self sd_setImageWithURL: _source.url
|
|
271
295
|
placeholderImage: _defaultSource
|
|
272
296
|
options: options
|
|
@@ -276,6 +300,18 @@
|
|
|
276
300
|
NSError* _Nullable error,
|
|
277
301
|
SDImageCacheType cacheType,
|
|
278
302
|
NSURL* _Nullable imageURL) {
|
|
303
|
+
// The download was running when the app went to the
|
|
304
|
+
// background, and failed: iOS suspends it there, and its
|
|
305
|
+
// timeout keeps counting, so it times out as the app comes
|
|
306
|
+
// back (NSURLErrorTimedOut), or loses its connection. Load it
|
|
307
|
+
// again once the app is active, as Android does (#758).
|
|
308
|
+
if (error && [error.domain isEqualToString: NSURLErrorDomain] &&
|
|
309
|
+
FFFEnteredBackgroundAt > startedAt && !weakSelf.retriedAfterBackground &&
|
|
310
|
+
weakSelf.source == source) {
|
|
311
|
+
weakSelf.retriedAfterBackground = YES;
|
|
312
|
+
[weakSelf downloadWhenActive: source options: options context: context];
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
279
315
|
if (error) {
|
|
280
316
|
weakSelf.hasErrored = YES;
|
|
281
317
|
if (weakSelf.onFastImageError) {
|
|
@@ -294,7 +330,37 @@
|
|
|
294
330
|
}];
|
|
295
331
|
}
|
|
296
332
|
|
|
333
|
+
// Downloads the source now if the app is active, or once it is (unless
|
|
334
|
+
// another load started meanwhile).
|
|
335
|
+
- (void) downloadWhenActive: (FFFastImageSource*)source options: (SDWebImageOptions)options context: (SDWebImageContext*)context {
|
|
336
|
+
// nil in app extensions, which don't get these notifications.
|
|
337
|
+
UIApplication* application = RCTSharedApplication();
|
|
338
|
+
if (!application || application.applicationState == UIApplicationStateActive) {
|
|
339
|
+
[self downloadImage: source options: options context: context];
|
|
340
|
+
return;
|
|
341
|
+
}
|
|
342
|
+
[self stopWaitingForActive];
|
|
343
|
+
__weak typeof(self) weakSelf = self;
|
|
344
|
+
self.activeObserver = [[NSNotificationCenter defaultCenter] addObserverForName: UIApplicationDidBecomeActiveNotification
|
|
345
|
+
object: nil
|
|
346
|
+
queue: [NSOperationQueue mainQueue]
|
|
347
|
+
usingBlock: ^(NSNotification* notification) {
|
|
348
|
+
[weakSelf stopWaitingForActive];
|
|
349
|
+
if (weakSelf.source == source) {
|
|
350
|
+
[weakSelf downloadImage: source options: options context: context];
|
|
351
|
+
}
|
|
352
|
+
}];
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
- (void) stopWaitingForActive {
|
|
356
|
+
if (self.activeObserver) {
|
|
357
|
+
[[NSNotificationCenter defaultCenter] removeObserver: self.activeObserver];
|
|
358
|
+
self.activeObserver = nil;
|
|
359
|
+
}
|
|
360
|
+
}
|
|
361
|
+
|
|
297
362
|
- (void) dealloc {
|
|
363
|
+
[self stopWaitingForActive];
|
|
298
364
|
[self sd_cancelCurrentImageLoad];
|
|
299
365
|
}
|
|
300
366
|
|