Skip to content

Commit f4fe9bf

Browse files
committed
init
0 parents  commit f4fe9bf

File tree

13 files changed

+827
-0
lines changed

13 files changed

+827
-0
lines changed

.flowconfig

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
[ignore]
2+
3+
[include]
4+
5+
[libs]
6+
7+
[options]

.gitignore

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,39 @@
1+
# OSX
2+
#
3+
.DS_Store
4+
5+
# Xcode
6+
#
7+
build/
8+
*.pbxuser
9+
!default.pbxuser
10+
*.mode1v3
11+
!default.mode1v3
12+
*.mode2v3
13+
!default.mode2v3
14+
*.perspectivev3
15+
!default.perspectivev3
16+
xcuserdata
17+
*.xccheckout
18+
*.moved-aside
19+
DerivedData
20+
*.hmap
21+
*.ipa
22+
*.xcuserstate
23+
project.xcworkspace
24+
25+
# node.js
26+
#
27+
node_modules/
28+
npm-debug.log
29+
30+
# android
31+
#
32+
android/build/
33+
android/.gradle/
34+
android/.idea/
35+
android/android.iml
36+
android/gradle/
37+
android/gradlew
38+
android/gradlew.bat
39+
android/local.properties

README.md

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
2+
# react-native-view-shot
3+
4+
Snapshot a view and save to an image.
5+
6+
The image will be stored in a temporary file that will only exist for as long as the app is running.
7+
8+
## Getting started
9+
10+
```
11+
npm install --save react-native-view-shot
12+
```
13+
14+
### Mostly automatic installation
15+
16+
```
17+
react-native link react-native-view-shot
18+
```
19+
20+
### Manual installation
21+
22+
#### iOS
23+
24+
1. In XCode, in the project navigator, right click `Libraries``Add Files to [your project's name]`
25+
2. Go to `node_modules``react-native-view-shot` and add `RNViewShot.xcodeproj`
26+
3. In XCode, in the project navigator, select your project. Add `libRNViewShot.a` to your project's `Build Phases``Link Binary With Libraries`
27+
4. Run your project (`Cmd+R`)<
28+
29+
#### Android
30+
31+
1. Open up `android/app/src/main/java/[...]/MainActivity.java`
32+
- Add `import com.reactlibrary.RNViewShotPackage;` to the imports at the top of the file
33+
- Add `new RNViewShotPackage()` to the list returned by the `getPackages()` method
34+
2. Append the following lines to `android/settings.gradle`:
35+
```
36+
include ':react-native-view-shot'
37+
project(':react-native-view-shot').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-view-shot/android')
38+
```
39+
3. Insert the following lines inside the dependencies block in `android/app/build.gradle`:
40+
```
41+
compile project(':react-native-view-shot')
42+
```
43+
44+
#### Windows
45+
46+
No support yet. Feel free to PR.
47+
48+
## Usage
49+
50+
```js
51+
import { findNodeHandle } from "react-native";
52+
import RNViewShot from "react-native-view-shot";
53+
54+
RNViewShot.takeSnapshot(findNodeHandle(aRef), {
55+
format: "jpeg",
56+
quality: 0.8
57+
})
58+
.then(
59+
uri => console.log("Image saved to", uri),
60+
error => console.error("Oops, snapshot failed", error)
61+
);
62+
```
63+
64+
## Example
65+
66+
[Checkout react-native-view-shot-example](https://github.com/gre/react-native-view-shot-example)
67+
68+
## Full API
69+
70+
### `RNViewShot.takeSnapshot(view, options)`
71+
72+
Returns a Promise of the image URI.
73+
74+
- **`view`** references to a React Native component.
75+
- **`options`** may include:
76+
- **`width`** / **`height`** *(number)*: the width and height of the image to capture.
77+
- **`format`** *(string)*: either `png` or `jpg`/`jpeg` or `webm` (Android). Defaults to `png`.
78+
- **`quality`** *(number)*: the quality. 0.0 - 1.0 (default). (only available on lossy formats like jpeg)
79+
80+
## Notes
81+
82+
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.
83+
84+
- Support of special components like Video / GL views remains untested.
85+
- It's preferable to **use a background color on the view you rasterize** to avoid transparent pixels and potential weirdness that some border appear around texts.
86+
87+
## Thanks
88+
89+
- To initial iOS work done by @jsierles in https://github.com/jsierles/react-native-view-snapshot
90+
- To React Native implementation of takeSnapshot in iOS by @nicklockwood

android/build.gradle

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
buildscript {
2+
repositories {
3+
jcenter()
4+
}
5+
6+
dependencies {
7+
classpath 'com.android.tools.build:gradle:2.1.3'
8+
}
9+
}
10+
11+
apply plugin: 'com.android.library'
12+
13+
android {
14+
compileSdkVersion 23
15+
buildToolsVersion "23.0.1"
16+
17+
defaultConfig {
18+
minSdkVersion 16
19+
targetSdkVersion 22
20+
versionCode 1
21+
versionName "1.0"
22+
}
23+
lintOptions {
24+
abortOnError false
25+
}
26+
}
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"
33+
}
34+
}
35+
36+
dependencies {
37+
compile "com.facebook.react:react-native:+" // From node_modules
38+
}
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
2+
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
3+
package="com.reactlibrary">
4+
5+
</manifest>
6+
Lines changed: 138 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,138 @@
1+
2+
package fr.greweb.reactnativeviewshot;
3+
4+
import android.content.Context;
5+
import android.graphics.Bitmap;
6+
import android.os.AsyncTask;
7+
import android.util.DisplayMetrics;
8+
9+
import com.facebook.react.bridge.ReactApplicationContext;
10+
import com.facebook.react.bridge.ReactContextBaseJavaModule;
11+
import com.facebook.react.bridge.ReactMethod;
12+
13+
import com.facebook.react.bridge.GuardedAsyncTask;
14+
import com.facebook.react.bridge.JSApplicationIllegalArgumentException;
15+
import com.facebook.react.bridge.Promise;
16+
import com.facebook.react.bridge.ReactContext;
17+
import com.facebook.react.bridge.ReadableMap;
18+
import com.facebook.react.uimanager.UIBlock;
19+
import com.facebook.react.uimanager.UIManagerModule;
20+
21+
import java.io.File;
22+
import java.io.FilenameFilter;
23+
import java.io.IOException;
24+
25+
public class RNViewShotModule extends ReactContextBaseJavaModule {
26+
27+
private final ReactApplicationContext reactContext;
28+
29+
public RNViewShotModule(ReactApplicationContext reactContext) {
30+
super(reactContext);
31+
this.reactContext = reactContext;
32+
}
33+
34+
@Override
35+
public String getName() {
36+
return "RNViewShot";
37+
}
38+
39+
@Override
40+
public void onCatalystInstanceDestroy() {
41+
super.onCatalystInstanceDestroy();
42+
new CleanTask(getReactApplicationContext()).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
43+
}
44+
45+
@ReactMethod
46+
public void takeSnapshot(int tag, ReadableMap options, Promise promise) {
47+
ReactApplicationContext context = getReactApplicationContext();
48+
String format = options.hasKey("format") ? options.getString("format") : "png";
49+
Bitmap.CompressFormat compressFormat =
50+
format.equals("png")
51+
? Bitmap.CompressFormat.PNG
52+
: format.equals("jpg")||format.equals("jpeg")
53+
? Bitmap.CompressFormat.JPEG
54+
: format.equals("webm")
55+
? Bitmap.CompressFormat.WEBP
56+
: null;
57+
if (compressFormat == null) {
58+
throw new JSApplicationIllegalArgumentException("Unsupported image format: " + format);
59+
}
60+
double quality = options.hasKey("quality") ? options.getDouble("quality") : 1.0;
61+
DisplayMetrics displayMetrics = context.getResources().getDisplayMetrics();
62+
Integer width = options.hasKey("width") ? (int)(displayMetrics.density * options.getDouble("width")) : null;
63+
Integer height = options.hasKey("height") ? (int)(displayMetrics.density * options.getDouble("height")) : null;
64+
try {
65+
File tmpFile = createTempFile(getReactApplicationContext(), format);
66+
UIManagerModule uiManager = this.reactContext.getNativeModule(UIManagerModule.class);
67+
uiManager.addUIBlock(new ViewShot(tag, compressFormat, quality, width, height, tmpFile, promise));
68+
}
69+
catch (Exception e) {
70+
promise.reject(ViewShot.ERROR_UNABLE_TO_SNAPSHOT, "Failed to snapshot view tag "+tag);
71+
}
72+
}
73+
74+
private static final String TEMP_FILE_PREFIX = "ReactNative_snapshot_image_";
75+
76+
/**
77+
* Asynchronous task that cleans up cache dirs (internal and, if available, external) of cropped
78+
* image files. This is run when the catalyst instance is being destroyed (i.e. app is shutting
79+
* down) and when the module is instantiated, to handle the case where the app crashed.
80+
*/
81+
private static class CleanTask extends GuardedAsyncTask<Void, Void> {
82+
private final Context mContext;
83+
84+
private CleanTask(ReactContext context) {
85+
super(context);
86+
mContext = context;
87+
}
88+
89+
@Override
90+
protected void doInBackgroundGuarded(Void... params) {
91+
cleanDirectory(mContext.getCacheDir());
92+
File externalCacheDir = mContext.getExternalCacheDir();
93+
if (externalCacheDir != null) {
94+
cleanDirectory(externalCacheDir);
95+
}
96+
}
97+
98+
private void cleanDirectory(File directory) {
99+
File[] toDelete = directory.listFiles(
100+
new FilenameFilter() {
101+
@Override
102+
public boolean accept(File dir, String filename) {
103+
return filename.startsWith(TEMP_FILE_PREFIX);
104+
}
105+
});
106+
if (toDelete != null) {
107+
for (File file: toDelete) {
108+
file.delete();
109+
}
110+
}
111+
}
112+
}
113+
114+
/**
115+
* Create a temporary file in the cache directory on either internal or external storage,
116+
* whichever is available and has more free space.
117+
*/
118+
private File createTempFile(Context context, String ext)
119+
throws IOException {
120+
File externalCacheDir = context.getExternalCacheDir();
121+
File internalCacheDir = context.getCacheDir();
122+
File cacheDir;
123+
if (externalCacheDir == null && internalCacheDir == null) {
124+
throw new IOException("No cache directory available");
125+
}
126+
if (externalCacheDir == null) {
127+
cacheDir = internalCacheDir;
128+
}
129+
else if (internalCacheDir == null) {
130+
cacheDir = externalCacheDir;
131+
} else {
132+
cacheDir = externalCacheDir.getFreeSpace() > internalCacheDir.getFreeSpace() ?
133+
externalCacheDir : internalCacheDir;
134+
}
135+
return File.createTempFile(TEMP_FILE_PREFIX, ext, cacheDir);
136+
}
137+
138+
}
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
2+
package fr.greweb.reactnativeviewshot;
3+
4+
import java.util.Arrays;
5+
import java.util.Collections;
6+
import java.util.List;
7+
8+
import com.facebook.react.ReactPackage;
9+
import com.facebook.react.bridge.NativeModule;
10+
import com.facebook.react.bridge.ReactApplicationContext;
11+
import com.facebook.react.uimanager.ViewManager;
12+
import com.facebook.react.bridge.JavaScriptModule;
13+
public class RNViewShotPackage implements ReactPackage {
14+
@Override
15+
public List<NativeModule> createNativeModules(ReactApplicationContext reactContext) {
16+
return Arrays.<NativeModule>asList(new RNViewShotModule(reactContext));
17+
}
18+
19+
@Override
20+
public List<Class<? extends JavaScriptModule>> createJSModules() {
21+
return Collections.emptyList();
22+
}
23+
24+
@Override
25+
public List<ViewManager> createViewManagers(ReactApplicationContext reactContext) {
26+
return Collections.emptyList();
27+
}
28+
}

0 commit comments

Comments
 (0)