diff --git a/ReactNativeFastImageExample/android/build.gradle b/ReactNativeFastImageExample/android/build.gradle index e64d31e5f..c20cd5fe6 100644 --- a/ReactNativeFastImageExample/android/build.gradle +++ b/ReactNativeFastImageExample/android/build.gradle @@ -1,5 +1,7 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. +def REACT_NATIVE_VERSION = new File(['node', '--print',"JSON.parse(require('fs').readFileSync(require.resolve('react-native/package.json'), 'utf-8')).version"].execute(null, rootDir).text.trim()) + buildscript { ext { buildToolsVersion = "30.0.2" @@ -34,5 +36,13 @@ allprojects { google() maven { url 'https://www.jitpack.io' } + + } + + configurations.all { + resolutionStrategy { + // Remove this override in 0.65+, as a proper fix is included in react-native itself. + force "com.facebook.react:react-native:" + REACT_NATIVE_VERSION + } } } diff --git a/ReactNativeFastImageExample/metro.config.js b/ReactNativeFastImageExample/metro.config.js index d65704821..2a0a8c597 100644 --- a/ReactNativeFastImageExample/metro.config.js +++ b/ReactNativeFastImageExample/metro.config.js @@ -5,13 +5,45 @@ * @format */ + + +const path = require('path') +const escape = require('escape-string-regexp') +const exclusionList = require('metro-config/src/defaults/exclusionList') +const pak = require('../package.json') + +const root = path.resolve(__dirname, '..') + +const modules = Object.keys({ + ...pak.peerDependencies, +}) + module.exports = { - transformer: { - getTransformOptions: async () => ({ - transform: { - experimentalImportSupport: false, - inlineRequires: true, - }, - }), - }, -} + projectRoot: __dirname, + watchFolders: [root], + + // We need to make sure that only one version is loaded for peerDependencies + // So we block them at the root, and alias them to the versions in example's node_modules + resolver: { + blacklistRE: exclusionList( + modules.map( + (m) => + new RegExp(`^${escape(path.join(root, 'node_modules', m))}\\/.*$`) + ) + ), + + extraNodeModules: modules.reduce((acc, name) => { + acc[name] = path.join(__dirname, 'node_modules', name) + return acc + }, {}), + }, + + transformer: { + getTransformOptions: async () => ({ + transform: { + experimentalImportSupport: false, + inlineRequires: true, + }, + }), + }, +} \ No newline at end of file diff --git a/ReactNativeFastImageExample/src/AutoSizeExample.tsx b/ReactNativeFastImageExample/src/AutoSizeExample.tsx index 75dfb8691..70f8db5ec 100644 --- a/ReactNativeFastImageExample/src/AutoSizeExample.tsx +++ b/ReactNativeFastImageExample/src/AutoSizeExample.tsx @@ -47,6 +47,7 @@ const AutoSizingImage = (props: AutoSizingImageProps) => { ) diff --git a/android/src/main/java/com/dylanvann/fastimage/BitmapSizeDecoder.java b/android/src/main/java/com/dylanvann/fastimage/BitmapSizeDecoder.java new file mode 100644 index 000000000..03ad01799 --- /dev/null +++ b/android/src/main/java/com/dylanvann/fastimage/BitmapSizeDecoder.java @@ -0,0 +1,31 @@ +package com.dylanvann.fastimage; + +import android.graphics.BitmapFactory; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.ResourceDecoder; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.resource.SimpleResource; + +import java.io.IOException; +import java.io.InputStream; + +public class BitmapSizeDecoder implements ResourceDecoder { + + @Override + public boolean handles(@NonNull InputStream source, @NonNull Options options) throws IOException { + return true; + } + + @Nullable + @Override + public Resource decode(@NonNull InputStream source, int width, int height, @NonNull Options options) throws IOException { + BitmapFactory.Options bitmapOptions = new BitmapFactory.Options(); + bitmapOptions.inJustDecodeBounds = true; + BitmapFactory.decodeStream(source, null, bitmapOptions); + return new SimpleResource(bitmapOptions); + } +} \ No newline at end of file diff --git a/android/src/main/java/com/dylanvann/fastimage/BitmapSizeTranscoder.java b/android/src/main/java/com/dylanvann/fastimage/BitmapSizeTranscoder.java new file mode 100644 index 000000000..255cb0d26 --- /dev/null +++ b/android/src/main/java/com/dylanvann/fastimage/BitmapSizeTranscoder.java @@ -0,0 +1,23 @@ +package com.dylanvann.fastimage; + +import android.graphics.BitmapFactory; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; + +import com.bumptech.glide.load.Options; +import com.bumptech.glide.load.engine.Resource; +import com.bumptech.glide.load.resource.SimpleResource; +import com.bumptech.glide.load.resource.transcode.ResourceTranscoder; + +public class BitmapSizeTranscoder implements ResourceTranscoder { + @Nullable + @Override + public Resource transcode(@NonNull Resource toTranscode, @NonNull Options options) { + BitmapFactory.Options bitmap = toTranscode.get(); + Size size = new Size(); + size.width = bitmap.outWidth; + size.height = bitmap.outHeight; + return new SimpleResource(size); + } +} diff --git a/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java b/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java index 811292aa7..b01df1930 100644 --- a/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java +++ b/android/src/main/java/com/dylanvann/fastimage/FastImageOkHttpProgressGlideModule.java @@ -1,6 +1,8 @@ package com.dylanvann.fastimage; import android.content.Context; +import android.graphics.BitmapFactory; + import androidx.annotation.NonNull; import com.bumptech.glide.Glide; @@ -47,6 +49,10 @@ public void registerComponents( .build(); OkHttpUrlLoader.Factory factory = new OkHttpUrlLoader.Factory(client); registry.replace(GlideUrl.class, InputStream.class, factory); + + // Decoder + Transcoder pair for InputStream -> Size + registry.prepend(InputStream.class, BitmapFactory.Options.class, new BitmapSizeDecoder()); + registry.register(BitmapFactory.Options.class, Size.class, new BitmapSizeTranscoder()); } private static Interceptor createInterceptor(final ResponseProgressListener listener) { diff --git a/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java b/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java index dbeb81313..bf8f21cb8 100644 --- a/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java +++ b/android/src/main/java/com/dylanvann/fastimage/FastImageRequestListener.java @@ -22,13 +22,6 @@ public class FastImageRequestListener implements RequestListener { this.key = key; } - private static WritableMap mapFromResource(Drawable resource) { - WritableMap resourceData = new WritableNativeMap(); - resourceData.putInt("width", resource.getIntrinsicWidth()); - resourceData.putInt("height", resource.getIntrinsicHeight()); - return resourceData; - } - @Override public boolean onLoadFailed(@androidx.annotation.Nullable GlideException e, Object model, Target target, boolean isFirstResource) { FastImageOkHttpProgressGlideModule.forget(key); @@ -53,7 +46,6 @@ public boolean onResourceReady(Drawable resource, Object model, Target ThemedReactContext context = (ThemedReactContext) view.getContext(); RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class); int viewId = view.getId(); - eventEmitter.receiveEvent(viewId, REACT_ON_LOAD_EVENT, mapFromResource(resource)); eventEmitter.receiveEvent(viewId, REACT_ON_LOAD_END_EVENT, new WritableNativeMap()); return false; } diff --git a/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java b/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java index 34fcf898d..503f2ec88 100644 --- a/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java +++ b/android/src/main/java/com/dylanvann/fastimage/FastImageViewWithUrl.java @@ -6,6 +6,7 @@ import android.content.Context; import android.graphics.drawable.Drawable; +import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.widget.AppCompatImageView; @@ -13,6 +14,8 @@ import com.bumptech.glide.RequestManager; import com.bumptech.glide.load.model.GlideUrl; import com.bumptech.glide.request.Request; +import com.bumptech.glide.request.target.SimpleTarget; +import com.bumptech.glide.request.transition.Transition; import com.facebook.react.bridge.ReadableMap; import com.facebook.react.bridge.WritableMap; import com.facebook.react.bridge.WritableNativeMap; @@ -148,6 +151,25 @@ public void onAfterUpdate( builder.listener(new FastImageRequestListener(key)); builder.into(this); + + // Used specifically to handle the `onLoad` event for the image + RCTEventEmitter eventEmitter = context.getJSModule(RCTEventEmitter.class); + int viewId = this.getId(); + requestManager + .as(Size.class) + .load(imageSource == null ? null : imageSource.getSourceForLoad()) + .into(new SimpleTarget() { + @Override + public void onResourceReady(@NonNull Size resource, @Nullable Transition transition) { + WritableMap resourceData = new WritableNativeMap(); + resourceData.putInt("width", resource.width); + resourceData.putInt("height", resource.height); + eventEmitter.receiveEvent(viewId, + "onFastImageLoad", + resourceData + ); + } + }); } } diff --git a/android/src/main/java/com/dylanvann/fastimage/Size.java b/android/src/main/java/com/dylanvann/fastimage/Size.java new file mode 100644 index 000000000..d3f33eb2b --- /dev/null +++ b/android/src/main/java/com/dylanvann/fastimage/Size.java @@ -0,0 +1,6 @@ +package com.dylanvann.fastimage; + +public class Size { + int width; + int height; +} diff --git a/package.json b/package.json index eaaa13c29..e3f4291a1 100644 --- a/package.json +++ b/package.json @@ -22,6 +22,8 @@ "main": "dist/index.cjs.js", "module": "dist/index.js", "typings": "dist/index.d.ts", + "source": "src/index", + "react-native": "src/index", "files": [ "android", "!android/build", @@ -79,4 +81,4 @@ "react": "^17 || ^18", "react-native": ">=0.60.0" } -} +} \ No newline at end of file