import React, { useCallback, useEffect, useState } from "react"; import { Dimensions, I18nManager, StyleSheet, useWindowDimensions, View, type ScaledSize, } from "react-native"; import Animated, { useAnimatedReaction, useAnimatedStyle, useSharedValue, withDelay, withTiming, } from "react-native-reanimated"; import { scheduleOnRN } from "react-native-worklets"; import { GestureHandlerRootView } from "react-native-gesture-handler"; import { GalleryPreviewForImageProps } from "../../types"; import { SPRING_CONFIG, MAX_SCALE } from "../../constants"; import { DefaultHeader } from "../DefaultHeader/DefaultHeader"; import { GalleryImageItem } from "./GalleryImageItem"; import { GalleryStatusBar } from "../GalleryStatusBar/GalleryStatusBar"; import { ModalContainer } from "../ModalContainer/ModalContainer"; import { DefaultImageComponent } from "../DefaultImageComponent/DefaultImageComponent"; export const GalleryPreviewForImage = ({ isVisible, onRequestClose, initialIndex = 0, gap = 24, simultaneousRenderedImages = 6, OverlayComponent = DefaultHeader, springConfig = SPRING_CONFIG, maxScale = MAX_SCALE, doubleTabEnabled = true, pinchEnabled = true, swipeToCloseEnabled = true, backgroundColor = "#000", headerTextColor = "#fff", images, ImageComponent = DefaultImageComponent, }: GalleryPreviewForImageProps) => { const rtl = I18nManager.isRTL; const dimensions = useWindowDimensions(); const [index, setIndex] = useState(initialIndex); const [isFocused, setIsFocused] = useState(true); const translateX = useSharedValue( initialIndex * -(dimensions.width + gap) * (rtl ? -1 : 1), ); const opacity = useSharedValue(1); const currentIndex = useSharedValue(initialIndex); useAnimatedReaction( () => currentIndex.value, (newIndex) => { scheduleOnRN(setIndex, newIndex); }, [currentIndex], ); const wrapperAnimatedStyle = useAnimatedStyle( () => ({ opacity: opacity.value, }), [isFocused], ); const containerAnimatedStyle = useAnimatedStyle(() => ({ transform: [{ translateX: translateX.value }], })); const isImageVisible = useCallback( (imageIndex: number): boolean => { const halfVisible = Math.floor(simultaneousRenderedImages / 2); const start = Math.max(0, index - halfVisible); const end = Math.min( images.length - 1, start + simultaneousRenderedImages - 1, ); return imageIndex >= start && imageIndex <= end; }, [images.length, index, simultaneousRenderedImages], ); const getImagePositionX = useCallback( (i: number) => { return i * -(Dimensions.get("window").width + gap) * (rtl ? -1 : 1); }, [gap, rtl], ); useEffect(() => { if (isVisible) { opacity.value = 1; currentIndex.value = initialIndex; translateX.value = getImagePositionX(initialIndex); setIsFocused(true); } }, [ currentIndex, getImagePositionX, initialIndex, isVisible, opacity, translateX, ]); useEffect(() => { const subscription = Dimensions.addEventListener( "change", ({ window }: { window: ScaledSize }) => { const { width } = window; translateX.value = withDelay( 0, withTiming(currentIndex.value * -(width + gap) * (rtl ? -1 : 1)), ); }, ); return () => { subscription.remove(); }; }, [currentIndex, gap, rtl, translateX]); return ( {images.map((image, i) => { const visible = isImageVisible(i); return ( {visible && ( )} ); })} ); }; const styles = StyleSheet.create({ wrapper: { flex: 1 }, gestureContainer: { flex: 1 }, container: { flexDirection: "row" }, });