mirror of
https://github.com/status-im/react-native-fast-image.git
synced 2025-02-23 11:48:16 +00:00
Add android progress callback.
This commit is contained in:
parent
ad4d8a5fe6
commit
c643347d4c
@ -10,26 +10,27 @@ buildscript {
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
mavenCentral()
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile 'com.facebook.react:react-native:+'
|
||||
compile 'com.github.bumptech.glide:glide:3.7.0'
|
||||
compile 'com.android.support:support-v4:19.1.0'
|
||||
compile 'com.facebook.react:react-native:+'
|
||||
compile group: 'com.github.bumptech.glide', name: 'glide', version: '3.8.0'
|
||||
compile group: 'com.github.bumptech.glide', name: 'okhttp3-integration', version: '1.5.0'
|
||||
compile 'com.android.support:support-v4:19.1.0'
|
||||
}
|
||||
|
@ -1,5 +1,17 @@
|
||||
<manifest
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.dylanvann.fastimage"
|
||||
>
|
||||
>
|
||||
<application>
|
||||
<meta-data
|
||||
android:name="com.dylanvann.fastimage.OkHttpProgressGlideModule"
|
||||
android:value="GlideModule"
|
||||
/>
|
||||
<meta-data
|
||||
android:name="com.bumptech.glide.integration.okhttp.OkHttpGlideModule"
|
||||
tools:node="remove"
|
||||
android:value="GlideModule"
|
||||
/>
|
||||
</application>
|
||||
</manifest>
|
||||
|
@ -7,9 +7,7 @@ import android.widget.ImageView;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.Priority;
|
||||
import com.bumptech.glide.load.data.DataFetcher;
|
||||
import com.bumptech.glide.load.model.GlideUrl;
|
||||
import com.bumptech.glide.load.model.stream.StreamModelLoader;
|
||||
import com.bumptech.glide.load.resource.drawable.GlideDrawable;
|
||||
import com.bumptech.glide.request.RequestListener;
|
||||
import com.bumptech.glide.request.target.ImageViewTarget;
|
||||
@ -23,22 +21,26 @@ import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
class FastImageViewManager extends SimpleViewManager<ImageView> {
|
||||
class FastImageViewManager extends SimpleViewManager<ImageView> implements UIProgressListener {
|
||||
|
||||
private static final String REACT_CLASS = "FastImageView";
|
||||
|
||||
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;
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
@ -46,7 +48,8 @@ class FastImageViewManager extends SimpleViewManager<ImageView> {
|
||||
|
||||
@Override
|
||||
protected ImageView createViewInstance(ThemedReactContext reactContext) {
|
||||
return new ImageView(reactContext);
|
||||
imageView = new ImageView(reactContext);
|
||||
return imageView;
|
||||
}
|
||||
|
||||
private static RequestListener<GlideUrl, GlideDrawable> LISTENER = new RequestListener<GlideUrl, GlideDrawable>() {
|
||||
@ -57,6 +60,7 @@ class FastImageViewManager extends SimpleViewManager<ImageView> {
|
||||
Target<GlideDrawable> target,
|
||||
boolean isFirstResource
|
||||
) {
|
||||
OkHttpProgressGlideModule.forget(uri.toStringUrl());
|
||||
if (!(target instanceof ImageViewTarget)) {
|
||||
return false;
|
||||
}
|
||||
@ -95,13 +99,14 @@ class FastImageViewManager extends SimpleViewManager<ImageView> {
|
||||
if (source == null) {
|
||||
// Cancel existing requests.
|
||||
Glide.clear(view);
|
||||
OkHttpProgressGlideModule.forget(glideUrl.toStringUrl());
|
||||
// Clear the image.
|
||||
view.setImageDrawable(null);
|
||||
return;
|
||||
}
|
||||
|
||||
// Get the GlideUrl which contains header info.
|
||||
final GlideUrl glideUrl = FastImageViewConverter.glideUrl(source);
|
||||
glideUrl = FastImageViewConverter.glideUrl(source);
|
||||
|
||||
// Get priority.
|
||||
final Priority priority = FastImageViewConverter.priority(source);
|
||||
@ -109,13 +114,16 @@ class FastImageViewManager extends SimpleViewManager<ImageView> {
|
||||
// Cancel existing request.
|
||||
Glide.clear(view);
|
||||
|
||||
String key = glideUrl.toStringUrl();
|
||||
OkHttpProgressGlideModule.expect(key, this);
|
||||
|
||||
Glide
|
||||
.with(view.getContext())
|
||||
.load(glideUrl)
|
||||
.priority(priority)
|
||||
.placeholder(TRANSPARENT_DRAWABLE)
|
||||
.listener(LISTENER)
|
||||
.into(view);
|
||||
.into(imageView);
|
||||
}
|
||||
|
||||
@ReactProp(name = "resizeMode")
|
||||
@ -128,6 +136,7 @@ class FastImageViewManager extends SimpleViewManager<ImageView> {
|
||||
public void onDropViewInstance(ImageView view) {
|
||||
// This will cancel existing requests.
|
||||
Glide.clear(view);
|
||||
OkHttpProgressGlideModule.forget(glideUrl.toString());
|
||||
super.onDropViewInstance(view);
|
||||
}
|
||||
|
||||
@ -135,6 +144,8 @@ class FastImageViewManager extends SimpleViewManager<ImageView> {
|
||||
@Nullable
|
||||
public Map getExportedCustomDirectEventTypeConstants() {
|
||||
return MapBuilder.of(
|
||||
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,
|
||||
@ -142,31 +153,20 @@ class FastImageViewManager extends SimpleViewManager<ImageView> {
|
||||
);
|
||||
}
|
||||
|
||||
// Used to attempt to load from cache only.
|
||||
private static final StreamModelLoader<GlideUrl> cacheOnlyStreamLoader = new StreamModelLoader<GlideUrl>() {
|
||||
@Override
|
||||
public DataFetcher<InputStream> getResourceFetcher(final GlideUrl model, int width, int height) {
|
||||
return new DataFetcher<InputStream>() {
|
||||
@Override
|
||||
public InputStream loadData(Priority priority) throws Exception {
|
||||
throw new IOException();
|
||||
}
|
||||
@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);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cleanup() {
|
||||
@Override
|
||||
public float getGranularityPercentage() {
|
||||
return 0.5f;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getId() {
|
||||
return model.getCacheKey();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void cancel() {
|
||||
|
||||
}
|
||||
};
|
||||
}
|
||||
};
|
||||
}
|
||||
|
@ -0,0 +1,179 @@
|
||||
package com.dylanvann.fastimage;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
|
||||
import com.bumptech.glide.Glide;
|
||||
import com.bumptech.glide.GlideBuilder;
|
||||
import com.bumptech.glide.integration.okhttp3.OkHttpUrlLoader;
|
||||
import com.bumptech.glide.load.model.GlideUrl;
|
||||
import com.bumptech.glide.module.GlideModule;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import okhttp3.Interceptor;
|
||||
import okhttp3.MediaType;
|
||||
import okhttp3.OkHttpClient;
|
||||
import okhttp3.Request;
|
||||
import okhttp3.Response;
|
||||
import okhttp3.ResponseBody;
|
||||
import okio.Buffer;
|
||||
import okio.BufferedSource;
|
||||
import okio.ForwardingSource;
|
||||
import okio.Okio;
|
||||
import okio.Source;
|
||||
|
||||
public class OkHttpProgressGlideModule implements GlideModule {
|
||||
|
||||
@Override
|
||||
public void applyOptions(Context context, GlideBuilder builder) { }
|
||||
|
||||
@Override
|
||||
public void registerComponents(Context context, Glide glide) {
|
||||
OkHttpClient client = new OkHttpClient
|
||||
.Builder()
|
||||
.addInterceptor(createInterceptor(new DispatchingProgressListener()))
|
||||
.build();
|
||||
glide.register(GlideUrl.class, InputStream.class, new OkHttpUrlLoader.Factory(client));
|
||||
}
|
||||
|
||||
private static Interceptor createInterceptor(final ResponseProgressListener listener) {
|
||||
return new Interceptor() {
|
||||
@Override
|
||||
public Response intercept(Chain chain) throws IOException {
|
||||
Request request = chain.request();
|
||||
Response response = chain.proceed(request);
|
||||
final String key = request.url().toString();
|
||||
return response
|
||||
.newBuilder()
|
||||
.body(new OkHttpProgressResponseBody(key, response.body(), listener))
|
||||
.build();
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
public static void forget(String url) {
|
||||
DispatchingProgressListener.forget(url);
|
||||
}
|
||||
|
||||
public static void expect(String url, UIProgressListener listener) {
|
||||
DispatchingProgressListener.expect(url, listener);
|
||||
}
|
||||
|
||||
private interface ResponseProgressListener {
|
||||
void update(String key, long bytesRead, long contentLength);
|
||||
}
|
||||
|
||||
private static class DispatchingProgressListener implements ResponseProgressListener {
|
||||
private static final Map<String, UIProgressListener> LISTENERS = new HashMap<>();
|
||||
private static final Map<String, Long> PROGRESSES = new HashMap<>();
|
||||
|
||||
private final Handler handler;
|
||||
|
||||
DispatchingProgressListener() {
|
||||
this.handler = new Handler(Looper.getMainLooper());
|
||||
}
|
||||
|
||||
static void forget(String url) {
|
||||
LISTENERS.remove(url);
|
||||
PROGRESSES.remove(url);
|
||||
}
|
||||
|
||||
static void expect(String url, UIProgressListener listener) {
|
||||
LISTENERS.put(url, listener);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void update(String key, final long bytesRead, final long contentLength) {
|
||||
final UIProgressListener listener = LISTENERS.get(key);
|
||||
if (listener == null) {
|
||||
return;
|
||||
}
|
||||
if (contentLength <= bytesRead) {
|
||||
forget(key);
|
||||
}
|
||||
if (needsDispatch(key, bytesRead, contentLength, listener.getGranularityPercentage())) {
|
||||
handler.post(new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
listener.onProgress(bytesRead, contentLength);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private boolean needsDispatch(String key, long current, long total, float granularity) {
|
||||
if (granularity == 0 || current == 0 || total == current) {
|
||||
return true;
|
||||
}
|
||||
float percent = 100f * current / total;
|
||||
long currentProgress = (long) (percent / granularity);
|
||||
Long lastProgress = PROGRESSES.get(key);
|
||||
if (lastProgress == null || currentProgress != lastProgress) {
|
||||
PROGRESSES.put(key, currentProgress);
|
||||
return true;
|
||||
} else {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static class OkHttpProgressResponseBody extends ResponseBody {
|
||||
private final String key;
|
||||
private final ResponseBody responseBody;
|
||||
private final ResponseProgressListener progressListener;
|
||||
private BufferedSource bufferedSource;
|
||||
|
||||
OkHttpProgressResponseBody(
|
||||
String key,
|
||||
ResponseBody responseBody,
|
||||
ResponseProgressListener progressListener
|
||||
) {
|
||||
this.key = key;
|
||||
this.responseBody = responseBody;
|
||||
this.progressListener = progressListener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public MediaType contentType() {
|
||||
return responseBody.contentType();
|
||||
}
|
||||
|
||||
@Override
|
||||
public long contentLength() {
|
||||
return responseBody.contentLength();
|
||||
}
|
||||
|
||||
@Override
|
||||
public BufferedSource source() {
|
||||
if (bufferedSource == null) {
|
||||
bufferedSource = Okio.buffer(source(responseBody.source()));
|
||||
}
|
||||
return bufferedSource;
|
||||
}
|
||||
|
||||
private Source source(Source source) {
|
||||
return new ForwardingSource(source) {
|
||||
long totalBytesRead = 0L;
|
||||
|
||||
@Override
|
||||
public long read(Buffer sink, long byteCount) throws IOException {
|
||||
long bytesRead = super.read(sink, byteCount);
|
||||
long fullLength = responseBody.contentLength();
|
||||
if (bytesRead == -1) {
|
||||
// this source is exhausted
|
||||
totalBytesRead = fullLength;
|
||||
} else {
|
||||
totalBytesRead += bytesRead;
|
||||
}
|
||||
progressListener.update(key, totalBytesRead, fullLength);
|
||||
return bytesRead;
|
||||
}
|
||||
};
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,14 @@
|
||||
package com.dylanvann.fastimage;
|
||||
|
||||
public interface UIProgressListener {
|
||||
|
||||
void onProgress(long bytesRead, long expectedLength);
|
||||
|
||||
/**
|
||||
* Control how often the listener needs an update. 0% and 100% will always be dispatched.
|
||||
*
|
||||
* @return in percentage (0.2 = call {@link #onProgress} around every 0.2 percent of progress)
|
||||
*/
|
||||
float getGranularityPercentage();
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user