diff --git a/ReactNativeClient/lib/components/CameraView.js b/ReactNativeClient/lib/components/CameraView.js index f15e29f29..ed9f29565 100644 --- a/ReactNativeClient/lib/components/CameraView.js +++ b/ReactNativeClient/lib/components/CameraView.js @@ -1,22 +1,39 @@ const React = require('react'); const Component = React.Component; -const { View, TouchableOpacity } = require('react-native'); +const { connect } = require('react-redux'); +const { View, TouchableOpacity, Text, Dimensions } = require('react-native'); import { RNCamera } from 'react-native-camera'; const Icon = require('react-native-vector-icons/Ionicons').default; const { _ } = require('lib/locale.js'); +const { shim } = require('lib/shim'); +const Setting = require('lib/models/Setting'); class CameraView extends Component { constructor() { super(); + const dimensions = Dimensions.get('window'); + this.state = { snapping: false, - camera: RNCamera.Constants.Type.back, + ratios: [], + screenWidth: dimensions.width, + screenHeight: dimensions.height, }; this.back_onPress = this.back_onPress.bind(this); this.photo_onPress = this.photo_onPress.bind(this); this.reverse_onPress = this.reverse_onPress.bind(this); + this.ratio_onPress = this.ratio_onPress.bind(this); + this.onCameraReady = this.onCameraReady.bind(this); + this.onLayout = this.onLayout.bind(this); + } + + onLayout(event) { + this.setState({ + screenWidth: event.nativeEvent.layout.width, + screenHeight: event.nativeEvent.layout.height, + }); } back_onPress() { @@ -24,17 +41,23 @@ class CameraView extends Component { } reverse_onPress() { - if (this.state.camera == RNCamera.Constants.Type.back) { - this.setState({ - camera: RNCamera.Constants.Type.front, - }); - } else if (this.state.camera == RNCamera.Constants.Type.front) { - this.setState({ - camera: RNCamera.Constants.Type.back, - }); + if (this.props.cameraType === RNCamera.Constants.Type.back) { + Setting.setValue('camera.type', RNCamera.Constants.Type.front); + } else { + Setting.setValue('camera.type', RNCamera.Constants.Type.back); } } + ratio_onPress() { + if (this.state.ratios.length <= 1) return; + + let index = this.state.ratios.indexOf(this.props.cameraRatio); + index++; + if (index >= this.state.ratios.length) index = 0; + Setting.setValue('camera.ratio', this.state.ratios[index]); + this.forceUpdate(); + } + async photo_onPress() { if (!this.camera || !this.props.onPhoto) return; @@ -51,29 +74,120 @@ class CameraView extends Component { this.setState({ snapping: false }); } + async onCameraReady() { + const ratios = await this.camera.getSupportedRatiosAsync(); + this.setState({ ratios: ratios }); + } + + renderButton(onPress, iconName, style) { + let icon = null; + + if (typeof iconName === 'string') { + icon = ( + + ); + } else { + icon = iconName; + } + + return ( + + + { icon } + + + ); + } + + fitRectIntoBounds(rect, bounds) { + var rectRatio = rect.width / rect.height; + var boundsRatio = bounds.width / bounds.height; + + var newDimensions = {}; + + // Rect is more landscape than bounds - fit to width + if (rectRatio > boundsRatio) { + newDimensions.width = bounds.width; + newDimensions.height = rect.height * (bounds.width / rect.width); + } else { // Rect is more portrait than bounds - fit to height + newDimensions.width = rect.width * (bounds.height / rect.height); + newDimensions.height = bounds.height; + } + + return newDimensions; + } + + cameraRect(ratio) { + // To keep the calculations simpler, it's assumed that the phone is in + // portrait orientation. Then at the end we swap the values if needed. + const splitted = ratio.split(':'); + + const output = this.fitRectIntoBounds({ + width: Number(splitted[1]), + height: Number(splitted[0]), + }, { + width: Math.min(this.state.screenWidth, this.state.screenHeight), + height: Math.max(this.state.screenWidth, this.state.screenHeight), + }); + + if (this.state.screenWidth > this.state.screenHeight) { + const w = output.width; + output.width = output.height; + output.height = w; + } + + return output; + } + render() { const photoIcon = this.state.snapping ? 'md-checkmark' : 'md-camera'; + const displayRatios = shim.mobilePlatform() === 'android' && this.state.ratios.length > 1; + + const reverseCameraButton = this.renderButton(this.reverse_onPress, 'md-reverse-camera', { flex: 1, flexDirection: 'row', justifyContent: 'flex-start', marginLeft: 20 }); + const ratioButton = !displayRatios ? : this.renderButton(this.ratio_onPress, {Setting.value('camera.ratio')}, { flex: 1, flexDirection: 'row', justifyContent: 'flex-end', marginRight: 20 }); + + let cameraRatio = '4:3'; + const cameraProps = {}; + if (displayRatios) { + cameraProps.ratio = this.props.cameraRatio; + cameraRatio = this.props.cameraRatio; + } + + const cameraRect = this.cameraRect(cameraRatio); + cameraRect.left = (this.state.screenWidth - cameraRect.width) / 2; + cameraRect.top = (this.state.screenHeight - cameraRect.height) / 2; + return ( - + + { this.camera = ref; }} - type={this.state.camera} + type={this.props.cameraType} captureAudio={false} + onCameraReady={this.onCameraReady} androidCameraPermissionOptions={{ title: _('Permission to use camera'), message: _('Your permission to use your camera is required.'), buttonPositive: _('OK'), buttonNegative: _('Cancel'), }} + + { ...cameraProps } > - + - - - - - - - - - + + + { reverseCameraButton } + + + { ratioButton } @@ -117,4 +222,12 @@ class CameraView extends Component { } } -module.exports = CameraView; +const mapStateToProps = state => { + return { + cameraRatio: state.settings['camera.ratio'], + cameraType: state.settings['camera.type'], + }; +}; + + +module.exports = connect(mapStateToProps)(CameraView); diff --git a/ReactNativeClient/lib/models/Setting.js b/ReactNativeClient/lib/models/Setting.js index 4cdfb7d9e..00755d73c 100644 --- a/ReactNativeClient/lib/models/Setting.js +++ b/ReactNativeClient/lib/models/Setting.js @@ -480,6 +480,9 @@ class Setting extends BaseModel { 'welcome.wasBuilt': { value: false, type: Setting.TYPE_BOOL, public: false }, 'welcome.enabled': { value: true, type: Setting.TYPE_BOOL, public: false }, + + 'camera.type': { value: 0, type: Setting.TYPE_INT, public: false, appTypes: ['mobile'] }, + 'camera.ratio': { value: '4:3', type: Setting.TYPE_STRING, public: false, appTypes: ['mobile'] }, }; return this.metadata_; diff --git a/ReactNativeClient/lib/shim.js b/ReactNativeClient/lib/shim.js index 44ae2c8d6..e27f1aca8 100644 --- a/ReactNativeClient/lib/shim.js +++ b/ReactNativeClient/lib/shim.js @@ -42,6 +42,7 @@ shim.platformName = function() { throw new Error('Cannot determine platform'); }; +// "ios" or "android", or "" if not on mobile shim.mobilePlatform = function() { return ''; // Default if we're not on mobile (React Native) };