@chem-po/react-native 0.0.26 → 0.0.27
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/lib/commonjs/components/form/input/file/index.js +53 -6
- package/lib/commonjs/components/form/input/file/index.js.map +1 -1
- package/lib/commonjs/components/image/ImageViewModal.js +52 -2
- package/lib/commonjs/components/image/ImageViewModal.js.map +1 -1
- package/lib/commonjs/components/loading/LoadingImage.js +8 -2
- package/lib/commonjs/components/loading/LoadingImage.js.map +1 -1
- package/lib/commonjs/utils/downloadFile.js +43 -0
- package/lib/commonjs/utils/downloadFile.js.map +1 -0
- package/lib/module/components/form/input/file/index.js +54 -7
- package/lib/module/components/form/input/file/index.js.map +1 -1
- package/lib/module/components/image/ImageViewModal.js +53 -3
- package/lib/module/components/image/ImageViewModal.js.map +1 -1
- package/lib/module/components/loading/LoadingImage.js +8 -2
- package/lib/module/components/loading/LoadingImage.js.map +1 -1
- package/lib/module/utils/downloadFile.js +35 -0
- package/lib/module/utils/downloadFile.js.map +1 -0
- package/lib/typescript/components/form/input/file/index.d.ts +2 -1
- package/lib/typescript/components/form/input/file/index.d.ts.map +1 -1
- package/lib/typescript/components/image/ImageViewModal.d.ts +3 -0
- package/lib/typescript/components/image/ImageViewModal.d.ts.map +1 -1
- package/lib/typescript/components/loading/LoadingImage.d.ts +4 -1
- package/lib/typescript/components/loading/LoadingImage.d.ts.map +1 -1
- package/lib/typescript/utils/downloadFile.d.ts +4 -0
- package/lib/typescript/utils/downloadFile.d.ts.map +1 -0
- package/package.json +4 -3
- package/src/components/form/input/file/index.tsx +52 -3
- package/src/components/image/ImageViewModal.tsx +57 -2
- package/src/components/loading/LoadingImage.tsx +16 -1
- package/src/utils/downloadFile.ts +36 -0
- package/lib/commonjs/components/image/ImageViewModal.backup.js +0 -285
- package/lib/commonjs/components/image/ImageViewModal.backup.js.map +0 -1
- package/lib/commonjs/components/image/ImageViewModal.old.js +0 -285
- package/lib/commonjs/components/image/ImageViewModal.old.js.map +0 -1
- package/lib/module/components/image/ImageViewModal.backup.js +0 -277
- package/lib/module/components/image/ImageViewModal.backup.js.map +0 -1
- package/lib/module/components/image/ImageViewModal.old.js +0 -277
- package/lib/module/components/image/ImageViewModal.old.js.map +0 -1
- package/lib/typescript/components/image/ImageViewModal.backup.d.ts +0 -9
- package/lib/typescript/components/image/ImageViewModal.backup.d.ts.map +0 -1
- package/lib/typescript/components/image/ImageViewModal.old.d.ts +0 -9
- package/lib/typescript/components/image/ImageViewModal.old.d.ts.map +0 -1
- package/src/components/image/ImageViewModal.backup.tsx +0 -261
- package/src/components/image/ImageViewModal.old.tsx +0 -261
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { useScreen } from '@chem-po/react'
|
|
2
|
-
import { Ionicons } from '@expo/vector-icons'
|
|
3
|
-
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
|
4
|
-
import { Animated, Image, Modal, StyleSheet, TouchableOpacity, View } from 'react-native'
|
|
5
|
-
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
6
|
-
import { LoadingLogo } from '../loading/Loading'
|
|
7
|
-
|
|
8
|
-
interface ImageViewModalProps {
|
|
9
|
-
isOpen: boolean
|
|
10
|
-
onClose: () => void
|
|
11
|
-
src: string | null
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const ImageViewModal: React.FC<ImageViewModalProps> = ({ isOpen, onClose, src }) => {
|
|
15
|
-
const [loading, setLoading] = useState(true)
|
|
16
|
-
const screenWidth = useScreen(s => s.width)
|
|
17
|
-
const screenHeight = useScreen(s => s.height)
|
|
18
|
-
const [imageSize, setImageSize] = useState({ width: screenWidth / 2, height: screenHeight / 2 })
|
|
19
|
-
|
|
20
|
-
// Animated values for zoom and pan
|
|
21
|
-
const scale = useRef(new Animated.Value(1)).current
|
|
22
|
-
const translateX = useRef(new Animated.Value(0)).current
|
|
23
|
-
const translateY = useRef(new Animated.Value(0)).current
|
|
24
|
-
|
|
25
|
-
// Gesture state
|
|
26
|
-
const savedScale = useRef(1)
|
|
27
|
-
const savedTranslateX = useRef(0)
|
|
28
|
-
const savedTranslateY = useRef(0)
|
|
29
|
-
const currentScale = useRef(1)
|
|
30
|
-
const currentTranslateX = useRef(0)
|
|
31
|
-
const currentTranslateY = useRef(0)
|
|
32
|
-
|
|
33
|
-
const { height, width } = useMemo(() => {
|
|
34
|
-
if (loading) return imageSize
|
|
35
|
-
const ratio = imageSize.width / imageSize.height
|
|
36
|
-
let h = Math.min(imageSize.height, screenHeight * 0.9)
|
|
37
|
-
let w = h * ratio
|
|
38
|
-
if (w > screenWidth * 0.9) {
|
|
39
|
-
w = Math.min(imageSize.width, screenWidth * 0.9)
|
|
40
|
-
h = w / ratio
|
|
41
|
-
}
|
|
42
|
-
return { height: h, width: w }
|
|
43
|
-
}, [screenHeight, screenWidth, imageSize, loading])
|
|
44
|
-
|
|
45
|
-
const onLoadStart = useCallback(() => setLoading(true), [])
|
|
46
|
-
const onLoad = useCallback((e: any) => {
|
|
47
|
-
const { width: naturalWidth, height: naturalHeight } = e.nativeEvent.source
|
|
48
|
-
setImageSize({ width: naturalWidth, height: naturalHeight })
|
|
49
|
-
setLoading(false)
|
|
50
|
-
}, [])
|
|
51
|
-
|
|
52
|
-
// Reset zoom and pan when modal opens/closes
|
|
53
|
-
const resetTransform = useCallback(() => {
|
|
54
|
-
scale.setValue(1)
|
|
55
|
-
translateX.setValue(0)
|
|
56
|
-
translateY.setValue(0)
|
|
57
|
-
savedScale.current = 1
|
|
58
|
-
savedTranslateX.current = 0
|
|
59
|
-
savedTranslateY.current = 0
|
|
60
|
-
currentScale.current = 1
|
|
61
|
-
currentTranslateX.current = 0
|
|
62
|
-
currentTranslateY.current = 0
|
|
63
|
-
}, [scale, translateX, translateY])
|
|
64
|
-
|
|
65
|
-
// Reset when modal closes or src changes
|
|
66
|
-
React.useEffect(() => {
|
|
67
|
-
if (!isOpen || !src) {
|
|
68
|
-
resetTransform()
|
|
69
|
-
}
|
|
70
|
-
}, [isOpen, src, resetTransform])
|
|
71
|
-
|
|
72
|
-
// Pan gesture
|
|
73
|
-
const panGesture = Gesture.Pan()
|
|
74
|
-
.onUpdate(event => {
|
|
75
|
-
// Only allow panning if zoomed in
|
|
76
|
-
if (savedScale.current > 1) {
|
|
77
|
-
const newTranslateX = savedTranslateX.current + event.translationX
|
|
78
|
-
const newTranslateY = savedTranslateY.current + event.translationY
|
|
79
|
-
|
|
80
|
-
// Calculate bounds to prevent panning too far
|
|
81
|
-
const maxTranslateX = (width * savedScale.current - width) / 2
|
|
82
|
-
const maxTranslateY = (height * savedScale.current - height) / 2
|
|
83
|
-
|
|
84
|
-
const boundedTranslateX = Math.max(-maxTranslateX, Math.min(maxTranslateX, newTranslateX))
|
|
85
|
-
const boundedTranslateY = Math.max(-maxTranslateY, Math.min(maxTranslateY, newTranslateY))
|
|
86
|
-
|
|
87
|
-
translateX.setValue(boundedTranslateX)
|
|
88
|
-
translateY.setValue(boundedTranslateY)
|
|
89
|
-
currentTranslateX.current = boundedTranslateX
|
|
90
|
-
currentTranslateY.current = boundedTranslateY
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
.onEnd(() => {
|
|
94
|
-
savedTranslateX.current = currentTranslateX.current
|
|
95
|
-
savedTranslateY.current = currentTranslateY.current
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
// Pinch gesture
|
|
99
|
-
const pinchGesture = Gesture.Pinch()
|
|
100
|
-
.onUpdate(event => {
|
|
101
|
-
const newScale = savedScale.current * event.scale
|
|
102
|
-
// Limit zoom between 1x and 5x
|
|
103
|
-
const boundedScale = Math.max(1, Math.min(5, newScale))
|
|
104
|
-
scale.setValue(boundedScale)
|
|
105
|
-
currentScale.current = boundedScale
|
|
106
|
-
|
|
107
|
-
// If zooming out to 1x, reset position
|
|
108
|
-
if (boundedScale <= 1) {
|
|
109
|
-
translateX.setValue(0)
|
|
110
|
-
translateY.setValue(0)
|
|
111
|
-
currentTranslateX.current = 0
|
|
112
|
-
currentTranslateY.current = 0
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
.onEnd(() => {
|
|
116
|
-
savedScale.current = currentScale.current
|
|
117
|
-
// If scale is close to 1, snap back to 1
|
|
118
|
-
if (savedScale.current < 1.1) {
|
|
119
|
-
Animated.parallel([
|
|
120
|
-
Animated.spring(scale, { toValue: 1, useNativeDriver: true }),
|
|
121
|
-
Animated.spring(translateX, { toValue: 0, useNativeDriver: true }),
|
|
122
|
-
Animated.spring(translateY, { toValue: 0, useNativeDriver: true }),
|
|
123
|
-
]).start()
|
|
124
|
-
savedScale.current = 1
|
|
125
|
-
savedTranslateX.current = 0
|
|
126
|
-
savedTranslateY.current = 0
|
|
127
|
-
currentScale.current = 1
|
|
128
|
-
currentTranslateX.current = 0
|
|
129
|
-
currentTranslateY.current = 0
|
|
130
|
-
} else {
|
|
131
|
-
savedTranslateX.current = currentTranslateX.current
|
|
132
|
-
savedTranslateY.current = currentTranslateY.current
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
// Double tap to zoom
|
|
137
|
-
const doubleTapGesture = Gesture.Tap()
|
|
138
|
-
.numberOfTaps(2)
|
|
139
|
-
.onEnd(() => {
|
|
140
|
-
if (savedScale.current > 1) {
|
|
141
|
-
// Zoom out
|
|
142
|
-
Animated.parallel([
|
|
143
|
-
Animated.spring(scale, { toValue: 1, useNativeDriver: true }),
|
|
144
|
-
Animated.spring(translateX, { toValue: 0, useNativeDriver: true }),
|
|
145
|
-
Animated.spring(translateY, { toValue: 0, useNativeDriver: true }),
|
|
146
|
-
]).start()
|
|
147
|
-
savedScale.current = 1
|
|
148
|
-
savedTranslateX.current = 0
|
|
149
|
-
savedTranslateY.current = 0
|
|
150
|
-
currentScale.current = 1
|
|
151
|
-
currentTranslateX.current = 0
|
|
152
|
-
currentTranslateY.current = 0
|
|
153
|
-
} else {
|
|
154
|
-
// Zoom in to 2x
|
|
155
|
-
Animated.spring(scale, { toValue: 2, useNativeDriver: true }).start()
|
|
156
|
-
savedScale.current = 2
|
|
157
|
-
currentScale.current = 2
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
// Test simple single tap gesture
|
|
162
|
-
|
|
163
|
-
// Combine gestures - simplified approach
|
|
164
|
-
const combinedGestures = Gesture.Race(
|
|
165
|
-
doubleTapGesture,
|
|
166
|
-
Gesture.Simultaneous(panGesture, pinchGesture),
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
if (!isOpen) {
|
|
170
|
-
return null
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return (
|
|
174
|
-
<Modal visible={isOpen} transparent animationType="fade" onRequestClose={onClose}>
|
|
175
|
-
<GestureHandlerRootView style={styles.gestureRoot}>
|
|
176
|
-
<View style={styles.modalOverlay}>
|
|
177
|
-
<TouchableOpacity
|
|
178
|
-
style={styles.backgroundTouchable}
|
|
179
|
-
activeOpacity={1}
|
|
180
|
-
onPress={onClose}
|
|
181
|
-
/>
|
|
182
|
-
<View style={styles.contentContainer}>
|
|
183
|
-
<GestureDetector gesture={combinedGestures}>
|
|
184
|
-
<Animated.View
|
|
185
|
-
style={[
|
|
186
|
-
styles.imageContainer,
|
|
187
|
-
{
|
|
188
|
-
width,
|
|
189
|
-
height,
|
|
190
|
-
opacity: loading ? 0 : 1,
|
|
191
|
-
transform: [{ scale }, { translateX }, { translateY }],
|
|
192
|
-
},
|
|
193
|
-
]}>
|
|
194
|
-
<Image
|
|
195
|
-
source={src ? { uri: src } : undefined}
|
|
196
|
-
style={styles.image}
|
|
197
|
-
onLoadStart={onLoadStart}
|
|
198
|
-
onLoad={onLoad}
|
|
199
|
-
resizeMode="contain"
|
|
200
|
-
/>
|
|
201
|
-
</Animated.View>
|
|
202
|
-
</GestureDetector>
|
|
203
|
-
|
|
204
|
-
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
|
|
205
|
-
<Ionicons name="close" size={24} color="white" />
|
|
206
|
-
</TouchableOpacity>
|
|
207
|
-
|
|
208
|
-
{loading || !src ? (
|
|
209
|
-
<View style={styles.loadingContainer}>
|
|
210
|
-
<LoadingLogo isLoading={loading} size={70} />
|
|
211
|
-
</View>
|
|
212
|
-
) : null}
|
|
213
|
-
</View>
|
|
214
|
-
</View>
|
|
215
|
-
</GestureHandlerRootView>
|
|
216
|
-
</Modal>
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const styles = StyleSheet.create({
|
|
221
|
-
gestureRoot: {
|
|
222
|
-
flex: 1,
|
|
223
|
-
},
|
|
224
|
-
modalOverlay: {
|
|
225
|
-
flex: 1,
|
|
226
|
-
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
227
|
-
},
|
|
228
|
-
backgroundTouchable: {
|
|
229
|
-
...StyleSheet.absoluteFillObject,
|
|
230
|
-
},
|
|
231
|
-
contentContainer: {
|
|
232
|
-
flex: 1,
|
|
233
|
-
justifyContent: 'center',
|
|
234
|
-
alignItems: 'center',
|
|
235
|
-
padding: 16,
|
|
236
|
-
},
|
|
237
|
-
imageContainer: {
|
|
238
|
-
overflow: 'hidden',
|
|
239
|
-
borderRadius: 4,
|
|
240
|
-
},
|
|
241
|
-
image: {
|
|
242
|
-
width: '100%',
|
|
243
|
-
height: '100%',
|
|
244
|
-
},
|
|
245
|
-
closeButton: {
|
|
246
|
-
position: 'absolute',
|
|
247
|
-
top: 16,
|
|
248
|
-
right: 16,
|
|
249
|
-
width: 40,
|
|
250
|
-
height: 40,
|
|
251
|
-
borderRadius: 20,
|
|
252
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
253
|
-
justifyContent: 'center',
|
|
254
|
-
alignItems: 'center',
|
|
255
|
-
},
|
|
256
|
-
loadingContainer: {
|
|
257
|
-
...StyleSheet.absoluteFillObject,
|
|
258
|
-
justifyContent: 'center',
|
|
259
|
-
alignItems: 'center',
|
|
260
|
-
},
|
|
261
|
-
})
|
|
@@ -1,261 +0,0 @@
|
|
|
1
|
-
import { useScreen } from '@chem-po/react'
|
|
2
|
-
import { Ionicons } from '@expo/vector-icons'
|
|
3
|
-
import React, { useCallback, useMemo, useRef, useState } from 'react'
|
|
4
|
-
import { Animated, Image, Modal, StyleSheet, TouchableOpacity, View } from 'react-native'
|
|
5
|
-
import { Gesture, GestureDetector, GestureHandlerRootView } from 'react-native-gesture-handler'
|
|
6
|
-
import { LoadingLogo } from '../loading/Loading'
|
|
7
|
-
|
|
8
|
-
interface ImageViewModalProps {
|
|
9
|
-
isOpen: boolean
|
|
10
|
-
onClose: () => void
|
|
11
|
-
src: string | null
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
export const ImageViewModal: React.FC<ImageViewModalProps> = ({ isOpen, onClose, src }) => {
|
|
15
|
-
const [loading, setLoading] = useState(true)
|
|
16
|
-
const screenWidth = useScreen(s => s.width)
|
|
17
|
-
const screenHeight = useScreen(s => s.height)
|
|
18
|
-
const [imageSize, setImageSize] = useState({ width: screenWidth / 2, height: screenHeight / 2 })
|
|
19
|
-
|
|
20
|
-
// Animated values for zoom and pan
|
|
21
|
-
const scale = useRef(new Animated.Value(1)).current
|
|
22
|
-
const translateX = useRef(new Animated.Value(0)).current
|
|
23
|
-
const translateY = useRef(new Animated.Value(0)).current
|
|
24
|
-
|
|
25
|
-
// Gesture state
|
|
26
|
-
const savedScale = useRef(1)
|
|
27
|
-
const savedTranslateX = useRef(0)
|
|
28
|
-
const savedTranslateY = useRef(0)
|
|
29
|
-
const currentScale = useRef(1)
|
|
30
|
-
const currentTranslateX = useRef(0)
|
|
31
|
-
const currentTranslateY = useRef(0)
|
|
32
|
-
|
|
33
|
-
const { height, width } = useMemo(() => {
|
|
34
|
-
if (loading) return imageSize
|
|
35
|
-
const ratio = imageSize.width / imageSize.height
|
|
36
|
-
let h = Math.min(imageSize.height, screenHeight * 0.9)
|
|
37
|
-
let w = h * ratio
|
|
38
|
-
if (w > screenWidth * 0.9) {
|
|
39
|
-
w = Math.min(imageSize.width, screenWidth * 0.9)
|
|
40
|
-
h = w / ratio
|
|
41
|
-
}
|
|
42
|
-
return { height: h, width: w }
|
|
43
|
-
}, [screenHeight, screenWidth, imageSize, loading])
|
|
44
|
-
|
|
45
|
-
const onLoadStart = useCallback(() => setLoading(true), [])
|
|
46
|
-
const onLoad = useCallback((e: any) => {
|
|
47
|
-
const { width: naturalWidth, height: naturalHeight } = e.nativeEvent.source
|
|
48
|
-
setImageSize({ width: naturalWidth, height: naturalHeight })
|
|
49
|
-
setLoading(false)
|
|
50
|
-
}, [])
|
|
51
|
-
|
|
52
|
-
// Reset zoom and pan when modal opens/closes
|
|
53
|
-
const resetTransform = useCallback(() => {
|
|
54
|
-
scale.setValue(1)
|
|
55
|
-
translateX.setValue(0)
|
|
56
|
-
translateY.setValue(0)
|
|
57
|
-
savedScale.current = 1
|
|
58
|
-
savedTranslateX.current = 0
|
|
59
|
-
savedTranslateY.current = 0
|
|
60
|
-
currentScale.current = 1
|
|
61
|
-
currentTranslateX.current = 0
|
|
62
|
-
currentTranslateY.current = 0
|
|
63
|
-
}, [scale, translateX, translateY])
|
|
64
|
-
|
|
65
|
-
// Reset when modal closes or src changes
|
|
66
|
-
React.useEffect(() => {
|
|
67
|
-
if (!isOpen || !src) {
|
|
68
|
-
resetTransform()
|
|
69
|
-
}
|
|
70
|
-
}, [isOpen, src, resetTransform])
|
|
71
|
-
|
|
72
|
-
// Pan gesture
|
|
73
|
-
const panGesture = Gesture.Pan()
|
|
74
|
-
.onUpdate(event => {
|
|
75
|
-
// Only allow panning if zoomed in
|
|
76
|
-
if (savedScale.current > 1) {
|
|
77
|
-
const newTranslateX = savedTranslateX.current + event.translationX
|
|
78
|
-
const newTranslateY = savedTranslateY.current + event.translationY
|
|
79
|
-
|
|
80
|
-
// Calculate bounds to prevent panning too far
|
|
81
|
-
const maxTranslateX = (width * savedScale.current - width) / 2
|
|
82
|
-
const maxTranslateY = (height * savedScale.current - height) / 2
|
|
83
|
-
|
|
84
|
-
const boundedTranslateX = Math.max(-maxTranslateX, Math.min(maxTranslateX, newTranslateX))
|
|
85
|
-
const boundedTranslateY = Math.max(-maxTranslateY, Math.min(maxTranslateY, newTranslateY))
|
|
86
|
-
|
|
87
|
-
translateX.setValue(boundedTranslateX)
|
|
88
|
-
translateY.setValue(boundedTranslateY)
|
|
89
|
-
currentTranslateX.current = boundedTranslateX
|
|
90
|
-
currentTranslateY.current = boundedTranslateY
|
|
91
|
-
}
|
|
92
|
-
})
|
|
93
|
-
.onEnd(() => {
|
|
94
|
-
savedTranslateX.current = currentTranslateX.current
|
|
95
|
-
savedTranslateY.current = currentTranslateY.current
|
|
96
|
-
})
|
|
97
|
-
|
|
98
|
-
// Pinch gesture
|
|
99
|
-
const pinchGesture = Gesture.Pinch()
|
|
100
|
-
.onUpdate(event => {
|
|
101
|
-
const newScale = savedScale.current * event.scale
|
|
102
|
-
// Limit zoom between 1x and 5x
|
|
103
|
-
const boundedScale = Math.max(1, Math.min(5, newScale))
|
|
104
|
-
scale.setValue(boundedScale)
|
|
105
|
-
currentScale.current = boundedScale
|
|
106
|
-
|
|
107
|
-
// If zooming out to 1x, reset position
|
|
108
|
-
if (boundedScale <= 1) {
|
|
109
|
-
translateX.setValue(0)
|
|
110
|
-
translateY.setValue(0)
|
|
111
|
-
currentTranslateX.current = 0
|
|
112
|
-
currentTranslateY.current = 0
|
|
113
|
-
}
|
|
114
|
-
})
|
|
115
|
-
.onEnd(() => {
|
|
116
|
-
savedScale.current = currentScale.current
|
|
117
|
-
// If scale is close to 1, snap back to 1
|
|
118
|
-
if (savedScale.current < 1.1) {
|
|
119
|
-
Animated.parallel([
|
|
120
|
-
Animated.spring(scale, { toValue: 1, useNativeDriver: true }),
|
|
121
|
-
Animated.spring(translateX, { toValue: 0, useNativeDriver: true }),
|
|
122
|
-
Animated.spring(translateY, { toValue: 0, useNativeDriver: true }),
|
|
123
|
-
]).start()
|
|
124
|
-
savedScale.current = 1
|
|
125
|
-
savedTranslateX.current = 0
|
|
126
|
-
savedTranslateY.current = 0
|
|
127
|
-
currentScale.current = 1
|
|
128
|
-
currentTranslateX.current = 0
|
|
129
|
-
currentTranslateY.current = 0
|
|
130
|
-
} else {
|
|
131
|
-
savedTranslateX.current = currentTranslateX.current
|
|
132
|
-
savedTranslateY.current = currentTranslateY.current
|
|
133
|
-
}
|
|
134
|
-
})
|
|
135
|
-
|
|
136
|
-
// Double tap to zoom
|
|
137
|
-
const doubleTapGesture = Gesture.Tap()
|
|
138
|
-
.numberOfTaps(2)
|
|
139
|
-
.onEnd(() => {
|
|
140
|
-
if (savedScale.current > 1) {
|
|
141
|
-
// Zoom out
|
|
142
|
-
Animated.parallel([
|
|
143
|
-
Animated.spring(scale, { toValue: 1, useNativeDriver: true }),
|
|
144
|
-
Animated.spring(translateX, { toValue: 0, useNativeDriver: true }),
|
|
145
|
-
Animated.spring(translateY, { toValue: 0, useNativeDriver: true }),
|
|
146
|
-
]).start()
|
|
147
|
-
savedScale.current = 1
|
|
148
|
-
savedTranslateX.current = 0
|
|
149
|
-
savedTranslateY.current = 0
|
|
150
|
-
currentScale.current = 1
|
|
151
|
-
currentTranslateX.current = 0
|
|
152
|
-
currentTranslateY.current = 0
|
|
153
|
-
} else {
|
|
154
|
-
// Zoom in to 2x
|
|
155
|
-
Animated.spring(scale, { toValue: 2, useNativeDriver: true }).start()
|
|
156
|
-
savedScale.current = 2
|
|
157
|
-
currentScale.current = 2
|
|
158
|
-
}
|
|
159
|
-
})
|
|
160
|
-
|
|
161
|
-
// Test simple single tap gesture
|
|
162
|
-
|
|
163
|
-
// Combine gestures - simplified approach
|
|
164
|
-
const combinedGestures = Gesture.Race(
|
|
165
|
-
doubleTapGesture,
|
|
166
|
-
Gesture.Simultaneous(panGesture, pinchGesture),
|
|
167
|
-
)
|
|
168
|
-
|
|
169
|
-
if (!isOpen) {
|
|
170
|
-
return null
|
|
171
|
-
}
|
|
172
|
-
|
|
173
|
-
return (
|
|
174
|
-
<Modal visible={isOpen} transparent animationType="fade" onRequestClose={onClose}>
|
|
175
|
-
<GestureHandlerRootView style={styles.gestureRoot}>
|
|
176
|
-
<View style={styles.modalOverlay}>
|
|
177
|
-
<TouchableOpacity
|
|
178
|
-
style={styles.backgroundTouchable}
|
|
179
|
-
activeOpacity={1}
|
|
180
|
-
onPress={onClose}
|
|
181
|
-
/>
|
|
182
|
-
<View style={styles.contentContainer}>
|
|
183
|
-
<GestureDetector gesture={combinedGestures}>
|
|
184
|
-
<Animated.View
|
|
185
|
-
style={[
|
|
186
|
-
styles.imageContainer,
|
|
187
|
-
{
|
|
188
|
-
width,
|
|
189
|
-
height,
|
|
190
|
-
opacity: loading ? 0 : 1,
|
|
191
|
-
transform: [{ scale }, { translateX }, { translateY }],
|
|
192
|
-
},
|
|
193
|
-
]}>
|
|
194
|
-
<Image
|
|
195
|
-
source={src ? { uri: src } : undefined}
|
|
196
|
-
style={styles.image}
|
|
197
|
-
onLoadStart={onLoadStart}
|
|
198
|
-
onLoad={onLoad}
|
|
199
|
-
resizeMode="contain"
|
|
200
|
-
/>
|
|
201
|
-
</Animated.View>
|
|
202
|
-
</GestureDetector>
|
|
203
|
-
|
|
204
|
-
<TouchableOpacity style={styles.closeButton} onPress={onClose}>
|
|
205
|
-
<Ionicons name="close" size={24} color="white" />
|
|
206
|
-
</TouchableOpacity>
|
|
207
|
-
|
|
208
|
-
{loading || !src ? (
|
|
209
|
-
<View style={styles.loadingContainer}>
|
|
210
|
-
<LoadingLogo isLoading={loading} size={70} />
|
|
211
|
-
</View>
|
|
212
|
-
) : null}
|
|
213
|
-
</View>
|
|
214
|
-
</View>
|
|
215
|
-
</GestureHandlerRootView>
|
|
216
|
-
</Modal>
|
|
217
|
-
)
|
|
218
|
-
}
|
|
219
|
-
|
|
220
|
-
const styles = StyleSheet.create({
|
|
221
|
-
gestureRoot: {
|
|
222
|
-
flex: 1,
|
|
223
|
-
},
|
|
224
|
-
modalOverlay: {
|
|
225
|
-
flex: 1,
|
|
226
|
-
backgroundColor: 'rgba(0, 0, 0, 0.7)',
|
|
227
|
-
},
|
|
228
|
-
backgroundTouchable: {
|
|
229
|
-
...StyleSheet.absoluteFillObject,
|
|
230
|
-
},
|
|
231
|
-
contentContainer: {
|
|
232
|
-
flex: 1,
|
|
233
|
-
justifyContent: 'center',
|
|
234
|
-
alignItems: 'center',
|
|
235
|
-
padding: 16,
|
|
236
|
-
},
|
|
237
|
-
imageContainer: {
|
|
238
|
-
overflow: 'hidden',
|
|
239
|
-
borderRadius: 4,
|
|
240
|
-
},
|
|
241
|
-
image: {
|
|
242
|
-
width: '100%',
|
|
243
|
-
height: '100%',
|
|
244
|
-
},
|
|
245
|
-
closeButton: {
|
|
246
|
-
position: 'absolute',
|
|
247
|
-
top: 16,
|
|
248
|
-
right: 16,
|
|
249
|
-
width: 40,
|
|
250
|
-
height: 40,
|
|
251
|
-
borderRadius: 20,
|
|
252
|
-
backgroundColor: 'rgba(0, 0, 0, 0.5)',
|
|
253
|
-
justifyContent: 'center',
|
|
254
|
-
alignItems: 'center',
|
|
255
|
-
},
|
|
256
|
-
loadingContainer: {
|
|
257
|
-
...StyleSheet.absoluteFillObject,
|
|
258
|
-
justifyContent: 'center',
|
|
259
|
-
alignItems: 'center',
|
|
260
|
-
},
|
|
261
|
-
})
|