Merge pull request #1071 from react-native-community/feature/cameraview
Add Camera implementation using CameraView on Android
This commit is contained in:
commit
14af35aca3
@ -1,11 +1,5 @@
|
||||
import React from 'react';
|
||||
import {
|
||||
Image,
|
||||
StatusBar,
|
||||
StyleSheet,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import { Image, StatusBar, StyleSheet, TouchableOpacity, View } from 'react-native';
|
||||
import Camera from 'react-native-camera';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
@ -68,37 +62,39 @@ export default class Example extends React.Component {
|
||||
orientation: Camera.constants.Orientation.auto,
|
||||
flashMode: Camera.constants.FlashMode.auto,
|
||||
},
|
||||
isRecording: false
|
||||
isRecording: false,
|
||||
};
|
||||
}
|
||||
|
||||
takePicture = () => {
|
||||
if (this.camera) {
|
||||
this.camera.capture()
|
||||
.then((data) => console.log(data))
|
||||
this.camera
|
||||
.capture()
|
||||
.then(data => console.log(data))
|
||||
.catch(err => console.error(err));
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
startRecording = () => {
|
||||
if (this.camera) {
|
||||
this.camera.capture({mode: Camera.constants.CaptureMode.video})
|
||||
.then((data) => console.log(data))
|
||||
.catch(err => console.error(err));
|
||||
this.camera
|
||||
.capture({ mode: Camera.constants.CaptureMode.video })
|
||||
.then(data => console.log(data))
|
||||
.catch(err => console.error(err));
|
||||
this.setState({
|
||||
isRecording: true
|
||||
isRecording: true,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
stopRecording = () => {
|
||||
if (this.camera) {
|
||||
this.camera.stopCapture();
|
||||
this.setState({
|
||||
isRecording: false
|
||||
isRecording: false,
|
||||
});
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
switchType = () => {
|
||||
let newType;
|
||||
@ -116,7 +112,7 @@ export default class Example extends React.Component {
|
||||
type: newType,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
get typeIcon() {
|
||||
let icon;
|
||||
@ -149,7 +145,7 @@ export default class Example extends React.Component {
|
||||
flashMode: newFlashMode,
|
||||
},
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
get flashIcon() {
|
||||
let icon;
|
||||
@ -169,12 +165,9 @@ export default class Example extends React.Component {
|
||||
render() {
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
<StatusBar
|
||||
animated
|
||||
hidden
|
||||
/>
|
||||
<StatusBar animated hidden />
|
||||
<Camera
|
||||
ref={(cam) => {
|
||||
ref={cam => {
|
||||
this.camera = cam;
|
||||
}}
|
||||
style={styles.preview}
|
||||
@ -190,60 +183,30 @@ export default class Example extends React.Component {
|
||||
permissionDialogMessage="Sample dialog message"
|
||||
/>
|
||||
<View style={[styles.overlay, styles.topOverlay]}>
|
||||
<TouchableOpacity
|
||||
style={styles.typeButton}
|
||||
onPress={this.switchType}
|
||||
>
|
||||
<Image
|
||||
source={this.typeIcon}
|
||||
/>
|
||||
<TouchableOpacity style={styles.typeButton} onPress={this.switchType}>
|
||||
<Image source={this.typeIcon} />
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={styles.flashButton}
|
||||
onPress={this.switchFlash}
|
||||
>
|
||||
<Image
|
||||
source={this.flashIcon}
|
||||
/>
|
||||
<TouchableOpacity style={styles.flashButton} onPress={this.switchFlash}>
|
||||
<Image source={this.flashIcon} />
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View style={[styles.overlay, styles.bottomOverlay]}>
|
||||
{
|
||||
!this.state.isRecording
|
||||
&&
|
||||
<TouchableOpacity
|
||||
style={styles.captureButton}
|
||||
onPress={this.takePicture}
|
||||
>
|
||||
<Image
|
||||
source={require('./assets/ic_photo_camera_36pt.png')}
|
||||
/>
|
||||
{(!this.state.isRecording && (
|
||||
<TouchableOpacity style={styles.captureButton} onPress={this.takePicture}>
|
||||
<Image source={require('./assets/ic_photo_camera_36pt.png')} />
|
||||
</TouchableOpacity>
|
||||
||
|
||||
null
|
||||
}
|
||||
)) ||
|
||||
null}
|
||||
<View style={styles.buttonsSpace} />
|
||||
{
|
||||
!this.state.isRecording
|
||||
&&
|
||||
<TouchableOpacity
|
||||
style={styles.captureButton}
|
||||
onPress={this.startRecording}
|
||||
>
|
||||
<Image
|
||||
source={require('./assets/ic_videocam_36pt.png')}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
||
|
||||
<TouchableOpacity
|
||||
style={styles.captureButton}
|
||||
onPress={this.stopRecording}
|
||||
>
|
||||
<Image
|
||||
source={require('./assets/ic_stop_36pt.png')}
|
||||
/>
|
||||
</TouchableOpacity>
|
||||
}
|
||||
{(!this.state.isRecording && (
|
||||
<TouchableOpacity style={styles.captureButton} onPress={this.startRecording}>
|
||||
<Image source={require('./assets/ic_videocam_36pt.png')} />
|
||||
</TouchableOpacity>
|
||||
)) || (
|
||||
<TouchableOpacity style={styles.captureButton} onPress={this.stopRecording}>
|
||||
<Image source={require('./assets/ic_stop_36pt.png')} />
|
||||
</TouchableOpacity>
|
||||
)}
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
|
||||
@ -1,8 +1,14 @@
|
||||
|
||||
# React Native Camera [](#backers) [](#sponsors) [](http://badge.fury.io/js/react-native-camera) [](https://gitter.im/lwansbrough/react-native-camera)
|
||||
|
||||
The comprehensive camera module for React Native. Including photographs, videos, and barcode scanning!
|
||||
|
||||
### Experimental
|
||||
RNCamera and FaceDetector module for Android based on Expo camera module (https://docs.expo.io/versions/latest/sdk/camera.html)
|
||||
|
||||
You can test and use this from master like this:
|
||||
|
||||
`import { RNCamera, FaceDetector } from 'react-native-camera';`
|
||||
|
||||
### Contributing
|
||||
- Pull Requests are welcome, if you open a pull request we will do our best to get to it in a timely manner
|
||||
- Pull Request Reviews and even more welcome! we need help testing, reviewing, and updating open PRs
|
||||
|
||||
8
RNCameraExample/.babelrc
Normal file
8
RNCameraExample/.babelrc
Normal file
@ -0,0 +1,8 @@
|
||||
{
|
||||
"plugins": [
|
||||
"transform-export-extensions"
|
||||
],
|
||||
"presets": [
|
||||
"react-native"
|
||||
]
|
||||
}
|
||||
6
RNCameraExample/.buckconfig
Normal file
6
RNCameraExample/.buckconfig
Normal file
@ -0,0 +1,6 @@
|
||||
|
||||
[android]
|
||||
target = Google Inc.:Google APIs:23
|
||||
|
||||
[maven_repositories]
|
||||
central = https://repo1.maven.org/maven2
|
||||
48
RNCameraExample/.flowconfig
Normal file
48
RNCameraExample/.flowconfig
Normal file
@ -0,0 +1,48 @@
|
||||
[ignore]
|
||||
; We fork some components by platform
|
||||
.*/*[.]android.js
|
||||
|
||||
; Ignore "BUCK" generated dirs
|
||||
<PROJECT_ROOT>/\.buckd/
|
||||
|
||||
; Ignore unexpected extra "@providesModule"
|
||||
.*/node_modules/.*/node_modules/fbjs/.*
|
||||
|
||||
; Ignore duplicate module providers
|
||||
; For RN Apps installed via npm, "Libraries" folder is inside
|
||||
; "node_modules/react-native" but in the source repo it is in the root
|
||||
.*/Libraries/react-native/React.js
|
||||
|
||||
; Ignore polyfills
|
||||
.*/Libraries/polyfills/.*
|
||||
|
||||
[include]
|
||||
|
||||
[libs]
|
||||
node_modules/react-native/Libraries/react-native/react-native-interface.js
|
||||
node_modules/react-native/flow/
|
||||
|
||||
[options]
|
||||
emoji=true
|
||||
|
||||
module.system=haste
|
||||
|
||||
munge_underscores=true
|
||||
|
||||
module.name_mapper='^[./a-zA-Z0-9$_-]+\.\(bmp\|gif\|jpg\|jpeg\|png\|psd\|svg\|webp\|m4v\|mov\|mp4\|mpeg\|mpg\|webm\|aac\|aiff\|caf\|m4a\|mp3\|wav\|html\|pdf\)$' -> 'RelativeImageStub'
|
||||
|
||||
suppress_type=$FlowIssue
|
||||
suppress_type=$FlowFixMe
|
||||
suppress_type=$FlowFixMeProps
|
||||
suppress_type=$FlowFixMeState
|
||||
suppress_type=$FixMe
|
||||
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixMe\\($\\|[^(]\\|(\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowIssue\\((\\(>=0\\.\\(5[0-7]\\|[1-4][0-9]\\|[0-9]\\).[0-9]\\)? *\\(site=[a-z,_]*react_native[a-z,_]*\\)?)\\)?:? #[0-9]+
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowFixedInNextDeploy
|
||||
suppress_comment=\\(.\\|\n\\)*\\$FlowExpectedError
|
||||
|
||||
unsafe.enable_getters_and_setters=true
|
||||
|
||||
[version]
|
||||
^0.57.0
|
||||
1
RNCameraExample/.gitattributes
vendored
Normal file
1
RNCameraExample/.gitattributes
vendored
Normal file
@ -0,0 +1 @@
|
||||
*.pbxproj -text
|
||||
53
RNCameraExample/.gitignore
vendored
Normal file
53
RNCameraExample/.gitignore
vendored
Normal file
@ -0,0 +1,53 @@
|
||||
# OSX
|
||||
#
|
||||
.DS_Store
|
||||
|
||||
# Xcode
|
||||
#
|
||||
build/
|
||||
*.pbxuser
|
||||
!default.pbxuser
|
||||
*.mode1v3
|
||||
!default.mode1v3
|
||||
*.mode2v3
|
||||
!default.mode2v3
|
||||
*.perspectivev3
|
||||
!default.perspectivev3
|
||||
xcuserdata
|
||||
*.xccheckout
|
||||
*.moved-aside
|
||||
DerivedData
|
||||
*.hmap
|
||||
*.ipa
|
||||
*.xcuserstate
|
||||
project.xcworkspace
|
||||
|
||||
# Android/IntelliJ
|
||||
#
|
||||
build/
|
||||
.idea
|
||||
.gradle
|
||||
local.properties
|
||||
*.iml
|
||||
|
||||
# node.js
|
||||
#
|
||||
node_modules/
|
||||
npm-debug.log
|
||||
yarn-error.log
|
||||
|
||||
# BUCK
|
||||
buck-out/
|
||||
\.buckd/
|
||||
*.keystore
|
||||
|
||||
# fastlane
|
||||
#
|
||||
# It is recommended to not store the screenshots in the git repo. Instead, use fastlane to re-generate the
|
||||
# screenshots whenever they are needed.
|
||||
# For more information about the recommended setup visit:
|
||||
# https://docs.fastlane.tools/best-practices/source-control/
|
||||
|
||||
*/fastlane/report.xml
|
||||
*/fastlane/Preview.html
|
||||
*/fastlane/screenshots
|
||||
1
RNCameraExample/.watchmanconfig
Normal file
1
RNCameraExample/.watchmanconfig
Normal file
@ -0,0 +1 @@
|
||||
{}
|
||||
361
RNCameraExample/App.js
Normal file
361
RNCameraExample/App.js
Normal file
@ -0,0 +1,361 @@
|
||||
import React from 'react';
|
||||
import { StyleSheet, Text, View, TouchableOpacity, Slider } from 'react-native';
|
||||
import { RNCamera } from 'react-native-camera';
|
||||
|
||||
const landmarkSize = 2;
|
||||
|
||||
const flashModeOrder = {
|
||||
off: 'on',
|
||||
on: 'auto',
|
||||
auto: 'torch',
|
||||
torch: 'off',
|
||||
};
|
||||
|
||||
const wbOrder = {
|
||||
auto: 'sunny',
|
||||
sunny: 'cloudy',
|
||||
cloudy: 'shadow',
|
||||
shadow: 'fluorescent',
|
||||
fluorescent: 'incandescent',
|
||||
incandescent: 'auto',
|
||||
};
|
||||
|
||||
export default class CameraScreen extends React.Component {
|
||||
state = {
|
||||
flash: 'off',
|
||||
zoom: 0,
|
||||
autoFocus: 'on',
|
||||
depth: 0,
|
||||
type: 'back',
|
||||
whiteBalance: 'auto',
|
||||
ratio: '16:9',
|
||||
ratios: [],
|
||||
photoId: 1,
|
||||
showGallery: false,
|
||||
photos: [],
|
||||
faces: [],
|
||||
};
|
||||
|
||||
getRatios = async function() {
|
||||
const ratios = await this.camera.getSupportedRatios();
|
||||
return ratios;
|
||||
};
|
||||
|
||||
toggleView() {
|
||||
this.setState({
|
||||
showGallery: !this.state.showGallery,
|
||||
});
|
||||
}
|
||||
|
||||
toggleFacing() {
|
||||
this.setState({
|
||||
type: this.state.type === 'back' ? 'front' : 'back',
|
||||
});
|
||||
}
|
||||
|
||||
toggleFlash() {
|
||||
this.setState({
|
||||
flash: flashModeOrder[this.state.flash],
|
||||
});
|
||||
}
|
||||
|
||||
setRatio(ratio) {
|
||||
this.setState({
|
||||
ratio,
|
||||
});
|
||||
}
|
||||
|
||||
toggleWB() {
|
||||
this.setState({
|
||||
whiteBalance: wbOrder[this.state.whiteBalance],
|
||||
});
|
||||
}
|
||||
|
||||
toggleFocus() {
|
||||
this.setState({
|
||||
autoFocus: this.state.autoFocus === 'on' ? 'off' : 'on',
|
||||
});
|
||||
}
|
||||
|
||||
zoomOut() {
|
||||
this.setState({
|
||||
zoom: this.state.zoom - 0.1 < 0 ? 0 : this.state.zoom - 0.1,
|
||||
});
|
||||
}
|
||||
|
||||
zoomIn() {
|
||||
this.setState({
|
||||
zoom: this.state.zoom + 0.1 > 1 ? 1 : this.state.zoom + 0.1,
|
||||
});
|
||||
}
|
||||
|
||||
setFocusDepth(depth) {
|
||||
this.setState({
|
||||
depth,
|
||||
});
|
||||
}
|
||||
|
||||
takePicture = async function() {
|
||||
if (this.camera) {
|
||||
this.camera.takePictureAsync().then(data => {
|
||||
console.log('data: ', data);
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
onFacesDetected = ({ faces }) => this.setState({ faces });
|
||||
onFaceDetectionError = state => console.warn('Faces detection error:', state);
|
||||
|
||||
renderFace({ bounds, faceID, rollAngle, yawAngle }) {
|
||||
return (
|
||||
<View
|
||||
key={faceID}
|
||||
transform={[
|
||||
{ perspective: 600 },
|
||||
{ rotateZ: `${rollAngle.toFixed(0)}deg` },
|
||||
{ rotateY: `${yawAngle.toFixed(0)}deg` },
|
||||
]}
|
||||
style={[
|
||||
styles.face,
|
||||
{
|
||||
...bounds.size,
|
||||
left: bounds.origin.x,
|
||||
top: bounds.origin.y,
|
||||
},
|
||||
]}
|
||||
>
|
||||
<Text style={styles.faceText}>ID: {faceID}</Text>
|
||||
<Text style={styles.faceText}>rollAngle: {rollAngle.toFixed(0)}</Text>
|
||||
<Text style={styles.faceText}>yawAngle: {yawAngle.toFixed(0)}</Text>
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderLandmarksOfFace(face) {
|
||||
const renderLandmark = position =>
|
||||
position && (
|
||||
<View
|
||||
style={[
|
||||
styles.landmark,
|
||||
{
|
||||
left: position.x - landmarkSize / 2,
|
||||
top: position.y - landmarkSize / 2,
|
||||
},
|
||||
]}
|
||||
/>
|
||||
);
|
||||
return (
|
||||
<View key={`landmarks-${face.faceID}`}>
|
||||
{renderLandmark(face.leftEyePosition)}
|
||||
{renderLandmark(face.rightEyePosition)}
|
||||
{renderLandmark(face.leftEarPosition)}
|
||||
{renderLandmark(face.rightEarPosition)}
|
||||
{renderLandmark(face.leftCheekPosition)}
|
||||
{renderLandmark(face.rightCheekPosition)}
|
||||
{renderLandmark(face.leftMouthPosition)}
|
||||
{renderLandmark(face.mouthPosition)}
|
||||
{renderLandmark(face.rightMouthPosition)}
|
||||
{renderLandmark(face.noseBasePosition)}
|
||||
{renderLandmark(face.bottomMouthPosition)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderFaces() {
|
||||
return (
|
||||
<View style={styles.facesContainer} pointerEvents="none">
|
||||
{this.state.faces.map(this.renderFace)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderLandmarks() {
|
||||
return (
|
||||
<View style={styles.facesContainer} pointerEvents="none">
|
||||
{this.state.faces.map(this.renderLandmarksOfFace)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
renderCamera() {
|
||||
return (
|
||||
<RNCamera
|
||||
ref={ref => {
|
||||
this.camera = ref;
|
||||
}}
|
||||
style={{
|
||||
flex: 1,
|
||||
}}
|
||||
type={this.state.type}
|
||||
flashMode={this.state.flash}
|
||||
autoFocus={this.state.autoFocus}
|
||||
zoom={this.state.zoom}
|
||||
whiteBalance={this.state.whiteBalance}
|
||||
ratio={this.state.ratio}
|
||||
faceDetectionLandmarks={RNCamera.Constants.FaceDetection.Landmarks.all}
|
||||
onFacesDetected={this.onFacesDetected}
|
||||
onFaceDetectionError={this.onFaceDetectionError}
|
||||
focusDepth={this.state.depth}
|
||||
>
|
||||
<View
|
||||
style={{
|
||||
flex: 0.5,
|
||||
backgroundColor: 'transparent',
|
||||
flexDirection: 'row',
|
||||
justifyContent: 'space-around',
|
||||
}}
|
||||
>
|
||||
<TouchableOpacity style={styles.flipButton} onPress={this.toggleFacing.bind(this)}>
|
||||
<Text style={styles.flipText}> FLIP </Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.flipButton} onPress={this.toggleFlash.bind(this)}>
|
||||
<Text style={styles.flipText}> FLASH: {this.state.flash} </Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity style={styles.flipButton} onPress={this.toggleWB.bind(this)}>
|
||||
<Text style={styles.flipText}> WB: {this.state.whiteBalance} </Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flex: 0.4,
|
||||
backgroundColor: 'transparent',
|
||||
flexDirection: 'row',
|
||||
alignSelf: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<Slider
|
||||
style={{ width: 150, marginTop: 15, alignSelf: 'flex-end' }}
|
||||
onValueChange={this.setFocusDepth.bind(this)}
|
||||
step={0.1}
|
||||
disabled={this.state.autoFocus === 'on'}
|
||||
/>
|
||||
</View>
|
||||
<View
|
||||
style={{
|
||||
flex: 0.1,
|
||||
backgroundColor: 'transparent',
|
||||
flexDirection: 'row',
|
||||
alignSelf: 'flex-end',
|
||||
}}
|
||||
>
|
||||
<TouchableOpacity
|
||||
style={[styles.flipButton, { flex: 0.1, alignSelf: 'flex-end' }]}
|
||||
onPress={this.zoomIn.bind(this)}
|
||||
>
|
||||
<Text style={styles.flipText}> + </Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.flipButton, { flex: 0.1, alignSelf: 'flex-end' }]}
|
||||
onPress={this.zoomOut.bind(this)}
|
||||
>
|
||||
<Text style={styles.flipText}> - </Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.flipButton, { flex: 0.25, alignSelf: 'flex-end' }]}
|
||||
onPress={this.toggleFocus.bind(this)}
|
||||
>
|
||||
<Text style={styles.flipText}> AF : {this.state.autoFocus} </Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.flipButton, styles.picButton, { flex: 0.3, alignSelf: 'flex-end' }]}
|
||||
onPress={this.takePicture.bind(this)}
|
||||
>
|
||||
<Text style={styles.flipText}> SNAP </Text>
|
||||
</TouchableOpacity>
|
||||
<TouchableOpacity
|
||||
style={[styles.flipButton, styles.galleryButton, { flex: 0.25, alignSelf: 'flex-end' }]}
|
||||
onPress={this.toggleView.bind(this)}
|
||||
>
|
||||
<Text style={styles.flipText}> Gallery </Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
{this.renderFaces()}
|
||||
{this.renderLandmarks()}
|
||||
</RNCamera>
|
||||
);
|
||||
}
|
||||
|
||||
render() {
|
||||
return <View style={styles.container}>{this.renderCamera()}</View>;
|
||||
}
|
||||
}
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
container: {
|
||||
flex: 1,
|
||||
paddingTop: 10,
|
||||
backgroundColor: '#000',
|
||||
},
|
||||
navigation: {
|
||||
flex: 1,
|
||||
},
|
||||
gallery: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
flexWrap: 'wrap',
|
||||
},
|
||||
flipButton: {
|
||||
flex: 0.3,
|
||||
height: 40,
|
||||
marginHorizontal: 2,
|
||||
marginBottom: 10,
|
||||
marginTop: 20,
|
||||
borderRadius: 8,
|
||||
borderColor: 'white',
|
||||
borderWidth: 1,
|
||||
padding: 5,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
flipText: {
|
||||
color: 'white',
|
||||
fontSize: 15,
|
||||
},
|
||||
item: {
|
||||
margin: 4,
|
||||
backgroundColor: 'indianred',
|
||||
height: 35,
|
||||
width: 80,
|
||||
borderRadius: 5,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
picButton: {
|
||||
backgroundColor: 'darkseagreen',
|
||||
},
|
||||
galleryButton: {
|
||||
backgroundColor: 'indianred',
|
||||
},
|
||||
facesContainer: {
|
||||
position: 'absolute',
|
||||
bottom: 0,
|
||||
right: 0,
|
||||
left: 0,
|
||||
top: 0,
|
||||
},
|
||||
face: {
|
||||
padding: 10,
|
||||
borderWidth: 2,
|
||||
borderRadius: 2,
|
||||
position: 'absolute',
|
||||
borderColor: '#FFD700',
|
||||
justifyContent: 'center',
|
||||
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
||||
},
|
||||
landmark: {
|
||||
width: landmarkSize,
|
||||
height: landmarkSize,
|
||||
position: 'absolute',
|
||||
backgroundColor: 'red',
|
||||
},
|
||||
faceText: {
|
||||
color: '#FFD700',
|
||||
fontWeight: 'bold',
|
||||
textAlign: 'center',
|
||||
margin: 10,
|
||||
backgroundColor: 'transparent',
|
||||
},
|
||||
row: {
|
||||
flexDirection: 'row',
|
||||
},
|
||||
});
|
||||
10
RNCameraExample/__tests__/App.js
Normal file
10
RNCameraExample/__tests__/App.js
Normal file
@ -0,0 +1,10 @@
|
||||
import 'react-native';
|
||||
import React from 'react';
|
||||
import App from '../App';
|
||||
|
||||
// Note: test renderer must be required after react-native.
|
||||
import renderer from 'react-test-renderer';
|
||||
|
||||
it('renders correctly', () => {
|
||||
const tree = renderer.create(<App />);
|
||||
});
|
||||
65
RNCameraExample/android/app/BUCK
Normal file
65
RNCameraExample/android/app/BUCK
Normal file
@ -0,0 +1,65 @@
|
||||
# To learn about Buck see [Docs](https://buckbuild.com/).
|
||||
# To run your application with Buck:
|
||||
# - install Buck
|
||||
# - `npm start` - to start the packager
|
||||
# - `cd android`
|
||||
# - `keytool -genkey -v -keystore keystores/debug.keystore -storepass android -alias androiddebugkey -keypass android -dname "CN=Android Debug,O=Android,C=US"`
|
||||
# - `./gradlew :app:copyDownloadableDepsToLibs` - make all Gradle compile dependencies available to Buck
|
||||
# - `buck install -r android/app` - compile, install and run application
|
||||
#
|
||||
|
||||
lib_deps = []
|
||||
|
||||
for jarfile in glob(['libs/*.jar']):
|
||||
name = 'jars__' + jarfile[jarfile.rindex('/') + 1: jarfile.rindex('.jar')]
|
||||
lib_deps.append(':' + name)
|
||||
prebuilt_jar(
|
||||
name = name,
|
||||
binary_jar = jarfile,
|
||||
)
|
||||
|
||||
for aarfile in glob(['libs/*.aar']):
|
||||
name = 'aars__' + aarfile[aarfile.rindex('/') + 1: aarfile.rindex('.aar')]
|
||||
lib_deps.append(':' + name)
|
||||
android_prebuilt_aar(
|
||||
name = name,
|
||||
aar = aarfile,
|
||||
)
|
||||
|
||||
android_library(
|
||||
name = "all-libs",
|
||||
exported_deps = lib_deps,
|
||||
)
|
||||
|
||||
android_library(
|
||||
name = "app-code",
|
||||
srcs = glob([
|
||||
"src/main/java/**/*.java",
|
||||
]),
|
||||
deps = [
|
||||
":all-libs",
|
||||
":build_config",
|
||||
":res",
|
||||
],
|
||||
)
|
||||
|
||||
android_build_config(
|
||||
name = "build_config",
|
||||
package = "com.rncameraexample",
|
||||
)
|
||||
|
||||
android_resource(
|
||||
name = "res",
|
||||
package = "com.rncameraexample",
|
||||
res = "src/main/res",
|
||||
)
|
||||
|
||||
android_binary(
|
||||
name = "app",
|
||||
keystore = "//android/keystores:debug",
|
||||
manifest = "src/main/AndroidManifest.xml",
|
||||
package_type = "debug",
|
||||
deps = [
|
||||
":app-code",
|
||||
],
|
||||
)
|
||||
151
RNCameraExample/android/app/build.gradle
Normal file
151
RNCameraExample/android/app/build.gradle
Normal file
@ -0,0 +1,151 @@
|
||||
apply plugin: "com.android.application"
|
||||
|
||||
import com.android.build.OutputFile
|
||||
|
||||
/**
|
||||
* The react.gradle file registers a task for each build variant (e.g. bundleDebugJsAndAssets
|
||||
* and bundleReleaseJsAndAssets).
|
||||
* These basically call `react-native bundle` with the correct arguments during the Android build
|
||||
* cycle. By default, bundleDebugJsAndAssets is skipped, as in debug/dev mode we prefer to load the
|
||||
* bundle directly from the development server. Below you can see all the possible configurations
|
||||
* and their defaults. If you decide to add a configuration block, make sure to add it before the
|
||||
* `apply from: "../../node_modules/react-native/react.gradle"` line.
|
||||
*
|
||||
* project.ext.react = [
|
||||
* // the name of the generated asset file containing your JS bundle
|
||||
* bundleAssetName: "index.android.bundle",
|
||||
*
|
||||
* // the entry file for bundle generation
|
||||
* entryFile: "index.android.js",
|
||||
*
|
||||
* // whether to bundle JS and assets in debug mode
|
||||
* bundleInDebug: false,
|
||||
*
|
||||
* // whether to bundle JS and assets in release mode
|
||||
* bundleInRelease: true,
|
||||
*
|
||||
* // whether to bundle JS and assets in another build variant (if configured).
|
||||
* // See http://tools.android.com/tech-docs/new-build-system/user-guide#TOC-Build-Variants
|
||||
* // The configuration property can be in the following formats
|
||||
* // 'bundleIn${productFlavor}${buildType}'
|
||||
* // 'bundleIn${buildType}'
|
||||
* // bundleInFreeDebug: true,
|
||||
* // bundleInPaidRelease: true,
|
||||
* // bundleInBeta: true,
|
||||
*
|
||||
* // whether to disable dev mode in custom build variants (by default only disabled in release)
|
||||
* // for example: to disable dev mode in the staging build type (if configured)
|
||||
* devDisabledInStaging: true,
|
||||
* // The configuration property can be in the following formats
|
||||
* // 'devDisabledIn${productFlavor}${buildType}'
|
||||
* // 'devDisabledIn${buildType}'
|
||||
*
|
||||
* // the root of your project, i.e. where "package.json" lives
|
||||
* root: "../../",
|
||||
*
|
||||
* // where to put the JS bundle asset in debug mode
|
||||
* jsBundleDirDebug: "$buildDir/intermediates/assets/debug",
|
||||
*
|
||||
* // where to put the JS bundle asset in release mode
|
||||
* jsBundleDirRelease: "$buildDir/intermediates/assets/release",
|
||||
*
|
||||
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||
* // require('./image.png')), in debug mode
|
||||
* resourcesDirDebug: "$buildDir/intermediates/res/merged/debug",
|
||||
*
|
||||
* // where to put drawable resources / React Native assets, e.g. the ones you use via
|
||||
* // require('./image.png')), in release mode
|
||||
* resourcesDirRelease: "$buildDir/intermediates/res/merged/release",
|
||||
*
|
||||
* // by default the gradle tasks are skipped if none of the JS files or assets change; this means
|
||||
* // that we don't look at files in android/ or ios/ to determine whether the tasks are up to
|
||||
* // date; if you have any other folders that you want to ignore for performance reasons (gradle
|
||||
* // indexes the entire tree), add them here. Alternatively, if you have JS files in android/
|
||||
* // for example, you might want to remove it from here.
|
||||
* inputExcludes: ["android/**", "ios/**"],
|
||||
*
|
||||
* // override which node gets called and with what additional arguments
|
||||
* nodeExecutableAndArgs: ["node"],
|
||||
*
|
||||
* // supply additional arguments to the packager
|
||||
* extraPackagerArgs: []
|
||||
* ]
|
||||
*/
|
||||
|
||||
project.ext.react = [
|
||||
entryFile: "index.js"
|
||||
]
|
||||
|
||||
apply from: "../../node_modules/react-native/react.gradle"
|
||||
|
||||
/**
|
||||
* Set this to true to create two separate APKs instead of one:
|
||||
* - An APK that only works on ARM devices
|
||||
* - An APK that only works on x86 devices
|
||||
* The advantage is the size of the APK is reduced by about 4MB.
|
||||
* Upload all the APKs to the Play Store and people will download
|
||||
* the correct one based on the CPU architecture of their device.
|
||||
*/
|
||||
def enableSeparateBuildPerCPUArchitecture = false
|
||||
|
||||
/**
|
||||
* Run Proguard to shrink the Java bytecode in release builds.
|
||||
*/
|
||||
def enableProguardInReleaseBuilds = false
|
||||
|
||||
android {
|
||||
compileSdkVersion 23
|
||||
buildToolsVersion "23.0.1"
|
||||
|
||||
defaultConfig {
|
||||
applicationId "com.rncameraexample"
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
ndk {
|
||||
abiFilters "armeabi-v7a", "x86"
|
||||
}
|
||||
}
|
||||
splits {
|
||||
abi {
|
||||
reset()
|
||||
enable enableSeparateBuildPerCPUArchitecture
|
||||
universalApk false // If true, also generate a universal APK
|
||||
include "armeabi-v7a", "x86"
|
||||
}
|
||||
}
|
||||
buildTypes {
|
||||
release {
|
||||
minifyEnabled enableProguardInReleaseBuilds
|
||||
proguardFiles getDefaultProguardFile("proguard-android.txt"), "proguard-rules.pro"
|
||||
}
|
||||
}
|
||||
// applicationVariants are e.g. debug, release
|
||||
applicationVariants.all { variant ->
|
||||
variant.outputs.each { output ->
|
||||
// For each separate APK per architecture, set a unique version code as described here:
|
||||
// http://tools.android.com/tech-docs/new-build-system/user-guide/apk-splits
|
||||
def versionCodes = ["armeabi-v7a":1, "x86":2]
|
||||
def abi = output.getFilter(OutputFile.ABI)
|
||||
if (abi != null) { // null for the universal-debug, universal-release variants
|
||||
output.versionCodeOverride =
|
||||
versionCodes.get(abi) * 1048576 + defaultConfig.versionCode
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile project(':react-native-camera')
|
||||
compile fileTree(dir: "libs", include: ["*.jar"])
|
||||
compile "com.android.support:appcompat-v7:23.0.1"
|
||||
compile "com.facebook.react:react-native:+" // From node_modules
|
||||
}
|
||||
|
||||
// Run this once to be able to run the application with BUCK
|
||||
// puts all compile dependencies into folder libs for BUCK to use
|
||||
task copyDownloadableDepsToLibs(type: Copy) {
|
||||
from configurations.compile
|
||||
into 'libs'
|
||||
}
|
||||
70
RNCameraExample/android/app/proguard-rules.pro
vendored
Normal file
70
RNCameraExample/android/app/proguard-rules.pro
vendored
Normal file
@ -0,0 +1,70 @@
|
||||
# Add project specific ProGuard rules here.
|
||||
# By default, the flags in this file are appended to flags specified
|
||||
# in /usr/local/Cellar/android-sdk/24.3.3/tools/proguard/proguard-android.txt
|
||||
# You can edit the include path and order by changing the proguardFiles
|
||||
# directive in build.gradle.
|
||||
#
|
||||
# For more details, see
|
||||
# http://developer.android.com/guide/developing/tools/proguard.html
|
||||
|
||||
# Add any project specific keep options here:
|
||||
|
||||
# If your project uses WebView with JS, uncomment the following
|
||||
# and specify the fully qualified class name to the JavaScript interface
|
||||
# class:
|
||||
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
|
||||
# public *;
|
||||
#}
|
||||
|
||||
# Disabling obfuscation is useful if you collect stack traces from production crashes
|
||||
# (unless you are using a system that supports de-obfuscate the stack traces).
|
||||
-dontobfuscate
|
||||
|
||||
# React Native
|
||||
|
||||
# Keep our interfaces so they can be used by other ProGuard rules.
|
||||
# See http://sourceforge.net/p/proguard/bugs/466/
|
||||
-keep,allowobfuscation @interface com.facebook.proguard.annotations.DoNotStrip
|
||||
-keep,allowobfuscation @interface com.facebook.proguard.annotations.KeepGettersAndSetters
|
||||
-keep,allowobfuscation @interface com.facebook.common.internal.DoNotStrip
|
||||
|
||||
# Do not strip any method/class that is annotated with @DoNotStrip
|
||||
-keep @com.facebook.proguard.annotations.DoNotStrip class *
|
||||
-keep @com.facebook.common.internal.DoNotStrip class *
|
||||
-keepclassmembers class * {
|
||||
@com.facebook.proguard.annotations.DoNotStrip *;
|
||||
@com.facebook.common.internal.DoNotStrip *;
|
||||
}
|
||||
|
||||
-keepclassmembers @com.facebook.proguard.annotations.KeepGettersAndSetters class * {
|
||||
void set*(***);
|
||||
*** get*();
|
||||
}
|
||||
|
||||
-keep class * extends com.facebook.react.bridge.JavaScriptModule { *; }
|
||||
-keep class * extends com.facebook.react.bridge.NativeModule { *; }
|
||||
-keepclassmembers,includedescriptorclasses class * { native <methods>; }
|
||||
-keepclassmembers class * { @com.facebook.react.uimanager.UIProp <fields>; }
|
||||
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactProp <methods>; }
|
||||
-keepclassmembers class * { @com.facebook.react.uimanager.annotations.ReactPropGroup <methods>; }
|
||||
|
||||
-dontwarn com.facebook.react.**
|
||||
|
||||
# TextLayoutBuilder uses a non-public Android constructor within StaticLayout.
|
||||
# See libs/proxy/src/main/java/com/facebook/fbui/textlayoutbuilder/proxy for details.
|
||||
-dontwarn android.text.StaticLayout
|
||||
|
||||
# okhttp
|
||||
|
||||
-keepattributes Signature
|
||||
-keepattributes *Annotation*
|
||||
-keep class okhttp3.** { *; }
|
||||
-keep interface okhttp3.** { *; }
|
||||
-dontwarn okhttp3.**
|
||||
|
||||
# okio
|
||||
|
||||
-keep class sun.misc.Unsafe { *; }
|
||||
-dontwarn java.nio.file.*
|
||||
-dontwarn org.codehaus.mojo.animal_sniffer.IgnoreJRERequirement
|
||||
-dontwarn okio.**
|
||||
35
RNCameraExample/android/app/src/main/AndroidManifest.xml
Normal file
35
RNCameraExample/android/app/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,35 @@
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="com.rncameraexample"
|
||||
android:versionCode="1"
|
||||
android:versionName="1.0">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.SYSTEM_ALERT_WINDOW"/>
|
||||
|
||||
<uses-sdk
|
||||
android:minSdkVersion="16"
|
||||
android:targetSdkVersion="26" />
|
||||
|
||||
<application
|
||||
android:name=".MainApplication"
|
||||
android:allowBackup="true"
|
||||
android:label="@string/app_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:theme="@style/AppTheme"
|
||||
tools:node="replace"
|
||||
>
|
||||
<activity
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/app_name"
|
||||
android:configChanges="keyboard|keyboardHidden|orientation|screenSize"
|
||||
android:windowSoftInputMode="adjustResize">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
<activity android:name="com.facebook.react.devsupport.DevSettingsActivity" />
|
||||
</application>
|
||||
|
||||
</manifest>
|
||||
@ -0,0 +1,15 @@
|
||||
package com.rncameraexample;
|
||||
|
||||
import com.facebook.react.ReactActivity;
|
||||
|
||||
public class MainActivity extends ReactActivity {
|
||||
|
||||
/**
|
||||
* Returns the name of the main component registered from JavaScript.
|
||||
* This is used to schedule rendering of the component.
|
||||
*/
|
||||
@Override
|
||||
protected String getMainComponentName() {
|
||||
return "RNCameraExample";
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,47 @@
|
||||
package com.rncameraexample;
|
||||
|
||||
import android.app.Application;
|
||||
|
||||
import com.facebook.react.ReactApplication;
|
||||
import com.lwansbrough.RCTCamera.RCTCameraPackage;
|
||||
import com.facebook.react.ReactNativeHost;
|
||||
import com.facebook.react.ReactPackage;
|
||||
import com.facebook.react.shell.MainReactPackage;
|
||||
import com.facebook.soloader.SoLoader;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
public class MainApplication extends Application implements ReactApplication {
|
||||
|
||||
private final ReactNativeHost mReactNativeHost = new ReactNativeHost(this) {
|
||||
@Override
|
||||
public boolean getUseDeveloperSupport() {
|
||||
return BuildConfig.DEBUG;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected List<ReactPackage> getPackages() {
|
||||
return Arrays.<ReactPackage>asList(
|
||||
new MainReactPackage(),
|
||||
new RCTCameraPackage()
|
||||
);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getJSMainModuleName() {
|
||||
return "index";
|
||||
}
|
||||
};
|
||||
|
||||
@Override
|
||||
public ReactNativeHost getReactNativeHost() {
|
||||
return mReactNativeHost;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onCreate() {
|
||||
super.onCreate();
|
||||
SoLoader.init(this, /* native exopackage */ false);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
|
After Width: | Height: | Size: 3.3 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 2.2 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 4.7 KiB |
Binary file not shown.
|
After Width: | Height: | Size: 7.5 KiB |
@ -0,0 +1,3 @@
|
||||
<resources>
|
||||
<string name="app_name">RNCameraExample</string>
|
||||
</resources>
|
||||
@ -0,0 +1,8 @@
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.NoActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
|
||||
</resources>
|
||||
45
RNCameraExample/android/build.gradle
Normal file
45
RNCameraExample/android/build.gradle
Normal file
@ -0,0 +1,45 @@
|
||||
// Top-level build file where you can add configuration options common to all sub-projects/modules.
|
||||
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven { url 'https://maven.google.com' }
|
||||
}
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:3.0.1'
|
||||
|
||||
// NOTE: Do not place your application dependencies here; they belong
|
||||
// in the individual module build.gradle files
|
||||
}
|
||||
}
|
||||
|
||||
allprojects {
|
||||
repositories {
|
||||
mavenLocal()
|
||||
jcenter()
|
||||
maven {
|
||||
// All of React Native (JS, Obj-C sources, Android binaries) is installed from npm
|
||||
url "$rootDir/../node_modules/react-native/android"
|
||||
}
|
||||
maven {
|
||||
url "https://maven.google.com" // Google's Maven repository
|
||||
}
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
}
|
||||
|
||||
ext {
|
||||
compileSdkVersion = 26
|
||||
buildToolsVersion = '26.0.2'
|
||||
}
|
||||
|
||||
subprojects { subproject ->
|
||||
afterEvaluate{
|
||||
if((subproject.plugins.hasPlugin('android') || subproject.plugins.hasPlugin('android-library'))) {
|
||||
android {
|
||||
compileSdkVersion rootProject.ext.compileSdkVersion
|
||||
buildToolsVersion rootProject.ext.buildToolsVersion
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
20
RNCameraExample/android/gradle.properties
Normal file
20
RNCameraExample/android/gradle.properties
Normal file
@ -0,0 +1,20 @@
|
||||
# Project-wide Gradle settings.
|
||||
|
||||
# IDE (e.g. Android Studio) users:
|
||||
# Gradle settings configured through the IDE *will override*
|
||||
# any settings specified in this file.
|
||||
|
||||
# For more details on how to configure your build environment visit
|
||||
# http://www.gradle.org/docs/current/userguide/build_environment.html
|
||||
|
||||
# Specifies the JVM arguments used for the daemon process.
|
||||
# The setting is particularly useful for tweaking memory settings.
|
||||
# Default value: -Xmx10248m -XX:MaxPermSize=256m
|
||||
# org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
|
||||
|
||||
# When configured, Gradle will run in incubating parallel mode.
|
||||
# This option should only be used with decoupled projects. More details, visit
|
||||
# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
|
||||
# org.gradle.parallel=true
|
||||
|
||||
android.useDeprecatedNdk=true
|
||||
BIN
RNCameraExample/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
RNCameraExample/android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
RNCameraExample/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
RNCameraExample/android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Sun Jan 07 08:54:18 PST 2018
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
|
||||
164
RNCameraExample/android/gradlew
vendored
Executable file
164
RNCameraExample/android/gradlew
vendored
Executable file
@ -0,0 +1,164 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# For Cygwin, ensure paths are in UNIX format before anything is touched.
|
||||
if $cygwin ; then
|
||||
[ -n "$JAVA_HOME" ] && JAVA_HOME=`cygpath --unix "$JAVA_HOME"`
|
||||
fi
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >&-
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >&-
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
90
RNCameraExample/android/gradlew.bat
vendored
Normal file
90
RNCameraExample/android/gradlew.bat
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
8
RNCameraExample/android/keystores/BUCK
Normal file
8
RNCameraExample/android/keystores/BUCK
Normal file
@ -0,0 +1,8 @@
|
||||
keystore(
|
||||
name = "debug",
|
||||
properties = "debug.keystore.properties",
|
||||
store = "debug.keystore",
|
||||
visibility = [
|
||||
"PUBLIC",
|
||||
],
|
||||
)
|
||||
@ -0,0 +1,4 @@
|
||||
key.store=debug.keystore
|
||||
key.alias=androiddebugkey
|
||||
key.store.password=android
|
||||
key.alias.password=android
|
||||
5
RNCameraExample/android/settings.gradle
Normal file
5
RNCameraExample/android/settings.gradle
Normal file
@ -0,0 +1,5 @@
|
||||
rootProject.name = 'RNCameraExample'
|
||||
include ':react-native-camera'
|
||||
project(':react-native-camera').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-camera/android')
|
||||
|
||||
include ':app'
|
||||
4
RNCameraExample/app.json
Normal file
4
RNCameraExample/app.json
Normal file
@ -0,0 +1,4 @@
|
||||
{
|
||||
"name": "RNCameraExample",
|
||||
"displayName": "RNCameraExample"
|
||||
}
|
||||
4
RNCameraExample/index.js
Normal file
4
RNCameraExample/index.js
Normal file
@ -0,0 +1,4 @@
|
||||
import { AppRegistry } from 'react-native';
|
||||
import App from './App';
|
||||
|
||||
AppRegistry.registerComponent('RNCameraExample', () => App);
|
||||
54
RNCameraExample/ios/RNCameraExample-tvOS/Info.plist
Normal file
54
RNCameraExample/ios/RNCameraExample-tvOS/Info.plist
Normal file
@ -0,0 +1,54 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ -->
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
24
RNCameraExample/ios/RNCameraExample-tvOSTests/Info.plist
Normal file
24
RNCameraExample/ios/RNCameraExample-tvOSTests/Info.plist
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
1336
RNCameraExample/ios/RNCameraExample.xcodeproj/project.pbxproj
Normal file
1336
RNCameraExample/ios/RNCameraExample.xcodeproj/project.pbxproj
Normal file
File diff suppressed because it is too large
Load Diff
@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0820"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "NO"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D2A28121D9B038B00D4039D"
|
||||
BuildableName = "libReact.a"
|
||||
BlueprintName = "React-tvOS"
|
||||
ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
|
||||
BuildableName = "RNCameraExample-tvOS.app"
|
||||
BlueprintName = "RNCameraExample-tvOS"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E48F1E0B4A5D006451C7"
|
||||
BuildableName = "RNCameraExample-tvOSTests.xctest"
|
||||
BlueprintName = "RNCameraExample-tvOSTests"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E48F1E0B4A5D006451C7"
|
||||
BuildableName = "RNCameraExample-tvOSTests.xctest"
|
||||
BlueprintName = "RNCameraExample-tvOSTests"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
|
||||
BuildableName = "RNCameraExample-tvOS.app"
|
||||
BlueprintName = "RNCameraExample-tvOS"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
|
||||
BuildableName = "RNCameraExample-tvOS.app"
|
||||
BlueprintName = "RNCameraExample-tvOS"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "2D02E47A1E0B4A5D006451C7"
|
||||
BuildableName = "RNCameraExample-tvOS.app"
|
||||
BlueprintName = "RNCameraExample-tvOS"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@ -0,0 +1,129 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "NO"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "83CBBA2D1A601D0E00E9B192"
|
||||
BuildableName = "libReact.a"
|
||||
BlueprintName = "React"
|
||||
ReferencedContainer = "container:../node_modules/react-native/React/React.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "RNCameraExample.app"
|
||||
BlueprintName = "RNCameraExample"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
||||
BuildableName = "RNCameraExampleTests.xctest"
|
||||
BlueprintName = "RNCameraExampleTests"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "00E356ED1AD99517003FC87E"
|
||||
BuildableName = "RNCameraExampleTests.xctest"
|
||||
BlueprintName = "RNCameraExampleTests"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "RNCameraExample.app"
|
||||
BlueprintName = "RNCameraExample"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "RNCameraExample.app"
|
||||
BlueprintName = "RNCameraExample"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "13B07F861A680F5B00A75B9A"
|
||||
BuildableName = "RNCameraExample.app"
|
||||
BlueprintName = "RNCameraExample"
|
||||
ReferencedContainer = "container:RNCameraExample.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
16
RNCameraExample/ios/RNCameraExample/AppDelegate.h
Normal file
16
RNCameraExample/ios/RNCameraExample/AppDelegate.h
Normal file
@ -0,0 +1,16 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
@interface AppDelegate : UIResponder <UIApplicationDelegate>
|
||||
|
||||
@property (nonatomic, strong) UIWindow *window;
|
||||
|
||||
@end
|
||||
37
RNCameraExample/ios/RNCameraExample/AppDelegate.m
Normal file
37
RNCameraExample/ios/RNCameraExample/AppDelegate.m
Normal file
@ -0,0 +1,37 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
#import <React/RCTBundleURLProvider.h>
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
@implementation AppDelegate
|
||||
|
||||
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
|
||||
{
|
||||
NSURL *jsCodeLocation;
|
||||
|
||||
jsCodeLocation = [[RCTBundleURLProvider sharedSettings] jsBundleURLForBundleRoot:@"index" fallbackResource:nil];
|
||||
|
||||
RCTRootView *rootView = [[RCTRootView alloc] initWithBundleURL:jsCodeLocation
|
||||
moduleName:@"RNCameraExample"
|
||||
initialProperties:nil
|
||||
launchOptions:launchOptions];
|
||||
rootView.backgroundColor = [[UIColor alloc] initWithRed:1.0f green:1.0f blue:1.0f alpha:1];
|
||||
|
||||
self.window = [[UIWindow alloc] initWithFrame:[UIScreen mainScreen].bounds];
|
||||
UIViewController *rootViewController = [UIViewController new];
|
||||
rootViewController.view = rootView;
|
||||
self.window.rootViewController = rootViewController;
|
||||
[self.window makeKeyAndVisible];
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@ -0,0 +1,42 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="7702" systemVersion="14D136" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" launchScreen="YES" useTraitCollections="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="iOS"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="7701"/>
|
||||
<capability name="Constraints with non-1.0 multipliers" minToolsVersion="5.1"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
|
||||
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
|
||||
<view contentMode="scaleToFill" id="iN0-l3-epB">
|
||||
<rect key="frame" x="0.0" y="0.0" width="480" height="480"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Powered by React Native" textAlignment="center" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" minimumFontSize="9" translatesAutoresizingMaskIntoConstraints="NO" id="8ie-xW-0ye">
|
||||
<rect key="frame" x="20" y="439" width="441" height="21"/>
|
||||
<fontDescription key="fontDescription" type="system" pointSize="17"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
<label opaque="NO" clipsSubviews="YES" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="RNCameraExample" textAlignment="center" lineBreakMode="middleTruncation" baselineAdjustment="alignBaselines" minimumFontSize="18" translatesAutoresizingMaskIntoConstraints="NO" id="kId-c2-rCX">
|
||||
<rect key="frame" x="20" y="140" width="441" height="43"/>
|
||||
<fontDescription key="fontDescription" type="boldSystem" pointSize="36"/>
|
||||
<color key="textColor" cocoaTouchSystemColor="darkTextColor"/>
|
||||
<nil key="highlightedColor"/>
|
||||
</label>
|
||||
</subviews>
|
||||
<color key="backgroundColor" white="1" alpha="1" colorSpace="custom" customColorSpace="calibratedWhite"/>
|
||||
<constraints>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="bottom" multiplier="1/3" constant="1" id="5cJ-9S-tgC"/>
|
||||
<constraint firstAttribute="centerX" secondItem="kId-c2-rCX" secondAttribute="centerX" id="Koa-jz-hwk"/>
|
||||
<constraint firstAttribute="bottom" secondItem="8ie-xW-0ye" secondAttribute="bottom" constant="20" id="Kzo-t9-V3l"/>
|
||||
<constraint firstItem="8ie-xW-0ye" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="MfP-vx-nX0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="8ie-xW-0ye" secondAttribute="centerX" id="ZEH-qu-HZ9"/>
|
||||
<constraint firstItem="kId-c2-rCX" firstAttribute="leading" secondItem="iN0-l3-epB" secondAttribute="leading" constant="20" symbolic="YES" id="fvb-Df-36g"/>
|
||||
</constraints>
|
||||
<nil key="simulatedStatusBarMetrics"/>
|
||||
<freeformSimulatedSizeMetrics key="simulatedDestinationMetrics"/>
|
||||
<point key="canvasLocation" x="548" y="455"/>
|
||||
</view>
|
||||
</objects>
|
||||
</document>
|
||||
@ -0,0 +1,38 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "29x29",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "40x40",
|
||||
"scale" : "3x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "iphone",
|
||||
"size" : "60x60",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
56
RNCameraExample/ios/RNCameraExample/Info.plist
Normal file
56
RNCameraExample/ios/RNCameraExample/Info.plist
Normal file
@ -0,0 +1,56 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleDisplayName</key>
|
||||
<string>RNCameraExample</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>APPL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
<key>LSRequiresIPhoneOS</key>
|
||||
<true/>
|
||||
<key>UILaunchStoryboardName</key>
|
||||
<string>LaunchScreen</string>
|
||||
<key>UIRequiredDeviceCapabilities</key>
|
||||
<array>
|
||||
<string>armv7</string>
|
||||
</array>
|
||||
<key>UISupportedInterfaceOrientations</key>
|
||||
<array>
|
||||
<string>UIInterfaceOrientationPortrait</string>
|
||||
<string>UIInterfaceOrientationLandscapeLeft</string>
|
||||
<string>UIInterfaceOrientationLandscapeRight</string>
|
||||
</array>
|
||||
<key>UIViewControllerBasedStatusBarAppearance</key>
|
||||
<false/>
|
||||
<key>NSLocationWhenInUseUsageDescription</key>
|
||||
<string></string>
|
||||
<key>NSAppTransportSecurity</key>
|
||||
<!--See http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/ -->
|
||||
<dict>
|
||||
<key>NSExceptionDomains</key>
|
||||
<dict>
|
||||
<key>localhost</key>
|
||||
<dict>
|
||||
<key>NSExceptionAllowsInsecureHTTPLoads</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</dict>
|
||||
</plist>
|
||||
18
RNCameraExample/ios/RNCameraExample/main.m
Normal file
18
RNCameraExample/ios/RNCameraExample/main.m
Normal file
@ -0,0 +1,18 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
|
||||
#import "AppDelegate.h"
|
||||
|
||||
int main(int argc, char * argv[]) {
|
||||
@autoreleasepool {
|
||||
return UIApplicationMain(argc, argv, nil, NSStringFromClass([AppDelegate class]));
|
||||
}
|
||||
}
|
||||
24
RNCameraExample/ios/RNCameraExampleTests/Info.plist
Normal file
24
RNCameraExample/ios/RNCameraExampleTests/Info.plist
Normal file
@ -0,0 +1,24 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>CFBundleDevelopmentRegion</key>
|
||||
<string>en</string>
|
||||
<key>CFBundleExecutable</key>
|
||||
<string>$(EXECUTABLE_NAME)</string>
|
||||
<key>CFBundleIdentifier</key>
|
||||
<string>org.reactjs.native.example.$(PRODUCT_NAME:rfc1034identifier)</string>
|
||||
<key>CFBundleInfoDictionaryVersion</key>
|
||||
<string>6.0</string>
|
||||
<key>CFBundleName</key>
|
||||
<string>$(PRODUCT_NAME)</string>
|
||||
<key>CFBundlePackageType</key>
|
||||
<string>BNDL</string>
|
||||
<key>CFBundleShortVersionString</key>
|
||||
<string>1.0</string>
|
||||
<key>CFBundleSignature</key>
|
||||
<string>????</string>
|
||||
<key>CFBundleVersion</key>
|
||||
<string>1</string>
|
||||
</dict>
|
||||
</plist>
|
||||
@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copyright (c) 2015-present, Facebook, Inc.
|
||||
* All rights reserved.
|
||||
*
|
||||
* This source code is licensed under the BSD-style license found in the
|
||||
* LICENSE file in the root directory of this source tree. An additional grant
|
||||
* of patent rights can be found in the PATENTS file in the same directory.
|
||||
*/
|
||||
|
||||
#import <UIKit/UIKit.h>
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import <React/RCTLog.h>
|
||||
#import <React/RCTRootView.h>
|
||||
|
||||
#define TIMEOUT_SECONDS 600
|
||||
#define TEXT_TO_LOOK_FOR @"Welcome to React Native!"
|
||||
|
||||
@interface RNCameraExampleTests : XCTestCase
|
||||
|
||||
@end
|
||||
|
||||
@implementation RNCameraExampleTests
|
||||
|
||||
- (BOOL)findSubviewInView:(UIView *)view matching:(BOOL(^)(UIView *view))test
|
||||
{
|
||||
if (test(view)) {
|
||||
return YES;
|
||||
}
|
||||
for (UIView *subview in [view subviews]) {
|
||||
if ([self findSubviewInView:subview matching:test]) {
|
||||
return YES;
|
||||
}
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)testRendersWelcomeScreen
|
||||
{
|
||||
UIViewController *vc = [[[RCTSharedApplication() delegate] window] rootViewController];
|
||||
NSDate *date = [NSDate dateWithTimeIntervalSinceNow:TIMEOUT_SECONDS];
|
||||
BOOL foundElement = NO;
|
||||
|
||||
__block NSString *redboxError = nil;
|
||||
RCTSetLogFunction(^(RCTLogLevel level, RCTLogSource source, NSString *fileName, NSNumber *lineNumber, NSString *message) {
|
||||
if (level >= RCTLogLevelError) {
|
||||
redboxError = message;
|
||||
}
|
||||
});
|
||||
|
||||
while ([date timeIntervalSinceNow] > 0 && !foundElement && !redboxError) {
|
||||
[[NSRunLoop mainRunLoop] runMode:NSDefaultRunLoopMode beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||
[[NSRunLoop mainRunLoop] runMode:NSRunLoopCommonModes beforeDate:[NSDate dateWithTimeIntervalSinceNow:0.1]];
|
||||
|
||||
foundElement = [self findSubviewInView:vc.view matching:^BOOL(UIView *view) {
|
||||
if ([view.accessibilityLabel isEqualToString:TEXT_TO_LOOK_FOR]) {
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}];
|
||||
}
|
||||
|
||||
RCTSetLogFunction(RCTDefaultLogFunction);
|
||||
|
||||
XCTAssertNil(redboxError, @"RedBox error: %@", redboxError);
|
||||
XCTAssertTrue(foundElement, @"Couldn't find element with text '%@' in %d seconds", TEXT_TO_LOOK_FOR, TIMEOUT_SECONDS);
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
25
RNCameraExample/package.json
Normal file
25
RNCameraExample/package.json
Normal file
@ -0,0 +1,25 @@
|
||||
{
|
||||
"name": "RNCameraExample",
|
||||
"version": "0.0.1",
|
||||
"dependencies": {
|
||||
"react": "16.0.0",
|
||||
"react-native": "0.51.0",
|
||||
"react-native-camera": "file:../"
|
||||
},
|
||||
"devDependencies": {
|
||||
"babel-jest": "22.0.4",
|
||||
"babel-plugin-transform-export-extensions": "^6.22.0",
|
||||
"babel-preset-react-native": "4.0.0",
|
||||
"jest": "22.0.4",
|
||||
"react-test-renderer": "16.0.0"
|
||||
},
|
||||
"jest": {
|
||||
"preset": "react-native"
|
||||
},
|
||||
"private": true,
|
||||
"scripts": {
|
||||
"clear": "node node_modules/react-native/local-cli/cli.js start --reset-cache",
|
||||
"start": "node node_modules/react-native/local-cli/cli.js start",
|
||||
"test": "jest"
|
||||
}
|
||||
}
|
||||
4640
RNCameraExample/yarn.lock
Normal file
4640
RNCameraExample/yarn.lock
Normal file
File diff suppressed because it is too large
Load Diff
246
THIRD-PARTY-LICENSES
Normal file
246
THIRD-PARTY-LICENSES
Normal file
@ -0,0 +1,246 @@
|
||||
===============================================================================
|
||||
|
||||
expo/expo
|
||||
https://github.com/expo/expo
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
BSD License
|
||||
|
||||
For Exponent software
|
||||
|
||||
Copyright (c) 2015-present, 650 Industries, Inc. All rights reserved.
|
||||
|
||||
Redistribution and use in source and binary forms, with or without modification,
|
||||
are permitted provided that the following conditions are met:
|
||||
|
||||
* Redistributions of source code must retain the above copyright notice, this
|
||||
list of conditions and the following disclaimer.
|
||||
|
||||
* Redistributions in binary form must reproduce the above copyright notice,
|
||||
this list of conditions and the following disclaimer in the documentation
|
||||
and/or other materials provided with the distribution.
|
||||
|
||||
* Neither the names 650 Industries, Exponent, nor the names of its contributors
|
||||
may be used to endorse or promote products derived from this software without
|
||||
specific prior written permission.
|
||||
|
||||
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||
ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
|
||||
ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
(INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
|
||||
ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
(INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
|
||||
===============================================================================
|
||||
|
||||
google/cameraview
|
||||
https://github.com/google/cameraview
|
||||
|
||||
-------------------------------------------------------------------------------
|
||||
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@ -1,24 +1,27 @@
|
||||
buildscript {
|
||||
repositories {
|
||||
jcenter()
|
||||
maven {
|
||||
url 'https://maven.google.com'
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
classpath 'com.android.tools.build:gradle:1.2.3'
|
||||
classpath 'com.android.tools.build:gradle:3.0.0'
|
||||
}
|
||||
}
|
||||
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
android {
|
||||
compileSdkVersion 25
|
||||
buildToolsVersion "25.0.2"
|
||||
compileSdkVersion 26
|
||||
buildToolsVersion "26.0.2"
|
||||
|
||||
defaultConfig {
|
||||
minSdkVersion 16
|
||||
targetSdkVersion 22
|
||||
targetSdkVersion 26
|
||||
versionCode 1
|
||||
versionName "1.0"
|
||||
versionName "1.0.0"
|
||||
}
|
||||
lintOptions {
|
||||
abortOnError false
|
||||
@ -28,10 +31,18 @@ android {
|
||||
|
||||
repositories {
|
||||
mavenCentral()
|
||||
maven {
|
||||
url 'https://maven.google.com'
|
||||
}
|
||||
maven { url "https://jitpack.io" }
|
||||
}
|
||||
|
||||
dependencies {
|
||||
compile "com.facebook.react:react-native:0.19.+"
|
||||
compile 'com.facebook.react:react-native:+'
|
||||
compile "com.google.zxing:core:3.2.1"
|
||||
compile "com.drewnoakes:metadata-extractor:2.9.1"
|
||||
compile 'com.google.android.gms:play-services-vision:+'
|
||||
compile "com.android.support:exifinterface:26.0.2"
|
||||
|
||||
compile 'com.github.react-native-community:cameraview:df60b07573'
|
||||
}
|
||||
|
||||
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
BIN
android/gradle/wrapper/gradle-wrapper.jar
vendored
Normal file
Binary file not shown.
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
6
android/gradle/wrapper/gradle-wrapper.properties
vendored
Normal file
@ -0,0 +1,6 @@
|
||||
#Sun Dec 31 13:43:56 BRST 2017
|
||||
distributionBase=GRADLE_USER_HOME
|
||||
distributionPath=wrapper/dists
|
||||
zipStoreBase=GRADLE_USER_HOME
|
||||
zipStorePath=wrapper/dists
|
||||
distributionUrl=https\://services.gradle.org/distributions/gradle-4.1-all.zip
|
||||
160
android/gradlew
vendored
Executable file
160
android/gradlew
vendored
Executable file
@ -0,0 +1,160 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
##############################################################################
|
||||
##
|
||||
## Gradle start up script for UN*X
|
||||
##
|
||||
##############################################################################
|
||||
|
||||
# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
DEFAULT_JVM_OPTS=""
|
||||
|
||||
APP_NAME="Gradle"
|
||||
APP_BASE_NAME=`basename "$0"`
|
||||
|
||||
# Use the maximum available, or set MAX_FD != -1 to use that value.
|
||||
MAX_FD="maximum"
|
||||
|
||||
warn ( ) {
|
||||
echo "$*"
|
||||
}
|
||||
|
||||
die ( ) {
|
||||
echo
|
||||
echo "$*"
|
||||
echo
|
||||
exit 1
|
||||
}
|
||||
|
||||
# OS specific support (must be 'true' or 'false').
|
||||
cygwin=false
|
||||
msys=false
|
||||
darwin=false
|
||||
case "`uname`" in
|
||||
CYGWIN* )
|
||||
cygwin=true
|
||||
;;
|
||||
Darwin* )
|
||||
darwin=true
|
||||
;;
|
||||
MINGW* )
|
||||
msys=true
|
||||
;;
|
||||
esac
|
||||
|
||||
# Attempt to set APP_HOME
|
||||
# Resolve links: $0 may be a link
|
||||
PRG="$0"
|
||||
# Need this for relative symlinks.
|
||||
while [ -h "$PRG" ] ; do
|
||||
ls=`ls -ld "$PRG"`
|
||||
link=`expr "$ls" : '.*-> \(.*\)$'`
|
||||
if expr "$link" : '/.*' > /dev/null; then
|
||||
PRG="$link"
|
||||
else
|
||||
PRG=`dirname "$PRG"`"/$link"
|
||||
fi
|
||||
done
|
||||
SAVED="`pwd`"
|
||||
cd "`dirname \"$PRG\"`/" >/dev/null
|
||||
APP_HOME="`pwd -P`"
|
||||
cd "$SAVED" >/dev/null
|
||||
|
||||
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
|
||||
|
||||
# Determine the Java command to use to start the JVM.
|
||||
if [ -n "$JAVA_HOME" ] ; then
|
||||
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
|
||||
# IBM's JDK on AIX uses strange locations for the executables
|
||||
JAVACMD="$JAVA_HOME/jre/sh/java"
|
||||
else
|
||||
JAVACMD="$JAVA_HOME/bin/java"
|
||||
fi
|
||||
if [ ! -x "$JAVACMD" ] ; then
|
||||
die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
else
|
||||
JAVACMD="java"
|
||||
which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
|
||||
Please set the JAVA_HOME variable in your environment to match the
|
||||
location of your Java installation."
|
||||
fi
|
||||
|
||||
# Increase the maximum file descriptors if we can.
|
||||
if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
|
||||
MAX_FD_LIMIT=`ulimit -H -n`
|
||||
if [ $? -eq 0 ] ; then
|
||||
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
|
||||
MAX_FD="$MAX_FD_LIMIT"
|
||||
fi
|
||||
ulimit -n $MAX_FD
|
||||
if [ $? -ne 0 ] ; then
|
||||
warn "Could not set maximum file descriptor limit: $MAX_FD"
|
||||
fi
|
||||
else
|
||||
warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
|
||||
fi
|
||||
fi
|
||||
|
||||
# For Darwin, add options to specify how the application appears in the dock
|
||||
if $darwin; then
|
||||
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
|
||||
fi
|
||||
|
||||
# For Cygwin, switch paths to Windows format before running java
|
||||
if $cygwin ; then
|
||||
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
|
||||
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
|
||||
JAVACMD=`cygpath --unix "$JAVACMD"`
|
||||
|
||||
# We build the pattern for arguments to be converted via cygpath
|
||||
ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
|
||||
SEP=""
|
||||
for dir in $ROOTDIRSRAW ; do
|
||||
ROOTDIRS="$ROOTDIRS$SEP$dir"
|
||||
SEP="|"
|
||||
done
|
||||
OURCYGPATTERN="(^($ROOTDIRS))"
|
||||
# Add a user-defined pattern to the cygpath arguments
|
||||
if [ "$GRADLE_CYGPATTERN" != "" ] ; then
|
||||
OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
|
||||
fi
|
||||
# Now convert the arguments - kludge to limit ourselves to /bin/sh
|
||||
i=0
|
||||
for arg in "$@" ; do
|
||||
CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
|
||||
CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
|
||||
|
||||
if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
|
||||
eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
|
||||
else
|
||||
eval `echo args$i`="\"$arg\""
|
||||
fi
|
||||
i=$((i+1))
|
||||
done
|
||||
case $i in
|
||||
(0) set -- ;;
|
||||
(1) set -- "$args0" ;;
|
||||
(2) set -- "$args0" "$args1" ;;
|
||||
(3) set -- "$args0" "$args1" "$args2" ;;
|
||||
(4) set -- "$args0" "$args1" "$args2" "$args3" ;;
|
||||
(5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
|
||||
(6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
|
||||
(7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
|
||||
(8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
|
||||
(9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
|
||||
esac
|
||||
fi
|
||||
|
||||
# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
|
||||
function splitJvmOpts() {
|
||||
JVM_OPTS=("$@")
|
||||
}
|
||||
eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
|
||||
JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
|
||||
|
||||
exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
|
||||
90
android/gradlew.bat
vendored
Normal file
90
android/gradlew.bat
vendored
Normal file
@ -0,0 +1,90 @@
|
||||
@if "%DEBUG%" == "" @echo off
|
||||
@rem ##########################################################################
|
||||
@rem
|
||||
@rem Gradle startup script for Windows
|
||||
@rem
|
||||
@rem ##########################################################################
|
||||
|
||||
@rem Set local scope for the variables with windows NT shell
|
||||
if "%OS%"=="Windows_NT" setlocal
|
||||
|
||||
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
|
||||
set DEFAULT_JVM_OPTS=
|
||||
|
||||
set DIRNAME=%~dp0
|
||||
if "%DIRNAME%" == "" set DIRNAME=.
|
||||
set APP_BASE_NAME=%~n0
|
||||
set APP_HOME=%DIRNAME%
|
||||
|
||||
@rem Find java.exe
|
||||
if defined JAVA_HOME goto findJavaFromJavaHome
|
||||
|
||||
set JAVA_EXE=java.exe
|
||||
%JAVA_EXE% -version >NUL 2>&1
|
||||
if "%ERRORLEVEL%" == "0" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:findJavaFromJavaHome
|
||||
set JAVA_HOME=%JAVA_HOME:"=%
|
||||
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
|
||||
|
||||
if exist "%JAVA_EXE%" goto init
|
||||
|
||||
echo.
|
||||
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
|
||||
echo.
|
||||
echo Please set the JAVA_HOME variable in your environment to match the
|
||||
echo location of your Java installation.
|
||||
|
||||
goto fail
|
||||
|
||||
:init
|
||||
@rem Get command-line arguments, handling Windowz variants
|
||||
|
||||
if not "%OS%" == "Windows_NT" goto win9xME_args
|
||||
if "%@eval[2+2]" == "4" goto 4NT_args
|
||||
|
||||
:win9xME_args
|
||||
@rem Slurp the command line arguments.
|
||||
set CMD_LINE_ARGS=
|
||||
set _SKIP=2
|
||||
|
||||
:win9xME_args_slurp
|
||||
if "x%~1" == "x" goto execute
|
||||
|
||||
set CMD_LINE_ARGS=%*
|
||||
goto execute
|
||||
|
||||
:4NT_args
|
||||
@rem Get arguments from the 4NT Shell from JP Software
|
||||
set CMD_LINE_ARGS=%$
|
||||
|
||||
:execute
|
||||
@rem Setup the command line
|
||||
|
||||
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
|
||||
|
||||
@rem Execute Gradle
|
||||
"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
|
||||
|
||||
:end
|
||||
@rem End local scope for the variables with windows NT shell
|
||||
if "%ERRORLEVEL%"=="0" goto mainEnd
|
||||
|
||||
:fail
|
||||
rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
|
||||
rem the _cmd.exe /c_ return code!
|
||||
if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
|
||||
exit /b 1
|
||||
|
||||
:mainEnd
|
||||
if "%OS%"=="Windows_NT" endlocal
|
||||
|
||||
:omega
|
||||
@ -10,11 +10,19 @@ import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.uimanager.ViewManager;
|
||||
import com.facebook.react.bridge.JavaScriptModule;
|
||||
|
||||
import org.reactnative.camera.CameraModule;
|
||||
import org.reactnative.camera.CameraViewManager;
|
||||
import org.reactnative.facedetector.FaceDetectorModule;
|
||||
|
||||
public class RCTCameraPackage implements ReactPackage {
|
||||
|
||||
@Override
|
||||
public List<NativeModule> createNativeModules(ReactApplicationContext reactApplicationContext) {
|
||||
return Collections.<NativeModule>singletonList(new RCTCameraModule(reactApplicationContext));
|
||||
return Arrays.<NativeModule>asList(
|
||||
new RCTCameraModule(reactApplicationContext),
|
||||
new CameraModule(reactApplicationContext),
|
||||
new FaceDetectorModule(reactApplicationContext)
|
||||
);
|
||||
}
|
||||
|
||||
// Deprecated in RN 0.47
|
||||
@ -24,8 +32,10 @@ public class RCTCameraPackage implements ReactPackage {
|
||||
|
||||
@Override
|
||||
public List<ViewManager> createViewManagers(ReactApplicationContext reactApplicationContext) {
|
||||
//noinspection ArraysAsListWithZeroOrOneArgument
|
||||
return Collections.<ViewManager>singletonList(new RCTCameraViewManager());
|
||||
return Arrays.<ViewManager>asList(
|
||||
new RCTCameraViewManager(),
|
||||
new CameraViewManager()
|
||||
);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
272
android/src/main/java/org/reactnative/MutableImage.java
Normal file
272
android/src/main/java/org/reactnative/MutableImage.java
Normal file
@ -0,0 +1,272 @@
|
||||
package org.reactnative;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Matrix;
|
||||
import android.media.ExifInterface;
|
||||
import android.util.Base64;
|
||||
import android.util.Log;
|
||||
|
||||
import com.drew.imaging.ImageMetadataReader;
|
||||
import com.drew.imaging.ImageProcessingException;
|
||||
import com.drew.metadata.Directory;
|
||||
import com.drew.metadata.Metadata;
|
||||
import com.drew.metadata.MetadataException;
|
||||
import com.drew.metadata.Tag;
|
||||
import com.drew.metadata.exif.ExifIFD0Directory;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import java.io.BufferedInputStream;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.ByteArrayOutputStream;
|
||||
import java.io.File;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class MutableImage {
|
||||
private static final String TAG = "RNCamera";
|
||||
|
||||
private final byte[] originalImageData;
|
||||
private Bitmap currentRepresentation;
|
||||
private Metadata originalImageMetaData;
|
||||
private boolean hasBeenReoriented = false;
|
||||
|
||||
public MutableImage(byte[] originalImageData) {
|
||||
this.originalImageData = originalImageData;
|
||||
this.currentRepresentation = toBitmap(originalImageData);
|
||||
}
|
||||
|
||||
public void mirrorImage() throws ImageMutationFailedException {
|
||||
Matrix m = new Matrix();
|
||||
|
||||
m.preScale(-1, 1);
|
||||
|
||||
Bitmap bitmap = Bitmap.createBitmap(
|
||||
currentRepresentation,
|
||||
0,
|
||||
0,
|
||||
currentRepresentation.getWidth(),
|
||||
currentRepresentation.getHeight(),
|
||||
m,
|
||||
false
|
||||
);
|
||||
|
||||
if (bitmap == null)
|
||||
throw new ImageMutationFailedException("failed to mirror");
|
||||
|
||||
this.currentRepresentation = bitmap;
|
||||
}
|
||||
|
||||
public void fixOrientation() throws ImageMutationFailedException {
|
||||
try {
|
||||
Metadata metadata = originalImageMetaData();
|
||||
|
||||
ExifIFD0Directory exifIFD0Directory = metadata.getFirstDirectoryOfType(ExifIFD0Directory.class);
|
||||
if (exifIFD0Directory == null) {
|
||||
return;
|
||||
} else if (exifIFD0Directory.containsTag(ExifIFD0Directory.TAG_ORIENTATION)) {
|
||||
int exifOrientation = exifIFD0Directory.getInt(ExifIFD0Directory.TAG_ORIENTATION);
|
||||
if(exifOrientation != 1) {
|
||||
rotate(exifOrientation);
|
||||
exifIFD0Directory.setInt(ExifIFD0Directory.TAG_ORIENTATION, 1);
|
||||
}
|
||||
}
|
||||
} catch (ImageProcessingException | IOException | MetadataException e) {
|
||||
throw new ImageMutationFailedException("failed to fix orientation", e);
|
||||
}
|
||||
}
|
||||
|
||||
//see http://www.impulseadventure.com/photo/exif-orientation.html
|
||||
private void rotate(int exifOrientation) throws ImageMutationFailedException {
|
||||
final Matrix bitmapMatrix = new Matrix();
|
||||
switch (exifOrientation) {
|
||||
case 1:
|
||||
return;//no rotation required
|
||||
case 2:
|
||||
bitmapMatrix.postScale(-1, 1);
|
||||
break;
|
||||
case 3:
|
||||
bitmapMatrix.postRotate(180);
|
||||
break;
|
||||
case 4:
|
||||
bitmapMatrix.postRotate(180);
|
||||
bitmapMatrix.postScale(-1, 1);
|
||||
break;
|
||||
case 5:
|
||||
bitmapMatrix.postRotate(90);
|
||||
bitmapMatrix.postScale(-1, 1);
|
||||
break;
|
||||
case 6:
|
||||
bitmapMatrix.postRotate(90);
|
||||
break;
|
||||
case 7:
|
||||
bitmapMatrix.postRotate(270);
|
||||
bitmapMatrix.postScale(-1, 1);
|
||||
break;
|
||||
case 8:
|
||||
bitmapMatrix.postRotate(270);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
||||
Bitmap transformedBitmap = Bitmap.createBitmap(
|
||||
currentRepresentation,
|
||||
0,
|
||||
0,
|
||||
currentRepresentation.getWidth(),
|
||||
currentRepresentation.getHeight(),
|
||||
bitmapMatrix,
|
||||
false
|
||||
);
|
||||
|
||||
if (transformedBitmap == null)
|
||||
throw new ImageMutationFailedException("failed to rotate");
|
||||
|
||||
this.currentRepresentation = transformedBitmap;
|
||||
this.hasBeenReoriented = true;
|
||||
}
|
||||
|
||||
private static Bitmap toBitmap(byte[] data) {
|
||||
try {
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(data);
|
||||
Bitmap photo = BitmapFactory.decodeStream(inputStream);
|
||||
inputStream.close();
|
||||
return photo;
|
||||
} catch (IOException e) {
|
||||
throw new IllegalStateException("Will not happen", e);
|
||||
}
|
||||
}
|
||||
|
||||
public String toBase64(int jpegQualityPercent) {
|
||||
return Base64.encodeToString(toJpeg(currentRepresentation, jpegQualityPercent), Base64.DEFAULT);
|
||||
}
|
||||
|
||||
public void writeDataToFile(File file, ReadableMap options, int jpegQualityPercent) throws IOException {
|
||||
FileOutputStream fos = new FileOutputStream(file);
|
||||
fos.write(toJpeg(currentRepresentation, jpegQualityPercent));
|
||||
fos.close();
|
||||
|
||||
try {
|
||||
ExifInterface exif = new ExifInterface(file.getAbsolutePath());
|
||||
|
||||
// copy original exif data to the output exif...
|
||||
// unfortunately, this Android ExifInterface class doesn't understand all the tags so we lose some
|
||||
for (Directory directory : originalImageMetaData().getDirectories()) {
|
||||
for (Tag tag : directory.getTags()) {
|
||||
int tagType = tag.getTagType();
|
||||
Object object = directory.getObject(tagType);
|
||||
exif.setAttribute(tag.getTagName(), object.toString());
|
||||
}
|
||||
}
|
||||
|
||||
writeLocationExifData(options, exif);
|
||||
|
||||
if(hasBeenReoriented)
|
||||
rewriteOrientation(exif);
|
||||
|
||||
exif.saveAttributes();
|
||||
} catch (ImageProcessingException | IOException e) {
|
||||
Log.e(TAG, "failed to save exif data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private void rewriteOrientation(ExifInterface exif) {
|
||||
exif.setAttribute(ExifInterface.TAG_ORIENTATION, String.valueOf(ExifInterface.ORIENTATION_NORMAL));
|
||||
}
|
||||
|
||||
private void writeLocationExifData(ReadableMap options, ExifInterface exif) {
|
||||
if(!options.hasKey("metadata"))
|
||||
return;
|
||||
|
||||
ReadableMap metadata = options.getMap("metadata");
|
||||
if (!metadata.hasKey("location"))
|
||||
return;
|
||||
|
||||
ReadableMap location = metadata.getMap("location");
|
||||
if(!location.hasKey("coords"))
|
||||
return;
|
||||
|
||||
try {
|
||||
ReadableMap coords = location.getMap("coords");
|
||||
double latitude = coords.getDouble("latitude");
|
||||
double longitude = coords.getDouble("longitude");
|
||||
|
||||
GPS.writeExifData(latitude, longitude, exif);
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "Couldn't write location data", e);
|
||||
}
|
||||
}
|
||||
|
||||
private Metadata originalImageMetaData() throws ImageProcessingException, IOException {
|
||||
if(this.originalImageMetaData == null) {//this is expensive, don't do it more than once
|
||||
originalImageMetaData = ImageMetadataReader.readMetadata(
|
||||
new BufferedInputStream(new ByteArrayInputStream(originalImageData)),
|
||||
originalImageData.length
|
||||
);
|
||||
}
|
||||
return originalImageMetaData;
|
||||
}
|
||||
|
||||
private static byte[] toJpeg(Bitmap bitmap, int quality) throws OutOfMemoryError {
|
||||
ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
|
||||
bitmap.compress(Bitmap.CompressFormat.JPEG, quality, outputStream);
|
||||
|
||||
try {
|
||||
return outputStream.toByteArray();
|
||||
} finally {
|
||||
try {
|
||||
outputStream.close();
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "problem compressing jpeg", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static class ImageMutationFailedException extends Exception {
|
||||
public ImageMutationFailedException(String detailMessage, Throwable throwable) {
|
||||
super(detailMessage, throwable);
|
||||
}
|
||||
|
||||
public ImageMutationFailedException(String detailMessage) {
|
||||
super(detailMessage);
|
||||
}
|
||||
}
|
||||
|
||||
private static class GPS {
|
||||
public static void writeExifData(double latitude, double longitude, ExifInterface exif) throws IOException {
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE, toDegreeMinuteSecods(latitude));
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LATITUDE_REF, latitudeRef(latitude));
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE, toDegreeMinuteSecods(longitude));
|
||||
exif.setAttribute(ExifInterface.TAG_GPS_LONGITUDE_REF, longitudeRef(longitude));
|
||||
}
|
||||
|
||||
private static String latitudeRef(double latitude) {
|
||||
return latitude < 0.0d ? "S" : "N";
|
||||
}
|
||||
|
||||
private static String longitudeRef(double longitude) {
|
||||
return longitude < 0.0d ? "W" : "E";
|
||||
}
|
||||
|
||||
private static String toDegreeMinuteSecods(double latitude) {
|
||||
latitude = Math.abs(latitude);
|
||||
int degree = (int) latitude;
|
||||
latitude *= 60;
|
||||
latitude -= (degree * 60.0d);
|
||||
int minute = (int) latitude;
|
||||
latitude *= 60;
|
||||
latitude -= (minute * 60.0d);
|
||||
int second = (int) (latitude * 1000.0d);
|
||||
|
||||
StringBuffer sb = new StringBuffer();
|
||||
sb.append(degree);
|
||||
sb.append("/1,");
|
||||
sb.append(minute);
|
||||
sb.append("/1,");
|
||||
sb.append(second);
|
||||
sb.append("/1000,");
|
||||
return sb.toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
211
android/src/main/java/org/reactnative/camera/CameraModule.java
Normal file
211
android/src/main/java/org/reactnative/camera/CameraModule.java
Normal file
@ -0,0 +1,211 @@
|
||||
package org.reactnative.camera;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.reactnative.facedetector.RNFaceDetector;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.google.android.cameraview.AspectRatio;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class CameraModule extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "CameraModule";
|
||||
|
||||
private static ReactApplicationContext mReactContext;
|
||||
|
||||
// private static ScopedContext mScopedContext;
|
||||
static final int VIDEO_2160P = 0;
|
||||
static final int VIDEO_1080P = 1;
|
||||
static final int VIDEO_720P = 2;
|
||||
static final int VIDEO_480P = 3;
|
||||
static final int VIDEO_4x3 = 4;
|
||||
|
||||
public static final Map<String, Object> VALID_BARCODE_TYPES =
|
||||
Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("aztec", BarcodeFormat.AZTEC.toString());
|
||||
put("ean13", BarcodeFormat.EAN_13.toString());
|
||||
put("ean8", BarcodeFormat.EAN_8.toString());
|
||||
put("qr", BarcodeFormat.QR_CODE.toString());
|
||||
put("pdf417", BarcodeFormat.PDF_417.toString());
|
||||
put("upc_e", BarcodeFormat.UPC_E.toString());
|
||||
put("datamatrix", BarcodeFormat.DATA_MATRIX.toString());
|
||||
put("code39", BarcodeFormat.CODE_39.toString());
|
||||
put("code93", BarcodeFormat.CODE_93.toString());
|
||||
put("interleaved2of5", BarcodeFormat.ITF.toString());
|
||||
put("codabar", BarcodeFormat.CODABAR.toString());
|
||||
put("code128", BarcodeFormat.CODE_128.toString());
|
||||
put("maxicode", BarcodeFormat.MAXICODE.toString());
|
||||
put("rss14", BarcodeFormat.RSS_14.toString());
|
||||
put("rssexpanded", BarcodeFormat.RSS_EXPANDED.toString());
|
||||
put("upc_a", BarcodeFormat.UPC_A.toString());
|
||||
put("upc_ean", BarcodeFormat.UPC_EAN_EXTENSION.toString());
|
||||
}
|
||||
});
|
||||
|
||||
public CameraModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mReactContext = reactContext;
|
||||
}
|
||||
|
||||
public static ReactApplicationContext getReactContextSingleton() {
|
||||
return mReactContext;
|
||||
}
|
||||
|
||||
public static Context getScopedContextSingleton() {
|
||||
return mReactContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return "RNCameraModule";
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("Type", getTypeConstants());
|
||||
put("FlashMode", getFlashModeConstants());
|
||||
put("AutoFocus", getAutoFocusConstants());
|
||||
put("WhiteBalance", getWhiteBalanceConstants());
|
||||
put("VideoQuality", getVideoQualityConstants());
|
||||
put("BarCodeType", getBarCodeConstants());
|
||||
put("FaceDetection", Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("Mode", getFaceDetectionModeConstants());
|
||||
put("Landmarks", getFaceDetectionLandmarksConstants());
|
||||
put("Classifications", getFaceDetectionClassificationsConstants());
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionModeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("fast", RNFaceDetector.FAST_MODE);
|
||||
put("accurate", RNFaceDetector.ACCURATE_MODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionClassificationsConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("all", RNFaceDetector.ALL_CLASSIFICATIONS);
|
||||
put("none", RNFaceDetector.NO_CLASSIFICATIONS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionLandmarksConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("all", RNFaceDetector.ALL_LANDMARKS);
|
||||
put("none", RNFaceDetector.NO_LANDMARKS);
|
||||
}
|
||||
});
|
||||
}
|
||||
}));
|
||||
}
|
||||
|
||||
private Map<String, Object> getTypeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("front", Constants.FACING_FRONT);
|
||||
put("back", Constants.FACING_BACK);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getFlashModeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("off", Constants.FLASH_OFF);
|
||||
put("on", Constants.FLASH_ON);
|
||||
put("auto", Constants.FLASH_AUTO);
|
||||
put("torch", Constants.FLASH_TORCH);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getAutoFocusConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("on", true);
|
||||
put("off", false);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getWhiteBalanceConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("auto", Constants.WB_AUTO);
|
||||
put("cloudy", Constants.WB_CLOUDY);
|
||||
put("sunny", Constants.WB_SUNNY);
|
||||
put("shadow", Constants.WB_SHADOW);
|
||||
put("fluorescent", Constants.WB_FLUORESCENT);
|
||||
put("incandescent", Constants.WB_INCANDESCENT);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getVideoQualityConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("2160p", VIDEO_2160P);
|
||||
put("1080p", VIDEO_1080P);
|
||||
put("720p", VIDEO_720P);
|
||||
put("480p", VIDEO_480P);
|
||||
put("4:3", VIDEO_4x3);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getBarCodeConstants() {
|
||||
return VALID_BARCODE_TYPES;
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void takePicture(ReadableMap options, final Promise promise) {
|
||||
CameraViewManager.getInstance().takePicture(options, promise);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void record(ReadableMap options, final Promise promise) {
|
||||
CameraViewManager.getInstance().record(options, promise);
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void stopRecording() {
|
||||
CameraViewManager.getInstance().stopRecording();
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void getSupportedRatios(final Promise promise) {
|
||||
WritableArray result = Arguments.createArray();
|
||||
Set<AspectRatio> ratios = CameraViewManager.getInstance().getSupportedRatios();
|
||||
if (ratios != null) {
|
||||
for (AspectRatio ratio : ratios) {
|
||||
result.pushString(ratio.toString());
|
||||
}
|
||||
promise.resolve(result);
|
||||
} else {
|
||||
promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,196 @@
|
||||
package org.reactnative.camera;
|
||||
|
||||
import android.Manifest;
|
||||
import android.graphics.Bitmap;
|
||||
import android.os.Build;
|
||||
import android.support.annotation.Nullable;
|
||||
|
||||
import org.reactnative.camera.tasks.ResolveTakenPictureAsyncTask;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableArray;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.common.MapBuilder;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.facebook.react.uimanager.ViewGroupManager;
|
||||
import com.facebook.react.uimanager.annotations.ReactProp;
|
||||
import com.google.android.cameraview.AspectRatio;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
|
||||
public class CameraViewManager extends ViewGroupManager<RNCameraView> {
|
||||
public enum Events {
|
||||
EVENT_CAMERA_READY("onCameraReady"),
|
||||
EVENT_ON_MOUNT_ERROR("onMountError"),
|
||||
EVENT_ON_BAR_CODE_READ("onBarCodeRead"),
|
||||
EVENT_ON_FACES_DETECTED("onFacesDetected"),
|
||||
EVENT_ON_FACE_DETECTION_ERROR("onFaceDetectionError");
|
||||
|
||||
private final String mName;
|
||||
|
||||
Events(final String name) {
|
||||
mName = name;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return mName;
|
||||
}
|
||||
}
|
||||
|
||||
private static final String REACT_CLASS = "RNCamera";
|
||||
|
||||
private static CameraViewManager instance;
|
||||
private RNCameraView mCameraView;
|
||||
|
||||
public CameraViewManager() {
|
||||
super();
|
||||
instance = this;
|
||||
}
|
||||
|
||||
public static CameraViewManager getInstance() { return instance; }
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return REACT_CLASS;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected RNCameraView createViewInstance(ThemedReactContext themedReactContext) {
|
||||
mCameraView = new RNCameraView(themedReactContext);
|
||||
return mCameraView;
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public Map<String, Object> getExportedCustomDirectEventTypeConstants() {
|
||||
MapBuilder.Builder<String, Object> builder = MapBuilder.builder();
|
||||
for (Events event : Events.values()) {
|
||||
builder.put(event.toString(), MapBuilder.of("registrationName", event.toString()));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
@ReactProp(name = "type")
|
||||
public void setType(RNCameraView view, int type) {
|
||||
view.setFacing(type);
|
||||
}
|
||||
|
||||
@ReactProp(name = "ratio")
|
||||
public void setRatio(RNCameraView view, String ratio) {
|
||||
view.setAspectRatio(AspectRatio.parse(ratio));
|
||||
}
|
||||
|
||||
@ReactProp(name = "flashMode")
|
||||
public void setFlashMode(RNCameraView view, int torchMode) {
|
||||
view.setFlash(torchMode);
|
||||
}
|
||||
|
||||
@ReactProp(name = "autoFocus")
|
||||
public void setAutoFocus(RNCameraView view, boolean autoFocus) {
|
||||
view.setAutoFocus(autoFocus);
|
||||
}
|
||||
|
||||
@ReactProp(name = "focusDepth")
|
||||
public void setFocusDepth(RNCameraView view, float depth) {
|
||||
view.setFocusDepth(depth);
|
||||
}
|
||||
|
||||
@ReactProp(name = "zoom")
|
||||
public void setZoom(RNCameraView view, float zoom) {
|
||||
view.setZoom(zoom);
|
||||
}
|
||||
|
||||
@ReactProp(name = "whiteBalance")
|
||||
public void setWhiteBalance(RNCameraView view, int whiteBalance) {
|
||||
view.setWhiteBalance(whiteBalance);
|
||||
}
|
||||
|
||||
@ReactProp(name = "barCodeTypes")
|
||||
public void setBarCodeTypes(RNCameraView view, ReadableArray barCodeTypes) {
|
||||
if (barCodeTypes == null) {
|
||||
return;
|
||||
}
|
||||
List<String> result = new ArrayList<>(barCodeTypes.size());
|
||||
for (int i = 0; i < barCodeTypes.size(); i++) {
|
||||
result.add(barCodeTypes.getString(i));
|
||||
}
|
||||
view.setBarCodeTypes(result);
|
||||
}
|
||||
|
||||
@ReactProp(name = "barCodeScannerEnabled")
|
||||
public void setBarCodeScanning(RNCameraView view, boolean barCodeScannerEnabled) {
|
||||
view.setShouldScanBarCodes(barCodeScannerEnabled);
|
||||
}
|
||||
|
||||
@ReactProp(name = "faceDetectorEnabled")
|
||||
public void setFaceDetecting(RNCameraView view, boolean faceDetectorEnabled) {
|
||||
view.setShouldDetectFaces(faceDetectorEnabled);
|
||||
}
|
||||
|
||||
@ReactProp(name = "faceDetectionMode")
|
||||
public void setFaceDetectionMode(RNCameraView view, int mode) {
|
||||
view.setFaceDetectionMode(mode);
|
||||
}
|
||||
|
||||
@ReactProp(name = "faceDetectionLandmarks")
|
||||
public void setFaceDetectionLandmarks(RNCameraView view, int landmarks) {
|
||||
view.setFaceDetectionLandmarks(landmarks);
|
||||
}
|
||||
|
||||
@ReactProp(name = "faceDetectionClassifications")
|
||||
public void setFaceDetectionClassifications(RNCameraView view, int classifications) {
|
||||
view.setFaceDetectionClassifications(classifications);
|
||||
}
|
||||
|
||||
public void takePicture(ReadableMap options, Promise promise) {
|
||||
if (!Build.FINGERPRINT.contains("generic")) {
|
||||
if (mCameraView.isCameraOpened()) {
|
||||
mCameraView.takePicture(options, promise);
|
||||
} else {
|
||||
promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
|
||||
}
|
||||
} else {
|
||||
Bitmap image = RNCameraViewHelper.generateSimulatorPhoto(mCameraView.getWidth(), mCameraView.getHeight());
|
||||
ByteBuffer byteBuffer = ByteBuffer.allocate(image.getRowBytes() * image.getHeight());
|
||||
image.copyPixelsToBuffer(byteBuffer);
|
||||
new ResolveTakenPictureAsyncTask(byteBuffer.array(), promise, options).execute();
|
||||
}
|
||||
}
|
||||
|
||||
public void record(final ReadableMap options, final Promise promise) {
|
||||
// TODO fix this
|
||||
// RN.getInstance().getPermissions(new RN.PermissionsListener() {
|
||||
// @Override
|
||||
// public void permissionsGranted() {
|
||||
// if (mCameraView.isCameraOpened()) {
|
||||
// mCameraView.record(options, promise);
|
||||
// } else {
|
||||
// promise.reject("E_CAMERA_UNAVAILABLE", "Camera is not running");
|
||||
// }
|
||||
// }
|
||||
//
|
||||
// @Override
|
||||
// public void permissionsDenied() {
|
||||
// promise.reject(new SecurityException("User rejected audio permissions"));
|
||||
// }
|
||||
// }, new String[]{Manifest.permission.RECORD_AUDIO});
|
||||
|
||||
}
|
||||
|
||||
public void stopRecording() {
|
||||
if (mCameraView.isCameraOpened()) {
|
||||
mCameraView.stopRecording();
|
||||
}
|
||||
}
|
||||
|
||||
public Set<AspectRatio> getSupportedRatios() {
|
||||
if (mCameraView.isCameraOpened()) {
|
||||
return mCameraView.getSupportedAspectRatios();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
}
|
||||
42
android/src/main/java/org/reactnative/camera/Constants.java
Normal file
42
android/src/main/java/org/reactnative/camera/Constants.java
Normal file
@ -0,0 +1,42 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package org.reactnative.camera;
|
||||
|
||||
import com.google.android.cameraview.AspectRatio;
|
||||
|
||||
public interface Constants {
|
||||
|
||||
AspectRatio DEFAULT_ASPECT_RATIO = AspectRatio.of(4, 3);
|
||||
|
||||
int FACING_BACK = 0;
|
||||
int FACING_FRONT = 1;
|
||||
|
||||
int FLASH_OFF = 0;
|
||||
int FLASH_ON = 1;
|
||||
int FLASH_TORCH = 2;
|
||||
int FLASH_AUTO = 3;
|
||||
int FLASH_RED_EYE = 4;
|
||||
|
||||
int LANDSCAPE_90 = 90;
|
||||
int LANDSCAPE_270 = 270;
|
||||
|
||||
int WB_AUTO = 0;
|
||||
int WB_CLOUDY = 1;
|
||||
int WB_SUNNY = 2;
|
||||
int WB_SHADOW = 3;
|
||||
int WB_FLUORESCENT = 4;
|
||||
int WB_INCANDESCENT = 5;
|
||||
}
|
||||
316
android/src/main/java/org/reactnative/camera/RNCameraView.java
Normal file
316
android/src/main/java/org/reactnative/camera/RNCameraView.java
Normal file
@ -0,0 +1,316 @@
|
||||
package org.reactnative.camera;
|
||||
|
||||
import android.Manifest;
|
||||
import android.content.Context;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.Color;
|
||||
import android.media.CamcorderProfile;
|
||||
import android.os.Build;
|
||||
import android.support.v4.content.ContextCompat;
|
||||
import android.util.SparseArray;
|
||||
import android.view.View;
|
||||
|
||||
import org.reactnative.camera.tasks.BarCodeScannerAsyncTask;
|
||||
import org.reactnative.camera.tasks.BarCodeScannerAsyncTaskDelegate;
|
||||
import org.reactnative.camera.tasks.FaceDetectorAsyncTask;
|
||||
import org.reactnative.camera.tasks.FaceDetectorAsyncTaskDelegate;
|
||||
import org.reactnative.camera.tasks.ResolveTakenPictureAsyncTask;
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import org.reactnative.facedetector.RNFaceDetector;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.LifecycleEventListener;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.ThemedReactContext;
|
||||
import com.google.android.cameraview.CameraView;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
import com.google.zxing.BarcodeFormat;
|
||||
import com.google.zxing.DecodeHintType;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.Result;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.EnumMap;
|
||||
import java.util.EnumSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Queue;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentLinkedQueue;
|
||||
|
||||
public class RNCameraView extends CameraView implements LifecycleEventListener, BarCodeScannerAsyncTaskDelegate, FaceDetectorAsyncTaskDelegate {
|
||||
private Queue<Promise> mPictureTakenPromises = new ConcurrentLinkedQueue<>();
|
||||
private Map<Promise, ReadableMap> mPictureTakenOptions = new ConcurrentHashMap<>();
|
||||
private Promise mVideoRecordedPromise;
|
||||
private List<String> mBarCodeTypes = null;
|
||||
|
||||
// Concurrency lock for scanners to avoid flooding the runtime
|
||||
public volatile boolean barCodeScannerTaskLock = false;
|
||||
public volatile boolean faceDetectorTaskLock = false;
|
||||
|
||||
// Scanning-related properties
|
||||
private final MultiFormatReader mMultiFormatReader = new MultiFormatReader();
|
||||
private final RNFaceDetector mFaceDetector;
|
||||
private boolean mShouldDetectFaces = false;
|
||||
private boolean mShouldScanBarCodes = false;
|
||||
private int mFaceDetectorMode = RNFaceDetector.FAST_MODE;
|
||||
private int mFaceDetectionLandmarks = RNFaceDetector.NO_LANDMARKS;
|
||||
private int mFaceDetectionClassifications = RNFaceDetector.NO_CLASSIFICATIONS;
|
||||
|
||||
public RNCameraView(ThemedReactContext themedReactContext) {
|
||||
super(themedReactContext);
|
||||
initBarcodeReader();
|
||||
mFaceDetector = new RNFaceDetector(themedReactContext);
|
||||
setupFaceDetector();
|
||||
themedReactContext.addLifecycleEventListener(this);
|
||||
|
||||
addCallback(new Callback() {
|
||||
@Override
|
||||
public void onCameraOpened(CameraView cameraView) {
|
||||
RNCameraViewHelper.emitCameraReadyEvent(cameraView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMountError(CameraView cameraView) {
|
||||
RNCameraViewHelper.emitMountErrorEvent(cameraView);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPictureTaken(CameraView cameraView, final byte[] data) {
|
||||
Promise promise = mPictureTakenPromises.poll();
|
||||
ReadableMap options = mPictureTakenOptions.remove(promise);
|
||||
new ResolveTakenPictureAsyncTask(data, promise, options).execute();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onVideoRecorded(CameraView cameraView, String path) {
|
||||
if (mVideoRecordedPromise != null) {
|
||||
if (path != null) {
|
||||
WritableMap result = Arguments.createMap();
|
||||
// TODO - fix this
|
||||
//result.putString("uri", ExpFileUtils.uriFromFile(new File(path)).toString());
|
||||
mVideoRecordedPromise.resolve(result);
|
||||
} else {
|
||||
mVideoRecordedPromise.reject("E_RECORDING", "Couldn't stop recording - there is none in progress");
|
||||
}
|
||||
mVideoRecordedPromise = null;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFramePreview(CameraView cameraView, byte[] data, int width, int height, int rotation) {
|
||||
int correctRotation = RNCameraViewHelper.getCorrectCameraRotation(rotation, getFacing());
|
||||
|
||||
if (mShouldScanBarCodes && !barCodeScannerTaskLock && cameraView instanceof BarCodeScannerAsyncTaskDelegate) {
|
||||
barCodeScannerTaskLock = true;
|
||||
BarCodeScannerAsyncTaskDelegate delegate = (BarCodeScannerAsyncTaskDelegate) cameraView;
|
||||
new BarCodeScannerAsyncTask(delegate, mMultiFormatReader, data, width, height).execute();
|
||||
}
|
||||
|
||||
if (mShouldDetectFaces && !faceDetectorTaskLock && cameraView instanceof FaceDetectorAsyncTaskDelegate) {
|
||||
faceDetectorTaskLock = true;
|
||||
FaceDetectorAsyncTaskDelegate delegate = (FaceDetectorAsyncTaskDelegate) cameraView;
|
||||
new FaceDetectorAsyncTask(delegate, mFaceDetector, data, width, height, correctRotation).execute();
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
View preview = getView();
|
||||
if (null == preview) {
|
||||
return;
|
||||
}
|
||||
this.setBackgroundColor(Color.BLACK);
|
||||
int width = right - left;
|
||||
int height = bottom - top;
|
||||
preview.layout(0, 0, width, height);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestLayout() {
|
||||
// React handles this for us, so we don't need to call super.requestLayout();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onViewAdded(View child) {
|
||||
if (this.getView() == child || this.getView() == null) return;
|
||||
// remove and readd view to make sure it is in the back.
|
||||
// @TODO figure out why there was a z order issue in the first place and fix accordingly.
|
||||
this.removeView(this.getView());
|
||||
this.addView(this.getView(), 0);
|
||||
}
|
||||
|
||||
public void setBarCodeTypes(List<String> barCodeTypes) {
|
||||
mBarCodeTypes = barCodeTypes;
|
||||
initBarcodeReader();
|
||||
}
|
||||
|
||||
public void takePicture(ReadableMap options, final Promise promise) {
|
||||
mPictureTakenPromises.add(promise);
|
||||
mPictureTakenOptions.put(promise, options);
|
||||
super.takePicture();
|
||||
}
|
||||
|
||||
public void record(ReadableMap options, final Promise promise) {
|
||||
// try {
|
||||
// TODO - fix this
|
||||
String path = "";
|
||||
//String path = ExpFileUtils.generateOutputPath(CameraModule.getScopedContextSingleton().getCacheDir(), "Camera", ".mp4");
|
||||
int maxDuration = options.hasKey("maxDuration") ? options.getInt("maxDuration") : -1;
|
||||
int maxFileSize = options.hasKey("maxFileSize") ? options.getInt("maxFileSize") : -1;
|
||||
|
||||
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
|
||||
if (options.hasKey("quality")) {
|
||||
profile = RNCameraViewHelper.getCamcorderProfile(options.getInt("quality"));
|
||||
}
|
||||
|
||||
boolean recordAudio = !options.hasKey("mute");
|
||||
|
||||
if (super.record(path, maxDuration * 1000, maxFileSize, recordAudio, profile)) {
|
||||
mVideoRecordedPromise = promise;
|
||||
} else {
|
||||
promise.reject("E_RECORDING_FAILED", "Starting video recording failed. Another recording might be in progress.");
|
||||
}
|
||||
// } catch (IOException e) {
|
||||
// promise.reject("E_RECORDING_FAILED", "Starting video recording failed - could not create video file.");
|
||||
// }
|
||||
}
|
||||
|
||||
/**
|
||||
* Initialize the barcode decoder.
|
||||
* Supports all iOS codes except [code138, code39mod43, itf14]
|
||||
* Additionally supports [codabar, code128, maxicode, rss14, rssexpanded, upc_a, upc_ean]
|
||||
*/
|
||||
private void initBarcodeReader() {
|
||||
EnumMap<DecodeHintType, Object> hints = new EnumMap<>(DecodeHintType.class);
|
||||
EnumSet<BarcodeFormat> decodeFormats = EnumSet.noneOf(BarcodeFormat.class);
|
||||
|
||||
if (mBarCodeTypes != null) {
|
||||
for (String code : mBarCodeTypes) {
|
||||
String formatString = (String) CameraModule.VALID_BARCODE_TYPES.get(code);
|
||||
if (formatString != null) {
|
||||
decodeFormats.add(BarcodeFormat.valueOf(code));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
hints.put(DecodeHintType.POSSIBLE_FORMATS, decodeFormats);
|
||||
mMultiFormatReader.setHints(hints);
|
||||
}
|
||||
|
||||
public void setShouldScanBarCodes(boolean shouldScanBarCodes) {
|
||||
this.mShouldScanBarCodes = shouldScanBarCodes;
|
||||
setScanning(mShouldDetectFaces || mShouldScanBarCodes);
|
||||
}
|
||||
|
||||
public void onBarCodeRead(Result barCode) {
|
||||
String barCodeType = barCode.getBarcodeFormat().toString();
|
||||
if (!mShouldScanBarCodes || !mBarCodeTypes.contains(barCodeType)) {
|
||||
return;
|
||||
}
|
||||
|
||||
RNCameraViewHelper.emitBarCodeReadEvent(this, barCode);
|
||||
}
|
||||
|
||||
public void onBarCodeScanningTaskCompleted() {
|
||||
barCodeScannerTaskLock = false;
|
||||
mMultiFormatReader.reset();
|
||||
}
|
||||
|
||||
/**
|
||||
* Initial setup of the face detector
|
||||
*/
|
||||
private void setupFaceDetector() {
|
||||
mFaceDetector.setMode(mFaceDetectorMode);
|
||||
mFaceDetector.setLandmarkType(mFaceDetectionLandmarks);
|
||||
mFaceDetector.setClassificationType(mFaceDetectionClassifications);
|
||||
mFaceDetector.setTracking(true);
|
||||
}
|
||||
|
||||
public void setFaceDetectionLandmarks(int landmarks) {
|
||||
mFaceDetectionLandmarks = landmarks;
|
||||
if (mFaceDetector != null) {
|
||||
mFaceDetector.setLandmarkType(landmarks);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFaceDetectionClassifications(int classifications) {
|
||||
mFaceDetectionClassifications = classifications;
|
||||
if (mFaceDetector != null) {
|
||||
mFaceDetector.setClassificationType(classifications);
|
||||
}
|
||||
}
|
||||
|
||||
public void setFaceDetectionMode(int mode) {
|
||||
mFaceDetectorMode = mode;
|
||||
if (mFaceDetector != null) {
|
||||
mFaceDetector.setMode(mode);
|
||||
}
|
||||
}
|
||||
|
||||
public void setShouldDetectFaces(boolean shouldDetectFaces) {
|
||||
this.mShouldDetectFaces = shouldDetectFaces;
|
||||
setScanning(mShouldDetectFaces || mShouldScanBarCodes);
|
||||
}
|
||||
|
||||
public void onFacesDetected(SparseArray<Face> facesReported, int sourceWidth, int sourceHeight, int sourceRotation) {
|
||||
if (!mShouldDetectFaces) {
|
||||
return;
|
||||
}
|
||||
|
||||
SparseArray<Face> facesDetected = facesReported == null ? new SparseArray<Face>() : facesReported;
|
||||
|
||||
ImageDimensions dimensions = new ImageDimensions(sourceWidth, sourceHeight, sourceRotation, getFacing());
|
||||
RNCameraViewHelper.emitFacesDetectedEvent(this, facesDetected, dimensions);
|
||||
}
|
||||
|
||||
public void onFaceDetectionError(RNFaceDetector faceDetector) {
|
||||
if (!mShouldDetectFaces) {
|
||||
return;
|
||||
}
|
||||
|
||||
RNCameraViewHelper.emitFaceDetectionErrorEvent(this, faceDetector);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFaceDetectingTaskCompleted() {
|
||||
faceDetectorTaskLock = false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostResume() {
|
||||
if (hasCameraPermissions()) {
|
||||
if (!Build.FINGERPRINT.contains("generic")) {
|
||||
start();
|
||||
}
|
||||
} else {
|
||||
WritableMap error = Arguments.createMap();
|
||||
error.putString("message", "Camera permissions not granted - component could not be rendered.");
|
||||
RNCameraViewHelper.emitMountErrorEvent(this);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostPause() {
|
||||
stop();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onHostDestroy() {
|
||||
mFaceDetector.release();
|
||||
stop();
|
||||
}
|
||||
|
||||
private boolean hasCameraPermissions() {
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
|
||||
int result = ContextCompat.checkSelfPermission(getContext(), Manifest.permission.CAMERA);
|
||||
return result == PackageManager.PERMISSION_GRANTED;
|
||||
} else {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,170 @@
|
||||
package org.reactnative.camera;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.Paint;
|
||||
import android.media.CamcorderProfile;
|
||||
import android.os.Build;
|
||||
import android.support.media.ExifInterface;
|
||||
import android.util.SparseArray;
|
||||
import android.view.ViewGroup;
|
||||
|
||||
import org.reactnative.camera.events.BarCodeReadEvent;
|
||||
import org.reactnative.camera.events.CameraMountErrorEvent;
|
||||
import org.reactnative.camera.events.CameraReadyEvent;
|
||||
import org.reactnative.camera.events.FaceDetectionErrorEvent;
|
||||
import org.reactnative.camera.events.FacesDetectedEvent;
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import org.reactnative.facedetector.RNFaceDetector;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReactContext;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.UIManagerModule;
|
||||
import com.google.android.cameraview.CameraView;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
import com.google.zxing.Result;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
|
||||
public class RNCameraViewHelper {
|
||||
// Mount error event
|
||||
|
||||
public static void emitMountErrorEvent(ViewGroup view) {
|
||||
CameraMountErrorEvent event = CameraMountErrorEvent.obtain(view.getId());
|
||||
ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Camera ready event
|
||||
|
||||
public static void emitCameraReadyEvent(ViewGroup view) {
|
||||
CameraReadyEvent event = CameraReadyEvent.obtain(view.getId());
|
||||
ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Face detection events
|
||||
|
||||
public static void emitFacesDetectedEvent(
|
||||
ViewGroup view,
|
||||
SparseArray<Face> faces,
|
||||
ImageDimensions dimensions
|
||||
) {
|
||||
float density = view.getResources().getDisplayMetrics().density;
|
||||
|
||||
double scaleX = (double) view.getWidth() / (dimensions.getWidth() * density);
|
||||
double scaleY = (double) view.getHeight() / (dimensions.getHeight() * density);
|
||||
|
||||
FacesDetectedEvent event = FacesDetectedEvent.obtain(
|
||||
view.getId(),
|
||||
faces,
|
||||
dimensions,
|
||||
scaleX,
|
||||
scaleY
|
||||
);
|
||||
|
||||
ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||
}
|
||||
|
||||
public static void emitFaceDetectionErrorEvent(ViewGroup view, RNFaceDetector faceDetector) {
|
||||
FaceDetectionErrorEvent event = FaceDetectionErrorEvent.obtain(view.getId(), faceDetector);
|
||||
ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Bar code read event
|
||||
|
||||
public static void emitBarCodeReadEvent(ViewGroup view, Result barCode) {
|
||||
BarCodeReadEvent event = BarCodeReadEvent.obtain(view.getId(), barCode);
|
||||
ReactContext reactContext = (ReactContext) view.getContext();
|
||||
reactContext.getNativeModule(UIManagerModule.class).getEventDispatcher().dispatchEvent(event);
|
||||
}
|
||||
|
||||
// Utilities
|
||||
|
||||
public static int getCorrectCameraRotation(int rotation, int facing) {
|
||||
if (facing == CameraView.FACING_FRONT) {
|
||||
return (rotation - 90 + 360) % 360;
|
||||
} else {
|
||||
return (-rotation + 90 + 360) % 360;
|
||||
}
|
||||
}
|
||||
|
||||
public static CamcorderProfile getCamcorderProfile(int quality) {
|
||||
CamcorderProfile profile = CamcorderProfile.get(CamcorderProfile.QUALITY_HIGH);
|
||||
switch (quality) {
|
||||
case CameraModule.VIDEO_2160P:
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_2160P);
|
||||
}
|
||||
break;
|
||||
case CameraModule.VIDEO_1080P:
|
||||
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_1080P);
|
||||
break;
|
||||
case CameraModule.VIDEO_720P:
|
||||
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_720P);
|
||||
break;
|
||||
case CameraModule.VIDEO_480P:
|
||||
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
|
||||
break;
|
||||
case CameraModule.VIDEO_4x3:
|
||||
profile = CamcorderProfile.get(CamcorderProfile.QUALITY_480P);
|
||||
profile.videoFrameWidth = 640;
|
||||
break;
|
||||
}
|
||||
return profile;
|
||||
}
|
||||
|
||||
public static WritableMap getExifData(ExifInterface exifInterface) {
|
||||
WritableMap exifMap = Arguments.createMap();
|
||||
// TODO - fix this
|
||||
// for (String[] tagInfo : ImagePickerModule.exifTags) {
|
||||
// String name = tagInfo[1];
|
||||
// if (exifInterface.getAttribute(name) != null) {
|
||||
// String type = tagInfo[0];
|
||||
// switch (type) {
|
||||
// case "string":
|
||||
// exifMap.putString(name, exifInterface.getAttribute(name));
|
||||
// break;
|
||||
// case "int":
|
||||
// exifMap.putInt(name, exifInterface.getAttributeInt(name, 0));
|
||||
// break;
|
||||
// case "double":
|
||||
// exifMap.putDouble(name, exifInterface.getAttributeDouble(name, 0));
|
||||
// break;
|
||||
// }
|
||||
// }
|
||||
// }
|
||||
|
||||
double[] latLong = exifInterface.getLatLong();
|
||||
if (latLong != null) {
|
||||
exifMap.putDouble(ExifInterface.TAG_GPS_LATITUDE, latLong[0]);
|
||||
exifMap.putDouble(ExifInterface.TAG_GPS_LONGITUDE, latLong[1]);
|
||||
exifMap.putDouble(ExifInterface.TAG_GPS_ALTITUDE, exifInterface.getAltitude(0));
|
||||
}
|
||||
|
||||
return exifMap;
|
||||
}
|
||||
|
||||
public static Bitmap generateSimulatorPhoto(int width, int height) {
|
||||
Bitmap fakePhoto = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(fakePhoto);
|
||||
Paint background = new Paint();
|
||||
background.setColor(Color.BLACK);
|
||||
canvas.drawRect(0, 0, width, height, background);
|
||||
Paint textPaint = new Paint();
|
||||
textPaint.setColor(Color.YELLOW);
|
||||
textPaint.setTextSize(35);
|
||||
Calendar calendar = Calendar.getInstance();
|
||||
SimpleDateFormat simpleDateFormat = new SimpleDateFormat("dd.MM.YY HH:mm:ss", Locale.getDefault());
|
||||
canvas.drawText(simpleDateFormat.format(calendar.getTime()), width * 0.1f, height * 0.9f, textPaint);
|
||||
return fakePhoto;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,68 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
|
||||
import org.reactnative.camera.CameraViewManager;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.google.zxing.Result;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class BarCodeReadEvent extends Event<BarCodeReadEvent> {
|
||||
private static final Pools.SynchronizedPool<BarCodeReadEvent> EVENTS_POOL =
|
||||
new Pools.SynchronizedPool<>(3);
|
||||
|
||||
private Result mBarCode;
|
||||
|
||||
private BarCodeReadEvent() {}
|
||||
|
||||
public static BarCodeReadEvent obtain(int viewTag, Result barCode) {
|
||||
BarCodeReadEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new BarCodeReadEvent();
|
||||
}
|
||||
event.init(viewTag);
|
||||
return event;
|
||||
}
|
||||
|
||||
private void init(int viewTag, Result barCode) {
|
||||
super.init(viewTag);
|
||||
mBarCode = barCode;
|
||||
}
|
||||
|
||||
/**
|
||||
* We want every distinct barcode to be reported to the JS listener.
|
||||
* If we return some static value as a coalescing key there may be two barcode events
|
||||
* containing two different barcodes waiting to be transmitted to JS
|
||||
* that would get coalesced (because both of them would have the same coalescing key).
|
||||
* So let's differentiate them with a hash of the contents (mod short's max value).
|
||||
*/
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
int hashCode = mBarCode.getText().hashCode() % Short.MAX_VALUE;
|
||||
return (short) hashCode;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return CameraViewManager.Events.EVENT_ON_BAR_CODE_READ.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
WritableMap event = Arguments.createMap();
|
||||
|
||||
event.putInt("target", getViewTag());
|
||||
event.putString("data", mBarCode.getText());
|
||||
event.putString("type", mBarCode.getBarcodeFormat().toString());
|
||||
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
|
||||
import org.reactnative.camera.CameraViewManager;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class CameraMountErrorEvent extends Event<CameraMountErrorEvent> {
|
||||
private static final Pools.SynchronizedPool<CameraMountErrorEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||
private CameraMountErrorEvent() {}
|
||||
|
||||
public static CameraMountErrorEvent obtain(int viewTag) {
|
||||
CameraMountErrorEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new CameraMountErrorEvent();
|
||||
}
|
||||
event.init(viewTag);
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return CameraViewManager.Events.EVENT_ON_MOUNT_ERROR.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
return Arguments.createMap();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,44 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
|
||||
import org.reactnative.camera.CameraViewManager;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class CameraReadyEvent extends Event<CameraReadyEvent> {
|
||||
private static final Pools.SynchronizedPool<CameraReadyEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||
private CameraReadyEvent() {}
|
||||
|
||||
public static CameraReadyEvent obtain(int viewTag) {
|
||||
CameraReadyEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new CameraReadyEvent();
|
||||
}
|
||||
event.init(viewTag);
|
||||
return event;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return CameraViewManager.Events.EVENT_CAMERA_READY.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
return Arguments.createMap();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,53 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
|
||||
import org.reactnative.camera.CameraViewManager;
|
||||
import org.reactnative.facedetector.RNFaceDetector;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class FaceDetectionErrorEvent extends Event<FaceDetectionErrorEvent> {
|
||||
private static final Pools.SynchronizedPool<FaceDetectionErrorEvent> EVENTS_POOL = new Pools.SynchronizedPool<>(3);
|
||||
private RNFaceDetector mFaceDetector;
|
||||
private FaceDetectionErrorEvent() {}
|
||||
|
||||
public static FaceDetectionErrorEvent obtain(int viewTag, RNFaceDetector faceDetector) {
|
||||
FaceDetectionErrorEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new FaceDetectionErrorEvent();
|
||||
}
|
||||
event.init(viewTag);
|
||||
return event;
|
||||
}
|
||||
|
||||
private void init(int viewTag, RNFaceDetector faceDetector) {
|
||||
super.init(viewTag);
|
||||
mFaceDetector = faceDetector;
|
||||
}
|
||||
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
return 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return CameraViewManager.Events.EVENT_ON_MOUNT_ERROR.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putBoolean("isOperational", mFaceDetector.isOperational());
|
||||
return map;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,103 @@
|
||||
package org.reactnative.camera.events;
|
||||
|
||||
import android.support.v4.util.Pools;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.reactnative.camera.CameraViewManager;
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import org.reactnative.facedetector.FaceDetectorUtils;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.facebook.react.uimanager.events.Event;
|
||||
import com.facebook.react.uimanager.events.RCTEventEmitter;
|
||||
import com.google.android.cameraview.CameraView;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
|
||||
import java.util.Date;
|
||||
|
||||
public class FacesDetectedEvent extends Event<FacesDetectedEvent> {
|
||||
private static final Pools.SynchronizedPool<FacesDetectedEvent> EVENTS_POOL =
|
||||
new Pools.SynchronizedPool<>(3);
|
||||
|
||||
private double mScaleX;
|
||||
private double mScaleY;
|
||||
private SparseArray<Face> mFaces;
|
||||
private ImageDimensions mImageDimensions;
|
||||
|
||||
private FacesDetectedEvent() {}
|
||||
|
||||
public static FacesDetectedEvent obtain(
|
||||
int viewTag,
|
||||
SparseArray<Face> faces,
|
||||
ImageDimensions dimensions,
|
||||
double scaleX,
|
||||
double scaleY
|
||||
) {
|
||||
FacesDetectedEvent event = EVENTS_POOL.acquire();
|
||||
if (event == null) {
|
||||
event = new FacesDetectedEvent();
|
||||
}
|
||||
event.init(viewTag, faces, dimensions, scaleX, scaleY);
|
||||
return event;
|
||||
}
|
||||
|
||||
private void init(
|
||||
int viewTag,
|
||||
SparseArray<Face> faces,
|
||||
ImageDimensions dimensions,
|
||||
double scaleX,
|
||||
double scaleY
|
||||
) {
|
||||
super.init(viewTag);
|
||||
mFaces = faces;
|
||||
mImageDimensions = dimensions;
|
||||
mScaleX = scaleX;
|
||||
mScaleY = scaleY;
|
||||
}
|
||||
|
||||
/**
|
||||
* note(@sjchmiela)
|
||||
* Should the events about detected faces coalesce, the best strategy will be
|
||||
* to ensure that events with different faces count are always being transmitted.
|
||||
*/
|
||||
@Override
|
||||
public short getCoalescingKey() {
|
||||
if (mFaces.size() > Short.MAX_VALUE) {
|
||||
return Short.MAX_VALUE;
|
||||
}
|
||||
|
||||
return (short) mFaces.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getEventName() {
|
||||
return CameraViewManager.Events.EVENT_ON_FACES_DETECTED.toString();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispatch(RCTEventEmitter rctEventEmitter) {
|
||||
rctEventEmitter.receiveEvent(getViewTag(), getEventName(), serializeEventData());
|
||||
}
|
||||
|
||||
private WritableMap serializeEventData() {
|
||||
WritableArray facesList = Arguments.createArray();
|
||||
|
||||
for(int i = 0; i < mFaces.size(); i++) {
|
||||
Face face = mFaces.valueAt(i);
|
||||
WritableMap serializedFace = FaceDetectorUtils.serializeFace(face, mScaleX, mScaleY);
|
||||
if (mImageDimensions.getFacing() == CameraView.FACING_FRONT) {
|
||||
serializedFace = FaceDetectorUtils.rotateFaceX(serializedFace, mImageDimensions.getWidth(), mScaleX);
|
||||
} else {
|
||||
serializedFace = FaceDetectorUtils.changeAnglesDirection(serializedFace);
|
||||
}
|
||||
facesList.pushMap(serializedFace);
|
||||
}
|
||||
|
||||
WritableMap event = Arguments.createMap();
|
||||
event.putString("type", "face");
|
||||
event.putArray("faces", facesList);
|
||||
event.putInt("target", getViewTag());
|
||||
return event;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,74 @@
|
||||
package org.reactnative.camera.tasks;
|
||||
|
||||
import com.google.zxing.BinaryBitmap;
|
||||
import com.google.zxing.MultiFormatReader;
|
||||
import com.google.zxing.NotFoundException;
|
||||
import com.google.zxing.PlanarYUVLuminanceSource;
|
||||
import com.google.zxing.Result;
|
||||
import com.google.zxing.common.HybridBinarizer;
|
||||
|
||||
public class BarCodeScannerAsyncTask extends android.os.AsyncTask<Void, Void, Result> {
|
||||
private byte[] mImageData;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private BarCodeScannerAsyncTaskDelegate mDelegate;
|
||||
private final MultiFormatReader mMultiFormatReader;
|
||||
|
||||
// note(sjchmiela): From my short research it's ok to ignore rotation of the image.
|
||||
public BarCodeScannerAsyncTask(
|
||||
BarCodeScannerAsyncTaskDelegate delegate,
|
||||
MultiFormatReader multiFormatReader,
|
||||
byte[] imageData,
|
||||
int width,
|
||||
int height
|
||||
) {
|
||||
mImageData = imageData;
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mDelegate = delegate;
|
||||
mMultiFormatReader = multiFormatReader;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Result doInBackground(Void... ignored) {
|
||||
if (isCancelled() || mDelegate == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
Result result = null;
|
||||
|
||||
try {
|
||||
BinaryBitmap bitmap = generateBitmapFromImageData(mImageData, mWidth, mHeight);
|
||||
result = mMultiFormatReader.decodeWithState(bitmap);
|
||||
} catch (NotFoundException e) {
|
||||
// No barcode found, result is already null.
|
||||
} catch (Throwable t) {
|
||||
t.printStackTrace();
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Result result) {
|
||||
super.onPostExecute(result);
|
||||
if (result != null) {
|
||||
mDelegate.onBarCodeRead(result);
|
||||
}
|
||||
mDelegate.onBarCodeScanningTaskCompleted();
|
||||
}
|
||||
|
||||
private BinaryBitmap generateBitmapFromImageData(byte[] imageData, int width, int height) {
|
||||
PlanarYUVLuminanceSource source = new PlanarYUVLuminanceSource(
|
||||
imageData, // byte[] yuvData
|
||||
width, // int dataWidth
|
||||
height, // int dataHeight
|
||||
0, // int left
|
||||
0, // int top
|
||||
width, // int width
|
||||
height, // int height
|
||||
false // boolean reverseHorizontal
|
||||
);
|
||||
return new BinaryBitmap(new HybridBinarizer(source));
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,8 @@
|
||||
package org.reactnative.camera.tasks;
|
||||
|
||||
import com.google.zxing.Result;
|
||||
|
||||
public interface BarCodeScannerAsyncTaskDelegate {
|
||||
void onBarCodeRead(Result barCode);
|
||||
void onBarCodeScanningTaskCompleted();
|
||||
}
|
||||
@ -0,0 +1,55 @@
|
||||
package org.reactnative.camera.tasks;
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.reactnative.facedetector.RNFaceDetector;
|
||||
import org.reactnative.facedetector.RNFrame;
|
||||
import org.reactnative.facedetector.RNFrameFactory;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
|
||||
public class FaceDetectorAsyncTask extends android.os.AsyncTask<Void, Void, SparseArray<Face>> {
|
||||
private byte[] mImageData;
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private int mRotation;
|
||||
private RNFaceDetector mFaceDetector;
|
||||
private FaceDetectorAsyncTaskDelegate mDelegate;
|
||||
|
||||
public FaceDetectorAsyncTask(
|
||||
FaceDetectorAsyncTaskDelegate delegate,
|
||||
RNFaceDetector faceDetector,
|
||||
byte[] imageData,
|
||||
int width,
|
||||
int height,
|
||||
int rotation
|
||||
) {
|
||||
mImageData = imageData;
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mRotation = rotation;
|
||||
mDelegate = delegate;
|
||||
mFaceDetector = faceDetector;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SparseArray<Face> doInBackground(Void... ignored) {
|
||||
if (isCancelled() || mDelegate == null || mFaceDetector == null || !mFaceDetector.isOperational()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
RNFrame frame = RNFrameFactory.buildFrame(mImageData, mWidth, mHeight, mRotation);
|
||||
return mFaceDetector.detect(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(SparseArray<Face> faces) {
|
||||
super.onPostExecute(faces);
|
||||
|
||||
if (faces == null) {
|
||||
mDelegate.onFaceDetectionError(mFaceDetector);
|
||||
} else {
|
||||
mDelegate.onFacesDetected(faces, mWidth, mHeight, mRotation);
|
||||
mDelegate.onFaceDetectingTaskCompleted();
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,12 @@
|
||||
package org.reactnative.camera.tasks;
|
||||
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.reactnative.facedetector.RNFaceDetector;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
|
||||
public interface FaceDetectorAsyncTaskDelegate {
|
||||
void onFacesDetected(SparseArray<Face> face, int sourceWidth, int sourceHeight, int sourceRotation);
|
||||
void onFaceDetectionError(RNFaceDetector faceDetector);
|
||||
void onFaceDetectingTaskCompleted();
|
||||
}
|
||||
@ -0,0 +1,75 @@
|
||||
package org.reactnative.camera.tasks;
|
||||
|
||||
import android.content.res.Resources;
|
||||
import android.graphics.Matrix;
|
||||
import android.os.AsyncTask;
|
||||
|
||||
import org.reactnative.MutableImage;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
|
||||
public class ResolveTakenPictureAsyncTask extends AsyncTask<Void, Void, WritableMap> {
|
||||
private static final String ERROR_TAG = "E_TAKING_PICTURE_FAILED";
|
||||
private Promise mPromise;
|
||||
private byte[] mImageData;
|
||||
private ReadableMap mOptions;
|
||||
|
||||
public ResolveTakenPictureAsyncTask(byte[] imageData, Promise promise, ReadableMap options) {
|
||||
mPromise = promise;
|
||||
mOptions = options;
|
||||
mImageData = imageData;
|
||||
}
|
||||
|
||||
private int getQuality() {
|
||||
return (int) (mOptions.getDouble("quality") * 100);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected WritableMap doInBackground(Void... voids) {
|
||||
WritableMap response = Arguments.createMap();
|
||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(mImageData);
|
||||
|
||||
try {
|
||||
MutableImage mutableImage = new MutableImage(mImageData);
|
||||
mutableImage.mirrorImage();
|
||||
mutableImage.fixOrientation();
|
||||
String encoded = mutableImage.toBase64(getQuality());
|
||||
|
||||
response.putString("base64", encoded);
|
||||
|
||||
return response;
|
||||
} catch (Resources.NotFoundException e) {
|
||||
mPromise.reject(ERROR_TAG, "Documents directory of the app could not be found.", e);
|
||||
e.printStackTrace();
|
||||
} catch (MutableImage.ImageMutationFailedException e) {
|
||||
e.printStackTrace();
|
||||
} finally {
|
||||
try {
|
||||
if (inputStream != null) {
|
||||
inputStream.close();
|
||||
}
|
||||
} catch (IOException e) {
|
||||
e.printStackTrace();
|
||||
}
|
||||
}
|
||||
|
||||
// An exception had to occur, promise has already been rejected. Do not try to resolve it again.
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(WritableMap response) {
|
||||
super.onPostExecute(response);
|
||||
|
||||
// If the response is not null everything went well and we can resolve the promise.
|
||||
if (response != null) {
|
||||
mPromise.resolve(response);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
@ -0,0 +1,64 @@
|
||||
package org.reactnative.camera.utils;
|
||||
|
||||
public class ImageDimensions {
|
||||
private int mWidth;
|
||||
private int mHeight;
|
||||
private int mFacing;
|
||||
private int mRotation;
|
||||
|
||||
public ImageDimensions(int width, int height) {
|
||||
this(width, height, 0);
|
||||
}
|
||||
|
||||
public ImageDimensions(int width, int height, int rotation) {
|
||||
this(width, height, rotation, -1);
|
||||
}
|
||||
|
||||
public ImageDimensions(int width, int height, int rotation, int facing) {
|
||||
mWidth = width;
|
||||
mHeight = height;
|
||||
mFacing = facing;
|
||||
mRotation = rotation;
|
||||
}
|
||||
|
||||
public boolean isLandscape() {
|
||||
return mRotation % 180 == 90;
|
||||
}
|
||||
|
||||
public int getWidth() {
|
||||
if (isLandscape()) {
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
public int getHeight() {
|
||||
if (isLandscape()) {
|
||||
return mWidth;
|
||||
}
|
||||
|
||||
return mHeight;
|
||||
}
|
||||
|
||||
public int getRotation() {
|
||||
return mRotation;
|
||||
}
|
||||
|
||||
public int getFacing() {
|
||||
return mFacing;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object obj) {
|
||||
if (obj instanceof ImageDimensions) {
|
||||
ImageDimensions otherDimensions = (ImageDimensions) obj;
|
||||
return (otherDimensions.getWidth() == getWidth() &&
|
||||
otherDimensions.getHeight() == getHeight() &&
|
||||
otherDimensions.getFacing() == getFacing() &&
|
||||
otherDimensions.getRotation() == getRotation());
|
||||
} else {
|
||||
return super.equals(obj);
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,76 @@
|
||||
package org.reactnative.facedetector;
|
||||
|
||||
import android.content.Context;
|
||||
|
||||
import org.reactnative.facedetector.tasks.FileFaceDetectionAsyncTask;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReactApplicationContext;
|
||||
import com.facebook.react.bridge.ReactContextBaseJavaModule;
|
||||
import com.facebook.react.bridge.ReactMethod;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.Map;
|
||||
|
||||
import javax.annotation.Nullable;
|
||||
|
||||
public class FaceDetectorModule extends ReactContextBaseJavaModule {
|
||||
private static final String TAG = "RNFaceDetector";
|
||||
// private ScopedContext mScopedContext;
|
||||
private static ReactApplicationContext mScopedContext;
|
||||
|
||||
public FaceDetectorModule(ReactApplicationContext reactContext) {
|
||||
super(reactContext);
|
||||
mScopedContext = reactContext;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getName() {
|
||||
return TAG;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public Map<String, Object> getConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("Mode", getFaceDetectionModeConstants());
|
||||
put("Landmarks", getFaceDetectionLandmarksConstants());
|
||||
put("Classifications", getFaceDetectionClassificationsConstants());
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionModeConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("fast", RNFaceDetector.FAST_MODE);
|
||||
put("accurate", RNFaceDetector.ACCURATE_MODE);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionClassificationsConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("all", RNFaceDetector.ALL_CLASSIFICATIONS);
|
||||
put("none", RNFaceDetector.NO_CLASSIFICATIONS);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private Map<String, Object> getFaceDetectionLandmarksConstants() {
|
||||
return Collections.unmodifiableMap(new HashMap<String, Object>() {
|
||||
{
|
||||
put("all", RNFaceDetector.ALL_LANDMARKS);
|
||||
put("none", RNFaceDetector.NO_LANDMARKS);
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@ReactMethod
|
||||
public void detectFaces(ReadableMap options, final Promise promise) {
|
||||
new FileFaceDetectionAsyncTask(mScopedContext, options, promise).execute();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,120 @@
|
||||
package org.reactnative.facedetector;
|
||||
|
||||
import android.graphics.PointF;
|
||||
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
import com.google.android.gms.vision.face.Landmark;
|
||||
|
||||
public class FaceDetectorUtils {
|
||||
// All the landmarks reported by Google Mobile Vision in constants' order.
|
||||
// https://developers.google.com/android/reference/com/google/android/gms/vision/face/Landmark
|
||||
private static final String[] landmarkNames = {
|
||||
"bottomMouthPosition", "leftCheekPosition", "leftEarPosition", "leftEarTipPosition",
|
||||
"leftEyePosition", "leftMouthPosition", "noseBasePosition", "rightCheekPosition",
|
||||
"rightEarPosition", "rightEarTipPosition", "rightEyePosition", "rightMouthPosition"
|
||||
};
|
||||
|
||||
public static WritableMap serializeFace(Face face) {
|
||||
return serializeFace(face, 1, 1);
|
||||
}
|
||||
|
||||
public static WritableMap serializeFace(Face face, double scaleX, double scaleY) {
|
||||
WritableMap encodedFace = Arguments.createMap();
|
||||
|
||||
encodedFace.putInt("faceID", face.getId());
|
||||
encodedFace.putDouble("rollAngle", face.getEulerZ());
|
||||
encodedFace.putDouble("yawAngle", face.getEulerY());
|
||||
|
||||
if (face.getIsSmilingProbability() >= 0) {
|
||||
encodedFace.putDouble("smilingProbability", face.getIsSmilingProbability());
|
||||
}
|
||||
if (face.getIsLeftEyeOpenProbability() >= 0) {
|
||||
encodedFace.putDouble("leftEyeOpenProbability", face.getIsLeftEyeOpenProbability());
|
||||
}
|
||||
if (face.getIsRightEyeOpenProbability() >= 0) {
|
||||
encodedFace.putDouble("rightEyeOpenProbability", face.getIsRightEyeOpenProbability());
|
||||
}
|
||||
|
||||
for(Landmark landmark : face.getLandmarks()) {
|
||||
encodedFace.putMap(landmarkNames[landmark.getType()], mapFromPoint(landmark.getPosition(), scaleX, scaleY));
|
||||
}
|
||||
|
||||
WritableMap origin = Arguments.createMap();
|
||||
origin.putDouble("x", face.getPosition().x * scaleX);
|
||||
origin.putDouble("y", face.getPosition().y * scaleY);
|
||||
|
||||
WritableMap size = Arguments.createMap();
|
||||
size.putDouble("width", face.getWidth() * scaleX);
|
||||
size.putDouble("height", face.getHeight() * scaleY);
|
||||
|
||||
WritableMap bounds = Arguments.createMap();
|
||||
bounds.putMap("origin", origin);
|
||||
bounds.putMap("size", size);
|
||||
|
||||
encodedFace.putMap("bounds", bounds);
|
||||
|
||||
return encodedFace;
|
||||
}
|
||||
|
||||
public static WritableMap rotateFaceX(WritableMap face, int sourceWidth, double scaleX) {
|
||||
ReadableMap faceBounds = face.getMap("bounds");
|
||||
|
||||
ReadableMap oldOrigin = faceBounds.getMap("origin");
|
||||
WritableMap mirroredOrigin = positionMirroredHorizontally(oldOrigin, sourceWidth, scaleX);
|
||||
|
||||
double translateX = -faceBounds.getMap("size").getDouble("width");
|
||||
WritableMap translatedMirroredOrigin = positionTranslatedHorizontally(mirroredOrigin, translateX);
|
||||
|
||||
WritableMap newBounds = Arguments.createMap();
|
||||
newBounds.merge(faceBounds);
|
||||
newBounds.putMap("origin", translatedMirroredOrigin);
|
||||
|
||||
for (String landmarkName : landmarkNames) {
|
||||
ReadableMap landmark = face.hasKey(landmarkName) ? face.getMap(landmarkName) : null;
|
||||
if (landmark != null) {
|
||||
WritableMap mirroredPosition = positionMirroredHorizontally(landmark, sourceWidth, scaleX);
|
||||
face.putMap(landmarkName, mirroredPosition);
|
||||
}
|
||||
}
|
||||
|
||||
face.putMap("bounds", newBounds);
|
||||
|
||||
return face;
|
||||
}
|
||||
|
||||
public static WritableMap changeAnglesDirection(WritableMap face) {
|
||||
face.putDouble("rollAngle", (-face.getDouble("rollAngle") + 360) % 360);
|
||||
face.putDouble("yawAngle", (-face.getDouble("yawAngle") + 360) % 360);
|
||||
return face;
|
||||
}
|
||||
|
||||
public static WritableMap mapFromPoint(PointF point, double scaleX, double scaleY) {
|
||||
WritableMap map = Arguments.createMap();
|
||||
map.putDouble("x", point.x * scaleX);
|
||||
map.putDouble("y", point.y * scaleY);
|
||||
return map;
|
||||
}
|
||||
|
||||
public static WritableMap positionTranslatedHorizontally(ReadableMap position, double translateX) {
|
||||
WritableMap newPosition = Arguments.createMap();
|
||||
newPosition.merge(position);
|
||||
newPosition.putDouble("x", position.getDouble("x") + translateX);
|
||||
return newPosition;
|
||||
}
|
||||
|
||||
public static WritableMap positionMirroredHorizontally(ReadableMap position, int containerWidth, double scaleX) {
|
||||
WritableMap newPosition = Arguments.createMap();
|
||||
newPosition.merge(position);
|
||||
newPosition.putDouble("x", valueMirroredHorizontally(position.getDouble("x"), containerWidth, scaleX));
|
||||
return newPosition;
|
||||
}
|
||||
|
||||
public static double valueMirroredHorizontally(double elementX, int containerWidth, double scaleX) {
|
||||
double originalX = elementX / scaleX;
|
||||
double mirroredX = containerWidth - originalX;
|
||||
return mirroredX * scaleX;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,113 @@
|
||||
package org.reactnative.facedetector;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
import com.google.android.gms.vision.face.FaceDetector;
|
||||
|
||||
public class RNFaceDetector {
|
||||
public static int ALL_CLASSIFICATIONS = FaceDetector.ALL_CLASSIFICATIONS;
|
||||
public static int NO_CLASSIFICATIONS = FaceDetector.NO_CLASSIFICATIONS;
|
||||
public static int ALL_LANDMARKS = FaceDetector.ALL_LANDMARKS;
|
||||
public static int NO_LANDMARKS = FaceDetector.NO_LANDMARKS;
|
||||
public static int ACCURATE_MODE = FaceDetector.ACCURATE_MODE;
|
||||
public static int FAST_MODE = FaceDetector.FAST_MODE;
|
||||
|
||||
private FaceDetector mFaceDetector = null;
|
||||
private ImageDimensions mPreviousDimensions;
|
||||
private FaceDetector.Builder mBuilder = null;
|
||||
|
||||
private int mClassificationType = NO_CLASSIFICATIONS;
|
||||
private int mLandmarkType = NO_LANDMARKS;
|
||||
private float mMinFaceSize = 0.15f;
|
||||
private int mMode = FAST_MODE;
|
||||
|
||||
public RNFaceDetector(Context context) {
|
||||
mBuilder = new FaceDetector.Builder(context);
|
||||
mBuilder.setMinFaceSize(mMinFaceSize);
|
||||
mBuilder.setMode(mMode);
|
||||
mBuilder.setLandmarkType(mLandmarkType);
|
||||
mBuilder.setClassificationType(mClassificationType);
|
||||
}
|
||||
|
||||
// Public API
|
||||
|
||||
public boolean isOperational() {
|
||||
if (mFaceDetector == null) {
|
||||
createFaceDetector();
|
||||
}
|
||||
|
||||
return mFaceDetector.isOperational();
|
||||
}
|
||||
|
||||
public SparseArray<Face> detect(RNFrame frame) {
|
||||
// If the frame has different dimensions, create another face detector.
|
||||
// Otherwise we will get nasty "inconsistent image dimensions" error from detector
|
||||
// and no face will be detected.
|
||||
if (!frame.getDimensions().equals(mPreviousDimensions)) {
|
||||
releaseFaceDetector();
|
||||
}
|
||||
|
||||
if (mFaceDetector == null) {
|
||||
createFaceDetector();
|
||||
mPreviousDimensions = frame.getDimensions();
|
||||
}
|
||||
|
||||
return mFaceDetector.detect(frame.getFrame());
|
||||
}
|
||||
|
||||
public void setTracking(boolean trackingEnabled) {
|
||||
release();
|
||||
mBuilder.setTrackingEnabled(trackingEnabled);
|
||||
}
|
||||
|
||||
public void setClassificationType(int classificationType) {
|
||||
if (classificationType != mClassificationType) {
|
||||
release();
|
||||
mBuilder.setClassificationType(classificationType);
|
||||
mClassificationType = classificationType;
|
||||
}
|
||||
}
|
||||
|
||||
public void setLandmarkType(int landmarkType) {
|
||||
if (landmarkType != mLandmarkType) {
|
||||
release();
|
||||
mBuilder.setLandmarkType(landmarkType);
|
||||
mLandmarkType = landmarkType;
|
||||
}
|
||||
}
|
||||
|
||||
public void setMode(int mode) {
|
||||
if (mode != mMode) {
|
||||
release();
|
||||
mBuilder.setMode(mode);
|
||||
mMode = mode;
|
||||
}
|
||||
}
|
||||
|
||||
public void setTrackingEnabled(boolean tracking) {
|
||||
release();
|
||||
mBuilder.setTrackingEnabled(tracking);
|
||||
}
|
||||
|
||||
public void release() {
|
||||
releaseFaceDetector();
|
||||
mPreviousDimensions = null;
|
||||
}
|
||||
|
||||
// Lifecycle methods
|
||||
|
||||
private void releaseFaceDetector() {
|
||||
if (mFaceDetector != null) {
|
||||
mFaceDetector.release();
|
||||
mFaceDetector = null;
|
||||
}
|
||||
}
|
||||
|
||||
private void createFaceDetector() {
|
||||
mFaceDetector = mBuilder.build();
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,28 @@
|
||||
package org.reactnative.facedetector;
|
||||
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import com.google.android.gms.vision.Frame;
|
||||
|
||||
/**
|
||||
* Wrapper around Frame allowing us to track Frame dimensions.
|
||||
* Tracking dimensions is used in RNFaceDetector to provide painless FaceDetector recreation
|
||||
* when image dimensions change.
|
||||
*/
|
||||
|
||||
public class RNFrame {
|
||||
private Frame mFrame;
|
||||
private ImageDimensions mDimensions;
|
||||
|
||||
public RNFrame(Frame frame, ImageDimensions dimensions) {
|
||||
mFrame = frame;
|
||||
mDimensions = dimensions;
|
||||
}
|
||||
|
||||
public Frame getFrame() {
|
||||
return mFrame;
|
||||
}
|
||||
|
||||
public ImageDimensions getDimensions() {
|
||||
return mDimensions;
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,43 @@
|
||||
package org.reactnative.facedetector;
|
||||
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.ImageFormat;
|
||||
|
||||
import org.reactnative.camera.utils.ImageDimensions;
|
||||
import com.google.android.gms.vision.Frame;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
|
||||
public class RNFrameFactory {
|
||||
public static RNFrame buildFrame(byte[] bitmapData, int width, int height, int rotation) {
|
||||
Frame.Builder builder = new Frame.Builder();
|
||||
|
||||
ByteBuffer byteBuffer = ByteBuffer.wrap(bitmapData);
|
||||
builder.setImageData(byteBuffer, width, height, ImageFormat.NV21);
|
||||
|
||||
switch (rotation) {
|
||||
case 90:
|
||||
builder.setRotation(Frame.ROTATION_90);
|
||||
break;
|
||||
case 180:
|
||||
builder.setRotation(Frame.ROTATION_180);
|
||||
break;
|
||||
case 270:
|
||||
builder.setRotation(Frame.ROTATION_270);
|
||||
break;
|
||||
default:
|
||||
builder.setRotation(Frame.ROTATION_0);
|
||||
}
|
||||
|
||||
ImageDimensions dimensions = new ImageDimensions(width, height, rotation);
|
||||
|
||||
return new RNFrame(builder.build(), dimensions);
|
||||
}
|
||||
|
||||
public static RNFrame buildFrame(Bitmap bitmap) {
|
||||
Frame.Builder builder = new Frame.Builder();
|
||||
builder.setBitmap(bitmap);
|
||||
ImageDimensions dimensions = new ImageDimensions(bitmap.getWidth(), bitmap.getHeight());
|
||||
return new RNFrame(builder.build(), dimensions);
|
||||
}
|
||||
}
|
||||
@ -0,0 +1,153 @@
|
||||
package org.reactnative.facedetector.tasks;
|
||||
|
||||
import android.content.Context;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.media.ExifInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.util.Log;
|
||||
import android.util.SparseArray;
|
||||
|
||||
import org.reactnative.facedetector.RNFaceDetector;
|
||||
import org.reactnative.facedetector.RNFrame;
|
||||
import org.reactnative.facedetector.RNFrameFactory;
|
||||
import org.reactnative.facedetector.FaceDetectorUtils;
|
||||
import com.facebook.react.bridge.Arguments;
|
||||
import com.facebook.react.bridge.Promise;
|
||||
import com.facebook.react.bridge.ReadableMap;
|
||||
import com.facebook.react.bridge.WritableArray;
|
||||
import com.facebook.react.bridge.WritableMap;
|
||||
import com.google.android.gms.vision.Frame;
|
||||
import com.google.android.gms.vision.face.Face;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.UnsupportedEncodingException;
|
||||
import java.net.URLDecoder;
|
||||
|
||||
public class FileFaceDetectionAsyncTask extends AsyncTask<Void, Void, SparseArray<Face>> {
|
||||
private static final String ERROR_TAG = "E_FACE_DETECTION_FAILED";
|
||||
|
||||
private static final String MODE_OPTION_KEY = "mode";
|
||||
private static final String DETECT_LANDMARKS_OPTION_KEY = "detectLandmarks";
|
||||
private static final String RUN_CLASSIFICATIONS_OPTION_KEY = "runClassifications";
|
||||
|
||||
private String mUri;
|
||||
private String mPath;
|
||||
private Promise mPromise;
|
||||
private int mWidth = 0;
|
||||
private int mHeight = 0;
|
||||
private Context mContext;
|
||||
private ReadableMap mOptions;
|
||||
private int mOrientation = ExifInterface.ORIENTATION_UNDEFINED;
|
||||
private RNFaceDetector mRNFaceDetector;
|
||||
|
||||
public FileFaceDetectionAsyncTask(Context context, ReadableMap options, Promise promise) {
|
||||
mUri = options.getString("uri");
|
||||
mPromise = promise;
|
||||
mOptions = options;
|
||||
mContext = context;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPreExecute() {
|
||||
if (mUri == null) {
|
||||
mPromise.reject(ERROR_TAG, "You have to provide an URI of an image.");
|
||||
cancel(true);
|
||||
return;
|
||||
}
|
||||
|
||||
Uri uri = Uri.parse(mUri);
|
||||
mPath = uri.getPath();
|
||||
|
||||
if (mPath == null) {
|
||||
mPromise.reject(ERROR_TAG, "Invalid URI provided: `" + mUri + "`.");
|
||||
cancel(true);
|
||||
return;
|
||||
}
|
||||
|
||||
// We have to check if the requested image is in a directory safely accessible by our app.
|
||||
boolean fileIsInSafeDirectories =
|
||||
mPath.startsWith(mContext.getCacheDir().getPath()) || mPath.startsWith(mContext.getFilesDir().getPath());
|
||||
|
||||
if (!fileIsInSafeDirectories) {
|
||||
mPromise.reject(ERROR_TAG, "The image has to be in the local app's directories.");
|
||||
cancel(true);
|
||||
return;
|
||||
}
|
||||
|
||||
if(!new File(mPath).exists()) {
|
||||
mPromise.reject(ERROR_TAG, "The file does not exist. Given path: `" + mPath + "`.");
|
||||
cancel(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected SparseArray<Face> doInBackground(Void... voids) {
|
||||
if (isCancelled()) {
|
||||
return null;
|
||||
}
|
||||
|
||||
mRNFaceDetector = detectorForOptions(mOptions, mContext);
|
||||
Bitmap bitmap = BitmapFactory.decodeFile(mPath);
|
||||
mWidth = bitmap.getWidth();
|
||||
mHeight = bitmap.getHeight();
|
||||
|
||||
try {
|
||||
ExifInterface exif = new ExifInterface(mPath);
|
||||
mOrientation = exif.getAttributeInt(ExifInterface.TAG_ORIENTATION, ExifInterface.ORIENTATION_UNDEFINED);
|
||||
} catch (IOException e) {
|
||||
Log.e(ERROR_TAG, "Reading orientation from file `" + mPath + "` failed.", e);
|
||||
}
|
||||
|
||||
RNFrame frame = RNFrameFactory.buildFrame(bitmap);
|
||||
return mRNFaceDetector.detect(frame);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(SparseArray<Face> faces) {
|
||||
super.onPostExecute(faces);
|
||||
WritableMap result = Arguments.createMap();
|
||||
WritableArray facesArray = Arguments.createArray();
|
||||
|
||||
for(int i = 0; i < faces.size(); i++) {
|
||||
Face face = faces.valueAt(i);
|
||||
WritableMap encodedFace = FaceDetectorUtils.serializeFace(face);
|
||||
encodedFace.putDouble("yawAngle", (-encodedFace.getDouble("yawAngle") + 360) % 360);
|
||||
encodedFace.putDouble("rollAngle", (-encodedFace.getDouble("rollAngle") + 360) % 360);
|
||||
facesArray.pushMap(encodedFace);
|
||||
}
|
||||
|
||||
result.putArray("faces", facesArray);
|
||||
|
||||
WritableMap image = Arguments.createMap();
|
||||
image.putInt("width", mWidth);
|
||||
image.putInt("height", mHeight);
|
||||
image.putInt("orientation", mOrientation);
|
||||
image.putString("uri", mUri);
|
||||
result.putMap("image", image);
|
||||
|
||||
mRNFaceDetector.release();
|
||||
mPromise.resolve(result);
|
||||
}
|
||||
|
||||
private static RNFaceDetector detectorForOptions(ReadableMap options, Context context) {
|
||||
RNFaceDetector detector = new RNFaceDetector(context);
|
||||
detector.setTrackingEnabled(false);
|
||||
|
||||
if(options.hasKey(MODE_OPTION_KEY)) {
|
||||
detector.setMode(options.getInt(MODE_OPTION_KEY));
|
||||
}
|
||||
|
||||
if(options.hasKey(RUN_CLASSIFICATIONS_OPTION_KEY)) {
|
||||
detector.setClassificationType(options.getInt(RUN_CLASSIFICATIONS_OPTION_KEY));
|
||||
}
|
||||
|
||||
if(options.hasKey(DETECT_LANDMARKS_OPTION_KEY)) {
|
||||
detector.setLandmarkType(options.getInt(DETECT_LANDMARKS_OPTION_KEY));
|
||||
}
|
||||
|
||||
return detector;
|
||||
}
|
||||
}
|
||||
@ -9,6 +9,7 @@
|
||||
"logo": "https://opencollective.com/opencollective/logo.txt"
|
||||
},
|
||||
"dependencies": {
|
||||
"lodash": "^4.17.4",
|
||||
"prop-types": "^15.5.10"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
||||
341
src/Camera.js
Normal file
341
src/Camera.js
Normal file
@ -0,0 +1,341 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
DeviceEventEmitter, // android
|
||||
NativeAppEventEmitter, // ios
|
||||
NativeModules,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
requireNativeComponent,
|
||||
ViewPropTypes,
|
||||
PermissionsAndroid,
|
||||
ActivityIndicator,
|
||||
View,
|
||||
Text,
|
||||
} from 'react-native';
|
||||
|
||||
const CameraManager = NativeModules.CameraManager || NativeModules.CameraModule;
|
||||
const CAMERA_REF = 'camera';
|
||||
|
||||
function convertNativeProps(props) {
|
||||
const newProps = { ...props };
|
||||
if (typeof props.aspect === 'string') {
|
||||
newProps.aspect = Camera.constants.Aspect[props.aspect];
|
||||
}
|
||||
|
||||
if (typeof props.flashMode === 'string') {
|
||||
newProps.flashMode = Camera.constants.FlashMode[props.flashMode];
|
||||
}
|
||||
|
||||
if (typeof props.orientation === 'string') {
|
||||
newProps.orientation = Camera.constants.Orientation[props.orientation];
|
||||
}
|
||||
|
||||
if (typeof props.torchMode === 'string') {
|
||||
newProps.torchMode = Camera.constants.TorchMode[props.torchMode];
|
||||
}
|
||||
|
||||
if (typeof props.type === 'string') {
|
||||
newProps.type = Camera.constants.Type[props.type];
|
||||
}
|
||||
|
||||
if (typeof props.captureQuality === 'string') {
|
||||
newProps.captureQuality = Camera.constants.CaptureQuality[props.captureQuality];
|
||||
}
|
||||
|
||||
if (typeof props.captureMode === 'string') {
|
||||
newProps.captureMode = Camera.constants.CaptureMode[props.captureMode];
|
||||
}
|
||||
|
||||
if (typeof props.captureTarget === 'string') {
|
||||
newProps.captureTarget = Camera.constants.CaptureTarget[props.captureTarget];
|
||||
}
|
||||
|
||||
// do not register barCodeTypes if no barcode listener
|
||||
if (typeof props.onBarCodeRead !== 'function') {
|
||||
newProps.barCodeTypes = [];
|
||||
}
|
||||
|
||||
newProps.barcodeScannerEnabled = typeof props.onBarCodeRead === 'function';
|
||||
|
||||
return newProps;
|
||||
}
|
||||
|
||||
export default class Camera extends Component {
|
||||
static constants = {
|
||||
Aspect: CameraManager.Aspect,
|
||||
BarCodeType: CameraManager.BarCodeType,
|
||||
Type: CameraManager.Type,
|
||||
CaptureMode: CameraManager.CaptureMode,
|
||||
CaptureTarget: CameraManager.CaptureTarget,
|
||||
CaptureQuality: CameraManager.CaptureQuality,
|
||||
Orientation: CameraManager.Orientation,
|
||||
FlashMode: CameraManager.FlashMode,
|
||||
TorchMode: CameraManager.TorchMode,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
...ViewPropTypes,
|
||||
aspect: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureAudio: PropTypes.bool,
|
||||
captureMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureQuality: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureTarget: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
defaultOnFocusComponent: PropTypes.bool,
|
||||
flashMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
keepAwake: PropTypes.bool,
|
||||
onBarCodeRead: PropTypes.func,
|
||||
barcodeScannerEnabled: PropTypes.bool,
|
||||
onFocusChanged: PropTypes.func,
|
||||
onZoomChanged: PropTypes.func,
|
||||
mirrorImage: PropTypes.bool,
|
||||
fixOrientation: PropTypes.bool,
|
||||
barCodeTypes: PropTypes.array,
|
||||
orientation: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
playSoundOnCapture: PropTypes.bool,
|
||||
torchMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
permissionDialogTitle: PropTypes.string,
|
||||
permissionDialogMessage: PropTypes.string,
|
||||
notAuthorizedView: PropTypes.element,
|
||||
pendingAuthorizationView: PropTypes.element,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
aspect: CameraManager.Aspect.fill,
|
||||
type: CameraManager.Type.back,
|
||||
orientation: CameraManager.Orientation.auto,
|
||||
fixOrientation: false,
|
||||
captureAudio: false,
|
||||
captureMode: CameraManager.CaptureMode.still,
|
||||
captureTarget: CameraManager.CaptureTarget.cameraRoll,
|
||||
captureQuality: CameraManager.CaptureQuality.high,
|
||||
defaultOnFocusComponent: true,
|
||||
flashMode: CameraManager.FlashMode.off,
|
||||
playSoundOnCapture: true,
|
||||
torchMode: CameraManager.TorchMode.off,
|
||||
mirrorImage: false,
|
||||
barCodeTypes: Object.values(CameraManager.BarCodeType),
|
||||
permissionDialogTitle: '',
|
||||
permissionDialogMessage: '',
|
||||
notAuthorizedView: (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<Text
|
||||
style={{
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
}}
|
||||
>
|
||||
Camera not authorized
|
||||
</Text>
|
||||
</View>
|
||||
),
|
||||
pendingAuthorizationView: (
|
||||
<View
|
||||
style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}
|
||||
>
|
||||
<ActivityIndicator size="small" />
|
||||
</View>
|
||||
),
|
||||
};
|
||||
|
||||
static checkDeviceAuthorizationStatus = CameraManager.checkDeviceAuthorizationStatus;
|
||||
static checkVideoAuthorizationStatus = CameraManager.checkVideoAuthorizationStatus;
|
||||
static checkAudioAuthorizationStatus = CameraManager.checkAudioAuthorizationStatus;
|
||||
|
||||
setNativeProps(props) {
|
||||
// eslint-disable-next-line
|
||||
this.refs[CAMERA_REF].setNativeProps(props);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
isAuthorized: false,
|
||||
isAuthorizationChecked: false,
|
||||
isRecording: false,
|
||||
};
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
this._addOnBarCodeReadListener();
|
||||
|
||||
let { captureMode } = convertNativeProps({ captureMode: this.props.captureMode });
|
||||
let hasVideoAndAudio =
|
||||
this.props.captureAudio && captureMode === Camera.constants.CaptureMode.video;
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
let check = hasVideoAndAudio
|
||||
? Camera.checkDeviceAuthorizationStatus
|
||||
: Camera.checkVideoAuthorizationStatus;
|
||||
|
||||
if (check) {
|
||||
const isAuthorized = await check();
|
||||
this.setState({ isAuthorized, isAuthorizationChecked: true });
|
||||
}
|
||||
} else if (Platform.OS === 'android') {
|
||||
const granted = await PermissionsAndroid.request(PermissionsAndroid.PERMISSIONS.CAMERA, {
|
||||
title: this.props.permissionDialogTitle,
|
||||
message: this.props.permissionDialogMessage,
|
||||
});
|
||||
|
||||
this.setState({
|
||||
isAuthorized: granted === PermissionsAndroid.RESULTS.GRANTED,
|
||||
isAuthorizationChecked: true,
|
||||
});
|
||||
} else {
|
||||
this.setState({ isAuthorized: true, isAuthorizationChecked: true });
|
||||
}
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._removeOnBarCodeReadListener();
|
||||
|
||||
if (this.state.isRecording) {
|
||||
this.stopCapture();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
const { onBarCodeRead } = this.props;
|
||||
if (onBarCodeRead !== newProps.onBarCodeRead) {
|
||||
this._addOnBarCodeReadListener(newProps);
|
||||
}
|
||||
}
|
||||
|
||||
_addOnBarCodeReadListener(props) {
|
||||
const { onBarCodeRead } = props || this.props;
|
||||
this._removeOnBarCodeReadListener();
|
||||
if (onBarCodeRead) {
|
||||
this.cameraBarCodeReadListener = Platform.select({
|
||||
ios: NativeAppEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead),
|
||||
android: DeviceEventEmitter.addListener('CameraBarCodeReadAndroid', this._onBarCodeRead),
|
||||
});
|
||||
}
|
||||
}
|
||||
_removeOnBarCodeReadListener() {
|
||||
const listener = this.cameraBarCodeReadListener;
|
||||
if (listener) {
|
||||
listener.remove();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// TODO - style is not used, figure it out why
|
||||
// eslint-disable-next-line
|
||||
const style = [styles.base, this.props.style];
|
||||
const nativeProps = convertNativeProps(this.props);
|
||||
|
||||
if (this.state.isAuthorized) {
|
||||
return <RCTCamera ref={CAMERA_REF} {...nativeProps} />;
|
||||
} else if (!this.state.isAuthorizationChecked) {
|
||||
return this.props.pendingAuthorizationView;
|
||||
} else {
|
||||
return this.props.notAuthorizedView;
|
||||
}
|
||||
}
|
||||
|
||||
_onBarCodeRead = data => {
|
||||
if (this.props.onBarCodeRead) {
|
||||
this.props.onBarCodeRead(data);
|
||||
}
|
||||
};
|
||||
|
||||
capture(options) {
|
||||
const props = convertNativeProps(this.props);
|
||||
options = {
|
||||
audio: props.captureAudio,
|
||||
barCodeTypes: props.barCodeTypes,
|
||||
mode: props.captureMode,
|
||||
playSoundOnCapture: props.playSoundOnCapture,
|
||||
target: props.captureTarget,
|
||||
quality: props.captureQuality,
|
||||
type: props.type,
|
||||
title: '',
|
||||
description: '',
|
||||
mirrorImage: props.mirrorImage,
|
||||
fixOrientation: props.fixOrientation,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (options.mode === Camera.constants.CaptureMode.video) {
|
||||
options.totalSeconds = options.totalSeconds > -1 ? options.totalSeconds : -1;
|
||||
options.preferredTimeScale = options.preferredTimeScale || 30;
|
||||
this.setState({ isRecording: true });
|
||||
}
|
||||
|
||||
return CameraManager.capture(options);
|
||||
}
|
||||
|
||||
startPreview() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
CameraManager.startPreview({
|
||||
type: props.type,
|
||||
});
|
||||
} else {
|
||||
CameraManager.startPreview();
|
||||
}
|
||||
}
|
||||
|
||||
stopPreview() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
CameraManager.stopPreview({
|
||||
type: props.type,
|
||||
});
|
||||
} else {
|
||||
CameraManager.stopPreview();
|
||||
}
|
||||
}
|
||||
|
||||
stopCapture() {
|
||||
if (this.state.isRecording) {
|
||||
this.setState({ isRecording: false });
|
||||
return CameraManager.stopCapture();
|
||||
}
|
||||
return Promise.resolve('Not Recording.');
|
||||
}
|
||||
|
||||
getFOV() {
|
||||
return CameraManager.getFOV();
|
||||
}
|
||||
|
||||
hasFlash() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
return CameraManager.hasFlash({
|
||||
type: props.type,
|
||||
});
|
||||
}
|
||||
return CameraManager.hasFlash();
|
||||
}
|
||||
}
|
||||
|
||||
export const constants = Camera.constants;
|
||||
|
||||
const RCTCamera = requireNativeComponent('RCTCamera', Camera, {
|
||||
nativeOnly: {
|
||||
testID: true,
|
||||
renderToHardwareTextureAndroid: true,
|
||||
accessibilityLabel: true,
|
||||
importantForAccessibility: true,
|
||||
accessibilityLiveRegion: true,
|
||||
accessibilityComponentType: true,
|
||||
onLayout: true,
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {},
|
||||
});
|
||||
60
src/FaceDetector.js
Normal file
60
src/FaceDetector.js
Normal file
@ -0,0 +1,60 @@
|
||||
// @flow
|
||||
import { NativeModules } from 'react-native';
|
||||
|
||||
const faceDetectionDisabledMessage = 'Face detection has not been included in this build.';
|
||||
|
||||
const FaceDetectorModule: Object = NativeModules.RNFaceDetector || {
|
||||
stubbed: true,
|
||||
Mode: {},
|
||||
Landmarks: {},
|
||||
Classifications: {},
|
||||
detectFaces: () => new Promise((_, reject) => reject(faceDetectionDisabledMessage)),
|
||||
};
|
||||
|
||||
type Point = { x: number, y: number };
|
||||
|
||||
export type FaceFeature = {
|
||||
bounds: {
|
||||
size: {
|
||||
width: number,
|
||||
height: number,
|
||||
},
|
||||
origin: Point,
|
||||
},
|
||||
smilingProbability?: number,
|
||||
leftEarPosition?: Point,
|
||||
rightEarPosition?: Point,
|
||||
leftEyePosition?: Point,
|
||||
leftEyeOpenProbability?: number,
|
||||
rightEyePosition?: Point,
|
||||
rightEyeOpenProbability?: number,
|
||||
leftCheekPosition?: Point,
|
||||
rightCheekPosition?: Point,
|
||||
leftMouthPosition?: Point,
|
||||
mouthPosition?: Point,
|
||||
rightMouthPosition?: Point,
|
||||
bottomMouthPosition?: Point,
|
||||
noseBasePosition?: Point,
|
||||
yawAngle?: number,
|
||||
rollAngle?: number,
|
||||
};
|
||||
|
||||
type DetectionOptions = {
|
||||
mode?: $Keys<typeof FaceDetectorModule.Mode>,
|
||||
detectLandmarks?: $Keys<typeof FaceDetectorModule.Landmarks>,
|
||||
runClassifications?: $Keys<typeof FaceDetectorModule.Classifications>,
|
||||
};
|
||||
|
||||
export default class FaceDetector {
|
||||
static Constants = {
|
||||
Mode: FaceDetectorModule.Mode,
|
||||
Landmarks: FaceDetectorModule.Landmarks,
|
||||
Classifications: FaceDetectorModule.Classifications,
|
||||
};
|
||||
|
||||
static detectFacesAsync(uri: string, options: ?DetectionOptions): Promise<Array<FaceFeature>> {
|
||||
return FaceDetectorModule.detectFaces({ ...options, uri });
|
||||
}
|
||||
}
|
||||
|
||||
export const Constants = FaceDetector.Constants;
|
||||
250
src/RNCamera.js
Normal file
250
src/RNCamera.js
Normal file
@ -0,0 +1,250 @@
|
||||
// @flow
|
||||
import React from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { mapValues } from 'lodash';
|
||||
import { Platform, NativeModules, ViewPropTypes, requireNativeComponent } from 'react-native';
|
||||
|
||||
import type { FaceFeature } from './FaceDetector';
|
||||
|
||||
type PictureOptions = {
|
||||
quality?: number,
|
||||
};
|
||||
|
||||
type TrackedFaceFeature = FaceFeature & {
|
||||
faceID?: number,
|
||||
};
|
||||
|
||||
type RecordingOptions = {
|
||||
maxDuration?: number,
|
||||
maxFileSize?: number,
|
||||
quality?: number | string,
|
||||
};
|
||||
|
||||
type EventCallbackArgumentsType = {
|
||||
nativeEvent: Object,
|
||||
};
|
||||
|
||||
type PropsType = ViewPropTypes & {
|
||||
zoom?: number,
|
||||
ratio?: string,
|
||||
focusDepth?: number,
|
||||
type?: number | string,
|
||||
onCameraReady?: Function,
|
||||
onBarCodeRead?: Function,
|
||||
faceDetectionMode?: number,
|
||||
flashMode?: number | string,
|
||||
barCodeTypes?: Array<string>,
|
||||
whiteBalance?: number | string,
|
||||
faceDetectionLandmarks?: number,
|
||||
autoFocus?: string | boolean | number,
|
||||
faceDetectionClassifications?: number,
|
||||
onFacesDetected?: ({ faces: Array<TrackedFaceFeature> }) => void,
|
||||
};
|
||||
|
||||
const CameraManager: Object =
|
||||
NativeModules.RNCameraManager || NativeModules.RNCameraModule || {
|
||||
stubbed: true,
|
||||
Type: {
|
||||
back: 1,
|
||||
},
|
||||
AutoFocus: {
|
||||
on: 1
|
||||
},
|
||||
FlashMode: {
|
||||
off: 1,
|
||||
},
|
||||
WhiteBalance: {},
|
||||
BarCodeType: {},
|
||||
FaceDetection: {
|
||||
fast: 1,
|
||||
Mode: {},
|
||||
Landmarks: {
|
||||
none: 0,
|
||||
},
|
||||
Classifications: {
|
||||
none: 0,
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
const EventThrottleMs = 500;
|
||||
|
||||
export default class Camera extends React.Component<PropsType> {
|
||||
static Constants = {
|
||||
Type: CameraManager.Type,
|
||||
FlashMode: CameraManager.FlashMode,
|
||||
AutoFocus: CameraManager.AutoFocus,
|
||||
WhiteBalance: CameraManager.WhiteBalance,
|
||||
VideoQuality: CameraManager.VideoQuality,
|
||||
BarCodeType: CameraManager.BarCodeType,
|
||||
FaceDetection: CameraManager.FaceDetection,
|
||||
};
|
||||
|
||||
// Values under keys from this object will be transformed to native options
|
||||
static ConversionTables = {
|
||||
type: CameraManager.Type,
|
||||
flashMode: CameraManager.FlashMode,
|
||||
autoFocus: CameraManager.AutoFocus,
|
||||
whiteBalance: CameraManager.WhiteBalance,
|
||||
faceDetectionMode: CameraManager.FaceDetection.Mode,
|
||||
faceDetectionLandmarks: CameraManager.FaceDetection.Landmarks,
|
||||
faceDetectionClassifications: CameraManager.FaceDetection.Classifications,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
...ViewPropTypes,
|
||||
zoom: PropTypes.number,
|
||||
ratio: PropTypes.string,
|
||||
focusDepth: PropTypes.number,
|
||||
onMountError: PropTypes.func,
|
||||
onCameraReady: PropTypes.func,
|
||||
onBarCodeRead: PropTypes.func,
|
||||
onFacesDetected: PropTypes.func,
|
||||
faceDetectionMode: PropTypes.number,
|
||||
faceDetectionLandmarks: PropTypes.number,
|
||||
faceDetectionClassifications: PropTypes.number,
|
||||
barCodeTypes: PropTypes.arrayOf(PropTypes.string),
|
||||
type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
flashMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
whiteBalance: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
autoFocus: PropTypes.oneOfType([PropTypes.string, PropTypes.number, PropTypes.bool]),
|
||||
};
|
||||
|
||||
static defaultProps: Object = {
|
||||
zoom: 0,
|
||||
ratio: '4:3',
|
||||
focusDepth: 0,
|
||||
type: CameraManager.Type.back,
|
||||
autoFocus: CameraManager.AutoFocus.on,
|
||||
flashMode: CameraManager.FlashMode.off,
|
||||
whiteBalance: CameraManager.WhiteBalance.auto,
|
||||
faceDetectionMode: CameraManager.FaceDetection.fast,
|
||||
barCodeTypes: Object.values(CameraManager.BarCodeType),
|
||||
faceDetectionLandmarks: CameraManager.FaceDetection.Landmarks.none,
|
||||
faceDetectionClassifications: CameraManager.FaceDetection.Classifications.none,
|
||||
};
|
||||
|
||||
_lastEvents: { [string]: string };
|
||||
_lastEventsTimes: { [string]: Date };
|
||||
|
||||
constructor(props: PropsType) {
|
||||
super(props);
|
||||
this._lastEvents = {};
|
||||
this._lastEventsTimes = {};
|
||||
}
|
||||
|
||||
async takePictureAsync(options?: PictureOptions) {
|
||||
if (!options) {
|
||||
options = {};
|
||||
}
|
||||
if (!options.quality) {
|
||||
options.quality = 1;
|
||||
}
|
||||
return await CameraManager.takePicture(options);
|
||||
}
|
||||
|
||||
async getSupportedRatiosAsync() {
|
||||
if (Platform.OS === 'android') {
|
||||
return await CameraManager.getSupportedRatios();
|
||||
} else {
|
||||
throw new Error('Ratio is not supported on iOS');
|
||||
}
|
||||
}
|
||||
|
||||
async recordAsync(options?: RecordingOptions) {
|
||||
if (!options || typeof options !== 'object') {
|
||||
options = {};
|
||||
} else if (typeof options.quality === 'string') {
|
||||
options.quality = Camera.Constants.VideoQuality[options.quality];
|
||||
}
|
||||
return await CameraManager.record(options);
|
||||
}
|
||||
|
||||
stopRecording() {
|
||||
CameraManager.stopRecording();
|
||||
}
|
||||
|
||||
_onMountError = () => {
|
||||
if (this.props.onMountError) {
|
||||
this.props.onMountError();
|
||||
}
|
||||
};
|
||||
|
||||
_onCameraReady = () => {
|
||||
if (this.props.onCameraReady) {
|
||||
this.props.onCameraReady();
|
||||
}
|
||||
};
|
||||
|
||||
_onObjectDetected = (callback: ?Function) => ({ nativeEvent }: EventCallbackArgumentsType) => {
|
||||
const { type } = nativeEvent;
|
||||
|
||||
if (
|
||||
this._lastEvents[type] &&
|
||||
this._lastEventsTimes[type] &&
|
||||
JSON.stringify(nativeEvent) === this._lastEvents[type] &&
|
||||
new Date() - this._lastEventsTimes[type] < EventThrottleMs
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (callback) {
|
||||
callback(nativeEvent);
|
||||
this._lastEventsTimes[type] = new Date();
|
||||
this._lastEvents[type] = JSON.stringify(nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
render() {
|
||||
const nativeProps = this._convertNativeProps(this.props);
|
||||
|
||||
return (
|
||||
<RNCamera
|
||||
{...nativeProps}
|
||||
onMountError={this._onMountError}
|
||||
onCameraRead={this._onCameraReady}
|
||||
onBarCodeRead={this._onObjectDetected(this.props.onBarCodeRead)}
|
||||
onFacesDetected={this._onObjectDetected(this.props.onFacesDetected)}
|
||||
/>
|
||||
);
|
||||
}
|
||||
|
||||
_convertNativeProps(props: PropsType) {
|
||||
const newProps = mapValues(props, this._convertProp);
|
||||
|
||||
if (props.onBarCodeRead) {
|
||||
newProps.barCodeScannerEnabled = true;
|
||||
}
|
||||
|
||||
if (props.onFacesDetected) {
|
||||
newProps.faceDetectorEnabled = true;
|
||||
}
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
delete newProps.ratio;
|
||||
}
|
||||
|
||||
return newProps;
|
||||
}
|
||||
|
||||
_convertProp(value: *, key: string): * {
|
||||
if (typeof value === 'string' && Camera.ConversionTables[key]) {
|
||||
return Camera.ConversionTables[key][value];
|
||||
}
|
||||
|
||||
return value;
|
||||
}
|
||||
}
|
||||
|
||||
export const Constants = Camera.Constants;
|
||||
|
||||
const RNCamera = requireNativeComponent('RNCamera', Camera, {
|
||||
nativeOnly: {
|
||||
onCameraReady: true,
|
||||
onMountError: true,
|
||||
onBarCodeRead: true,
|
||||
onFaceDetected: true,
|
||||
faceDetectorEnabled: true,
|
||||
barCodeScannerEnabled: true,
|
||||
},
|
||||
});
|
||||
338
src/index.js
338
src/index.js
@ -1,335 +1,7 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import {
|
||||
DeviceEventEmitter, // android
|
||||
NativeAppEventEmitter, // ios
|
||||
NativeModules,
|
||||
Platform,
|
||||
StyleSheet,
|
||||
requireNativeComponent,
|
||||
ViewPropTypes,
|
||||
PermissionsAndroid,
|
||||
ActivityIndicator,
|
||||
View,
|
||||
Text,
|
||||
} from 'react-native';
|
||||
import Camera from './Camera';
|
||||
import RNCamera from './RNCamera';
|
||||
import FaceDetector from './FaceDetector';
|
||||
|
||||
const CameraManager = NativeModules.CameraManager || NativeModules.CameraModule;
|
||||
const CAMERA_REF = 'camera';
|
||||
export { RNCamera, FaceDetector };
|
||||
|
||||
function convertNativeProps(props) {
|
||||
const newProps = { ...props };
|
||||
if (typeof props.aspect === 'string') {
|
||||
newProps.aspect = Camera.constants.Aspect[props.aspect];
|
||||
}
|
||||
|
||||
if (typeof props.flashMode === 'string') {
|
||||
newProps.flashMode = Camera.constants.FlashMode[props.flashMode];
|
||||
}
|
||||
|
||||
if (typeof props.orientation === 'string') {
|
||||
newProps.orientation = Camera.constants.Orientation[props.orientation];
|
||||
}
|
||||
|
||||
if (typeof props.torchMode === 'string') {
|
||||
newProps.torchMode = Camera.constants.TorchMode[props.torchMode];
|
||||
}
|
||||
|
||||
if (typeof props.type === 'string') {
|
||||
newProps.type = Camera.constants.Type[props.type];
|
||||
}
|
||||
|
||||
if (typeof props.captureQuality === 'string') {
|
||||
newProps.captureQuality = Camera.constants.CaptureQuality[props.captureQuality];
|
||||
}
|
||||
|
||||
if (typeof props.captureMode === 'string') {
|
||||
newProps.captureMode = Camera.constants.CaptureMode[props.captureMode];
|
||||
}
|
||||
|
||||
if (typeof props.captureTarget === 'string') {
|
||||
newProps.captureTarget = Camera.constants.CaptureTarget[props.captureTarget];
|
||||
}
|
||||
|
||||
// do not register barCodeTypes if no barcode listener
|
||||
if (typeof props.onBarCodeRead !== 'function') {
|
||||
newProps.barCodeTypes = [];
|
||||
}
|
||||
|
||||
newProps.barcodeScannerEnabled = typeof props.onBarCodeRead === 'function';
|
||||
|
||||
return newProps;
|
||||
}
|
||||
|
||||
export default class Camera extends Component {
|
||||
static constants = {
|
||||
Aspect: CameraManager.Aspect,
|
||||
BarCodeType: CameraManager.BarCodeType,
|
||||
Type: CameraManager.Type,
|
||||
CaptureMode: CameraManager.CaptureMode,
|
||||
CaptureTarget: CameraManager.CaptureTarget,
|
||||
CaptureQuality: CameraManager.CaptureQuality,
|
||||
Orientation: CameraManager.Orientation,
|
||||
FlashMode: CameraManager.FlashMode,
|
||||
TorchMode: CameraManager.TorchMode,
|
||||
};
|
||||
|
||||
static propTypes = {
|
||||
...ViewPropTypes,
|
||||
aspect: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureAudio: PropTypes.bool,
|
||||
captureMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureQuality: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
captureTarget: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
defaultOnFocusComponent: PropTypes.bool,
|
||||
flashMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
keepAwake: PropTypes.bool,
|
||||
onBarCodeRead: PropTypes.func,
|
||||
barcodeScannerEnabled: PropTypes.bool,
|
||||
onFocusChanged: PropTypes.func,
|
||||
onZoomChanged: PropTypes.func,
|
||||
mirrorImage: PropTypes.bool,
|
||||
fixOrientation: PropTypes.bool,
|
||||
barCodeTypes: PropTypes.array,
|
||||
orientation: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
playSoundOnCapture: PropTypes.bool,
|
||||
torchMode: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
type: PropTypes.oneOfType([PropTypes.string, PropTypes.number]),
|
||||
permissionDialogTitle: PropTypes.string,
|
||||
permissionDialogMessage: PropTypes.string,
|
||||
notAuthorizedView: PropTypes.element,
|
||||
pendingAuthorizationView: PropTypes.element,
|
||||
};
|
||||
|
||||
static defaultProps = {
|
||||
aspect: CameraManager.Aspect.fill,
|
||||
type: CameraManager.Type.back,
|
||||
orientation: CameraManager.Orientation.auto,
|
||||
fixOrientation: false,
|
||||
captureAudio: false,
|
||||
captureMode: CameraManager.CaptureMode.still,
|
||||
captureTarget: CameraManager.CaptureTarget.cameraRoll,
|
||||
captureQuality: CameraManager.CaptureQuality.high,
|
||||
defaultOnFocusComponent: true,
|
||||
flashMode: CameraManager.FlashMode.off,
|
||||
playSoundOnCapture: true,
|
||||
torchMode: CameraManager.TorchMode.off,
|
||||
mirrorImage: false,
|
||||
barCodeTypes: Object.values(CameraManager.BarCodeType),
|
||||
permissionDialogTitle: '',
|
||||
permissionDialogMessage: '',
|
||||
notAuthorizedView: (
|
||||
<View style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<Text style={{
|
||||
textAlign: 'center',
|
||||
fontSize: 16,
|
||||
}}>
|
||||
Camera not authorized
|
||||
</Text>
|
||||
</View>
|
||||
),
|
||||
pendingAuthorizationView: (
|
||||
<View style={{
|
||||
flex: 1,
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
}}>
|
||||
<ActivityIndicator size="small"/>
|
||||
</View>
|
||||
),
|
||||
};
|
||||
|
||||
static checkDeviceAuthorizationStatus = CameraManager.checkDeviceAuthorizationStatus;
|
||||
static checkVideoAuthorizationStatus = CameraManager.checkVideoAuthorizationStatus;
|
||||
static checkAudioAuthorizationStatus = CameraManager.checkAudioAuthorizationStatus;
|
||||
|
||||
setNativeProps(props) {
|
||||
// eslint-disable-next-line
|
||||
this.refs[CAMERA_REF].setNativeProps(props);
|
||||
}
|
||||
|
||||
constructor() {
|
||||
super();
|
||||
this.state = {
|
||||
isAuthorized: false,
|
||||
isAuthorizationChecked: false,
|
||||
isRecording: false,
|
||||
};
|
||||
}
|
||||
|
||||
async componentWillMount() {
|
||||
this._addOnBarCodeReadListener();
|
||||
|
||||
let { captureMode } = convertNativeProps({ captureMode: this.props.captureMode });
|
||||
let hasVideoAndAudio = this.props.captureAudio && captureMode === Camera.constants.CaptureMode.video;
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
|
||||
let check = hasVideoAndAudio ? Camera.checkDeviceAuthorizationStatus : Camera.checkVideoAuthorizationStatus;
|
||||
|
||||
if (check) {
|
||||
const isAuthorized = await check();
|
||||
this.setState({ isAuthorized, isAuthorizationChecked: true });
|
||||
}
|
||||
|
||||
} else if (Platform.OS === 'android') {
|
||||
|
||||
const granted = await PermissionsAndroid.request( PermissionsAndroid.PERMISSIONS.CAMERA, {
|
||||
title: this.props.permissionDialogTitle,
|
||||
message: this.props.permissionDialogMessage,
|
||||
}
|
||||
);
|
||||
|
||||
this.setState({ isAuthorized: granted === PermissionsAndroid.RESULTS.GRANTED, isAuthorizationChecked: true });
|
||||
} else {
|
||||
|
||||
this.setState({ isAuthorized: true, isAuthorizationChecked: true })
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
componentWillUnmount() {
|
||||
this._removeOnBarCodeReadListener();
|
||||
|
||||
if (this.state.isRecording) {
|
||||
this.stopCapture();
|
||||
}
|
||||
}
|
||||
|
||||
componentWillReceiveProps(newProps) {
|
||||
const { onBarCodeRead } = this.props;
|
||||
if (onBarCodeRead !== newProps.onBarCodeRead) {
|
||||
this._addOnBarCodeReadListener(newProps);
|
||||
}
|
||||
}
|
||||
|
||||
_addOnBarCodeReadListener(props) {
|
||||
const { onBarCodeRead } = props || this.props;
|
||||
this._removeOnBarCodeReadListener();
|
||||
if (onBarCodeRead) {
|
||||
this.cameraBarCodeReadListener = Platform.select({
|
||||
ios: NativeAppEventEmitter.addListener('CameraBarCodeRead', this._onBarCodeRead),
|
||||
android: DeviceEventEmitter.addListener('CameraBarCodeReadAndroid', this._onBarCodeRead),
|
||||
});
|
||||
}
|
||||
}
|
||||
_removeOnBarCodeReadListener() {
|
||||
const listener = this.cameraBarCodeReadListener;
|
||||
if (listener) {
|
||||
listener.remove();
|
||||
}
|
||||
}
|
||||
|
||||
render() {
|
||||
// TODO - style is not used, figure it out why
|
||||
// eslint-disable-next-line
|
||||
const style = [styles.base, this.props.style];
|
||||
const nativeProps = convertNativeProps(this.props);
|
||||
|
||||
if(this.state.isAuthorized) {
|
||||
return <RCTCamera ref={CAMERA_REF} {...nativeProps} />;
|
||||
} else if (!this.state.isAuthorizationChecked) {
|
||||
return this.props.pendingAuthorizationView
|
||||
} else {
|
||||
return this.props.notAuthorizedView
|
||||
}
|
||||
}
|
||||
|
||||
_onBarCodeRead = data => {
|
||||
if (this.props.onBarCodeRead) {
|
||||
this.props.onBarCodeRead(data);
|
||||
}
|
||||
};
|
||||
|
||||
capture(options) {
|
||||
const props = convertNativeProps(this.props);
|
||||
options = {
|
||||
audio: props.captureAudio,
|
||||
barCodeTypes: props.barCodeTypes,
|
||||
mode: props.captureMode,
|
||||
playSoundOnCapture: props.playSoundOnCapture,
|
||||
target: props.captureTarget,
|
||||
quality: props.captureQuality,
|
||||
type: props.type,
|
||||
title: '',
|
||||
description: '',
|
||||
mirrorImage: props.mirrorImage,
|
||||
fixOrientation: props.fixOrientation,
|
||||
...options,
|
||||
};
|
||||
|
||||
if (options.mode === Camera.constants.CaptureMode.video) {
|
||||
options.totalSeconds = options.totalSeconds > -1 ? options.totalSeconds : -1;
|
||||
options.preferredTimeScale = options.preferredTimeScale || 30;
|
||||
this.setState({ isRecording: true });
|
||||
}
|
||||
|
||||
return CameraManager.capture(options);
|
||||
}
|
||||
|
||||
startPreview() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
CameraManager.startPreview({
|
||||
type: props.type,
|
||||
});
|
||||
} else {
|
||||
CameraManager.startPreview();
|
||||
}
|
||||
}
|
||||
|
||||
stopPreview() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
CameraManager.stopPreview({
|
||||
type: props.type,
|
||||
});
|
||||
} else {
|
||||
CameraManager.stopPreview();
|
||||
}
|
||||
}
|
||||
|
||||
stopCapture() {
|
||||
if (this.state.isRecording) {
|
||||
this.setState({ isRecording: false });
|
||||
return CameraManager.stopCapture();
|
||||
}
|
||||
return Promise.resolve('Not Recording.');
|
||||
}
|
||||
|
||||
getFOV() {
|
||||
return CameraManager.getFOV();
|
||||
}
|
||||
|
||||
hasFlash() {
|
||||
if (Platform.OS === 'android') {
|
||||
const props = convertNativeProps(this.props);
|
||||
return CameraManager.hasFlash({
|
||||
type: props.type,
|
||||
});
|
||||
}
|
||||
return CameraManager.hasFlash();
|
||||
}
|
||||
}
|
||||
|
||||
export const constants = Camera.constants;
|
||||
|
||||
const RCTCamera = requireNativeComponent('RCTCamera', Camera, {
|
||||
nativeOnly: {
|
||||
testID: true,
|
||||
renderToHardwareTextureAndroid: true,
|
||||
accessibilityLabel: true,
|
||||
importantForAccessibility: true,
|
||||
accessibilityLiveRegion: true,
|
||||
accessibilityComponentType: true,
|
||||
onLayout: true,
|
||||
},
|
||||
});
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {},
|
||||
});
|
||||
export default Camera;
|
||||
|
||||
Loading…
Reference in New Issue
Block a user