import type { ReactElement } from 'react'; import { Modal, Pressable, StyleSheet, View } from 'react-native'; import { Image } from 'expo-image'; import { useVideoPlayer, VideoView } from 'expo-video'; import { Gesture, GestureDetector } from 'react-native-gesture-handler'; import Animated, { useAnimatedStyle, useSharedValue, withTiming, } from 'react-native-reanimated'; import { useSafeAreaInsets } from 'react-native-safe-area-context'; import { Icon } from '@/components'; export interface ViewerSource { url: string; isVideo: boolean; } interface Props { source: ViewerSource | null; onClose: () => void; } const ZoomableImage = ({ url }: { url: string }): ReactElement => { const scale = useSharedValue(1); const savedScale = useSharedValue(1); const translateX = useSharedValue(0); const translateY = useSharedValue(0); const pinch = Gesture.Pinch() .onUpdate((event) => { scale.value = Math.max(1, savedScale.value * event.scale); }) .onEnd(() => { savedScale.value = scale.value; if (scale.value <= 1) { translateX.value = withTiming(0); translateY.value = withTiming(0); } }); const pan = Gesture.Pan() .onUpdate((event) => { if (scale.value > 1) { translateX.value = event.translationX; translateY.value = event.translationY; } }); const composed = Gesture.Simultaneous(pinch, pan); const animatedStyle = useAnimatedStyle(() => ({ transform: [ { translateX: translateX.value }, { translateY: translateY.value }, { scale: scale.value }, ], })); return ( ); }; const ViewerVideo = ({ url }: { url: string }): ReactElement => { const player = useVideoPlayer({ uri: url }, (instance) => { instance.play(); }); return ; }; // Full-screen image (pinch-zoom) / video overlay opened from a media bubble. export const MediaViewer = ({ source, onClose }: Props): ReactElement => { const insets = useSafeAreaInsets(); return ( {source !== null ? ( source.isVideo ? ( ) : ( ) ) : null} ); }; const styles = StyleSheet.create({ backdrop: { flex: 1, backgroundColor: '#000' }, fill: { flex: 1, width: '100%', height: '100%' }, close: { position: 'absolute', right: 16, width: 40, height: 40, borderRadius: 20, backgroundColor: 'rgba(0,0,0,0.5)', alignItems: 'center', justifyContent: 'center', }, });