Browse Source

wip implementing dirs

Gaëtan Renaudeau 7 years ago
parent
commit
9d5903316c

+ 23
- 6
README.md View File

@@ -10,9 +10,9 @@ Snapshot a React Native view and save it to an image.
10 10
 ## Usage
11 11
 
12 12
 ```js
13
-import RNViewShot from "react-native-view-shot";
13
+import { takeSnapshot } from "react-native-view-shot";
14 14
 
15
-RNViewShot.takeSnapshot(viewRef, {
15
+takeSnapshot(viewRef, {
16 16
   format: "jpeg",
17 17
   quality: 0.8
18 18
 })
@@ -28,7 +28,7 @@ RNViewShot.takeSnapshot(viewRef, {
28 28
 
29 29
 ## Full API
30 30
 
31
-### `RNViewShot.takeSnapshot(view, options)`
31
+### `takeSnapshot(view, options)`
32 32
 
33 33
 Returns a Promise of the image URI.
34 34
 
@@ -41,9 +41,27 @@ Returns a Promise of the image URI.
41 41
     - `"file"` (default): save to a temporary file *(that will only exist for as long as the app is running)*.
42 42
     - `"base64"`: encode as base64 and returns the raw string. Use only with small images as this may result of lags (the string is sent over the bridge). *N.B. This is not a data uri, use `data-uri` instead*.
43 43
     - `"data-uri"`: same as `base64` but also includes the [Data URI scheme](https://en.wikipedia.org/wiki/Data_URI_scheme) header.
44
- - **`filename`** *(string)*: the name of the generated file if any (Android only). Defaults to `ReactNative_snapshot_image_${timestamp}`.
44
+ - **`path`** *(string)*: The absolute path where the file get generated. See *`dirs` constants* for more information.
45 45
  - **`snapshotContentContainer`** *(bool)*: if true and when view is a ScrollView, the "content container" height will be evaluated instead of the container height. (Android only)
46 46
 
47
+### `dirs` constants
48
+
49
+By default, takeSnapshot will export in a temporary folder and the snapshot file will be deleted as soon as the app leaves. If you use the `path` option, you make the snapshot file more permanent and at a specific file location. To make file location more 'universal', the library exports some classic directory constants:
50
+
51
+```js
52
+import { takeSnapshot, dirs } from "react-native-view-shot";
53
+// cross platform dirs:
54
+const { CacheDir, DocumentDir, MainBundleDir, MovieDir, MusicDir, PictureDir } = dirs;
55
+// only available Android:
56
+const { DCIMDir, DownloadDir, RingtoneDir, SDCardDir } = dirs;
57
+
58
+takeSnapshot(viewRef, { path: PictureDir+"/foo.png" })
59
+.then(
60
+  uri => console.log("Image saved to", uri),
61
+  error => console.error("Oops, snapshot failed", error)
62
+);
63
+```
64
+
47 65
 ## Caveats
48 66
 
49 67
 Snapshots are not guaranteed to be pixel perfect. It also depends on the platform. Here is some difference we have noticed and how to workaround.
@@ -54,8 +72,7 @@ Snapshots are not guaranteed to be pixel perfect. It also depends on the platfor
54 72
 ### specific to Android implementation
55 73
 
56 74
 - you need to make sure `collapsable` is set to `false` if you want to snapshot a **View**. Otherwise that view won't reflect any UI View. ([found by @gaguirre](https://github.com/gre/react-native-view-shot/issues/7#issuecomment-245302844))
57
-- if you want to share out the screenshoted file, you will have to copy it somewhere first so it's accessible to an Intent, see comment: https://github.com/gre/react-native-view-shot/issues/11#issuecomment-251080804 .
58
--  if you implement a third party library and want to get back a File, you must first resolve the `Uri`. the `file` result returns an `Uri` so it's consistent with iOS and you can give it to `Image.getSize` for instance.
75
+-  if you implement a third party library and want to get back a File, you must first resolve the `Uri`. (the `file` result returns an `Uri` so it's consistent with iOS and can be given to APIs like `Image.getSize`)
59 76
 
60 77
 ## Getting started
61 78
 

+ 9
- 6
android/build.gradle View File

@@ -25,14 +25,17 @@ android {
25 25
     }
26 26
 }
27 27
 
28
-repositories {
29
-    mavenCentral()
30
-    maven {
31
-        // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
32
-        url "$projectDir/../../../node_modules/react-native/android"
28
+allprojects {
29
+    repositories {
30
+        mavenLocal()
31
+        jcenter()
32
+        maven {
33
+            // All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
34
+            url "$rootDir/../node_modules/react-native/android"
35
+        }
33 36
     }
34 37
 }
35 38
 
36 39
 dependencies {
37
-    compile "com.facebook.react:react-native:+"  // From node_modules
40
+    compile 'com.facebook.react:react-native:+'
38 41
 }

+ 40
- 11
android/src/main/java/fr/greweb/reactnativeviewshot/RNViewShotModule.java View File

@@ -4,6 +4,7 @@ package fr.greweb.reactnativeviewshot;
4 4
 import android.content.Context;
5 5
 import android.graphics.Bitmap;
6 6
 import android.os.AsyncTask;
7
+import android.os.Environment;
7 8
 import android.util.DisplayMetrics;
8 9
 import android.view.View;
9 10
 
@@ -22,6 +23,8 @@ import com.facebook.react.uimanager.UIManagerModule;
22 23
 import java.io.File;
23 24
 import java.io.FilenameFilter;
24 25
 import java.io.IOException;
26
+import java.util.HashMap;
27
+import java.util.Map;
25 28
 
26 29
 public class RNViewShotModule extends ReactContextBaseJavaModule {
27 30
 
@@ -37,6 +40,11 @@ public class RNViewShotModule extends ReactContextBaseJavaModule {
37 40
         return "RNViewShot";
38 41
     }
39 42
 
43
+    @Override
44
+    public Map<String, Object> getConstants() {
45
+        return getSystemFolders(this.getReactApplicationContext());
46
+    }
47
+
40 48
     @Override
41 49
     public void onCatalystInstanceDestroy() {
42 50
         super.onCatalystInstanceDestroy();
@@ -66,17 +74,25 @@ public class RNViewShotModule extends ReactContextBaseJavaModule {
66 74
         String result = options.hasKey("result") ? options.getString("result") : "file";
67 75
         Boolean snapshotContentContainer = options.hasKey("snapshotContentContainer") ? options.getBoolean("snapshotContentContainer") : false;
68 76
         try {
69
-            String name = options.hasKey("filename") ? options.getString("filename") : null;
70
-            File tmpFile = "file".equals(result) ? createTempFile(getReactApplicationContext(), format, name) : null;
77
+            File file = null;
78
+            if ("file".equals(result)) {
79
+                if (options.hasKey("path")) {
80
+                    file = new File(options.getString("path"));
81
+                    file.createNewFile();
82
+                }
83
+                else {
84
+                    file = createTempFile(getReactApplicationContext(), format);
85
+                }
86
+            }
71 87
             UIManagerModule uiManager = this.reactContext.getNativeModule(UIManagerModule.class);
72
-            uiManager.addUIBlock(new ViewShot(tag, format, compressFormat, quality, width, height, tmpFile, result, snapshotContentContainer, promise));
88
+            uiManager.addUIBlock(new ViewShot(tag, format, compressFormat, quality, width, height, file, result, snapshotContentContainer, promise));
73 89
         }
74 90
         catch (Exception e) {
75 91
             promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag "+tag);
76 92
         }
77 93
     }
78 94
 
79
-    private static final String TEMP_FILE_PREFIX = "ReactNative_snapshot_image_";
95
+    private static final String TEMP_FILE_PREFIX = "ReactNative-snapshot-image";
80 96
 
81 97
     /**
82 98
      * Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
@@ -116,11 +132,30 @@ public class RNViewShotModule extends ReactContextBaseJavaModule {
116 132
         }
117 133
     }
118 134
 
135
+    static private Map<String, Object> getSystemFolders(ReactApplicationContext ctx) {
136
+        Map<String, Object> res = new HashMap<>();
137
+        res.put("CacheDir", ctx.getCacheDir().getAbsolutePath());
138
+        res.put("DCIMDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DCIM).getAbsolutePath());
139
+        res.put("DocumentDir", ctx.getFilesDir().getAbsolutePath());
140
+        res.put("DownloadDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS).getAbsolutePath());
141
+        res.put("MainBundleDir", ctx.getApplicationInfo().dataDir);
142
+        res.put("MovieDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES).getAbsolutePath());
143
+        res.put("MusicDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MUSIC).getAbsolutePath());
144
+        res.put("PictureDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES).getAbsolutePath());
145
+        res.put("RingtoneDir", Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_RINGTONES).getAbsolutePath());
146
+        String state;
147
+        state = Environment.getExternalStorageState();
148
+        if (state.equals(Environment.MEDIA_MOUNTED)) {
149
+            res.put("SDCardDir", Environment.getExternalStorageDirectory().getAbsolutePath());
150
+        }
151
+        return res;
152
+    }
153
+
119 154
     /**
120 155
      * Create a temporary file in the cache directory on either internal or external storage,
121 156
      * whichever is available and has more free space.
122 157
      */
123
-    private File createTempFile(Context context, String ext, String name)
158
+    private File createTempFile(Context context, String ext)
124 159
             throws IOException {
125 160
         File externalCacheDir = context.getExternalCacheDir();
126 161
         File internalCacheDir = context.getCacheDir();
@@ -139,12 +174,6 @@ public class RNViewShotModule extends ReactContextBaseJavaModule {
139 174
         }
140 175
         String suffix = "." + ext;
141 176
         File tmpFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, cacheDir);
142
-        if (name != null) {
143
-            File renamed = new File(cacheDir, name + suffix);
144
-            tmpFile.renameTo(renamed);
145
-            return renamed;
146
-        }
147
-
148 177
         return tmpFile;
149 178
     }
150 179
 

+ 1
- 1
android/src/main/java/fr/greweb/reactnativeviewshot/ViewShot.java View File

@@ -124,7 +124,7 @@ public class ViewShot implements UIBlock {
124 124
         }
125 125
 
126 126
         //evaluate real height
127
-        if (this.snapshotContentContainer){
127
+        if (snapshotContentContainer) {
128 128
             h=0;
129 129
             ScrollView scrollView = (ScrollView)view;
130 130
             for (int i = 0; i < scrollView.getChildCount(); i++) {

+ 25
- 11
index.js View File

@@ -1,19 +1,33 @@
1 1
 //@flow
2
-
3 2
 import { NativeModules, findNodeHandle } from "react-native";
4
-
5 3
 const { RNViewShot } = NativeModules;
6 4
 
5
+export const dirs = {
6
+  // cross platform
7
+  CacheDir: RNViewShot.CacheDir,
8
+  DocumentDir: RNViewShot.DocumentDir,
9
+  MainBundleDir: RNViewShot.MainBundleDir,
10
+  MovieDir: RNViewShot.MovieDir,
11
+  MusicDir: RNViewShot.MusicDir,
12
+  PictureDir: RNViewShot.PictureDir,
13
+  // only Android
14
+  DCIMDir: RNViewShot.DCIMDir,
15
+  DownloadDir: RNViewShot.DownloadDir,
16
+  RingtoneDir: RNViewShot.RingtoneDir,
17
+  SDCardDir: RNViewShot.SDCardDir,
18
+};
19
+
7 20
 export function takeSnapshot(
8 21
   view: number | ReactElement<any>,
9
-  options ?: {
10
-    width ?: number;
11
-    height ?: number;
12
-    filename ?: string;
13
-    format ?: "png" | "jpg" | "jpeg" | "webm";
14
-    quality ?: number;
15
-    result ?: "file" | "base64" | "data-uri";
16
-  }
22
+  options?: {
23
+    width?: number,
24
+    height?: number,
25
+    path?: string,
26
+    format?: "png" | "jpg" | "jpeg" | "webm",
27
+    quality?: number,
28
+    result?: "file" | "base64" | "data-uri",
29
+    snapshotContentContainer?: bool
30
+  } = {}
17 31
 ): Promise<string> {
18 32
   if (typeof view !== "number") {
19 33
     const node = findNodeHandle(view);
@@ -23,4 +37,4 @@ export function takeSnapshot(
23 37
   return RNViewShot.takeSnapshot(view, options);
24 38
 }
25 39
 
26
-export default { takeSnapshot };
40
+export default { takeSnapshot, dirs };

+ 30
- 6
ios/RNViewShot.m View File

@@ -20,6 +20,18 @@ RCT_EXPORT_MODULE()
20 20
   return self.bridge.uiManager.methodQueue;
21 21
 }
22 22
 
23
+- (NSDictionary *)constantsToExport
24
+{
25
+  return @{
26
+           @"CacheDir" : [NSSearchPathForDirectoriesInDomains(NSCachesDirectory, NSUserDomainMask, YES) firstObject],
27
+           @"DocumentDir": [NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES) firstObject],
28
+           @"MainBundleDir" : [[NSBundle mainBundle] bundlePath],
29
+           @"MovieDir": [NSSearchPathForDirectoriesInDomains(NSMoviesDirectory, NSUserDomainMask, YES) firstObject],
30
+           @"MusicDir": [NSSearchPathForDirectoriesInDomains(NSMusicDirectory, NSUserDomainMask, YES) firstObject],
31
+           @"PictureDir": [NSSearchPathForDirectoriesInDomains(NSPicturesDirectory, NSUserDomainMask, YES) firstObject],
32
+           };
33
+}
34
+
23 35
 // forked from RN implementation
24 36
 // https://github.com/facebook/react-native/blob/f35b372883a76b5666b016131d59268b42f3c40d/React/Modules/RCTUIManager.m#L1367
25 37
 
@@ -75,10 +87,22 @@ RCT_EXPORT_METHOD(takeSnapshot:(nonnull NSNumber *)target
75 87
       NSString *res = nil;
76 88
       if ([result isEqualToString:@"file"]) {
77 89
         // Save to a temp file
78
-        NSString *tempFilePath = RCTTempFilePath(format, &error);
79
-        if (tempFilePath) {
80
-          if ([data writeToFile:tempFilePath options:(NSDataWritingOptions)0 error:&error]) {
81
-            res = tempFilePath;
90
+        NSString *path;
91
+        if (options[@"path"]) {
92
+          path = options[@"path"];
93
+          NSString * folder = [path stringByDeletingLastPathComponent];
94
+          NSFileManager * fm = [NSFileManager defaultManager];
95
+          if(![fm fileExistsAtPath:folder]) {
96
+            [fm createDirectoryAtPath:folder withIntermediateDirectories:YES attributes:NULL error:&error];
97
+            [fm createFileAtPath:path contents:nil attributes:nil];
98
+          }
99
+        }
100
+        else {
101
+          path = RCTTempFilePath(format, &error);
102
+        }
103
+        if (path && !error) {
104
+          if ([data writeToFile:path options:(NSDataWritingOptions)0 error:&error]) {
105
+            res = path;
82 106
           }
83 107
         }
84 108
       }
@@ -95,13 +119,13 @@ RCT_EXPORT_METHOD(takeSnapshot:(nonnull NSNumber *)target
95 119
         reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unsupported result: %@. Try one of: file | base64 | data-uri", result], nil);
96 120
         return;
97 121
       }
98
-      if (res != nil) {
122
+      if (res && !error) {
99 123
         resolve(res);
100 124
         return;
101 125
       }
102 126
       
103 127
       // If we reached here, something went wrong
104
-      if (error != nil) reject(RCTErrorUnspecified, error.localizedDescription, error);
128
+      if (error) reject(RCTErrorUnspecified, error.localizedDescription, error);
105 129
       else reject(RCTErrorUnspecified, @"viewshot unknown error", nil);
106 130
     });
107 131
   }];