Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions ReactNativeFastImageExample/src/LocalImagesExample.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,11 +16,11 @@ import BulletText from './BulletText';
// @ts-ignore
import FieldsImage from './images/fields.jpg';
// @ts-ignore
import FieldsWebP from './images/fields.webp';
import FieldsWebP from './images/fields_alt.webp';
// @ts-ignore
import JellyfishGIF from './images/jellyfish.gif';
// @ts-ignore
import JellyfishWebP from './images/jellyfish.webp';
import JellyfishWebP from './images/jellyfish_anim.webp';
// @ts-ignore
import ReactSvg from './images/react-native.svg';

Expand Down
92 changes: 86 additions & 6 deletions android/src/main/java/com/dylanvann/fastimage/FastImageSource.java
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,10 @@ public class FastImageSource {
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 static final String RAW_RESOURCE_FOLDER = "raw";
private static final String HTTP_SCHEME_PREFIX = "http";
private static final String DATA_URI_PREFIX = "data:";

private final Headers mHeaders;
private Uri mUri;
private String mSource;
Expand Down Expand Up @@ -53,19 +57,95 @@ public FastImageSource(Context context, String source, double width, double heig
ImageSource imageSource = new ImageSource(context, source, width, height);
mSource = imageSource.getSource();
mHeaders = headers == null ? Headers.DEFAULT : headers;
mUri = imageSource.getUri();
mUri = resolveResourceUri(context, source, imageSource.getUri());

boolean isUriEmpty = mUri == null || TextUtils.isEmpty(mUri.toString());
boolean isResourceMissing = isResource() && isUriEmpty;

if (isResource() && TextUtils.isEmpty(mUri.toString())) {
if (isResourceMissing) {
throw new Resources.NotFoundException("Local Resource Not Found. Resource: '" + getSource() + "'.");
}

if (isLocalResourceUri(mUri)) {
// Convert res:/ scheme to android.resource:// so
// glide can understand the uri.
mUri = Uri.parse(mUri.toString().replace("res:/", ANDROID_RESOURCE_SCHEME + "://" + context.getPackageName() + "/"));
boolean shouldConvertLocalResourceScheme = mUri != null && isLocalResourceUri(mUri);

if (shouldConvertLocalResourceScheme) {
// Convert res:/ scheme to android.resource:// so glide can understand the uri.
String convertedUri = mUri.toString().replace("res:/", ANDROID_RESOURCE_SCHEME + "://" + context.getPackageName() + "/");
mUri = Uri.parse(convertedUri);
}
}

/**
* Attempts to find a resource URI for the given source.
* First uses the URI from ImageSource (checks drawable folder),
* then falls back to raw folder for assets like SVGs in release builds.
*
* @param context Application context
* @param source Original source string
* @param initialUri URI resolved by ImageSource
* @return Resolved URI or null if not found
*/
@Nullable
private Uri resolveResourceUri(Context context, String source, Uri initialUri) {
boolean isInitialUriValid = initialUri != null && !TextUtils.isEmpty(initialUri.toString());

if (isInitialUriValid) {
return initialUri;
}

boolean isSourceNull = source == null;
boolean isRemoteUrl = source != null && source.startsWith(HTTP_SCHEME_PREFIX);
boolean isDataUri = source != null && source.startsWith(DATA_URI_PREFIX);
boolean shouldSkipRawLookup = isSourceNull || isRemoteUrl || isDataUri;

if (shouldSkipRawLookup) {
return initialUri;
}

return tryResolveFromRawFolder(context, source);
}

/**
* Attempts to find a resource in the res/raw folder.
* This is needed because SVG and some other assets are bundled to res/raw/
* in Android release builds, but ImageSource only checks res/drawable/.
*
* @param context Application context
* @param source Original source string
* @return URI pointing to raw resource, or null if not found
*/
@Nullable
private Uri tryResolveFromRawFolder(Context context, String source) {
String rawResourceName = toAndroidResourceName(source);
int rawResId = context.getResources().getIdentifier(
rawResourceName,
RAW_RESOURCE_FOLDER,
context.getPackageName()
);

boolean resourceNotFound = rawResId == 0;

if (resourceNotFound) {
return null;
}

String rawResourceUri = ANDROID_RESOURCE_SCHEME + "://" +
context.getPackageName() + "/" + RAW_RESOURCE_FOLDER + "/" + rawResourceName;

return Uri.parse(rawResourceUri);
}

/**
* Converts a source name to Android resource name format.
* Android resources must be lowercase and cannot contain hyphens.
* Hyphens are removed to match React Native's bundling behavior.
*
* @param source Original source name
* @return Android-compatible resource name
*/
private String toAndroidResourceName(String source) {
return source.toLowerCase().replace("-", "");
}

public boolean isBase64Resource() {
return mUri != null && FastImageSource.isBase64Uri(mUri);
Expand Down