const { RNCamera } = require('react-native-camera'); const React = require('react'); const Component = React.Component; const { connect } = require('react-redux'); const { View, TouchableOpacity, Text, Dimensions } = require('react-native'); const Icon = require('react-native-vector-icons/Ionicons').default; const { _ } = require('@joplin/lib/locale'); import shim from '@joplin/lib/shim'; import Setting from '@joplin/lib/models/Setting'; // We need this to suppress the useless warning // https://github.com/oblador/react-native-vector-icons/issues/1465 // eslint-disable-next-line no-console Icon.loadFont().catch((error: any) => { console.info(error); }); class CameraView extends Component { public constructor() { super(); const dimensions = Dimensions.get('window'); this.state = { snapping: false, 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); } public onLayout(event: any) { this.setState({ screenWidth: event.nativeEvent.layout.width, screenHeight: event.nativeEvent.layout.height, }); } private back_onPress() { if (this.props.onCancel) this.props.onCancel(); } private reverse_onPress() { 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); } } private 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]); } private async photo_onPress() { if (!this.camera || !this.props.onPhoto) return; this.setState({ snapping: true }); const result = await this.camera.takePictureAsync({ quality: 0.8, exif: true, fixOrientation: true, }); this.setState({ snapping: false }); if (this.props.onPhoto) this.props.onPhoto(result); } public async onCameraReady() { if (this.supportsRatios()) { const ratios = await this.camera.getSupportedRatiosAsync(); this.setState({ ratios: ratios }); } } // eslint-disable-next-line @typescript-eslint/ban-types -- Old code before rule was applied public renderButton(onPress: Function, iconNameOrIcon: any, style: any) { let icon = null; if (typeof iconNameOrIcon === 'string') { icon = ( ); } else { icon = iconNameOrIcon; } return ( { icon } ); } public fitRectIntoBounds(rect: any, bounds: any) { const rectRatio = rect.width / rect.height; const boundsRatio = bounds.width / bounds.height; const newDimensions: any = {}; // 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; } public cameraRect(ratio: string) { // 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; } public supportsRatios() { return shim.mobilePlatform() === 'android'; } public render() { const photoIcon = this.state.snapping ? 'md-checkmark' : 'md-camera'; const displayRatios = this.supportsRatios() && this.state.ratios.length > 1; const reverseCameraButton = this.renderButton(this.reverse_onPress, 'md-camera-reverse', { 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: any = {}; 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.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 } ); } } const mapStateToProps = (state: any) => { return { cameraRatio: state.settings['camera.ratio'], cameraType: state.settings['camera.type'], }; }; export default connect(mapStateToProps)(CameraView);