Skip to content

Commit

Permalink
feat: support defaultSource on iOS and Android (DylanVann#921)
Browse files Browse the repository at this point in the history
Co-authored-by: Anton Sapozhnikov <a.sapozhnikov@sdventures.com>
Co-authored-by: Daniel Cohen Gindi <danielgindi@gmail.com>
Co-authored-by: 张贵广 <guiguangzhang@clubfactory.com>
  • Loading branch information
4 people authored Aug 29, 2022
1 parent cc9da99 commit ec7c453
Show file tree
Hide file tree
Showing 18 changed files with 600 additions and 261 deletions.
7 changes: 7 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,13 @@ Headers to load the image with. e.g. `{ Authorization: 'someAuthToken' }`.

---

### `defaultSource?: number`

- An asset loaded with `require(...)`.
- Note that like the built-in `Image` implementation, on Android `defaultSource` does not work in debug mode. This is due to the fact that assets are sent from the dev server, but RN's functions only know how to load it from `res`.

---

### `resizeMode?: enum`

- `FastImage.resizeMode.contain` - Scale the image uniformly (maintain the image's aspect ratio) so that both dimensions (width and height) of the image will be equal to or less than the corresponding dimension of the view (minus padding).
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@
@GlideModule
public class FastImageOkHttpProgressGlideModule extends LibraryGlideModule {

private static DispatchingProgressListener progressListener = new DispatchingProgressListener();
private static final DispatchingProgressListener progressListener = new DispatchingProgressListener();

@Override
public void registerComponents(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,7 @@ public class FastImageRequestListener implements RequestListener<Drawable> {
static final String REACT_ON_ERROR_EVENT = "onFastImageError";
static final String REACT_ON_LOAD_EVENT = "onFastImageLoad";
static final String REACT_ON_LOAD_END_EVENT = "onFastImageLoadEnd";

private String key;
private final String key;

FastImageRequestListener(String key) {
this.key = key;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ public class FastImageSource extends ImageSource {
private static final String ANDROID_RESOURCE_SCHEME = "android.resource";
private static final String ANDROID_CONTENT_SCHEME = "content";
private static final String LOCAL_FILE_SCHEME = "file";
private Headers mHeaders;
private final Headers mHeaders;
private Uri mUri;

public static boolean isBase64Uri(Uri uri) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,18 +1,16 @@
package com.dylanvann.fastimage;

import static com.bumptech.glide.request.RequestOptions.signatureOf;

import android.content.Context;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.media.Image;
import android.net.Uri;
import android.util.Log;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

import com.bumptech.glide.Priority;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.Headers;
import com.bumptech.glide.load.model.LazyHeaders;
import com.bumptech.glide.request.RequestOptions;
Expand All @@ -21,18 +19,12 @@
import com.facebook.react.bridge.NoSuchKeyException;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.views.imagehelper.ImageSource;

import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.HashMap;
import java.util.Map;

import javax.annotation.Nullable;

import static com.bumptech.glide.request.RequestOptions.signatureOf;

class FastImageViewConverter {
private static final Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);

Expand All @@ -57,10 +49,13 @@ class FastImageViewConverter {
put("stretch", ScaleType.FIT_XY);
put("center", ScaleType.CENTER_INSIDE);
}};

// Resolve the source uri to a file path that android understands.
static FastImageSource getImageSource(Context context, ReadableMap source) {
return new FastImageSource(context, source.getString("uri"), getHeaders(source));
static @Nullable
FastImageSource getImageSource(Context context, @Nullable ReadableMap source) {
return source == null
? null
: new FastImageSource(context, source.getString("uri"), getHeaders(source));
}

static Headers getHeaders(ReadableMap source) {
Expand Down Expand Up @@ -90,8 +85,8 @@ static RequestOptions getOptions(Context context, FastImageSource imageSource, R
// Get cache control method.
final FastImageCacheControl cacheControl = FastImageViewConverter.getCacheControl(source);
DiskCacheStrategy diskCacheStrategy = DiskCacheStrategy.AUTOMATIC;
Boolean onlyFromCache = false;
Boolean skipMemoryCache = false;
boolean onlyFromCache = false;
boolean skipMemoryCache = false;
switch (cacheControl) {
case WEB:
// If using none then OkHttp integration should be used for caching.
Expand All @@ -107,12 +102,12 @@ static RequestOptions getOptions(Context context, FastImageSource imageSource, R
}

RequestOptions options = new RequestOptions()
.diskCacheStrategy(diskCacheStrategy)
.onlyRetrieveFromCache(onlyFromCache)
.skipMemoryCache(skipMemoryCache)
.priority(priority)
.placeholder(TRANSPARENT_DRAWABLE);
.diskCacheStrategy(diskCacheStrategy)
.onlyRetrieveFromCache(onlyFromCache)
.skipMemoryCache(skipMemoryCache)
.priority(priority)
.placeholder(TRANSPARENT_DRAWABLE);

if (imageSource.isResource()) {
// Every local resource (drawable) in Android has its own unique numeric id, which are
// generated at build time. Although these ids are unique, they are not guaranteed unique
Expand All @@ -123,7 +118,7 @@ static RequestOptions getOptions(Context context, FastImageSource imageSource, R
options = options.apply(signatureOf(ApplicationVersionSignature.obtain(context)));
}

return options;
return options;
}

private static FastImageCacheControl getCacheControl(ReadableMap source) {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,15 +1,19 @@
package com.dylanvann.fastimage;

import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_ERROR_EVENT;
import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_LOAD_END_EVENT;
import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_LOAD_EVENT;

import android.app.Activity;
import android.content.Context;
import android.content.ContextWrapper;
import android.graphics.PorterDuff;
import android.os.Build;

import androidx.annotation.NonNull;

import com.bumptech.glide.Glide;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.request.Request;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
Expand All @@ -18,36 +22,33 @@
import com.facebook.react.uimanager.ThemedReactContext;
import com.facebook.react.uimanager.annotations.ReactProp;
import com.facebook.react.uimanager.events.RCTEventEmitter;
import com.facebook.react.views.imagehelper.ResourceDrawableIdHelper;

import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.WeakHashMap;

import javax.annotation.Nullable;

import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_ERROR_EVENT;
import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_LOAD_END_EVENT;
import static com.dylanvann.fastimage.FastImageRequestListener.REACT_ON_LOAD_EVENT;

class FastImageViewManager extends SimpleViewManager<FastImageViewWithUrl> implements FastImageProgressListener {

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";
static final String REACT_CLASS = "FastImageView";
static final String REACT_ON_LOAD_START_EVENT = "onFastImageLoadStart";
static final String REACT_ON_PROGRESS_EVENT = "onFastImageProgress";
private static final Map<String, List<FastImageViewWithUrl>> VIEWS_FOR_URLS = new WeakHashMap<>();

@Nullable
private RequestManager requestManager = null;

@NonNull
@Override
public String getName() {
return REACT_CLASS;
}

@NonNull
@Override
protected FastImageViewWithUrl createViewInstance(ThemedReactContext reactContext) {
protected FastImageViewWithUrl createViewInstance(@NonNull ThemedReactContext reactContext) {
if (isValidContextForGlide(reactContext)) {
requestManager = Glide.with(reactContext);
}
Expand All @@ -56,76 +57,15 @@ protected FastImageViewWithUrl createViewInstance(ThemedReactContext reactContex
}

@ReactProp(name = "source")
public void setSrc(FastImageViewWithUrl view, @Nullable ReadableMap source) {
if (source == null || !source.hasKey("uri") || isNullOrEmpty(source.getString("uri"))) {
// Cancel existing requests.
clearView(view);

if (view.glideUrl != null) {
FastImageOkHttpProgressGlideModule.forget(view.glideUrl.toStringUrl());
}
// Clear the image.
view.setImageDrawable(null);
return;
}

//final GlideUrl glideUrl = FastImageViewConverter.getGlideUrl(view.getContext(), source);
final FastImageSource imageSource = FastImageViewConverter.getImageSource(view.getContext(), source);
if (imageSource.getUri().toString().length() == 0) {
ThemedReactContext context = (ThemedReactContext) view.getContext();
RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class);
int viewId = view.getId();
WritableMap event = new WritableNativeMap();
event.putString("message", "Invalid source prop:" + source);
eventEmitter.receiveEvent(viewId, REACT_ON_ERROR_EVENT, event);

// Cancel existing requests.
if (requestManager != null) {
requestManager.clear(view);
}

if (view.glideUrl != null) {
FastImageOkHttpProgressGlideModule.forget(view.glideUrl.toStringUrl());
}
// Clear the image.
view.setImageDrawable(null);
return;
}

final GlideUrl glideUrl = imageSource.getGlideUrl();

// Cancel existing request.
view.glideUrl = glideUrl;
clearView(view);

String key = glideUrl.toStringUrl();
FastImageOkHttpProgressGlideModule.expect(key, this);
List<FastImageViewWithUrl> viewsForKey = VIEWS_FOR_URLS.get(key);
if (viewsForKey != null && !viewsForKey.contains(view)) {
viewsForKey.add(view);
} else if (viewsForKey == null) {
List<FastImageViewWithUrl> newViewsForKeys = new ArrayList<>(Collections.singletonList(view));
VIEWS_FOR_URLS.put(key, newViewsForKeys);
}
public void setSource(FastImageViewWithUrl view, @Nullable ReadableMap source) {
view.setSource(source);
}

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());

if (requestManager != null) {
requestManager
// This will make this work for remote and local images. e.g.
// - file:///
// - content://
// - res:/
// - android.resource://
// - data:image/png;base64
.load(imageSource.getSourceForLoad())
.apply(FastImageViewConverter.getOptions(context, imageSource, source))
.listener(new FastImageRequestListener(key))
.into(view);
}
@ReactProp(name = "defaultSource")
public void setDefaultSource(FastImageViewWithUrl view, @Nullable String source) {
view.setDefaultSource(
ResourceDrawableIdHelper.getInstance()
.getResourceDrawable(view.getContext(), source));
}

@ReactProp(name = "tintColor", customType = "Color")
Expand All @@ -144,9 +84,9 @@ public void setResizeMode(FastImageViewWithUrl view, String resizeMode) {
}

@Override
public void onDropViewInstance(FastImageViewWithUrl view) {
public void onDropViewInstance(@NonNull FastImageViewWithUrl view) {
// This will cancel existing requests.
clearView(view);
view.clearView(requestManager);

if (view.glideUrl != null) {
final String key = view.glideUrl.toString();
Expand Down Expand Up @@ -193,11 +133,6 @@ public float getGranularityPercentage() {
return 0.5f;
}

private boolean isNullOrEmpty(final String url) {
return url == null || url.trim().isEmpty();
}


private static boolean isValidContextForGlide(final Context context) {
Activity activity = getActivityFromContext(context);

Expand Down Expand Up @@ -235,14 +170,14 @@ private static boolean isActivityDestroyed(Activity activity) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN_MR1) {
return activity.isDestroyed() || activity.isFinishing();
} else {
return activity.isDestroyed() || activity.isFinishing() || activity.isChangingConfigurations();
return activity.isFinishing() || activity.isChangingConfigurations();
}

}

private void clearView(FastImageViewWithUrl view) {
if (requestManager != null && view != null && view.getTag() != null && view.getTag() instanceof Request) {
requestManager.clear(view);
}
@Override
protected void onAfterUpdateTransaction(@NonNull FastImageViewWithUrl view) {
super.onAfterUpdateTransaction(view);
view.onAfterUpdate(this, requestManager, VIEWS_FOR_URLS);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@

import android.app.Activity;

import androidx.annotation.NonNull;

import com.bumptech.glide.Glide;
import com.bumptech.glide.load.model.GlideUrl;
import com.facebook.react.bridge.Promise;
Expand All @@ -20,6 +22,7 @@ class FastImageViewModule extends ReactContextBaseJavaModule {
super(reactContext);
}

@NonNull
@Override
public String getName() {
return REACT_CLASS;
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
package com.dylanvann.fastimage;

import androidx.annotation.NonNull;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.ReactApplicationContext;
Expand All @@ -9,13 +11,15 @@
import java.util.List;

public class FastImageViewPackage implements ReactPackage {
@NonNull
@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
public List<NativeModule> createNativeModules(@NonNull ReactApplicationContext reactContext) {
return Collections.<NativeModule>singletonList(new FastImageViewModule(reactContext));
}

@NonNull
@Override
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
public List<ViewManager> createViewManagers(@NonNull ReactApplicationContext reactContext) {
return Collections.<ViewManager>singletonList(new FastImageViewManager());
}
}
Loading

0 comments on commit ec7c453

Please sign in to comment.