1
0
mirror of https://github.com/laurent22/joplin.git synced 2024-12-24 10:27:10 +02:00

Mobile: Resolves #1998: Allow selecting camera ratio and fixed camera view aspect ratio. Also made camera type persistent.

This commit is contained in:
Laurent Cozic 2019-10-16 23:52:22 +02:00
parent 3281ab05b1
commit 58b307ba02
3 changed files with 147 additions and 30 deletions

View File

@ -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 = (
<Icon
name={iconName}
style={{
fontSize: 40,
color: 'black',
}}
/>
);
} else {
icon = iconName;
}
return (
<TouchableOpacity onPress={onPress} style={Object.assign({}, style)}>
<View style={{borderRadius: 32, width: 60, height: 60, borderColor: '#00000040', borderWidth: 1, borderStyle: 'solid', backgroundColor: '#ffffff77', justifyContent: 'center', alignItems: 'center', alignSelf: 'baseline' }}>
{ icon }
</View>
</TouchableOpacity>
);
}
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 ? <View style={{ flex: 1 }}/> : this.renderButton(this.ratio_onPress, <Text style={{fontWeight: 'bold', fontSize: 20}}>{Setting.value('camera.ratio')}</Text>, { 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 (
<View style={this.props.style}>
<View style={Object.assign({}, this.props.style, { position: 'relative' })} onLayout={this.onLayout}>
<View style={{ position: 'absolute', backgroundColor: '#000000', width: '100%', height: '100%' }}/>
<RNCamera
style={{ flex: 1 }}
style={Object.assign({ position: 'absolute' }, cameraRect)}
ref={ref => {
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 }
>
<View style={{ flex: 1, justifyContent: 'space-between', flexDirection: 'column' }}>
<View style={{ flex: 1, justifyContent: 'flex-start' }}>
<TouchableOpacity onPress={this.back_onPress}>
<View style={{ marginLeft: 5, marginTop: 5, borderRadius: 90, width: 50, height: 50, display: 'flex', backgroundColor: '#ffffff55', justifyContent: 'center', alignItems: 'center' }}>
<View style={{ marginLeft: 5, marginTop: 5, borderColor: '#00000040', borderWidth: 1, borderStyle: 'solid', borderRadius: 90, width: 50, height: 50, display: 'flex', backgroundColor: '#ffffff77', justifyContent: 'center', alignItems: 'center' }}>
<Icon
name={'md-arrow-back'}
style={{
@ -84,21 +198,11 @@ class CameraView extends Component {
</View>
</TouchableOpacity>
</View>
<View style={{ flex: 1, alignItems: 'flex-end', flexDirection: 'row', width: '100%' }}>
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center' }}>
<TouchableOpacity onPress={this.reverse_onPress} style={{width: '35%', marginLeft: 20}}>
<View style={{borderRadius: 32, width: 60, height: 60, backgroundColor: '#ffffffaa', justifyContent: 'center', alignItems: 'center', alignSelf: 'baseline' }}>
<Icon
name="md-reverse-camera"
style={{
fontSize: 40,
color: 'black',
}}
/>
</View>
</TouchableOpacity>
<TouchableOpacity onPress={this.photo_onPress} style={{width: '65%'}}>
<View style={{ marginBottom: 20, borderRadius: 90, width: 90, height: 90, backgroundColor: '#ffffffaa', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'flex-end' }}>
<View style={{ flex: 1, flexDirection: 'row', justifyContent: 'center', alignItems: 'center', marginBottom: 20 }}>
{ reverseCameraButton }
<TouchableOpacity onPress={this.photo_onPress}>
<View style={{ flexDirection: 'row', borderRadius: 90, width: 90, height: 90, backgroundColor: '#ffffffaa', display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<Icon
name={photoIcon}
style={{
@ -108,6 +212,7 @@ class CameraView extends Component {
/>
</View>
</TouchableOpacity>
{ ratioButton }
</View>
</View>
</View>
@ -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);

View File

@ -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_;

View File

@ -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)
};