2020-10-16 17:26:19 +02:00
const { RNCamera } = require ( 'react-native-camera' ) ;
2019-07-30 09:35:42 +02:00
const React = require ( 'react' ) ;
const Component = React . Component ;
2019-10-16 23:52:22 +02:00
const { connect } = require ( 'react-redux' ) ;
const { View , TouchableOpacity , Text , Dimensions } = require ( 'react-native' ) ;
2018-10-13 11:32:44 +02:00
const Icon = require ( 'react-native-vector-icons/Ionicons' ) . default ;
2020-11-07 17:59:37 +02:00
const { _ } = require ( '@joplin/lib/locale' ) ;
2021-01-22 19:41:11 +02:00
import shim from '@joplin/lib/shim' ;
import Setting from '@joplin/lib/models/Setting' ;
2018-10-13 11:32:44 +02:00
class CameraView extends Component {
2023-03-06 16:22:01 +02:00
public constructor ( ) {
2018-10-13 11:32:44 +02:00
super ( ) ;
2019-10-16 23:52:22 +02:00
const dimensions = Dimensions . get ( 'window' ) ;
2018-10-13 11:32:44 +02:00
this . state = {
snapping : false ,
2019-10-16 23:52:22 +02:00
ratios : [ ] ,
screenWidth : dimensions.width ,
screenHeight : dimensions.height ,
2018-10-13 11:32:44 +02:00
} ;
2019-07-30 09:35:42 +02:00
2018-10-13 11:32:44 +02:00
this . back_onPress = this . back_onPress . bind ( this ) ;
this . photo_onPress = this . photo_onPress . bind ( this ) ;
2019-10-03 23:23:43 +02:00
this . reverse_onPress = this . reverse_onPress . bind ( this ) ;
2019-10-16 23:52:22 +02:00
this . ratio_onPress = this . ratio_onPress . bind ( this ) ;
this . onCameraReady = this . onCameraReady . bind ( this ) ;
this . onLayout = this . onLayout . bind ( this ) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-03-06 16:22:01 +02:00
public onLayout ( event : any ) {
2019-10-16 23:52:22 +02:00
this . setState ( {
screenWidth : event.nativeEvent.layout.width ,
screenHeight : event.nativeEvent.layout.height ,
} ) ;
2018-10-13 11:32:44 +02:00
}
2023-03-06 16:22:01 +02:00
private back_onPress() {
2018-10-13 11:32:44 +02:00
if ( this . props . onCancel ) this . props . onCancel ( ) ;
}
2023-03-06 16:22:01 +02:00
private reverse_onPress() {
2019-10-16 23:52:22 +02:00
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 ) ;
2019-10-03 23:23:43 +02:00
}
}
2023-03-06 16:22:01 +02:00
private ratio_onPress() {
2019-10-16 23:52:22 +02:00
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 ] ) ;
}
2023-03-06 16:22:01 +02:00
private async photo_onPress() {
2018-10-13 11:32:44 +02:00
if ( ! this . camera || ! this . props . onPhoto ) return ;
this . setState ( { snapping : true } ) ;
const result = await this . camera . takePictureAsync ( {
quality : 0.8 ,
exif : true ,
2019-07-30 09:35:42 +02:00
fixOrientation : true ,
2018-10-13 11:32:44 +02:00
} ) ;
2020-10-16 17:26:19 +02:00
this . setState ( { snapping : false } ) ;
2018-10-13 11:32:44 +02:00
if ( this . props . onPhoto ) this . props . onPhoto ( result ) ;
}
2023-03-06 16:22:01 +02:00
public async onCameraReady() {
2020-10-16 17:26:19 +02:00
if ( this . supportsRatios ( ) ) {
const ratios = await this . camera . getSupportedRatiosAsync ( ) ;
this . setState ( { ratios : ratios } ) ;
}
2019-10-16 23:52:22 +02:00
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/ban-types, @typescript-eslint/no-explicit-any -- Old code before rule was applied, Old code before rule was applied
2023-03-06 16:22:01 +02:00
public renderButton ( onPress : Function , iconNameOrIcon : any , style : any ) {
2019-10-16 23:52:22 +02:00
let icon = null ;
2020-10-16 17:26:19 +02:00
if ( typeof iconNameOrIcon === 'string' ) {
2019-10-16 23:52:22 +02:00
icon = (
< Icon
2020-10-16 17:26:19 +02:00
name = { iconNameOrIcon }
2019-10-16 23:52:22 +02:00
style = { {
fontSize : 40 ,
color : 'black' ,
} }
/ >
) ;
} else {
2020-10-16 17:26:19 +02:00
icon = iconNameOrIcon ;
2019-10-16 23:52:22 +02:00
}
return (
2023-06-01 13:02:36 +02:00
< TouchableOpacity onPress = { onPress } style = { { . . . style } } >
2020-02-05 00:09:34 +02:00
< View style = { { borderRadius : 32 , width : 60 , height : 60 , borderColor : '#00000040' , borderWidth : 1 , borderStyle : 'solid' , backgroundColor : '#ffffff77' , justifyContent : 'center' , alignItems : 'center' , alignSelf : 'baseline' } } >
2019-10-16 23:52:22 +02:00
{ icon }
< / View >
< / TouchableOpacity >
) ;
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2023-03-06 16:22:01 +02:00
public fitRectIntoBounds ( rect : any , bounds : any ) {
2020-03-14 01:46:14 +02:00
const rectRatio = rect . width / rect . height ;
const boundsRatio = bounds . width / bounds . height ;
2019-10-16 23:52:22 +02:00
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-11-12 21:13:28 +02:00
const newDimensions : any = { } ;
2019-10-16 23:52:22 +02:00
// 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 ;
}
2023-03-06 16:22:01 +02:00
public cameraRect ( ratio : string ) {
2019-10-16 23:52:22 +02:00
// 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 ;
}
2023-03-06 16:22:01 +02:00
public supportsRatios() {
2020-10-16 17:26:19 +02:00
return shim . mobilePlatform ( ) === 'android' ;
}
2023-03-06 16:22:01 +02:00
public render() {
2023-10-07 18:25:03 +02:00
const photoIcon = this . state . snapping ? 'checkmark' : 'camera' ;
2018-10-13 11:32:44 +02:00
2020-10-16 17:26:19 +02:00
const displayRatios = this . supportsRatios ( ) && this . state . ratios . length > 1 ;
2019-10-16 23:52:22 +02:00
2023-10-07 18:25:03 +02:00
const reverseCameraButton = this . renderButton ( this . reverse_onPress , 'camera-reverse' , { flex : 1 , flexDirection : 'row' , justifyContent : 'flex-start' , marginLeft : 20 } ) ;
2020-02-05 00:09:34 +02:00
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 } ) ;
2019-10-16 23:52:22 +02:00
let cameraRatio = '4:3' ;
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-11-12 21:13:28 +02:00
const cameraProps : any = { } ;
2020-10-16 17:26:19 +02:00
2019-10-16 23:52:22 +02:00
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 ;
2018-10-13 11:32:44 +02:00
return (
2023-06-01 13:02:36 +02:00
< View style = { { . . . this . props . style , position : 'relative' } } onLayout = { this . onLayout } >
2019-10-16 23:52:22 +02:00
< View style = { { position : 'absolute' , backgroundColor : '#000000' , width : '100%' , height : '100%' } } / >
2018-10-13 11:32:44 +02:00
< RNCamera
2023-06-01 13:02:36 +02:00
style = { ( { position : 'absolute' , . . . cameraRect } ) }
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-11-12 21:13:28 +02:00
ref = { ( ref : any ) = > {
2019-07-30 09:35:42 +02:00
this . camera = ref ;
} }
2019-10-16 23:52:22 +02:00
type = { this . props . cameraType }
2019-06-14 23:31:01 +02:00
captureAudio = { false }
2019-10-16 23:52:22 +02:00
onCameraReady = { this . onCameraReady }
2019-06-14 23:31:01 +02:00
androidCameraPermissionOptions = { {
title : _ ( 'Permission to use camera' ) ,
message : _ ( 'Your permission to use your camera is required.' ) ,
buttonPositive : _ ( 'OK' ) ,
buttonNegative : _ ( 'Cancel' ) ,
} }
2019-10-16 23:52:22 +02:00
{ . . . cameraProps }
2018-10-13 11:32:44 +02:00
>
2019-07-30 09:35:42 +02:00
< View style = { { flex : 1 , justifyContent : 'space-between' , flexDirection : 'column' } } >
< View style = { { flex : 1 , justifyContent : 'flex-start' } } >
2018-10-13 11:32:44 +02:00
< TouchableOpacity onPress = { this . back_onPress } >
2019-10-16 23:52:22 +02:00
< 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' } } >
2019-07-30 09:35:42 +02:00
< Icon
2023-10-07 18:25:03 +02:00
name = { 'arrow-back' }
2019-07-30 09:35:42 +02:00
style = { {
fontSize : 40 ,
color : 'black' ,
} }
/ >
2018-10-13 11:32:44 +02:00
< / View >
< / TouchableOpacity >
< / View >
2019-10-16 23:52:22 +02:00
< View style = { { flex : 1 , flexDirection : 'row' , justifyContent : 'center' , alignItems : 'flex-end' } } >
< View style = { { flex : 1 , flexDirection : 'row' , justifyContent : 'center' , alignItems : 'center' , marginBottom : 20 } } >
{ reverseCameraButton }
2022-03-07 12:25:08 +02:00
< TouchableOpacity onPress = { this . photo_onPress } disabled = { this . state . snapping } >
2019-10-16 23:52:22 +02:00
< View style = { { flexDirection : 'row' , borderRadius : 90 , width : 90 , height : 90 , backgroundColor : '#ffffffaa' , display : 'flex' , justifyContent : 'center' , alignItems : 'center' } } >
2019-10-03 23:23:43 +02:00
< Icon
name = { photoIcon }
style = { {
fontSize : 60 ,
color : 'black' ,
} }
/ >
< / View >
< / TouchableOpacity >
2019-10-16 23:52:22 +02:00
{ ratioButton }
2019-10-03 23:23:43 +02:00
< / View >
2018-10-13 11:32:44 +02:00
< / View >
< / View >
< / RNCamera >
< / View >
) ;
}
}
2024-04-05 13:16:49 +02:00
// eslint-disable-next-line @typescript-eslint/no-explicit-any -- Old code before rule was applied
2020-11-12 21:13:28 +02:00
const mapStateToProps = ( state : any ) = > {
2019-10-16 23:52:22 +02:00
return {
cameraRatio : state.settings [ 'camera.ratio' ] ,
cameraType : state.settings [ 'camera.type' ] ,
} ;
} ;
2020-10-16 17:26:19 +02:00
export default connect ( mapStateToProps ) ( CameraView ) ;