Browse Source

Merge pull request #42 from gre/dirs

drop filename option, add path option
Gaëtan Renaudeau 7 years ago
parent
commit
36c8271be4

+ 23
- 6
README.md View File

10
 ## Usage
10
 ## Usage
11
 
11
 
12
 ```js
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
   format: "jpeg",
16
   format: "jpeg",
17
   quality: 0.8
17
   quality: 0.8
18
 })
18
 })
28
 
28
 
29
 ## Full API
29
 ## Full API
30
 
30
 
31
-### `RNViewShot.takeSnapshot(view, options)`
31
+### `takeSnapshot(view, options)`
32
 
32
 
33
 Returns a Promise of the image URI.
33
 Returns a Promise of the image URI.
34
 
34
 
41
     - `"file"` (default): save to a temporary file *(that will only exist for as long as the app is running)*.
41
     - `"file"` (default): save to a temporary file *(that will only exist for as long as the app is running)*.
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*.
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
     - `"data-uri"`: same as `base64` but also includes the [Data URI scheme](https://en.wikipedia.org/wiki/Data_URI_scheme) header.
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
  - **`snapshotContentContainer`** *(bool)*: if true and when view is a ScrollView, the "content container" height will be evaluated instead of the container height. (Android only)
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
 ## Caveats
65
 ## Caveats
48
 
66
 
49
 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.
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
 ### specific to Android implementation
72
 ### specific to Android implementation
55
 
73
 
56
 - 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))
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
 ## Getting started
77
 ## Getting started
61
 
78
 

+ 9
- 6
android/build.gradle View File

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
 dependencies {
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
 import android.content.Context;
4
 import android.content.Context;
5
 import android.graphics.Bitmap;
5
 import android.graphics.Bitmap;
6
 import android.os.AsyncTask;
6
 import android.os.AsyncTask;
7
+import android.os.Environment;
7
 import android.util.DisplayMetrics;
8
 import android.util.DisplayMetrics;
8
 import android.view.View;
9
 import android.view.View;
9
 
10
 
22
 import java.io.File;
23
 import java.io.File;
23
 import java.io.FilenameFilter;
24
 import java.io.FilenameFilter;
24
 import java.io.IOException;
25
 import java.io.IOException;
26
+import java.util.HashMap;
27
+import java.util.Map;
25
 
28
 
26
 public class RNViewShotModule extends ReactContextBaseJavaModule {
29
 public class RNViewShotModule extends ReactContextBaseJavaModule {
27
 
30
 
37
         return "RNViewShot";
40
         return "RNViewShot";
38
     }
41
     }
39
 
42
 
43
+    @Override
44
+    public Map<String, Object> getConstants() {
45
+        return getSystemFolders(this.getReactApplicationContext());
46
+    }
47
+
40
     @Override
48
     @Override
41
     public void onCatalystInstanceDestroy() {
49
     public void onCatalystInstanceDestroy() {
42
         super.onCatalystInstanceDestroy();
50
         super.onCatalystInstanceDestroy();
66
         String result = options.hasKey("result") ? options.getString("result") : "file";
74
         String result = options.hasKey("result") ? options.getString("result") : "file";
67
         Boolean snapshotContentContainer = options.hasKey("snapshotContentContainer") ? options.getBoolean("snapshotContentContainer") : false;
75
         Boolean snapshotContentContainer = options.hasKey("snapshotContentContainer") ? options.getBoolean("snapshotContentContainer") : false;
68
         try {
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
             UIManagerModule uiManager = this.reactContext.getNativeModule(UIManagerModule.class);
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
         catch (Exception e) {
90
         catch (Exception e) {
75
             promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag "+tag);
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
      * Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
98
      * Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
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
      * Create a temporary file in the cache directory on either internal or external storage,
155
      * Create a temporary file in the cache directory on either internal or external storage,
121
      * whichever is available and has more free space.
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
             throws IOException {
159
             throws IOException {
125
         File externalCacheDir = context.getExternalCacheDir();
160
         File externalCacheDir = context.getExternalCacheDir();
126
         File internalCacheDir = context.getCacheDir();
161
         File internalCacheDir = context.getCacheDir();
139
         }
174
         }
140
         String suffix = "." + ext;
175
         String suffix = "." + ext;
141
         File tmpFile = File.createTempFile(TEMP_FILE_PREFIX, suffix, cacheDir);
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
         return tmpFile;
177
         return tmpFile;
149
     }
178
     }
150
 
179
 

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

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

+ 25
- 11
index.js View File

1
 //@flow
1
 //@flow
2
-
3
 import { NativeModules, findNodeHandle } from "react-native";
2
 import { NativeModules, findNodeHandle } from "react-native";
4
-
5
 const { RNViewShot } = NativeModules;
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
 export function takeSnapshot(
20
 export function takeSnapshot(
8
   view: number | ReactElement<any>,
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
 ): Promise<string> {
31
 ): Promise<string> {
18
   if (typeof view !== "number") {
32
   if (typeof view !== "number") {
19
     const node = findNodeHandle(view);
33
     const node = findNodeHandle(view);
23
   return RNViewShot.takeSnapshot(view, options);
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
   return self.bridge.uiManager.methodQueue;
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
 // forked from RN implementation
35
 // forked from RN implementation
24
 // https://github.com/facebook/react-native/blob/f35b372883a76b5666b016131d59268b42f3c40d/React/Modules/RCTUIManager.m#L1367
36
 // https://github.com/facebook/react-native/blob/f35b372883a76b5666b016131d59268b42f3c40d/React/Modules/RCTUIManager.m#L1367
25
 
37
 
75
       NSString *res = nil;
87
       NSString *res = nil;
76
       if ([result isEqualToString:@"file"]) {
88
       if ([result isEqualToString:@"file"]) {
77
         // Save to a temp file
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
         reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unsupported result: %@. Try one of: file | base64 | data-uri", result], nil);
119
         reject(RCTErrorUnspecified, [NSString stringWithFormat:@"Unsupported result: %@. Try one of: file | base64 | data-uri", result], nil);
96
         return;
120
         return;
97
       }
121
       }
98
-      if (res != nil) {
122
+      if (res && !error) {
99
         resolve(res);
123
         resolve(res);
100
         return;
124
         return;
101
       }
125
       }
102
       
126
       
103
       // If we reached here, something went wrong
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
       else reject(RCTErrorUnspecified, @"viewshot unknown error", nil);
129
       else reject(RCTErrorUnspecified, @"viewshot unknown error", nil);
106
     });
130
     });
107
   }];
131
   }];