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 PropTypes from 'prop-types';
|
||||
import PropTypes from 'prop-types'
|
||||
import {
|
||||
Image,
|
||||
NativeModules,
|
||||
|
@ -16,7 +16,7 @@ class FastImage extends Component {
|
|||
this._root.setNativeProps(nativeProps)
|
||||
}
|
||||
|
||||
render () {
|
||||
render() {
|
||||
const {
|
||||
source,
|
||||
onLoadStart,
|
||||
|
@ -24,7 +24,7 @@ class FastImage extends Component {
|
|||
onLoad,
|
||||
onError,
|
||||
onLoadEnd,
|
||||
...props,
|
||||
...props
|
||||
} = this.props
|
||||
|
||||
// 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`
|
||||
|
||||
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`
|
||||
|
||||
`FastImage` does not currently support children.
|
||||
|
|
|
@ -2,9 +2,11 @@
|
|||
|
||||
exports[`FastImage renders correctly. 1`] = `
|
||||
<FastImageView
|
||||
onFastImageError={[Function]}
|
||||
onFastImageLoad={[Function]}
|
||||
onFastImageProgress={[Function]}
|
||||
onFastImageError={undefined}
|
||||
onFastImageLoad={undefined}
|
||||
onFastImageLoadEnd={undefined}
|
||||
onFastImageLoadStart={undefined}
|
||||
onFastImageProgress={undefined}
|
||||
resizeMode="cover"
|
||||
source={
|
||||
Object {
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package com.dylanvann.fastimage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.drawable.ColorDrawable;
|
||||
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.events.RCTEventEmitter;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
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_ON_LOAD_START_EVENT = "onFastImageLoadStart";
|
||||
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 Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
|
||||
|
||||
private ImageView imageView;
|
||||
|
||||
private GlideUrl glideUrl;
|
||||
private static final String REACT_ON_LOAD_EVENT = "onFastImageLoad";
|
||||
private static final String REACT_ON_LOAD_END_EVENT = "onFastImageLoadEnd";
|
||||
private static final Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);
|
||||
private static final Map<String, List<ImageViewWithUrl>> VIEWS_FOR_URLS = new HashMap<>();
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
|
@ -47,9 +54,8 @@ class FastImageViewManager extends SimpleViewManager<ImageView> implements UIPro
|
|||
}
|
||||
|
||||
@Override
|
||||
protected ImageView createViewInstance(ThemedReactContext reactContext) {
|
||||
imageView = new ImageView(reactContext);
|
||||
return imageView;
|
||||
protected ImageViewWithUrl createViewInstance(ThemedReactContext reactContext) {
|
||||
return new ImageViewWithUrl(reactContext);
|
||||
}
|
||||
|
||||
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)) {
|
||||
return false;
|
||||
}
|
||||
ImageView view = (ImageView) ((ImageViewTarget) target).getView();
|
||||
WritableMap event = new WritableNativeMap();
|
||||
ImageViewWithUrl view = (ImageViewWithUrl) ((ImageViewTarget) target).getView();
|
||||
ThemedReactContext context = (ThemedReactContext) view.getContext();
|
||||
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -84,29 +90,32 @@ class FastImageViewManager extends SimpleViewManager<ImageView> implements UIPro
|
|||
if (!(target instanceof ImageViewTarget)) {
|
||||
return false;
|
||||
}
|
||||
ImageView view = (ImageView) ((ImageViewTarget) target).getView();
|
||||
WritableMap event = new WritableNativeMap();
|
||||
ImageViewWithUrl view = (ImageViewWithUrl) ((ImageViewTarget) target).getView();
|
||||
ThemedReactContext context = (ThemedReactContext) view.getContext();
|
||||
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
|
||||
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;
|
||||
}
|
||||
};
|
||||
|
||||
@ReactProp(name = "source")
|
||||
public void setSrc(ImageView view, @Nullable ReadableMap source) {
|
||||
public void setSrc(ImageViewWithUrl view, @Nullable ReadableMap source) {
|
||||
if (source == null) {
|
||||
// Cancel existing requests.
|
||||
Glide.clear(view);
|
||||
OkHttpProgressGlideModule.forget(glideUrl.toStringUrl());
|
||||
if (view.glideUrl != null) {
|
||||
OkHttpProgressGlideModule.forget(view.glideUrl.toStringUrl());
|
||||
}
|
||||
// Clear the image.
|
||||
view.setImageDrawable(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the GlideUrl which contains header info.
|
||||
glideUrl = FastImageViewConverter.glideUrl(source);
|
||||
GlideUrl glideUrl = FastImageViewConverter.glideUrl(source);
|
||||
view.glideUrl = glideUrl;
|
||||
|
||||
// Get priority.
|
||||
final Priority priority = FastImageViewConverter.priority(source);
|
||||
|
@ -116,6 +125,17 @@ class FastImageViewManager extends SimpleViewManager<ImageView> implements UIPro
|
|||
|
||||
String key = glideUrl.toStringUrl();
|
||||
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
|
||||
.with(view.getContext())
|
||||
|
@ -123,20 +143,26 @@ class FastImageViewManager extends SimpleViewManager<ImageView> implements UIPro
|
|||
.priority(priority)
|
||||
.placeholder(TRANSPARENT_DRAWABLE)
|
||||
.listener(LISTENER)
|
||||
.into(imageView);
|
||||
.into(view);
|
||||
}
|
||||
|
||||
@ReactProp(name = "resizeMode")
|
||||
public void setResizeMode(ImageView view, String resizeMode) {
|
||||
final ImageView.ScaleType scaleType = FastImageViewConverter.scaleType(resizeMode);
|
||||
public void setResizeMode(ImageViewWithUrl view, String resizeMode) {
|
||||
final ImageViewWithUrl.ScaleType scaleType = FastImageViewConverter.scaleType(resizeMode);
|
||||
view.setScaleType(scaleType);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDropViewInstance(ImageView view) {
|
||||
public void onDropViewInstance(ImageViewWithUrl view) {
|
||||
// This will cancel existing requests.
|
||||
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);
|
||||
}
|
||||
|
||||
|
@ -144,24 +170,33 @@ class FastImageViewManager extends SimpleViewManager<ImageView> implements UIPro
|
|||
@Nullable
|
||||
public Map getExportedCustomDirectEventTypeConstants() {
|
||||
return MapBuilder.of(
|
||||
REACT_ON_LOAD_START_EVENT,
|
||||
MapBuilder.of("registrationName", REACT_ON_LOAD_START_EVENT),
|
||||
REACT_ON_PROGRESS_EVENT,
|
||||
MapBuilder.of("registrationName", REACT_ON_PROGRESS_EVENT),
|
||||
REACT_ON_LOAD_EVENT,
|
||||
MapBuilder.of("registrationName", REACT_ON_LOAD_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
|
||||
public void onProgress(long bytesRead, long expectedLength) {
|
||||
WritableMap event = new WritableNativeMap();
|
||||
double progress = ((float) bytesRead / (float) expectedLength) * 100;
|
||||
event.putDouble("progress", progress);
|
||||
ThemedReactContext context = (ThemedReactContext) imageView.getContext();
|
||||
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
|
||||
int viewId = imageView.getId();
|
||||
eventEmitter.receiveEvent(viewId, REACT_ON_PROGRESS_EVENT, event);
|
||||
public void onProgress(String key, long bytesRead, long expectedLength) {
|
||||
List<ImageViewWithUrl> viewsForKey = VIEWS_FOR_URLS.get(key);
|
||||
if (viewsForKey != null) {
|
||||
for (ImageViewWithUrl view: viewsForKey) {
|
||||
WritableMap event = new WritableNativeMap();
|
||||
event.putInt("loaded", (int) bytesRead);
|
||||
event.putInt("total", (int) expectedLength);
|
||||
ThemedReactContext context = (ThemedReactContext) view.getContext();
|
||||
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
|
||||
int viewId = view.getId();
|
||||
eventEmitter.receiveEvent(viewId, REACT_ON_PROGRESS_EVENT, event);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
@ -56,12 +56,12 @@ public class OkHttpProgressGlideModule implements GlideModule {
|
|||
};
|
||||
}
|
||||
|
||||
public static void forget(String url) {
|
||||
DispatchingProgressListener.forget(url);
|
||||
public static void forget(String key) {
|
||||
DispatchingProgressListener.forget(key);
|
||||
}
|
||||
|
||||
public static void expect(String url, UIProgressListener listener) {
|
||||
DispatchingProgressListener.expect(url, listener);
|
||||
public static void expect(String key, ProgressListener listener) {
|
||||
DispatchingProgressListener.expect(key, listener);
|
||||
}
|
||||
|
||||
private interface ResponseProgressListener {
|
||||
|
@ -69,7 +69,7 @@ public class OkHttpProgressGlideModule implements GlideModule {
|
|||
}
|
||||
|
||||
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 final Handler handler;
|
||||
|
@ -78,18 +78,18 @@ public class OkHttpProgressGlideModule implements GlideModule {
|
|||
this.handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
static void forget(String url) {
|
||||
LISTENERS.remove(url);
|
||||
PROGRESSES.remove(url);
|
||||
static void forget(String key) {
|
||||
LISTENERS.remove(key);
|
||||
PROGRESSES.remove(key);
|
||||
}
|
||||
|
||||
static void expect(String url, UIProgressListener listener) {
|
||||
LISTENERS.put(url, listener);
|
||||
static void expect(String key, ProgressListener listener) {
|
||||
LISTENERS.put(key, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(String key, final long bytesRead, final long contentLength) {
|
||||
final UIProgressListener listener = LISTENERS.get(key);
|
||||
public void update(final String key, final long bytesRead, final long contentLength) {
|
||||
final ProgressListener listener = LISTENERS.get(key);
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
|
@ -100,7 +100,7 @@ public class OkHttpProgressGlideModule implements GlideModule {
|
|||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onProgress(bytesRead, contentLength);
|
||||
listener.onProgress(key, bytesRead, contentLength);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
|
|
@ -111,7 +111,8 @@ class FastImageExample extends Component {
|
|||
priority: FastImage.priority.high,
|
||||
}}
|
||||
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')}
|
||||
onError={() => console.log('onError')}
|
||||
onLoadEnd={() => console.log('onLoadEnd')}
|
||||
|
|
|
@ -61,9 +61,11 @@
|
|||
progress:^(NSInteger receivedSize, NSInteger expectedSize, NSURL * _Nullable targetURL) {
|
||||
double progress = MIN(1, MAX(0, (double) receivedSize / (double) expectedSize));
|
||||
if (_onFastImageProgress) {
|
||||
_onFastImageProgress(@{ @"progress": @(progress) });
|
||||
_onFastImageProgress(@{
|
||||
@"loaded": @(receivedSize),
|
||||
@"total": @(expectedSize)
|
||||
});
|
||||
}
|
||||
|
||||
} completed:^(UIImage * _Nullable image,
|
||||
NSError * _Nullable error,
|
||||
SDImageCacheType cacheType,
|
||||
|
|
Loading…
Reference in New Issue