/** * Copyright (c) JOB TODAY S.A. and its affiliates. * * This source code is licensed under the MIT license found in the * LICENSE file in the root directory of this source tree. * */ // Original code copied and simplified from the link below as the codebase is currently not maintained: // https://github.com/jobtoday/react-native-image-viewing import React, {useCallback, useMemo, useState} from 'react' import { Dimensions, LayoutAnimation, Platform, StyleSheet, View, } from 'react-native' import PagerView from 'react-native-pager-view' import {MeasuredDimensions} from 'react-native-reanimated' import Animated, {useAnimatedStyle, withSpring} from 'react-native-reanimated' import {useSafeAreaInsets} from 'react-native-safe-area-context' import {Edge, SafeAreaView} from 'react-native-safe-area-context' import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome' import {Trans} from '@lingui/macro' import {colors, s} from '#/lib/styles' import {isIOS} from '#/platform/detection' import {Button} from '#/view/com/util/forms/Button' import {Text} from '#/view/com/util/text/Text' import {ScrollView} from '#/view/com/util/Views' import {ImageSource} from './@types' import ImageDefaultHeader from './components/ImageDefaultHeader' import ImageItem from './components/ImageItem/ImageItem' type Props = { images: ImageSource[] thumbDims: MeasuredDimensions | null initialImageIndex: number visible: boolean onRequestClose: () => void backgroundColor?: string onPressSave: (uri: string) => void onPressShare: (uri: string) => void } const SCREEN_HEIGHT = Dimensions.get('window').height const DEFAULT_BG_COLOR = '#000' function ImageViewing({ images, thumbDims: _thumbDims, // TODO: Pass down and use for animation. initialImageIndex, visible, onRequestClose, backgroundColor = DEFAULT_BG_COLOR, onPressSave, onPressShare, }: Props) { const [isScaled, setIsScaled] = useState(false) const [isDragging, setIsDragging] = useState(false) const [imageIndex, setImageIndex] = useState(initialImageIndex) const [showControls, setShowControls] = useState(true) const animatedHeaderStyle = useAnimatedStyle(() => ({ pointerEvents: showControls ? 'auto' : 'none', opacity: withClampedSpring(showControls ? 1 : 0), transform: [ { translateY: withClampedSpring(showControls ? 0 : -30), }, ], })) const animatedFooterStyle = useAnimatedStyle(() => ({ pointerEvents: showControls ? 'auto' : 'none', opacity: withClampedSpring(showControls ? 1 : 0), transform: [ { translateY: withClampedSpring(showControls ? 0 : 30), }, ], })) const onTap = useCallback(() => { setShowControls(show => !show) }, []) const onZoom = useCallback((nextIsScaled: boolean) => { setIsScaled(nextIsScaled) if (nextIsScaled) { setShowControls(false) } }, []) const edges = useMemo(() => { if (Platform.OS === 'android') { return ['top', 'bottom', 'left', 'right'] satisfies Edge[] } return ['left', 'right'] satisfies Edge[] // iOS, so no top/bottom safe area }, []) if (!visible) { return null } return ( { setImageIndex(e.nativeEvent.position) setIsScaled(false) }} onPageScrollStateChanged={e => { setIsDragging(e.nativeEvent.pageScrollState !== 'idle') }} overdrag={true} style={styles.pager}> {images.map(imageSrc => ( ))} ) } function LightboxFooter({ images, index, onPressSave, onPressShare, }: { images: ImageSource[] index: number onPressSave: (uri: string) => void onPressShare: (uri: string) => void }) { const {alt: altText, uri} = images[index] const [isAltExpanded, setAltExpanded] = React.useState(false) const insets = useSafeAreaInsets() const svMaxHeight = SCREEN_HEIGHT - insets.top - 50 const isMomentumScrolling = React.useRef(false) return ( { isMomentumScrolling.current = true }} onMomentumScrollEnd={() => { isMomentumScrolling.current = false }} contentContainerStyle={{ paddingTop: 16, paddingBottom: insets.bottom + 10, paddingHorizontal: 24, }}> {altText ? ( { if (isMomentumScrolling.current) { return } LayoutAnimation.configureNext({ duration: 450, update: {type: 'spring', springDamping: 1}, }) setAltExpanded(prev => !prev) }} onLongPress={() => {}}> {altText} ) : null} ) } const styles = StyleSheet.create({ screen: { position: 'absolute', top: 0, left: 0, bottom: 0, right: 0, }, container: { flex: 1, backgroundColor: '#000', }, pager: { flex: 1, }, header: { position: 'absolute', width: '100%', zIndex: 1, top: 0, pointerEvents: 'box-none', }, footer: { position: 'absolute', width: '100%', zIndex: 1, bottom: 0, }, footerText: { paddingBottom: isIOS ? 20 : 16, }, footerBtns: { flexDirection: 'row', justifyContent: 'center', gap: 8, }, footerBtn: { flexDirection: 'row', alignItems: 'center', gap: 8, backgroundColor: 'transparent', borderColor: colors.white, }, }) const EnhancedImageViewing = (props: Props) => ( ) function withClampedSpring(value: any) { 'worklet' return withSpring(value, {overshootClamping: true, stiffness: 300}) } export default EnhancedImageViewing