diff options
Diffstat (limited to 'src/view/com/lightbox/ImageViewing/index.tsx')
-rw-r--r-- | src/view/com/lightbox/ImageViewing/index.tsx | 235 |
1 files changed, 76 insertions, 159 deletions
diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx index bc2a8a448..b6835793d 100644 --- a/src/view/com/lightbox/ImageViewing/index.tsx +++ b/src/view/com/lightbox/ImageViewing/index.tsx @@ -8,121 +8,72 @@ // 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, { - ComponentType, - createRef, - useCallback, - useRef, - useMemo, - useState, -} from 'react' -import { - Animated, - Dimensions, - NativeSyntheticEvent, - NativeScrollEvent, - StyleSheet, - View, - VirtualizedList, - ModalProps, - Platform, -} from 'react-native' -import {ModalsContainer} from '../../modals/Modal' +import React, {ComponentType, useCallback, useMemo, useState} from 'react' +import {StyleSheet, View, Platform} from 'react-native' import ImageItem from './components/ImageItem/ImageItem' import ImageDefaultHeader from './components/ImageDefaultHeader' import {ImageSource} from './@types' -import {ScrollView, GestureType} from 'react-native-gesture-handler' +import Animated, {useAnimatedStyle, withSpring} from 'react-native-reanimated' import {Edge, SafeAreaView} from 'react-native-safe-area-context' +import PagerView from 'react-native-pager-view' type Props = { images: ImageSource[] - keyExtractor?: (imageSrc: ImageSource, index: number) => string - imageIndex: number + initialImageIndex: number visible: boolean onRequestClose: () => void - presentationStyle?: ModalProps['presentationStyle'] - animationType?: ModalProps['animationType'] backgroundColor?: string HeaderComponent?: ComponentType<{imageIndex: number}> FooterComponent?: ComponentType<{imageIndex: number}> } const DEFAULT_BG_COLOR = '#000' -const SCREEN = Dimensions.get('screen') -const SCREEN_WIDTH = SCREEN.width -const INITIAL_POSITION = {x: 0, y: 0} -const ANIMATION_CONFIG = { - duration: 200, - useNativeDriver: true, -} function ImageViewing({ images, - keyExtractor, - imageIndex, + initialImageIndex, visible, onRequestClose, backgroundColor = DEFAULT_BG_COLOR, HeaderComponent, FooterComponent, }: Props) { - const imageList = useRef<VirtualizedList<ImageSource>>(null) const [isScaled, setIsScaled] = useState(false) const [isDragging, setIsDragging] = useState(false) - const [opacity, setOpacity] = useState(1) - const [currentImageIndex, setImageIndex] = useState(imageIndex) - const [headerTranslate] = useState( - () => new Animated.ValueXY(INITIAL_POSITION), - ) - const [footerTranslate] = useState( - () => new Animated.ValueXY(INITIAL_POSITION), - ) - - const toggleBarsVisible = (isVisible: boolean) => { - if (isVisible) { - Animated.parallel([ - Animated.timing(headerTranslate.y, {...ANIMATION_CONFIG, toValue: 0}), - Animated.timing(footerTranslate.y, {...ANIMATION_CONFIG, toValue: 0}), - ]).start() - } else { - Animated.parallel([ - Animated.timing(headerTranslate.y, { - ...ANIMATION_CONFIG, - toValue: -300, - }), - Animated.timing(footerTranslate.y, { - ...ANIMATION_CONFIG, - toValue: 300, - }), - ]).start() - } - } - - const onRequestCloseEnhanced = () => { - setOpacity(0) - onRequestClose() - setTimeout(() => setOpacity(1), 0) - } - - const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => { - const { - nativeEvent: { - contentOffset: {x: scrollX}, + 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), }, - } = event + ], + })) - if (SCREEN.width) { - const nextIndex = Math.round(scrollX / SCREEN.width) - setImageIndex(nextIndex < 0 ? 0 : nextIndex) - } - } + const onTap = useCallback(() => { + setShowControls(show => !show) + }, []) - const onZoom = (nextIsScaled: boolean) => { - toggleBarsVisible(!nextIsScaled) - setIsScaled(false) - } + const onZoom = useCallback((nextIsScaled: boolean) => { + setIsScaled(nextIsScaled) + if (nextIsScaled) { + setShowControls(false) + } + }, []) const edges = useMemo(() => { if (Platform.OS === 'android') { @@ -131,100 +82,54 @@ function ImageViewing({ return ['left', 'right'] satisfies Edge[] // iOS, so no top/bottom safe area }, []) - const onLayout = useCallback(() => { - if (imageIndex) { - imageList.current?.scrollToIndex({index: imageIndex, animated: false}) - } - }, [imageList, imageIndex]) - - // This is a hack. - // RNGH doesn't have an easy way to express that pinch of individual items - // should "steal" all pinches from the scroll view. So we're keeping a ref - // to all pinch gestures so that we may give them to <ScrollView waitFor={...}>. - const [pinchGestureRefs] = useState(new Map()) - for (let imageSrc of images) { - if (!pinchGestureRefs.get(imageSrc)) { - pinchGestureRefs.set(imageSrc, createRef<GestureType | undefined>()) - } - } - if (!visible) { return null } - const headerTransform = headerTranslate.getTranslateTransform() - const footerTransform = footerTranslate.getTranslateTransform() return ( <SafeAreaView style={styles.screen} - onLayout={onLayout} edges={edges} aria-modal accessibilityViewIsModal> - <ModalsContainer /> - <View style={[styles.container, {opacity, backgroundColor}]}> - <Animated.View style={[styles.header, {transform: headerTransform}]}> + <View style={[styles.container, {backgroundColor}]}> + <Animated.View style={[styles.header, animatedHeaderStyle]}> {typeof HeaderComponent !== 'undefined' ? ( React.createElement(HeaderComponent, { - imageIndex: currentImageIndex, + imageIndex, }) ) : ( - <ImageDefaultHeader onRequestClose={onRequestCloseEnhanced} /> + <ImageDefaultHeader onRequestClose={onRequestClose} /> )} </Animated.View> - <VirtualizedList - ref={imageList} - data={images} - horizontal - pagingEnabled - scrollEnabled={!isScaled || isDragging} - showsHorizontalScrollIndicator={false} - showsVerticalScrollIndicator={false} - getItem={(_, index) => images[index]} - getItemCount={() => images.length} - getItemLayout={(_, index) => ({ - length: SCREEN_WIDTH, - offset: SCREEN_WIDTH * index, - index, - })} - renderItem={({item: imageSrc}) => ( - <ImageItem - onZoom={onZoom} - imageSrc={imageSrc} - onRequestClose={onRequestCloseEnhanced} - pinchGestureRef={pinchGestureRefs.get(imageSrc)} - isScrollViewBeingDragged={isDragging} - /> - )} - renderScrollComponent={props => ( - <ScrollView - {...props} - waitFor={Array.from(pinchGestureRefs.values())} - /> - )} - onScrollBeginDrag={() => { - setIsDragging(true) - }} - onScrollEndDrag={() => { - setIsDragging(false) - }} - onMomentumScrollEnd={e => { + <PagerView + scrollEnabled={!isScaled} + initialPage={initialImageIndex} + onPageSelected={e => { + setImageIndex(e.nativeEvent.position) setIsScaled(false) - onScroll(e) }} - //@ts-ignore - keyExtractor={(imageSrc, index) => - keyExtractor - ? keyExtractor(imageSrc, index) - : typeof imageSrc === 'number' - ? `${imageSrc}` - : imageSrc.uri - } - /> + onPageScrollStateChanged={e => { + setIsDragging(e.nativeEvent.pageScrollState !== 'idle') + }} + overdrag={true} + style={styles.pager}> + {images.map(imageSrc => ( + <View key={imageSrc.uri}> + <ImageItem + onTap={onTap} + onZoom={onZoom} + imageSrc={imageSrc} + onRequestClose={onRequestClose} + isScrollViewBeingDragged={isDragging} + /> + </View> + ))} + </PagerView> {typeof FooterComponent !== 'undefined' && ( - <Animated.View style={[styles.footer, {transform: footerTransform}]}> + <Animated.View style={[styles.footer, animatedFooterStyle]}> {React.createElement(FooterComponent, { - imageIndex: currentImageIndex, + imageIndex, })} </Animated.View> )} @@ -236,11 +141,18 @@ function ImageViewing({ 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%', @@ -257,7 +169,12 @@ const styles = StyleSheet.create({ }) const EnhancedImageViewing = (props: Props) => ( - <ImageViewing key={props.imageIndex} {...props} /> + <ImageViewing key={props.initialImageIndex} {...props} /> ) +function withClampedSpring(value: any) { + 'worklet' + return withSpring(value, {overshootClamping: true, stiffness: 300}) +} + export default EnhancedImageViewing |