mirror of
https://github.com/status-im/react-native-fast-image.git
synced 2025-02-23 11:48:16 +00:00
Make android and iOS view loading events conform to API of React Native's Image.
This will make using this library as a drop in replacement easier. This commit also fixes some issues in the android progress implementation.
This commit is contained in:
parent
6c720f5020
commit
1666f7c4e5
@ -1,5 +1,5 @@
|
|||||||
import React, { Component } from 'react'
|
import React, { Component } from 'react'
|
||||||
import PropTypes from 'prop-types';
|
import PropTypes from 'prop-types'
|
||||||
import {
|
import {
|
||||||
Image,
|
Image,
|
||||||
NativeModules,
|
NativeModules,
|
||||||
@ -16,7 +16,7 @@ class FastImage extends Component {
|
|||||||
this._root.setNativeProps(nativeProps)
|
this._root.setNativeProps(nativeProps)
|
||||||
}
|
}
|
||||||
|
|
||||||
render () {
|
render() {
|
||||||
const {
|
const {
|
||||||
source,
|
source,
|
||||||
onLoadStart,
|
onLoadStart,
|
||||||
@ -24,7 +24,7 @@ class FastImage extends Component {
|
|||||||
onLoad,
|
onLoad,
|
||||||
onError,
|
onError,
|
||||||
onLoadEnd,
|
onLoadEnd,
|
||||||
...props,
|
...props
|
||||||
} = this.props
|
} = this.props
|
||||||
|
|
||||||
// If there's no source or source uri just fallback to Image.
|
// If there's no source or source uri just fallback to Image.
|
||||||
|
20
README.md
20
README.md
@ -99,6 +99,20 @@ Headers to load the image with. e.g. `{ Authorization: 'someAuthToken' }`.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### `onLoadStart?: () => void`
|
||||||
|
|
||||||
|
Called when the image starts to load.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
### `onProgress?: (event) => void`
|
||||||
|
|
||||||
|
Called when the image is loading.
|
||||||
|
|
||||||
|
e.g. `onProgress={e => console.log(e.nativeEvent.loaded / e.nativeEvent.total)}`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### `onLoad?: () => void`
|
### `onLoad?: () => void`
|
||||||
|
|
||||||
Called on a successful image fetch.
|
Called on a successful image fetch.
|
||||||
@ -111,6 +125,12 @@ Called on an image fetching error.
|
|||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
### `onLoadEnd?: () => void`
|
||||||
|
|
||||||
|
Called when the image finishes loading, whether it was successful or an error.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
### `children`
|
### `children`
|
||||||
|
|
||||||
`FastImage` does not currently support children.
|
`FastImage` does not currently support children.
|
||||||
|
@ -2,9 +2,11 @@
|
|||||||
|
|
||||||
exports[`FastImage renders correctly. 1`] = `
|
exports[`FastImage renders correctly. 1`] = `
|
||||||
<FastImageView
|
<FastImageView
|
||||||
onFastImageError={[Function]}
|
onFastImageError={undefined}
|
||||||
onFastImageLoad={[Function]}
|
onFastImageLoad={undefined}
|
||||||
onFastImageProgress={[Function]}
|
onFastImageLoadEnd={undefined}
|
||||||
|
onFastImageLoadStart={undefined}
|
||||||
|
onFastImageProgress={undefined}
|
||||||
resizeMode="cover"
|
resizeMode="cover"
|
||||||
source={
|
source={
|
||||||
Object {
|
Object {
|
||||||
|
@ -1,5 +1,6 @@
|
|||||||
package com.dylanvann.fastimage;
|
package com.dylanvann.fastimage;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import android.graphics.drawable.ColorDrawable;
|
import android.graphics.drawable.ColorDrawable;
|
||||||
import android.graphics.drawable.Drawable;
|
import android.graphics.drawable.Drawable;
|
||||||
@ -21,25 +22,31 @@ import com.facebook.react.uimanager.ThemedReactContext;
|
|||||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||||
|
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
import javax.annotation.Nullable;
|
import javax.annotation.Nullable;
|
||||||
|
|
||||||
class FastImageViewManager extends SimpleViewManager<ImageView> implements UIProgressListener {
|
class ImageViewWithUrl extends ImageView {
|
||||||
|
public GlideUrl glideUrl;
|
||||||
|
|
||||||
|
public ImageViewWithUrl(Context context) {
|
||||||
|
super(context);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
class FastImageViewManager extends SimpleViewManager<ImageViewWithUrl> implements ProgressListener {
|
||||||
|
|
||||||
private static final String REACT_CLASS = "FastImageView";
|
private static final String REACT_CLASS = "FastImageView";
|
||||||
|
private static final String REACT_ON_LOAD_START_EVENT = "onFastImageLoadStart";
|
||||||
private static final String REACT_ON_PROGRESS_EVENT = "onFastImageProgress";
|
private static final String REACT_ON_PROGRESS_EVENT = "onFastImageProgress";
|
||||||
|
|
||||||
private static final String REACT_ON_LOAD_EVENT = "onFastImageLoad";
|
|
||||||
|
|
||||||
private static final String REACT_ON_ERROR_EVENT = "onFastImageError";
|
private static final String REACT_ON_ERROR_EVENT = "onFastImageError";
|
||||||
|
private static final String REACT_ON_LOAD_EVENT = "onFastImageLoad";
|
||||||
private static Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
|
private static final String REACT_ON_LOAD_END_EVENT = "onFastImageLoadEnd";
|
||||||
|
private static final Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
|
||||||
private ImageView imageView;
|
private static final Map<String, List<ImageViewWithUrl>> VIEWS_FOR_URLS = new HashMap<>();
|
||||||
|
|
||||||
private GlideUrl glideUrl;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public String getName() {
|
public String getName() {
|
||||||
@ -47,9 +54,8 @@ class FastImageViewManager extends SimpleViewManager<ImageView> implements UIPro
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected ImageView createViewInstance(ThemedReactContext reactContext) {
|
protected ImageViewWithUrl createViewInstance(ThemedReactContext reactContext) {
|
||||||
imageView = new ImageView(reactContext);
|
return new ImageViewWithUrl(reactContext);
|
||||||
return imageView;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private static RequestListener<GlideUrl, GlideDrawable> LISTENER = new RequestListener<GlideUrl, GlideDrawable>() {
|
private static RequestListener<GlideUrl, GlideDrawable> LISTENER = new RequestListener<GlideUrl, GlideDrawable>() {
|
||||||
@ -64,12 +70,12 @@ class FastImageViewManager extends SimpleViewManager<ImageView> implements UIPro
|
|||||||
if (!(target instanceof ImageViewTarget)) {
|
if (!(target instanceof ImageViewTarget)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ImageView view = (ImageView) ((ImageViewTarget) target).getView();
|
ImageViewWithUrl view = (ImageViewWithUrl) ((ImageViewTarget) target).getView();
|
||||||
WritableMap event = new WritableNativeMap();
|
|
||||||
ThemedReactContext context = (ThemedReactContext) view.getContext();
|
ThemedReactContext context = (ThemedReactContext) view.getContext();
|
||||||
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
|
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
|
||||||
int viewId = view.getId();
|
int viewId = view.getId();
|
||||||
eventEmitter.receiveEvent(viewId, REACT_ON_ERROR_EVENT, event);
|
eventEmitter.receiveEvent(viewId, REACT_ON_ERROR_EVENT, new WritableNativeMap());
|
||||||
|
eventEmitter.receiveEvent(viewId, REACT_ON_LOAD_END_EVENT, new WritableNativeMap());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,29 +90,32 @@ class FastImageViewManager extends SimpleViewManager<ImageView> implements UIPro
|
|||||||
if (!(target instanceof ImageViewTarget)) {
|
if (!(target instanceof ImageViewTarget)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
ImageView view = (ImageView) ((ImageViewTarget) target).getView();
|
ImageViewWithUrl view = (ImageViewWithUrl) ((ImageViewTarget) target).getView();
|
||||||
WritableMap event = new WritableNativeMap();
|
|
||||||
ThemedReactContext context = (ThemedReactContext) view.getContext();
|
ThemedReactContext context = (ThemedReactContext) view.getContext();
|
||||||
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
|
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
|
||||||
int viewId = view.getId();
|
int viewId = view.getId();
|
||||||
eventEmitter.receiveEvent(viewId, REACT_ON_LOAD_EVENT, event);
|
eventEmitter.receiveEvent(viewId, REACT_ON_LOAD_EVENT, new WritableNativeMap());
|
||||||
|
eventEmitter.receiveEvent(viewId, REACT_ON_LOAD_END_EVENT, new WritableNativeMap());
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
@ReactProp(name = "source")
|
@ReactProp(name = "source")
|
||||||
public void setSrc(ImageView view, @Nullable ReadableMap source) {
|
public void setSrc(ImageViewWithUrl view, @Nullable ReadableMap source) {
|
||||||
if (source == null) {
|
if (source == null) {
|
||||||
// Cancel existing requests.
|
// Cancel existing requests.
|
||||||
Glide.clear(view);
|
Glide.clear(view);
|
||||||
OkHttpProgressGlideModule.forget(glideUrl.toStringUrl());
|
if (view.glideUrl != null) {
|
||||||
|
OkHttpProgressGlideModule.forget(view.glideUrl.toStringUrl());
|
||||||
|
}
|
||||||
// Clear the image.
|
// Clear the image.
|
||||||
view.setImageDrawable(null);
|
view.setImageDrawable(null);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Get the GlideUrl which contains header info.
|
// Get the GlideUrl which contains header info.
|
||||||
glideUrl = FastImageViewConverter.glideUrl(source);
|
GlideUrl glideUrl = FastImageViewConverter.glideUrl(source);
|
||||||
|
view.glideUrl = glideUrl;
|
||||||
|
|
||||||
// Get priority.
|
// Get priority.
|
||||||
final Priority priority = FastImageViewConverter.priority(source);
|
final Priority priority = FastImageViewConverter.priority(source);
|
||||||
@ -116,6 +125,17 @@ class FastImageViewManager extends SimpleViewManager<ImageView> implements UIPro
|
|||||||
|
|
||||||
String key = glideUrl.toStringUrl();
|
String key = glideUrl.toStringUrl();
|
||||||
OkHttpProgressGlideModule.expect(key, this);
|
OkHttpProgressGlideModule.expect(key, this);
|
||||||
|
List<ImageViewWithUrl> viewsForKey = VIEWS_FOR_URLS.get(key);
|
||||||
|
if (viewsForKey != null && !viewsForKey.contains(view)) {
|
||||||
|
viewsForKey.add(view);
|
||||||
|
} else if (viewsForKey == null) {
|
||||||
|
VIEWS_FOR_URLS.put(key, Collections.singletonList(view));
|
||||||
|
}
|
||||||
|
|
||||||
|
ThemedReactContext context = (ThemedReactContext) view.getContext();
|
||||||
|
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
|
||||||
|
int viewId = view.getId();
|
||||||
|
eventEmitter.receiveEvent(viewId, REACT_ON_LOAD_START_EVENT, new WritableNativeMap());
|
||||||
|
|
||||||
Glide
|
Glide
|
||||||
.with(view.getContext())
|
.with(view.getContext())
|
||||||
@ -123,20 +143,26 @@ class FastImageViewManager extends SimpleViewManager<ImageView> implements UIPro
|
|||||||
.priority(priority)
|
.priority(priority)
|
||||||
.placeholder(TRANSPARENT_DRAWABLE)
|
.placeholder(TRANSPARENT_DRAWABLE)
|
||||||
.listener(LISTENER)
|
.listener(LISTENER)
|
||||||
.into(imageView);
|
.into(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ReactProp(name = "resizeMode")
|
@ReactProp(name = "resizeMode")
|
||||||
public void setResizeMode(ImageView view, String resizeMode) {
|
public void setResizeMode(ImageViewWithUrl view, String resizeMode) {
|
||||||
final ImageView.ScaleType scaleType = FastImageViewConverter.scaleType(resizeMode);
|
final ImageViewWithUrl.ScaleType scaleType = FastImageViewConverter.scaleType(resizeMode);
|
||||||
view.setScaleType(scaleType);
|
view.setScaleType(scaleType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDropViewInstance(ImageView view) {
|
public void onDropViewInstance(ImageViewWithUrl view) {
|
||||||
// This will cancel existing requests.
|
// This will cancel existing requests.
|
||||||
Glide.clear(view);
|
Glide.clear(view);
|
||||||
OkHttpProgressGlideModule.forget(glideUrl.toString());
|
final String key = view.glideUrl.toString();
|
||||||
|
OkHttpProgressGlideModule.forget(key);
|
||||||
|
List<ImageViewWithUrl> viewsForKey = VIEWS_FOR_URLS.get(key);
|
||||||
|
if (viewsForKey != null) {
|
||||||
|
viewsForKey.remove(view);
|
||||||
|
if (viewsForKey.size() == 0) VIEWS_FOR_URLS.remove(key);
|
||||||
|
}
|
||||||
super.onDropViewInstance(view);
|
super.onDropViewInstance(view);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,24 +170,33 @@ class FastImageViewManager extends SimpleViewManager<ImageView> implements UIPro
|
|||||||
@Nullable
|
@Nullable
|
||||||
public Map getExportedCustomDirectEventTypeConstants() {
|
public Map getExportedCustomDirectEventTypeConstants() {
|
||||||
return MapBuilder.of(
|
return MapBuilder.of(
|
||||||
|
REACT_ON_LOAD_START_EVENT,
|
||||||
|
MapBuilder.of("registrationName", REACT_ON_LOAD_START_EVENT),
|
||||||
REACT_ON_PROGRESS_EVENT,
|
REACT_ON_PROGRESS_EVENT,
|
||||||
MapBuilder.of("registrationName", REACT_ON_PROGRESS_EVENT),
|
MapBuilder.of("registrationName", REACT_ON_PROGRESS_EVENT),
|
||||||
REACT_ON_LOAD_EVENT,
|
REACT_ON_LOAD_EVENT,
|
||||||
MapBuilder.of("registrationName", REACT_ON_LOAD_EVENT),
|
MapBuilder.of("registrationName", REACT_ON_LOAD_EVENT),
|
||||||
REACT_ON_ERROR_EVENT,
|
REACT_ON_ERROR_EVENT,
|
||||||
MapBuilder.of("registrationName", REACT_ON_ERROR_EVENT)
|
MapBuilder.of("registrationName", REACT_ON_ERROR_EVENT),
|
||||||
|
REACT_ON_LOAD_END_EVENT,
|
||||||
|
MapBuilder.of("registrationName", REACT_ON_LOAD_END_EVENT)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onProgress(long bytesRead, long expectedLength) {
|
public void onProgress(String key, long bytesRead, long expectedLength) {
|
||||||
WritableMap event = new WritableNativeMap();
|
List<ImageViewWithUrl> viewsForKey = VIEWS_FOR_URLS.get(key);
|
||||||
double progress = ((float) bytesRead / (float) expectedLength) * 100;
|
if (viewsForKey != null) {
|
||||||
event.putDouble("progress", progress);
|
for (ImageViewWithUrl view: viewsForKey) {
|
||||||
ThemedReactContext context = (ThemedReactContext) imageView.getContext();
|
WritableMap event = new WritableNativeMap();
|
||||||
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
|
event.putInt("loaded", (int) bytesRead);
|
||||||
int viewId = imageView.getId();
|
event.putInt("total", (int) expectedLength);
|
||||||
eventEmitter.receiveEvent(viewId, REACT_ON_PROGRESS_EVENT, event);
|
ThemedReactContext context = (ThemedReactContext) view.getContext();
|
||||||
|
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
|
||||||
|
int viewId = view.getId();
|
||||||
|
eventEmitter.receiveEvent(viewId, REACT_ON_PROGRESS_EVENT, event);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -56,12 +56,12 @@ public class OkHttpProgressGlideModule implements GlideModule {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void forget(String url) {
|
public static void forget(String key) {
|
||||||
DispatchingProgressListener.forget(url);
|
DispatchingProgressListener.forget(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static void expect(String url, UIProgressListener listener) {
|
public static void expect(String key, ProgressListener listener) {
|
||||||
DispatchingProgressListener.expect(url, listener);
|
DispatchingProgressListener.expect(key, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
private interface ResponseProgressListener {
|
private interface ResponseProgressListener {
|
||||||
@ -69,7 +69,7 @@ public class OkHttpProgressGlideModule implements GlideModule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static class DispatchingProgressListener implements ResponseProgressListener {
|
private static class DispatchingProgressListener implements ResponseProgressListener {
|
||||||
private static final Map<String, UIProgressListener> LISTENERS = new HashMap<>();
|
private static final Map<String, ProgressListener> LISTENERS = new HashMap<>();
|
||||||
private static final Map<String, Long> PROGRESSES = new HashMap<>();
|
private static final Map<String, Long> PROGRESSES = new HashMap<>();
|
||||||
|
|
||||||
private final Handler handler;
|
private final Handler handler;
|
||||||
@ -78,18 +78,18 @@ public class OkHttpProgressGlideModule implements GlideModule {
|
|||||||
this.handler = new Handler(Looper.getMainLooper());
|
this.handler = new Handler(Looper.getMainLooper());
|
||||||
}
|
}
|
||||||
|
|
||||||
static void forget(String url) {
|
static void forget(String key) {
|
||||||
LISTENERS.remove(url);
|
LISTENERS.remove(key);
|
||||||
PROGRESSES.remove(url);
|
PROGRESSES.remove(key);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void expect(String url, UIProgressListener listener) {
|
static void expect(String key, ProgressListener listener) {
|
||||||
LISTENERS.put(url, listener);
|
LISTENERS.put(key, listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void update(String key, final long bytesRead, final long contentLength) {
|
public void update(final String key, final long bytesRead, final long contentLength) {
|
||||||
final UIProgressListener listener = LISTENERS.get(key);
|
final ProgressListener listener = LISTENERS.get(key);
|
||||||
if (listener == null) {
|
if (listener == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -100,7 +100,7 @@ public class OkHttpProgressGlideModule implements GlideModule {
|
|||||||
handler.post(new Runnable() {
|
handler.post(new Runnable() {
|
||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
listener.onProgress(bytesRead, contentLength);
|
listener.onProgress(key, bytesRead, contentLength);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
@ -111,7 +111,8 @@ class FastImageExample extends Component {
|
|||||||
priority: FastImage.priority.high,
|
priority: FastImage.priority.high,
|
||||||
}}
|
}}
|
||||||
onLoadStart={() => console.log('onLoadStart')}
|
onLoadStart={() => console.log('onLoadStart')}
|
||||||
onProgress={e => console.log(e.nativeEvent.progress)}
|
onProgress={e =>
|
||||||
|
console.log(e.nativeEvent.loaded / e.nativeEvent.total)}
|
||||||
onLoad={() => console.log('onLoad')}
|
onLoad={() => console.log('onLoad')}
|
||||||
onError={() => console.log('onError')}
|
onError={() => console.log('onError')}
|
||||||
onLoadEnd={() => console.log('onLoadEnd')}
|
onLoadEnd={() => console.log('onLoadEnd')}
|
||||||
|
@ -61,9 +61,11 @@
|
|||||||
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
|
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
|
||||||
double progress = MIN(1, MAX(0, (double) receivedSize / (double) expectedSize));
|
double progress = MIN(1, MAX(0, (double) receivedSize / (double) expectedSize));
|
||||||
if (_onFastImageProgress) {
|
if (_onFastImageProgress) {
|
||||||
_onFastImageProgress(@{ @"progress": @(progress) });
|
_onFastImageProgress(@{
|
||||||
|
@"loaded": @(receivedSize),
|
||||||
|
@"total": @(expectedSize)
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
} completed:^(UIImage * _Nullable image,
|
} completed:^(UIImage * _Nullable image,
|
||||||
NSError * _Nullable error,
|
NSError * _Nullable error,
|
||||||
SDImageCacheType cacheType,
|
SDImageCacheType cacheType,
|
||||||
|
Loading…
x
Reference in New Issue
Block a user