mirror of https://git.lenooby09.tech/LeNooby09/social-app.git
1import React from 'react'
2import {StyleSheet, TouchableOpacity, View} from 'react-native'
3import {manipulateAsync, SaveFormat} from 'expo-image-manipulator'
4import {LinearGradient} from 'expo-linear-gradient'
5import {msg, Trans} from '@lingui/macro'
6import {useLingui} from '@lingui/react'
7import ReactCrop, {type PercentCrop} from 'react-image-crop'
8
9import {usePalette} from '#/lib/hooks/usePalette'
10import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries'
11import {type PickerImage} from '#/lib/media/picker.shared'
12import {getDataUriSize} from '#/lib/media/util'
13import {gradients, s} from '#/lib/styles'
14import {useModalControls} from '#/state/modals'
15import {Text} from '#/view/com/util/text/Text'
16
17export const snapPoints = ['0%']
18
19export function Component({
20 uri,
21 aspect,
22 circular,
23 onSelect,
24}: {
25 uri: string
26 aspect?: number
27 circular?: boolean
28 onSelect: (img?: PickerImage) => void
29}) {
30 const pal = usePalette('default')
31 const {_} = useLingui()
32
33 const {closeModal} = useModalControls()
34 const {isMobile} = useWebMediaQueries()
35
36 const imageRef = React.useRef<HTMLImageElement>(null)
37 const [crop, setCrop] = React.useState<PercentCrop>()
38
39 const isEmpty = !crop || (crop.width || crop.height) === 0
40
41 const onPressCancel = () => {
42 onSelect(undefined)
43 closeModal()
44 }
45 const onPressDone = async () => {
46 const img = imageRef.current!
47
48 const result = await manipulateAsync(
49 uri,
50 isEmpty
51 ? []
52 : [
53 {
54 crop: {
55 originX: (crop.x * img.naturalWidth) / 100,
56 originY: (crop.y * img.naturalHeight) / 100,
57 width: (crop.width * img.naturalWidth) / 100,
58 height: (crop.height * img.naturalHeight) / 100,
59 },
60 },
61 ],
62 {
63 base64: true,
64 format: SaveFormat.JPEG,
65 },
66 )
67
68 onSelect({
69 path: result.uri,
70 mime: 'image/jpeg',
71 size: result.base64 !== undefined ? getDataUriSize(result.base64) : 0,
72 width: result.width,
73 height: result.height,
74 })
75
76 closeModal()
77 }
78
79 return (
80 <View>
81 <View style={[styles.cropper, pal.borderDark]}>
82 <ReactCrop
83 aspect={aspect}
84 crop={crop}
85 onChange={(_pixelCrop, percentCrop) => setCrop(percentCrop)}
86 circularCrop={circular}>
87 <img ref={imageRef} src={uri} style={{maxHeight: '75vh'}} />
88 </ReactCrop>
89 </View>
90 <View style={[styles.btns, isMobile && {paddingHorizontal: 16}]}>
91 <TouchableOpacity
92 onPress={onPressCancel}
93 accessibilityRole="button"
94 accessibilityLabel={_(msg`Cancel image crop`)}
95 accessibilityHint={_(msg`Exits image cropping process`)}>
96 <Text type="xl" style={pal.link}>
97 <Trans>Cancel</Trans>
98 </Text>
99 </TouchableOpacity>
100 <View style={s.flex1} />
101 <TouchableOpacity
102 onPress={onPressDone}
103 accessibilityRole="button"
104 accessibilityLabel={_(msg`Save image crop`)}
105 accessibilityHint={_(msg`Saves image crop settings`)}>
106 <LinearGradient
107 colors={[gradients.blueLight.start, gradients.blueLight.end]}
108 start={{x: 0, y: 0}}
109 end={{x: 1, y: 1}}
110 style={[styles.btn]}>
111 <Text type="xl-medium" style={s.white}>
112 <Trans>Done</Trans>
113 </Text>
114 </LinearGradient>
115 </TouchableOpacity>
116 </View>
117 </View>
118 )
119}
120
121const styles = StyleSheet.create({
122 cropper: {
123 marginLeft: 'auto',
124 marginRight: 'auto',
125 borderWidth: 1,
126 borderRadius: 4,
127 overflow: 'hidden',
128 alignItems: 'center',
129 },
130 ctrls: {
131 flexDirection: 'row',
132 alignItems: 'center',
133 marginTop: 10,
134 },
135 btns: {
136 flexDirection: 'row',
137 alignItems: 'center',
138 marginTop: 10,
139 },
140 btn: {
141 borderRadius: 4,
142 paddingVertical: 8,
143 paddingHorizontal: 24,
144 },
145})