-
Notifications
You must be signed in to change notification settings - Fork 3k
/
Copy pathImageCropView.js
109 lines (92 loc) · 4.34 KB
/
ImageCropView.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
import PropTypes from 'prop-types';
import React from 'react';
import {View} from 'react-native';
import {PanGestureHandler} from 'react-native-gesture-handler';
import Animated, {interpolate, useAnimatedStyle} from 'react-native-reanimated';
import Icon from '@components/Icon';
import * as Expensicons from '@components/Icon/Expensicons';
import useStyleUtils from '@hooks/useStyleUtils';
import useTheme from '@hooks/useTheme';
import useThemeStyles from '@hooks/useThemeStyles';
import ControlSelection from '@libs/ControlSelection';
import gestureHandlerPropTypes from './gestureHandlerPropTypes';
const propTypes = {
/** Link to image for cropping */
imageUri: PropTypes.string,
/** Size of the image container that will be rendered */
containerSize: PropTypes.number,
/** The height of the selected image */
originalImageHeight: PropTypes.shape({value: PropTypes.number}).isRequired,
/** The width of the selected image */
originalImageWidth: PropTypes.shape({value: PropTypes.number}).isRequired,
/** The rotation value of the selected image */
rotation: PropTypes.shape({value: PropTypes.number}).isRequired,
/** The relative image shift along X-axis */
translateX: PropTypes.shape({value: PropTypes.number}).isRequired,
/** The relative image shift along Y-axis */
translateY: PropTypes.shape({value: PropTypes.number}).isRequired,
/** The scale factor of the image */
scale: PropTypes.shape({value: PropTypes.number}).isRequired,
/** React-native-reanimated lib handler which executes when the user is panning image */
panGestureEventHandler: gestureHandlerPropTypes,
/** Image crop vector mask */
maskImage: PropTypes.func,
};
const defaultProps = {
imageUri: '',
containerSize: 0,
panGestureEventHandler: () => {},
maskImage: Expensicons.ImageCropCircleMask,
};
function ImageCropView(props) {
const theme = useTheme();
const styles = useThemeStyles();
const StyleUtils = useStyleUtils();
const containerStyle = StyleUtils.getWidthAndHeightStyle(props.containerSize, props.containerSize);
const originalImageHeight = props.originalImageHeight;
const originalImageWidth = props.originalImageWidth;
const rotation = props.rotation;
const translateX = props.translateX;
const translateY = props.translateY;
const scale = props.scale;
// A reanimated memoized style, which updates when the image's size or scale changes.
const imageStyle = useAnimatedStyle(() => {
const height = originalImageHeight.value;
const width = originalImageWidth.value;
const aspectRatio = height > width ? height / width : width / height;
const rotate = interpolate(rotation.value, [0, 360], [0, 360]);
return {
transform: [{translateX: translateX.value}, {translateY: translateY.value}, {scale: scale.value * aspectRatio}, {rotate: `${rotate}deg`}],
};
}, [originalImageHeight, originalImageWidth, rotation, translateX, translateY, scale]);
// We're preventing text selection with ControlSelection.blockElement to prevent safari
// default behaviour of cursor - I-beam cursor on drag. See https://github.com/Expensify/App/issues/13688
return (
<PanGestureHandler onGestureEvent={props.panGestureEventHandler}>
<Animated.View
ref={ControlSelection.blockElement}
style={[containerStyle, styles.imageCropContainer]}
>
<Animated.Image
style={[imageStyle, styles.h100, styles.w100]}
source={{uri: props.imageUri}}
resizeMode="contain"
/>
<View style={[containerStyle, styles.l0, styles.b0, styles.pAbsolute]}>
<Icon
src={props.maskImage}
fill={theme.iconReversed}
width={props.containerSize}
height={props.containerSize}
/>
</View>
</Animated.View>
</PanGestureHandler>
);
}
ImageCropView.displayName = 'ImageCropView';
ImageCropView.propTypes = propTypes;
ImageCropView.defaultProps = defaultProps;
// React.memo is needed here to prevent styles recompilation
// which sometimes may cause glitches during rerender of the modal
export default React.memo(ImageCropView);