Нет описания

RNViewShotModule.java 7.6KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182
  1. package fr.greweb.reactnativeviewshot;
  2. import android.content.Context;
  3. import android.graphics.Bitmap;
  4. import android.os.AsyncTask;
  5. import android.os.Environment;
  6. import android.util.DisplayMetrics;
  7. import android.view.View;
  8. import com.facebook.react.bridge.ReactApplicationContext;
  9. import com.facebook.react.bridge.ReactContextBaseJavaModule;
  10. import com.facebook.react.bridge.ReactMethod;
  11. import com.facebook.react.bridge.GuardedAsyncTask;
  12. import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
  13. import com.facebook.react.bridge.Promise;
  14. import com.facebook.react.bridge.ReactContext;
  15. import com.facebook.react.bridge.ReadableMap;
  16. import com.facebook.react.uimanager.UIBlock;
  17. import com.facebook.react.uimanager.UIManagerModule;
  18. import java.io.File;
  19. import java.io.FilenameFilter;
  20. import java.io.IOException;
  21. import java.util.HashMap;
  22. import java.util.Map;
  23. public class RNViewShotModule extends ReactContextBaseJavaModule {
  24. private final ReactApplicationContext reactContext;
  25. public RNViewShotModule(ReactApplicationContext reactContext) {
  26. super(reactContext);
  27. this.reactContext = reactContext;
  28. }
  29. @Override
  30. public String getName() {
  31. return "RNViewShot";
  32. }
  33. @Override
  34. public Map<String, Object> getConstants() {
  35. return getSystemFolders(this.getReactApplicationContext());
  36. }
  37. @Override
  38. public void onCatalystInstanceDestroy() {
  39. super.onCatalystInstanceDestroy();
  40. new CleanTask(getReactApplicationContext()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
  41. }
  42. @ReactMethod
  43. public void takeSnapshot(int tag, ReadableMap options, Promise promise) {
  44. ReactApplicationContext context = getReactApplicationContext();
  45. String format = options.hasKey("format") ? options.getString("format") : "png";
  46. Bitmap.CompressFormat compressFormat =
  47. format.equals("png")
  48. ? Bitmap.CompressFormat.PNG
  49. : format.equals("jpg")||format.equals("jpeg")
  50. ? Bitmap.CompressFormat.JPEG
  51. : format.equals("webm")
  52. ? Bitmap.CompressFormat.WEBP
  53. : null;
  54. if (compressFormat == null) {
  55. promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Unsupported image format: "+format+". Try one of: png | jpg | jpeg");
  56. return;
  57. }
  58. double quality = options.hasKey("quality") ? options.getDouble("quality") : 1.0;
  59. DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
  60. Integer width = options.hasKey("width") ? (int)(displayMetrics.density * options.getDouble("width")) : null;
  61. Integer height = options.hasKey("height") ? (int)(displayMetrics.density * options.getDouble("height")) : null;
  62. String result = options.hasKey("result") ? options.getString("result") : "file";
  63. Boolean snapshotContentContainer = options.hasKey("snapshotContentContainer") ? options.getBoolean("snapshotContentContainer") : false;
  64. try {
  65. File file = null;
  66. if ("file".equals(result)) {
  67. if (options.hasKey("path")) {
  68. file = new File(options.getString("path"));
  69. file.mkdirs();
  70. file.createNewFile();
  71. }
  72. else {
  73. file = createTempFile(getReactApplicationContext(), format);
  74. }
  75. }
  76. UIManagerModule uiManager = this.reactContext.getNativeModule(UIManagerModule.class);
  77. uiManager.addUIBlock(new ViewShot(tag, format, compressFormat, quality, width, height, file, result, snapshotContentContainer, promise));
  78. }
  79. catch (Exception e) {
  80. promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag "+tag);
  81. }
  82. }
  83. private static final String TEMP_FILE_PREFIX = "ReactNative-snapshot-image";
  84. /**
  85. * Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
  86. * image files. This is run when the catalyst instance is being destroyed (i.e. app is shutting
  87. * down) and when the module is instantiated, to handle the case where the app crashed.
  88. */
  89. private static class CleanTask extends GuardedAsyncTask<Void, Void> {
  90. private final Context mContext;
  91. private CleanTask(ReactContext context) {
  92. super(context);
  93. mContext = context;
  94. }
  95. @Override
  96. protected void doInBackgroundGuarded(Void... params) {
  97. cleanDirectory(mContext.getCacheDir());
  98. File externalCacheDir = mContext.getExternalCacheDir();
  99. if (externalCacheDir != null) {
  100. cleanDirectory(externalCacheDir);
  101. }
  102. }
  103. private void cleanDirectory(File directory) {
  104. File[] toDelete = directory.listFiles(
  105. new FilenameFilter() {
  106. @Override
  107. public boolean accept(File dir, String filename) {
  108. return filename.startsWith(TEMP_FILE_PREFIX);
  109. }
  110. });
  111. if (toDelete != null) {
  112. for (File file: toDelete) {
  113. file.delete();
  114. }
  115. }
  116. }
  117. }
  118. static private Map<String, Object> getSystemFolders(ReactApplicationContext ctx) {
  119. Map<String, Object> res = new HashMap<>();
  120. res.put("CacheDir", ctx.getCacheDir().getAbsolutePath());
  121. res.put("DCIMDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath());
  122. res.put("DocumentDir", ctx.getFilesDir().getAbsolutePath());
  123. res.put("DownloadDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath());
  124. res.put("MainBundleDir", ctx.getApplicationInfo().dataDir);
  125. res.put("MovieDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath());
  126. res.put("MusicDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath());
  127. res.put("PictureDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath());
  128. res.put("RingtoneDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES).getAbsolutePath());
  129. String state;
  130. state = Environment.getExternalStorageState();
  131. if (state.equals(Environment.MEDIA_MOUNTED)) {
  132. res.put("SDCardDir", Environment.getExternalStorageDirectory().getAbsolutePath());
  133. }
  134. return res;
  135. }
  136. /**
  137. * Create a temporary file in the cache directory on either internal or external storage,
  138. * whichever is available and has more free space.
  139. */
  140. private File createTempFile(Context context, String ext)
  141. throws IOException {
  142. File externalCacheDir = context.getExternalCacheDir();
  143. File internalCacheDir = context.getCacheDir();
  144. File cacheDir;
  145. if (externalCacheDir == null && internalCacheDir == null) {
  146. throw new IOException("No cache directory available");
  147. }
  148. if (externalCacheDir == null) {
  149. cacheDir = internalCacheDir;
  150. }
  151. else if (internalCacheDir == null) {
  152. cacheDir = externalCacheDir;
  153. } else {
  154. cacheDir = externalCacheDir.getFreeSpace() > internalCacheDir.getFreeSpace() ?
  155. externalCacheDir : internalCacheDir;
  156. }
  157. String suffix = "." + ext;
  158. File tmpFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, cacheDir);
  159. return tmpFile;
  160. }
  161. }