From 4b8549857af111cf15d803fed01a2320ad8070a3 Mon Sep 17 00:00:00 2001 From: LeoMacherla Date: Thu, 30 Jun 2022 17:42:03 +0100 Subject: [PATCH] Added image loading customisation --- src/ImageViewing.tsx | 354 ++++++++++-------- .../ImageItem/ImageItem.android.tsx | 308 ++++++++------- src/components/ImageItem/ImageItem.d.ts | 45 ++- src/components/ImageItem/ImageItem.ios.tsx | 323 +++++++++------- 4 files changed, 564 insertions(+), 466 deletions(-) diff --git a/src/ImageViewing.tsx b/src/ImageViewing.tsx index 3782ee0e..265fd566 100644 --- a/src/ImageViewing.tsx +++ b/src/ImageViewing.tsx @@ -6,187 +6,211 @@ * */ -import React, { ComponentType, useCallback, useRef, useEffect } from "react"; +import React, { + ComponentType, + useCallback, + useRef, + useEffect, + ReactNode +} from 'react' import { - Animated, - Dimensions, - StyleSheet, - View, - VirtualizedList, - ModalProps, - Modal, -} from "react-native"; + Animated, + Dimensions, + StyleSheet, + View, + VirtualizedList, + ModalProps, + Modal +} from 'react-native' -import ImageItem from "./components/ImageItem/ImageItem"; -import ImageDefaultHeader from "./components/ImageDefaultHeader"; -import StatusBarManager from "./components/StatusBarManager"; +import ImageItem from './components/ImageItem/ImageItem' +import ImageDefaultHeader from './components/ImageDefaultHeader' +import StatusBarManager from './components/StatusBarManager' -import useAnimatedComponents from "./hooks/useAnimatedComponents"; -import useImageIndexChange from "./hooks/useImageIndexChange"; -import useRequestClose from "./hooks/useRequestClose"; -import { ImageSource } from "./@types"; +import useAnimatedComponents from './hooks/useAnimatedComponents' +import useImageIndexChange from './hooks/useImageIndexChange' +import useRequestClose from './hooks/useRequestClose' +import { ImageSource } from './@types' type Props = { - images: ImageSource[]; - keyExtractor?: (imageSrc: ImageSource, index: number) => string; - imageIndex: number; - visible: boolean; - onRequestClose: () => void; - onLongPress?: (image: ImageSource) => void; - onImageIndexChange?: (imageIndex: number) => void; - presentationStyle?: ModalProps["presentationStyle"]; - animationType?: ModalProps["animationType"]; - backgroundColor?: string; - swipeToCloseEnabled?: boolean; - doubleTapToZoomEnabled?: boolean; - delayLongPress?: number; - HeaderComponent?: ComponentType<{ imageIndex: number }>; - FooterComponent?: ComponentType<{ imageIndex: number }>; -}; + images: ImageSource[] + keyExtractor?: (imageSrc: ImageSource, index: number) => string + imageIndex: number + visible: boolean + onRequestClose: () => void + onLongPress?: (image: ImageSource) => void + onImageIndexChange?: (imageIndex: number) => void + presentationStyle?: ModalProps['presentationStyle'] + animationType?: ModalProps['animationType'] + backgroundColor?: string + swipeToCloseEnabled?: boolean + doubleTapToZoomEnabled?: boolean + delayLongPress?: number + HeaderComponent?: ComponentType<{ imageIndex: number }> + FooterComponent?: ComponentType<{ imageIndex: number }> + hideDefaultLoadingComponent?: boolean + customLoadingComponent?: ReactNode + onImageLoad?: () => void +} -const DEFAULT_ANIMATION_TYPE = "fade"; -const DEFAULT_BG_COLOR = "#000"; -const DEFAULT_DELAY_LONG_PRESS = 800; -const SCREEN = Dimensions.get("screen"); -const SCREEN_WIDTH = SCREEN.width; +const DEFAULT_ANIMATION_TYPE = 'fade' +const DEFAULT_BG_COLOR = '#000' +const DEFAULT_DELAY_LONG_PRESS = 800 +const SCREEN = Dimensions.get('screen') +const SCREEN_WIDTH = SCREEN.width function ImageViewing({ - images, - keyExtractor, - imageIndex, - visible, - onRequestClose, - onLongPress = () => {}, - onImageIndexChange, - animationType = DEFAULT_ANIMATION_TYPE, - backgroundColor = DEFAULT_BG_COLOR, - presentationStyle, - swipeToCloseEnabled, - doubleTapToZoomEnabled, - delayLongPress = DEFAULT_DELAY_LONG_PRESS, - HeaderComponent, - FooterComponent, + images, + keyExtractor, + imageIndex, + visible, + onRequestClose, + onLongPress = () => {}, + onImageIndexChange, + animationType = DEFAULT_ANIMATION_TYPE, + backgroundColor = DEFAULT_BG_COLOR, + presentationStyle, + swipeToCloseEnabled, + doubleTapToZoomEnabled, + delayLongPress = DEFAULT_DELAY_LONG_PRESS, + HeaderComponent, + FooterComponent, + hideDefaultLoadingComponent, + customLoadingComponent, + onImageLoad }: Props) { - const imageList = useRef>(null); - const [opacity, onRequestCloseEnhanced] = useRequestClose(onRequestClose); - const [currentImageIndex, onScroll] = useImageIndexChange(imageIndex, SCREEN); - const [headerTransform, footerTransform, toggleBarsVisible] = - useAnimatedComponents(); + const imageList = useRef>(null) + const [opacity, onRequestCloseEnhanced] = useRequestClose(onRequestClose) + const [currentImageIndex, onScroll] = useImageIndexChange( + imageIndex, + SCREEN + ) + const [headerTransform, footerTransform, toggleBarsVisible] = + useAnimatedComponents() - useEffect(() => { - if (onImageIndexChange) { - onImageIndexChange(currentImageIndex); - } - }, [currentImageIndex]); + useEffect(() => { + if (onImageIndexChange) { + onImageIndexChange(currentImageIndex) + } + }, [currentImageIndex]) - const onZoom = useCallback( - (isScaled: boolean) => { - // @ts-ignore - imageList?.current?.setNativeProps({ scrollEnabled: !isScaled }); - toggleBarsVisible(!isScaled); - }, - [imageList] - ); + const onZoom = useCallback( + (isScaled: boolean) => { + // @ts-ignore + imageList?.current?.setNativeProps({ scrollEnabled: !isScaled }) + toggleBarsVisible(!isScaled) + }, + [imageList] + ) - if (!visible) { - return null; - } + if (!visible) { + return null + } - return ( - - - - - {typeof HeaderComponent !== "undefined" ? ( - React.createElement(HeaderComponent, { - imageIndex: currentImageIndex, - }) - ) : ( - - )} - - images[index]} - getItemCount={() => images.length} - getItemLayout={(_, index) => ({ - length: SCREEN_WIDTH, - offset: SCREEN_WIDTH * index, - index, - })} - renderItem={({ item: imageSrc }) => ( - - )} - onMomentumScrollEnd={onScroll} - //@ts-ignore - keyExtractor={(imageSrc, index) => - keyExtractor - ? keyExtractor(imageSrc, index) - : typeof imageSrc === "number" - ? `${imageSrc}` - : imageSrc.uri - } - /> - {typeof FooterComponent !== "undefined" && ( - - {React.createElement(FooterComponent, { - imageIndex: currentImageIndex, - })} - - )} - - - ); + return ( + + + + + {typeof HeaderComponent !== 'undefined' ? ( + React.createElement(HeaderComponent, { + imageIndex: currentImageIndex + }) + ) : ( + + )} + + images[index]} + getItemCount={() => images.length} + getItemLayout={(_, index) => ({ + length: SCREEN_WIDTH, + offset: SCREEN_WIDTH * index, + index + })} + renderItem={({ item: imageSrc }) => ( + + )} + onMomentumScrollEnd={onScroll} + //@ts-ignore + keyExtractor={(imageSrc, index) => + keyExtractor + ? keyExtractor(imageSrc, index) + : typeof imageSrc === 'number' + ? `${imageSrc}` + : imageSrc.uri + } + /> + {typeof FooterComponent !== 'undefined' && ( + + {React.createElement(FooterComponent, { + imageIndex: currentImageIndex + })} + + )} + + + ) } const styles = StyleSheet.create({ - container: { - flex: 1, - backgroundColor: "#000", - }, - header: { - position: "absolute", - width: "100%", - zIndex: 1, - top: 0, - }, - footer: { - position: "absolute", - width: "100%", - zIndex: 1, - bottom: 0, - }, -}); + container: { + flex: 1, + backgroundColor: '#000' + }, + header: { + position: 'absolute', + width: '100%', + zIndex: 1, + top: 0 + }, + footer: { + position: 'absolute', + width: '100%', + zIndex: 1, + bottom: 0 + } +}) const EnhancedImageViewing = (props: Props) => ( - -); + +) -export default EnhancedImageViewing; +export default EnhancedImageViewing diff --git a/src/components/ImageItem/ImageItem.android.tsx b/src/components/ImageItem/ImageItem.android.tsx index f463b0b1..80ee6a63 100644 --- a/src/components/ImageItem/ImageItem.android.tsx +++ b/src/components/ImageItem/ImageItem.android.tsx @@ -6,150 +6,182 @@ * */ -import React, { useCallback, useRef, useState } from "react"; +import React, { ReactNode, useCallback, useRef, useState } from 'react' import { - Animated, - ScrollView, - Dimensions, - StyleSheet, - NativeScrollEvent, - NativeSyntheticEvent, - NativeMethodsMixin, -} from "react-native"; - -import useImageDimensions from "../../hooks/useImageDimensions"; -import usePanResponder from "../../hooks/usePanResponder"; - -import { getImageStyles, getImageTransform } from "../../utils"; -import { ImageSource } from "../../@types"; -import { ImageLoading } from "./ImageLoading"; - -const SWIPE_CLOSE_OFFSET = 75; -const SWIPE_CLOSE_VELOCITY = 1.75; -const SCREEN = Dimensions.get("window"); -const SCREEN_WIDTH = SCREEN.width; -const SCREEN_HEIGHT = SCREEN.height; + Animated, + ScrollView, + Dimensions, + StyleSheet, + NativeScrollEvent, + NativeSyntheticEvent, + NativeMethodsMixin, + View +} from 'react-native' + +import useImageDimensions from '../../hooks/useImageDimensions' +import usePanResponder from '../../hooks/usePanResponder' + +import { getImageStyles, getImageTransform } from '../../utils' +import { ImageSource } from '../../@types' +import { ImageLoading } from './ImageLoading' + +const SWIPE_CLOSE_OFFSET = 75 +const SWIPE_CLOSE_VELOCITY = 1.75 +const SCREEN = Dimensions.get('window') +const SCREEN_WIDTH = SCREEN.width +const SCREEN_HEIGHT = SCREEN.height type Props = { - imageSrc: ImageSource; - onRequestClose: () => void; - onZoom: (isZoomed: boolean) => void; - onLongPress: (image: ImageSource) => void; - delayLongPress: number; - swipeToCloseEnabled?: boolean; - doubleTapToZoomEnabled?: boolean; -}; + imageSrc: ImageSource + onRequestClose: () => void + onZoom: (isZoomed: boolean) => void + onLongPress: (image: ImageSource) => void + delayLongPress: number + swipeToCloseEnabled?: boolean + doubleTapToZoomEnabled?: boolean + hideDefaultLoadingComponent?: boolean + customLoadingComponent?: ReactNode + onImageLoad?: () => void +} const ImageItem = ({ - imageSrc, - onZoom, - onRequestClose, - onLongPress, - delayLongPress, - swipeToCloseEnabled = true, - doubleTapToZoomEnabled = true, + imageSrc, + onZoom, + onRequestClose, + onLongPress, + delayLongPress, + swipeToCloseEnabled = true, + doubleTapToZoomEnabled = true, + hideDefaultLoadingComponent, + customLoadingComponent, + onImageLoad }: Props) => { - const imageContainer = useRef(null); - const imageDimensions = useImageDimensions(imageSrc); - const [translate, scale] = getImageTransform(imageDimensions, SCREEN); - const scrollValueY = new Animated.Value(0); - const [isLoaded, setLoadEnd] = useState(false); - - const onLoaded = useCallback(() => setLoadEnd(true), []); - const onZoomPerformed = useCallback( - (isZoomed: boolean) => { - onZoom(isZoomed); - if (imageContainer?.current) { - imageContainer.current.setNativeProps({ - scrollEnabled: !isZoomed, - }); - } - }, - [imageContainer] - ); - - const onLongPressHandler = useCallback(() => { - onLongPress(imageSrc); - }, [imageSrc, onLongPress]); - - const [panHandlers, scaleValue, translateValue] = usePanResponder({ - initialScale: scale || 1, - initialTranslate: translate || { x: 0, y: 0 }, - onZoom: onZoomPerformed, - doubleTapToZoomEnabled, - onLongPress: onLongPressHandler, - delayLongPress, - }); - - const imagesStyles = getImageStyles( - imageDimensions, - translateValue, - scaleValue - ); - const imageOpacity = scrollValueY.interpolate({ - inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET], - outputRange: [0.7, 1, 0.7], - }); - const imageStylesWithOpacity = { ...imagesStyles, opacity: imageOpacity }; - - const onScrollEndDrag = ({ - nativeEvent, - }: NativeSyntheticEvent) => { - const velocityY = nativeEvent?.velocity?.y ?? 0; - const offsetY = nativeEvent?.contentOffset?.y ?? 0; - - if ( - (Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY && - offsetY > SWIPE_CLOSE_OFFSET) || - offsetY > SCREEN_HEIGHT / 2 - ) { - onRequestClose(); - } - }; - - const onScroll = ({ - nativeEvent, - }: NativeSyntheticEvent) => { - const offsetY = nativeEvent?.contentOffset?.y ?? 0; - - scrollValueY.setValue(offsetY); - }; - - return ( - - - {(!isLoaded || !imageDimensions) && } - - ); -}; + const imageContainer = useRef(null) + const imageDimensions = useImageDimensions(imageSrc) + const [translate, scale] = getImageTransform(imageDimensions, SCREEN) + const scrollValueY = new Animated.Value(0) + const [isLoaded, setLoadEnd] = useState(false) + + const onLoaded = useCallback(() => { + setLoadEnd(true) + + if (!onImageLoad || typeof onImageLoad !== 'function') return + + onImageLoad() + }, []) + const onZoomPerformed = useCallback( + (isZoomed: boolean) => { + onZoom(isZoomed) + if (imageContainer?.current) { + imageContainer.current.setNativeProps({ + scrollEnabled: !isZoomed + }) + } + }, + [imageContainer] + ) + + const onLongPressHandler = useCallback(() => { + onLongPress(imageSrc) + }, [imageSrc, onLongPress]) + + const [panHandlers, scaleValue, translateValue] = usePanResponder({ + initialScale: scale || 1, + initialTranslate: translate || { x: 0, y: 0 }, + onZoom: onZoomPerformed, + doubleTapToZoomEnabled, + onLongPress: onLongPressHandler, + delayLongPress + }) + + const imagesStyles = getImageStyles( + imageDimensions, + translateValue, + scaleValue + ) + const imageOpacity = scrollValueY.interpolate({ + inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET], + outputRange: [0.7, 1, 0.7] + }) + const imageStylesWithOpacity = { ...imagesStyles, opacity: imageOpacity } + + const onScrollEndDrag = ({ + nativeEvent + }: NativeSyntheticEvent) => { + const velocityY = nativeEvent?.velocity?.y ?? 0 + const offsetY = nativeEvent?.contentOffset?.y ?? 0 + + if ( + (Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY && + offsetY > SWIPE_CLOSE_OFFSET) || + offsetY > SCREEN_HEIGHT / 2 + ) { + onRequestClose() + } + } + + const onScroll = ({ + nativeEvent + }: NativeSyntheticEvent) => { + const offsetY = nativeEvent?.contentOffset?.y ?? 0 + + scrollValueY.setValue(offsetY) + } + + return ( + + + {(!isLoaded || !imageDimensions) && + (customLoadingComponent ? ( + + {customLoadingComponent} + + ) : !hideDefaultLoadingComponent ? ( + + ) : ( + + ))} + + ) +} const styles = StyleSheet.create({ - listItem: { - width: SCREEN_WIDTH, - height: SCREEN_HEIGHT, - }, - imageScrollContainer: { - height: SCREEN_HEIGHT * 2, - }, -}); - -export default React.memo(ImageItem); + listItem: { + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT + }, + imageScrollContainer: { + height: SCREEN_HEIGHT * 2 + }, + customLoadingComponent: { + position: 'absolute', + top: 0, + left: 0, + display: 'flex', + height: SCREEN_HEIGHT, + width: SCREEN_WIDTH, + alignItems: 'center', + justifyContent: 'center' + } +}) + +export default React.memo(ImageItem) diff --git a/src/components/ImageItem/ImageItem.d.ts b/src/components/ImageItem/ImageItem.d.ts index 57a902e9..5d7b1975 100644 --- a/src/components/ImageItem/ImageItem.d.ts +++ b/src/components/ImageItem/ImageItem.d.ts @@ -6,27 +6,32 @@ * */ -import React from "react"; -import { GestureResponderEvent } from "react-native"; -import { ImageSource } from "../../@types"; +import React, { ReactNode } from 'react' +import { GestureResponderEvent } from 'react-native' +import { ImageSource } from '../../@types' declare type Props = { - imageSrc: ImageSource; - onRequestClose: () => void; - onZoom: (isZoomed: boolean) => void; - onLongPress: (image: ImageSource) => void; - delayLongPress: number; - swipeToCloseEnabled?: boolean; - doubleTapToZoomEnabled?: boolean; -}; + imageSrc: ImageSource + onRequestClose: () => void + onZoom: (isZoomed: boolean) => void + onLongPress: (image: ImageSource) => void + delayLongPress: number + swipeToCloseEnabled?: boolean + doubleTapToZoomEnabled?: boolean + hideDefaultLoadingComponent?: boolean + customLoadingComponent?: ReactNode + onImageLoad?: () => void +} -declare const _default: React.MemoExoticComponent<({ - imageSrc, - onZoom, - onRequestClose, - onLongPress, - delayLongPress, - swipeToCloseEnabled, -}: Props) => JSX.Element>; +declare const _default: React.MemoExoticComponent< + ({ + imageSrc, + onZoom, + onRequestClose, + onLongPress, + delayLongPress, + swipeToCloseEnabled + }: Props) => JSX.Element +> -export default _default; +export default _default diff --git a/src/components/ImageItem/ImageItem.ios.tsx b/src/components/ImageItem/ImageItem.ios.tsx index de85a146..fd5803f4 100644 --- a/src/components/ImageItem/ImageItem.ios.tsx +++ b/src/components/ImageItem/ImageItem.ios.tsx @@ -6,155 +6,192 @@ * */ -import React, { useCallback, useRef, useState } from "react"; +import React, { useCallback, useRef, useState, ReactNode } from 'react' import { - Animated, - Dimensions, - ScrollView, - StyleSheet, - View, - NativeScrollEvent, - NativeSyntheticEvent, - TouchableWithoutFeedback, - GestureResponderEvent, -} from "react-native"; - -import useDoubleTapToZoom from "../../hooks/useDoubleTapToZoom"; -import useImageDimensions from "../../hooks/useImageDimensions"; - -import { getImageStyles, getImageTransform } from "../../utils"; -import { ImageSource } from "../../@types"; -import { ImageLoading } from "./ImageLoading"; - -const SWIPE_CLOSE_OFFSET = 75; -const SWIPE_CLOSE_VELOCITY = 1.55; -const SCREEN = Dimensions.get("screen"); -const SCREEN_WIDTH = SCREEN.width; -const SCREEN_HEIGHT = SCREEN.height; + Animated, + Dimensions, + ScrollView, + StyleSheet, + View, + NativeScrollEvent, + NativeSyntheticEvent, + TouchableWithoutFeedback, + GestureResponderEvent +} from 'react-native' + +import useDoubleTapToZoom from '../../hooks/useDoubleTapToZoom' +import useImageDimensions from '../../hooks/useImageDimensions' + +import { getImageStyles, getImageTransform } from '../../utils' +import { ImageSource } from '../../@types' +import { ImageLoading } from './ImageLoading' + +const SWIPE_CLOSE_OFFSET = 75 +const SWIPE_CLOSE_VELOCITY = 1.55 +const SCREEN = Dimensions.get('screen') +const SCREEN_WIDTH = SCREEN.width +const SCREEN_HEIGHT = SCREEN.height type Props = { - imageSrc: ImageSource; - onRequestClose: () => void; - onZoom: (scaled: boolean) => void; - onLongPress: (image: ImageSource) => void; - delayLongPress: number; - swipeToCloseEnabled?: boolean; - doubleTapToZoomEnabled?: boolean; -}; + imageSrc: ImageSource + onRequestClose: () => void + onZoom: (scaled: boolean) => void + onLongPress: (image: ImageSource) => void + delayLongPress: number + swipeToCloseEnabled?: boolean + doubleTapToZoomEnabled?: boolean + hideDefaultLoadingComponent?: boolean + customLoadingComponent?: ReactNode + onImageLoad?: () => void +} const ImageItem = ({ - imageSrc, - onZoom, - onRequestClose, - onLongPress, - delayLongPress, - swipeToCloseEnabled = true, - doubleTapToZoomEnabled = true, + imageSrc, + onZoom, + onRequestClose, + onLongPress, + delayLongPress, + swipeToCloseEnabled = true, + doubleTapToZoomEnabled = true, + hideDefaultLoadingComponent, + customLoadingComponent, + onImageLoad }: Props) => { - const scrollViewRef = useRef(null); - const [loaded, setLoaded] = useState(false); - const [scaled, setScaled] = useState(false); - const imageDimensions = useImageDimensions(imageSrc); - const handleDoubleTap = useDoubleTapToZoom(scrollViewRef, scaled, SCREEN); - - const [translate, scale] = getImageTransform(imageDimensions, SCREEN); - const scrollValueY = new Animated.Value(0); - const scaleValue = new Animated.Value(scale || 1); - const translateValue = new Animated.ValueXY(translate); - const maxScale = scale && scale > 0 ? Math.max(1 / scale, 1) : 1; - - const imageOpacity = scrollValueY.interpolate({ - inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET], - outputRange: [0.5, 1, 0.5], - }); - const imagesStyles = getImageStyles( - imageDimensions, - translateValue, - scaleValue - ); - const imageStylesWithOpacity = { ...imagesStyles, opacity: imageOpacity }; - - const onScrollEndDrag = useCallback( - ({ nativeEvent }: NativeSyntheticEvent) => { - const velocityY = nativeEvent?.velocity?.y ?? 0; - const scaled = nativeEvent?.zoomScale > 1; - - onZoom(scaled); - setScaled(scaled); - - if ( - !scaled && - swipeToCloseEnabled && - Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY - ) { - onRequestClose(); - } - }, - [scaled] - ); - - const onScroll = ({ - nativeEvent, - }: NativeSyntheticEvent) => { - const offsetY = nativeEvent?.contentOffset?.y ?? 0; - - if (nativeEvent?.zoomScale > 1) { - return; - } - - scrollValueY.setValue(offsetY); - }; - - const onLongPressHandler = useCallback( - (event: GestureResponderEvent) => { - onLongPress(imageSrc); - }, - [imageSrc, onLongPress] - ); - - return ( - - - {(!loaded || !imageDimensions) && } - - setLoaded(true)} - /> - - - - ); -}; + const scrollViewRef = useRef(null) + const [loaded, setLoaded] = useState(false) + const [scaled, setScaled] = useState(false) + const imageDimensions = useImageDimensions(imageSrc) + const handleDoubleTap = useDoubleTapToZoom(scrollViewRef, scaled, SCREEN) + + const [translate, scale] = getImageTransform(imageDimensions, SCREEN) + const scrollValueY = new Animated.Value(0) + const scaleValue = new Animated.Value(scale || 1) + const translateValue = new Animated.ValueXY(translate) + const maxScale = scale && scale > 0 ? Math.max(1 / scale, 1) : 1 + + const imageOpacity = scrollValueY.interpolate({ + inputRange: [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET], + outputRange: [0.5, 1, 0.5] + }) + const imagesStyles = getImageStyles( + imageDimensions, + translateValue, + scaleValue + ) + const imageStylesWithOpacity = { ...imagesStyles, opacity: imageOpacity } + + const onScrollEndDrag = useCallback( + ({ nativeEvent }: NativeSyntheticEvent) => { + const velocityY = nativeEvent?.velocity?.y ?? 0 + const scaled = nativeEvent?.zoomScale > 1 + + onZoom(scaled) + setScaled(scaled) + + if ( + !scaled && + swipeToCloseEnabled && + Math.abs(velocityY) > SWIPE_CLOSE_VELOCITY + ) { + onRequestClose() + } + }, + [scaled] + ) + + const onScroll = ({ + nativeEvent + }: NativeSyntheticEvent) => { + const offsetY = nativeEvent?.contentOffset?.y ?? 0 + + if (nativeEvent?.zoomScale > 1) { + return + } + + scrollValueY.setValue(offsetY) + } + + const onLongPressHandler = useCallback( + (event: GestureResponderEvent) => { + onLongPress(imageSrc) + }, + [imageSrc, onLongPress] + ) + + return ( + + + {(!loaded || !imageDimensions) && + (customLoadingComponent ? ( + + {customLoadingComponent} + + ) : !hideDefaultLoadingComponent ? ( + + ) : ( + + ))} + + { + setLoaded(true) + + if ( + !onImageLoad || + typeof onImageLoad !== 'function' + ) + return + + onImageLoad() + }} + /> + + + + ) +} const styles = StyleSheet.create({ - listItem: { - width: SCREEN_WIDTH, - height: SCREEN_HEIGHT, - }, - imageScrollContainer: { - height: SCREEN_HEIGHT, - }, -}); - -export default React.memo(ImageItem); + listItem: { + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT + }, + imageScrollContainer: { + height: SCREEN_HEIGHT + }, + customLoadingComponent: { + position: 'absolute', + top: 0, + left: 0, + display: 'flex', + height: SCREEN_HEIGHT, + width: SCREEN_WIDTH, + alignItems: 'center', + justifyContent: 'center' + } +}) + +export default React.memo(ImageItem)