Change lightbox to use Pager (#1666)
* Change lightbox to use Pager * Fix crash issue on ios --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com>zio/stable
parent
aa085b0b14
commit
209d8b683c
|
@ -1,9 +1,8 @@
|
|||
import React, {MutableRefObject, useState} from 'react'
|
||||
import React, {useState} from 'react'
|
||||
|
||||
import {ActivityIndicator, Dimensions, StyleSheet} from 'react-native'
|
||||
import {Image} from 'expo-image'
|
||||
import Animated, {
|
||||
measure,
|
||||
runOnJS,
|
||||
useAnimatedRef,
|
||||
useAnimatedStyle,
|
||||
|
@ -12,11 +11,7 @@ import Animated, {
|
|||
withDecay,
|
||||
withSpring,
|
||||
} from 'react-native-reanimated'
|
||||
import {
|
||||
GestureDetector,
|
||||
Gesture,
|
||||
GestureType,
|
||||
} from 'react-native-gesture-handler'
|
||||
import {GestureDetector, Gesture} from 'react-native-gesture-handler'
|
||||
import useImageDimensions from '../../hooks/useImageDimensions'
|
||||
import {
|
||||
createTransform,
|
||||
|
@ -40,7 +35,6 @@ type Props = {
|
|||
imageSrc: ImageSource
|
||||
onRequestClose: () => void
|
||||
onZoom: (isZoomed: boolean) => void
|
||||
pinchGestureRef: MutableRefObject<GestureType | undefined>
|
||||
isScrollViewBeingDragged: boolean
|
||||
}
|
||||
const ImageItem = ({
|
||||
|
@ -48,7 +42,6 @@ const ImageItem = ({
|
|||
onZoom,
|
||||
onRequestClose,
|
||||
isScrollViewBeingDragged,
|
||||
pinchGestureRef,
|
||||
}: Props) => {
|
||||
const [isScaled, setIsScaled] = useState(false)
|
||||
const [isLoaded, setIsLoaded] = useState(false)
|
||||
|
@ -140,28 +133,7 @@ const ImageItem = ({
|
|||
return [dx, dy]
|
||||
}
|
||||
|
||||
// This is a hack.
|
||||
// We need to disallow any gestures (and let the native parent scroll view scroll) while you're scrolling it.
|
||||
// However, there is no great reliable way to coordinate this yet in RGNH.
|
||||
// This "fake" manual gesture handler whenever you're trying to touch something while the parent scrollview is not at rest.
|
||||
const consumeHScroll = Gesture.Manual().onTouchesDown((e, manager) => {
|
||||
if (isScrollViewBeingDragged) {
|
||||
// Steal the gesture (and do nothing, so native ScrollView does its thing).
|
||||
manager.activate()
|
||||
return
|
||||
}
|
||||
const measurement = measure(containerRef)
|
||||
if (!measurement || measurement.pageX !== 0) {
|
||||
// Steal the gesture (and do nothing, so native ScrollView does its thing).
|
||||
manager.activate()
|
||||
return
|
||||
}
|
||||
// Fail this "fake" gesture so that the gestures after it can proceed.
|
||||
manager.fail()
|
||||
})
|
||||
|
||||
const pinch = Gesture.Pinch()
|
||||
.withRef(pinchGestureRef)
|
||||
.onStart(e => {
|
||||
pinchOrigin.value = {
|
||||
x: e.focalX - SCREEN.width / 2,
|
||||
|
@ -318,19 +290,22 @@ const ImageItem = ({
|
|||
}
|
||||
})
|
||||
|
||||
const composedGesture = isScrollViewBeingDragged
|
||||
? // If the parent is not at rest, provide a no-op gesture.
|
||||
Gesture.Manual()
|
||||
: Gesture.Exclusive(
|
||||
dismissSwipePan,
|
||||
Gesture.Simultaneous(pinch, pan),
|
||||
doubleTap,
|
||||
)
|
||||
|
||||
const isLoading = !isLoaded || !imageDimensions
|
||||
return (
|
||||
<Animated.View ref={containerRef} style={styles.container}>
|
||||
{isLoading && (
|
||||
<ActivityIndicator size="small" color="#FFF" style={styles.loading} />
|
||||
)}
|
||||
<GestureDetector
|
||||
gesture={Gesture.Exclusive(
|
||||
consumeHScroll,
|
||||
dismissSwipePan,
|
||||
Gesture.Simultaneous(pinch, pan),
|
||||
doubleTap,
|
||||
)}>
|
||||
<GestureDetector gesture={composedGesture}>
|
||||
<AnimatedImage
|
||||
source={imageSrc}
|
||||
contentFit="contain"
|
||||
|
|
|
@ -6,7 +6,7 @@
|
|||
*
|
||||
*/
|
||||
|
||||
import React, {MutableRefObject, useCallback, useState} from 'react'
|
||||
import React, {useCallback, useState} from 'react'
|
||||
|
||||
import {
|
||||
Dimensions,
|
||||
|
@ -25,7 +25,6 @@ import Animated, {
|
|||
useAnimatedStyle,
|
||||
useSharedValue,
|
||||
} from 'react-native-reanimated'
|
||||
import {GestureType} from 'react-native-gesture-handler'
|
||||
|
||||
import useImageDimensions from '../../hooks/useImageDimensions'
|
||||
|
||||
|
@ -43,7 +42,6 @@ type Props = {
|
|||
imageSrc: ImageSource
|
||||
onRequestClose: () => void
|
||||
onZoom: (scaled: boolean) => void
|
||||
pinchGestureRef: MutableRefObject<GestureType>
|
||||
isScrollViewBeingDragged: boolean
|
||||
}
|
||||
|
||||
|
@ -145,7 +143,7 @@ const ImageItem = ({imageSrc, onZoom, onRequestClose}: Props) => {
|
|||
accessibilityHint="">
|
||||
<AnimatedImage
|
||||
contentFit="contain"
|
||||
source={imageSrc}
|
||||
source={{uri: imageSrc.uri}}
|
||||
style={[styles.image, animatedStyle]}
|
||||
onLoad={() => setLoaded(true)}
|
||||
/>
|
||||
|
|
|
@ -1,15 +1,13 @@
|
|||
// default implementation fallback for web
|
||||
|
||||
import React, {MutableRefObject} from 'react'
|
||||
import React from 'react'
|
||||
import {View} from 'react-native'
|
||||
import {GestureType} from 'react-native-gesture-handler'
|
||||
import {ImageSource} from '../../@types'
|
||||
|
||||
type Props = {
|
||||
imageSrc: ImageSource
|
||||
onRequestClose: () => void
|
||||
onZoom: (scaled: boolean) => void
|
||||
pinchGestureRef: MutableRefObject<GestureType | undefined>
|
||||
isScrollViewBeingDragged: boolean
|
||||
}
|
||||
|
||||
|
|
|
@ -8,32 +8,15 @@
|
|||
// 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 React, {ComponentType, useMemo, useState} from 'react'
|
||||
import {Animated, StyleSheet, View, ModalProps, 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 {Edge, SafeAreaView} from 'react-native-safe-area-context'
|
||||
import PagerView from 'react-native-pager-view'
|
||||
|
||||
type Props = {
|
||||
images: ImageSource[]
|
||||
|
@ -48,8 +31,6 @@ type Props = {
|
|||
}
|
||||
|
||||
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,
|
||||
|
@ -65,7 +46,6 @@ function ImageViewing({
|
|||
HeaderComponent,
|
||||
FooterComponent,
|
||||
}: Props) {
|
||||
const imageList = useRef<VirtualizedList<ImageSource>>(null)
|
||||
const [isScaled, setIsScaled] = useState(false)
|
||||
const [isDragging, setIsDragging] = useState(false)
|
||||
const [imageIndex, setImageIndex] = useState(initialImageIndex)
|
||||
|
@ -96,19 +76,6 @@ function ImageViewing({
|
|||
}
|
||||
}
|
||||
|
||||
const onScroll = (event: NativeSyntheticEvent<NativeScrollEvent>) => {
|
||||
const {
|
||||
nativeEvent: {
|
||||
contentOffset: {x: scrollX},
|
||||
},
|
||||
} = event
|
||||
|
||||
if (SCREEN.width) {
|
||||
const nextIndex = Math.round(scrollX / SCREEN.width)
|
||||
setImageIndex(nextIndex < 0 ? 0 : nextIndex)
|
||||
}
|
||||
}
|
||||
|
||||
const onZoom = (nextIsScaled: boolean) => {
|
||||
toggleBarsVisible(!nextIsScaled)
|
||||
setIsScaled(false)
|
||||
|
@ -121,26 +88,6 @@ function ImageViewing({
|
|||
return ['left', 'right'] satisfies Edge[] // iOS, so no top/bottom safe area
|
||||
}, [])
|
||||
|
||||
const onLayout = useCallback(() => {
|
||||
if (initialImageIndex) {
|
||||
imageList.current?.scrollToIndex({
|
||||
index: initialImageIndex,
|
||||
animated: false,
|
||||
})
|
||||
}
|
||||
}, [imageList, initialImageIndex])
|
||||
|
||||
// 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
|
||||
}
|
||||
|
@ -150,7 +97,6 @@ function ImageViewing({
|
|||
return (
|
||||
<SafeAreaView
|
||||
style={styles.screen}
|
||||
onLayout={onLayout}
|
||||
edges={edges}
|
||||
aria-modal
|
||||
accessibilityViewIsModal>
|
||||
|
@ -164,48 +110,29 @@ function ImageViewing({
|
|||
<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}) => (
|
||||
<PagerView
|
||||
scrollEnabled={!isScaled}
|
||||
initialPage={initialImageIndex}
|
||||
onPageSelected={e => {
|
||||
setImageIndex(e.nativeEvent.position)
|
||||
setIsScaled(false)
|
||||
}}
|
||||
onPageScrollStateChanged={e => {
|
||||
setIsDragging(e.nativeEvent.pageScrollState !== 'idle')
|
||||
}}
|
||||
overdrag={true}
|
||||
style={styles.pager}>
|
||||
{images.map(imageSrc => (
|
||||
<View key={imageSrc.uri}>
|
||||
<ImageItem
|
||||
onZoom={onZoom}
|
||||
imageSrc={imageSrc}
|
||||
onRequestClose={onRequestClose}
|
||||
pinchGestureRef={pinchGestureRefs.get(imageSrc)}
|
||||
isScrollViewBeingDragged={isDragging}
|
||||
/>
|
||||
)}
|
||||
renderScrollComponent={props => (
|
||||
<ScrollView
|
||||
{...props}
|
||||
waitFor={Array.from(pinchGestureRefs.values())}
|
||||
/>
|
||||
)}
|
||||
onScrollBeginDrag={() => {
|
||||
setIsDragging(true)
|
||||
}}
|
||||
onScrollEndDrag={() => {
|
||||
setIsDragging(false)
|
||||
}}
|
||||
onMomentumScrollEnd={e => {
|
||||
setIsScaled(false)
|
||||
onScroll(e)
|
||||
}}
|
||||
keyExtractor={imageSrc => imageSrc.uri}
|
||||
/>
|
||||
</View>
|
||||
))}
|
||||
</PagerView>
|
||||
{typeof FooterComponent !== 'undefined' && (
|
||||
<Animated.View style={[styles.footer, {transform: footerTransform}]}>
|
||||
{React.createElement(FooterComponent, {
|
||||
|
@ -221,11 +148,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%',
|
||||
|
|
Loading…
Reference in New Issue