Skip to content

Commit ab8b3cd

Browse files
authored
Merge pull request #9 from discord/charles/rna-ota
[RNA][OTA] Adding image support
2 parents 5cdf434 + a37897d commit ab8b3cd

File tree

4 files changed

+214
-4
lines changed

4 files changed

+214
-4
lines changed
Lines changed: 172 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,172 @@
1+
package com.facebook.react.views.imagehelper;
2+
3+
import android.content.Context;
4+
import android.graphics.drawable.Drawable;
5+
import android.net.Uri;
6+
import android.util.DisplayMetrics;
7+
import android.view.WindowManager;
8+
9+
import androidx.annotation.NonNull;
10+
import androidx.annotation.Nullable;
11+
12+
import java.io.File;
13+
import java.util.concurrent.ConcurrentHashMap;
14+
15+
public class ImageOTAUtils {
16+
private static final ConcurrentHashMap<String, String> mResourceCacheMap = new ConcurrentHashMap<>();
17+
private static final String CACHE_DRAWABLE_DIRECTORY_SCHEME = "otas/app/src/main/res";
18+
private static final String[] RESOURCE_EXTENSIONS = {
19+
"xml",
20+
"png",
21+
"svg",
22+
"jpg"
23+
};
24+
25+
private static int densityDpi;
26+
27+
@Nullable
28+
static Drawable getResourceDrawable(Context context, @Nullable String name) {
29+
if (name == null || name.isEmpty()) {
30+
return null;
31+
}
32+
33+
name = sanitizeResourceDrawableId(name);
34+
35+
// Checks to see if we have an ota version of the file, otherwise default to normal behavior.
36+
File otaFile = getDrawableFileFromCache(context, name);
37+
if (otaFile != null) {
38+
return Drawable.createFromPath(otaFile.getAbsolutePath());
39+
}
40+
41+
return null;
42+
}
43+
44+
@Nullable
45+
static Uri getResourceUri(Context context, @Nullable String name) {
46+
if (name == null || name.isEmpty()) {
47+
return Uri.EMPTY;
48+
}
49+
50+
name = sanitizeResourceDrawableId(name);
51+
52+
// Checks to see if we have an ota version of the file, otherwise default to normal behavior.
53+
File otaFile = ImageOTAUtils.getDrawableFileFromCache(context, name);
54+
if (otaFile != null) {
55+
return Uri.fromFile(otaFile);
56+
}
57+
58+
return null;
59+
}
60+
61+
/**
62+
* Checks the cache to see if there is a drawable file downloaded via OTA.
63+
*/
64+
@Nullable
65+
private static File getDrawableFileFromCache(Context context, @Nullable String fileName) {
66+
if (fileName == null || fileName.isEmpty()) {
67+
return null;
68+
}
69+
70+
String cacheMapFileName = mResourceCacheMap.get(fileName);
71+
72+
// Check the cache to see if we've already looked up the file before.
73+
if (cacheMapFileName != null) {
74+
return new File(cacheMapFileName);
75+
}
76+
77+
File file = null;
78+
int densityDpi = getDensityDpi(context);
79+
PhoneDensity[] phoneDensities = PhoneDensity.values();
80+
81+
// We start from the medium dpi and go up.
82+
for (PhoneDensity phoneDensity : phoneDensities) {
83+
String drawableFileParent = String.format("drawable-%s", phoneDensity.fileParentSuffix);
84+
String mipMapFileParent = String.format("mipmap-%s", phoneDensity.fileParentSuffix);
85+
86+
String[] parentFileNames = { drawableFileParent, mipMapFileParent };
87+
88+
File resourceFile = checkFiles(context, parentFileNames, fileName);
89+
90+
if (resourceFile != null) {
91+
file = resourceFile;
92+
}
93+
94+
// If we've found a file at our current dpi level, return it.
95+
// Otherwise continue looking up the chain.
96+
if (densityDpi <= phoneDensity.density) {
97+
if (file != null) {
98+
mResourceCacheMap.put(fileName, file.getAbsolutePath());
99+
return file;
100+
}
101+
}
102+
}
103+
104+
// As a last resort, check the drawable/raw folders.
105+
String[] parentFileNames = { "drawable", "raw" };
106+
file = checkFiles(context, parentFileNames, fileName);
107+
108+
if (file != null) {
109+
mResourceCacheMap.put(fileName, file.getAbsolutePath());
110+
}
111+
112+
return file;
113+
}
114+
115+
/**
116+
* Given a list of files, check if any of them exist.
117+
* Checks multiple extension types.
118+
*/
119+
private static File checkFiles(Context context, String[] parentFileNames, String fileName) {
120+
for(String parentFileName : parentFileNames) {
121+
for (String extension : RESOURCE_EXTENSIONS) {
122+
File file = getFile(context, parentFileName, fileName, extension);
123+
if (file.exists()) {
124+
return file;
125+
}
126+
}
127+
}
128+
129+
return null;
130+
}
131+
132+
/**
133+
* Returns a file object with the correct directory extensions.
134+
*/
135+
private static File getFile(
136+
Context context,
137+
String parentFileName,
138+
String fileName,
139+
String extension
140+
) {
141+
String fullDrawableFileName = String.format(
142+
"%s/%s/%s.%s",
143+
CACHE_DRAWABLE_DIRECTORY_SCHEME,
144+
parentFileName,
145+
fileName,
146+
extension
147+
);
148+
149+
return new File(context.getCacheDir(), fullDrawableFileName);
150+
}
151+
152+
/**
153+
* Returns the density dpi for the device.
154+
*/
155+
private static int getDensityDpi(Context context) {
156+
// Cache this so we only have to do this once.
157+
if (densityDpi == 0) {
158+
DisplayMetrics metrics = new DisplayMetrics();
159+
160+
WindowManager windowManager = (WindowManager) context.getSystemService(Context.WINDOW_SERVICE);
161+
windowManager.getDefaultDisplay().getMetrics(metrics);
162+
163+
densityDpi = metrics.densityDpi;
164+
}
165+
166+
return densityDpi;
167+
}
168+
169+
private static String sanitizeResourceDrawableId(@NonNull String name) {
170+
return name.toLowerCase().replace("-", "_");
171+
}
172+
}
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
package com.facebook.react.views.imagehelper;
2+
3+
import android.util.DisplayMetrics;
4+
5+
import androidx.annotation.NonNull;
6+
7+
enum PhoneDensity {
8+
Medium(DisplayMetrics.DENSITY_MEDIUM, "mdpi"),
9+
High(DisplayMetrics.DENSITY_HIGH, "hdpi"),
10+
XHigh(DisplayMetrics.DENSITY_XHIGH, "xhdpi"),
11+
XXHigh(DisplayMetrics.DENSITY_XXHIGH, "xxhdpi"),
12+
XXXHigh(DisplayMetrics.DENSITY_XXXHIGH, "xxxhdpi");
13+
14+
int density;
15+
16+
@NonNull
17+
String fileParentSuffix;
18+
19+
PhoneDensity(
20+
int density,
21+
@NonNull String fileParentSuffix
22+
) {
23+
this.density = density;
24+
this.fileParentSuffix = fileParentSuffix;
25+
}
26+
}

ReactAndroid/src/main/java/com/facebook/react/views/imagehelper/ResourceDrawableIdHelper.java

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -43,7 +43,7 @@ public synchronized void clear() {
4343
mResourceDrawableIdMap.clear();
4444
}
4545

46-
public int getResourceDrawableId(Context context, @Nullable String name) {
46+
private int getResourceDrawableId(Context context, @Nullable String name) {
4747
if (name == null || name.isEmpty()) {
4848
return 0;
4949
}
@@ -67,11 +67,23 @@ public int getResourceDrawableId(Context context, @Nullable String name) {
6767
}
6868

6969
public @Nullable Drawable getResourceDrawable(Context context, @Nullable String name) {
70+
Drawable otaDrawable = ImageOTAUtils.getResourceDrawable(context, name);
71+
72+
if (otaDrawable != null) {
73+
return otaDrawable;
74+
}
75+
7076
int resId = getResourceDrawableId(context, name);
7177
return resId > 0 ? context.getResources().getDrawable(resId) : null;
7278
}
7379

7480
public Uri getResourceDrawableUri(Context context, @Nullable String name) {
81+
Uri otaUri = ImageOTAUtils.getResourceUri(context, name);
82+
83+
if (otaUri != null) {
84+
return otaUri;
85+
}
86+
7587
int resId = getResourceDrawableId(context, name);
7688
return resId > 0
7789
? new Uri.Builder().scheme(LOCAL_RESOURCE_SCHEME).path(String.valueOf(resId)).build()

ReactAndroid/src/main/java/com/facebook/react/views/textinput/ReactTextInputManager.java

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -669,9 +669,9 @@ public void setTextAlignVertical(ReactEditText view, @Nullable String textAlignV
669669

670670
@ReactProp(name = "inlineImageLeft")
671671
public void setInlineImageLeft(ReactEditText view, @Nullable String resource) {
672-
int id =
673-
ResourceDrawableIdHelper.getInstance().getResourceDrawableId(view.getContext(), resource);
674-
view.setCompoundDrawablesWithIntrinsicBounds(id, 0, 0, 0);
672+
Drawable drawable =
673+
ResourceDrawableIdHelper.getInstance().getResourceDrawable(view.getContext(), resource);
674+
view.setCompoundDrawablesWithIntrinsicBounds(drawable, null, null, null);
675675
}
676676

677677
@ReactProp(name = "inlineImagePadding")

0 commit comments

Comments
 (0)