Skip to content

Commit

Permalink
Merge branch 'fjcaetano-feature/prefetch'
Browse files Browse the repository at this point in the history
  • Loading branch information
DylanVann committed Jun 20, 2017
2 parents 1d25cb2 + 46e08b4 commit 4e69ddd
Show file tree
Hide file tree
Showing 10 changed files with 244 additions and 90 deletions.
13 changes: 12 additions & 1 deletion FastImage.js
@@ -1,8 +1,15 @@
import React, { PropTypes, Component } from 'react'
import { requireNativeComponent, Image, View } from 'react-native'
import {
requireNativeComponent,
Image,
NativeModules,
View,
} from 'react-native'

const resolveAssetSource = require('react-native/Libraries/Image/resolveAssetSource')

const FastImageViewNativeModule = NativeModules.FastImageView

class FastImage extends Component {
setNativeProps(nativeProps) {
this._root.setNativeProps(nativeProps)
Expand Down Expand Up @@ -50,6 +57,10 @@ FastImage.priority = {
high: 'high',
}

FastImage.preload = sources => {
FastImageViewNativeModule.preload(sources)
}

const FastImageSourcePropType = PropTypes.shape({
uri: PropTypes.string,
headers: PropTypes.objectOf(PropTypes.string),
Expand Down
41 changes: 35 additions & 6 deletions README.md
Expand Up @@ -36,6 +36,7 @@ and
- [x] Aggressively cache images.
- [x] Add authorization headers.
- [x] Prioritize images.
- [x] Preload images.
- [x] GIF support.

## Usage
Expand Down Expand Up @@ -63,13 +64,13 @@ const YourImage = () =>

## Properties

`source?: object`
### `source?: object`

Source for the remote image to load.

---

`source.uri?: string`
### `source.uri?: string`

Remote url to load the image from. e.g. `'https://facebook.github.io/react/img/logo_og.png'`.

Expand All @@ -81,15 +82,15 @@ Headers to load the image with. e.g. `{ Authorization: 'someAuthToken' }`.

---

`source.priority?: enum`
### `source.priority?: enum`

- `FastImage.priority.low` - Low Priority
- `FastImage.priority.normal` **(Default)** - Normal Priority
- `FastImage.priority.high` - High Priority

---

`resizeMode?: enum`
### `resizeMode?: enum`

- `FastImage.resizeMode.contain` **(Default)** - 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).
- `FastImage.resizeMode.cover` - 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 larger than the corresponding dimension of the view (minus padding).
Expand All @@ -98,16 +99,44 @@ Headers to load the image with. e.g. `{ Authorization: 'someAuthToken' }`.

---

`onLoad?: () => void`
### `onLoad?: () => void`

Called on a successful image fetch.

---

`onError?: () => void`
### `onError?: () => void`

Called on an image fetching error.

---

### `children`

`FastImage` does not currently support children.
Absolute positioning can be used as an alternative.

(This is because `FastImage` supplies a `android.widget.imageview` and not a `android.view.viewgroup`.)

## Static Methods

### `FastImage.preload: (source[]) => void`

Preload images to display later. e.g.

```js
FastImage.preload([
{
uri: 'https://facebook.github.io/react/img/logo_og.png',
headers: { Authorization: 'someAuthToken' },
},
{
uri: 'https://facebook.github.io/react/img/logo_og.png',
headers: { Authorization: 'someAuthToken' },
},
])
```

## Development

```bash
Expand Down
@@ -0,0 +1,71 @@
package com.dylanvann.fastimage;

import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

import com.bumptech.glide.Priority;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.LazyHeaders;
import com.facebook.react.bridge.NoSuchKeyException;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;

import java.util.HashMap;
import java.util.Map;

class FastImageViewConverter {
static GlideUrl glideUrl(ReadableMap source) {
final String uriProp = source.getString("uri");
// Get the headers prop and add to glideUrl.
GlideUrl glideUrl;
try {
final ReadableMap headersMap = source.getMap("headers");
ReadableMapKeySetIterator headersIterator = headersMap.keySetIterator();
LazyHeaders.Builder headersBuilder = new LazyHeaders.Builder();
while (headersIterator.hasNextKey()) {
String key = headersIterator.nextKey();
String value = headersMap.getString(key);
headersBuilder.addHeader(key, value);
}
LazyHeaders headers = headersBuilder.build();
glideUrl = new GlideUrl(uriProp, headers);
} catch (NoSuchKeyException e) {
// If there is no headers object.
glideUrl = new GlideUrl(uriProp);
}
return glideUrl;
}

private static Map<String, Priority> REACT_PRIORITY_MAP =
new HashMap<String, Priority>() {{
put("low", Priority.LOW);
put("normal", Priority.NORMAL);
put("high", Priority.HIGH);
}};

static Priority priority(ReadableMap source) {
// Get the priority prop.
String priorityProp = "normal";
try {
priorityProp = source.getString("priority");
} catch (Exception e) {
// Noop.
}
final Priority priority = REACT_PRIORITY_MAP.get(priorityProp);
return priority;
}

private static Map<String, ImageView.ScaleType> REACT_RESIZE_MODE_MAP =
new HashMap<String, ImageView.ScaleType>() {{
put("contain", ScaleType.FIT_CENTER);
put("cover", ScaleType.CENTER_CROP);
put("stretch", ScaleType.FIT_XY);
put("center", ScaleType.CENTER);
}};

public static ScaleType scaleType(String resizeMode) {
if (resizeMode == null) resizeMode = "contain";
final ImageView.ScaleType scaleType = REACT_RESIZE_MODE_MAP.get(resizeMode);
return scaleType;
}
}
Expand Up @@ -4,25 +4,17 @@
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;
import android.widget.ImageView;
import android.widget.ImageView.ScaleType;

import com.bumptech.glide.DrawableRequestBuilder;
import com.bumptech.glide.DrawableTypeRequest;
import com.bumptech.glide.Glide;
import com.bumptech.glide.Priority;
import com.bumptech.glide.RequestManager;
import com.bumptech.glide.load.data.DataFetcher;
import com.bumptech.glide.load.model.GlideUrl;
import com.bumptech.glide.load.model.LazyHeaders;
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;
import com.bumptech.glide.request.target.Target;
import com.bumptech.glide.signature.StringSignature;
import com.facebook.react.bridge.NoSuchKeyException;
import com.facebook.react.bridge.ReadableMap;
import com.facebook.react.bridge.ReadableMapKeySetIterator;
import com.facebook.react.bridge.WritableMap;
import com.facebook.react.bridge.WritableNativeMap;
import com.facebook.react.common.MapBuilder;
Expand All @@ -33,9 +25,7 @@

import java.io.IOException;
import java.io.InputStream;
import java.util.HashMap;
import java.util.Map;
import java.util.UUID;

import javax.annotation.Nullable;

Expand All @@ -49,21 +39,6 @@ class FastImageViewManager extends SimpleViewManager<ImageView> {

private static Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);

private static Map<String, Priority> REACT_PRIORITY_MAP =
new HashMap<String, Priority>() {{
put("low", Priority.LOW);
put("normal", Priority.NORMAL);
put("high", Priority.HIGH);
}};

private static Map<String, ImageView.ScaleType> REACT_RESIZE_MODE_MAP =
new HashMap<String, ImageView.ScaleType>() {{
put("contain", ScaleType.FIT_CENTER);
put("cover", ScaleType.CENTER_CROP);
put("stretch", ScaleType.FIT_XY);
put("center", ScaleType.CENTER);
}};

@Override
public String getName() {
return REACT_CLASS;
Expand Down Expand Up @@ -125,51 +100,27 @@ public void setSrc(ImageView view, @Nullable ReadableMap source) {
return;
}

final String uriProp = source.getString("uri");

// Get the headers prop and add to glideUrl.
GlideUrl glideUrl;
try {
final ReadableMap headersMap = source.getMap("headers");
ReadableMapKeySetIterator headersIterator = headersMap.keySetIterator();
LazyHeaders.Builder headersBuilder = new LazyHeaders.Builder();
while (headersIterator.hasNextKey()) {
String key = headersIterator.nextKey();
String value = headersMap.getString(key);
headersBuilder.addHeader(key, value);
}
LazyHeaders headers = headersBuilder.build();
glideUrl = new GlideUrl(uriProp, headers);
} catch (NoSuchKeyException e) {
// If there is no headers object.
glideUrl = new GlideUrl(uriProp);
}
// Get the GlideUrl which contains header info.
final GlideUrl glideUrl = FastImageViewConverter.glideUrl(source);

// Get the priority prop.
String priorityProp = "normal";
try {
priorityProp = source.getString("priority");
} catch (Exception e) {
// Noop.
}
final Priority priority = REACT_PRIORITY_MAP.get(priorityProp);
// Get priority.
final Priority priority = FastImageViewConverter.priority(source);

// Cancel existing request.
Glide.clear(view);

Glide
.with(view.getContext())
.load(glideUrl)
.priority(priority)
.placeholder(TRANSPARENT_DRAWABLE)
.listener(LISTENER)
.into(view);
.with(view.getContext())
.load(glideUrl)
.priority(priority)
.placeholder(TRANSPARENT_DRAWABLE)
.listener(LISTENER)
.into(view);
}

@ReactProp(name = "resizeMode")
public void setResizeMode(ImageView view, String resizeMode) {
if (resizeMode == null) resizeMode = "contain";
final ImageView.ScaleType scaleType = REACT_RESIZE_MODE_MAP.get(resizeMode);
final ImageView.ScaleType scaleType = FastImageViewConverter.scaleType(resizeMode);
view.setScaleType(scaleType);
}

Expand Down
@@ -0,0 +1,54 @@
package com.dylanvann.fastimage;

import android.app.Activity;
import android.graphics.Color;
import android.graphics.drawable.ColorDrawable;
import android.graphics.drawable.Drawable;

import com.bumptech.glide.Glide;
import com.bumptech.glide.Priority;
import com.bumptech.glide.load.engine.DiskCacheStrategy;
import com.bumptech.glide.load.model.GlideUrl;
import com.facebook.react.bridge.ReactApplicationContext;
import com.facebook.react.bridge.ReactContextBaseJavaModule;
import com.facebook.react.bridge.ReactMethod;
import com.facebook.react.bridge.ReadableArray;
import com.facebook.react.bridge.ReadableMap;

class FastImageViewModule extends ReactContextBaseJavaModule {

private static final String REACT_CLASS = "FastImageView";

FastImageViewModule(ReactApplicationContext reactContext) {
super(reactContext);
}

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

private static Drawable TRANSPARENT_DRAWABLE = new ColorDrawable(Color.TRANSPARENT);

@ReactMethod
public void preload(final ReadableArray sources) {
final Activity activity = getCurrentActivity();
activity.runOnUiThread(new Runnable() {
@Override
public void run() {
for (int i = 0; i < sources.size(); i++) {
final ReadableMap source = sources.getMap(i);
final GlideUrl glideUrl = FastImageViewConverter.glideUrl(source);
final Priority priority = FastImageViewConverter.priority(source);
Glide
.with(activity.getApplicationContext())
.load(glideUrl)
.priority(priority)
.placeholder(TRANSPARENT_DRAWABLE)
.diskCacheStrategy(DiskCacheStrategy.SOURCE)
.preload();
}
}
});
}
}
@@ -1,5 +1,7 @@
package com.dylanvann.fastimage;

import com.dylanvann.fastimage.FastImageViewModule;

import com.facebook.react.ReactPackage;
import com.facebook.react.bridge.NativeModule;
import com.facebook.react.bridge.JavaScriptModule;
Expand All @@ -10,10 +12,9 @@
import java.util.List;

public class FastImageViewPackage implements ReactPackage {

@Override
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
return Collections.emptyList();
return Collections.<NativeModule>singletonList(new FastImageViewModule(reactContext));
}

@Override
Expand Down

0 comments on commit 4e69ddd

Please sign in to comment.