diff options
Diffstat (limited to 'src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx')
-rw-r--r-- | src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx | 108 |
1 files changed, 60 insertions, 48 deletions
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx index a96a1c913..e8f36d520 100644 --- a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.ios.tsx @@ -7,15 +7,18 @@ */ import React, {useState} from 'react' -import {ActivityIndicator, Dimensions, StyleSheet} from 'react-native' +import {ActivityIndicator, StyleSheet, View} from 'react-native' import {Gesture, GestureDetector} from 'react-native-gesture-handler' import Animated, { + AnimatedRef, interpolate, + measure, runOnJS, useAnimatedRef, useAnimatedStyle, useSharedValue, } from 'react-native-reanimated' +import {useSafeAreaFrame} from 'react-native-safe-area-context' import {Image} from 'expo-image' import {useAnimatedScrollHandler} from '#/lib/hooks/useAnimatedScrollHandler_FIXED' @@ -24,7 +27,6 @@ import {ImageSource} from '../../@types' const SWIPE_CLOSE_OFFSET = 75 const SWIPE_CLOSE_VELOCITY = 1 -const SCREEN = Dimensions.get('screen') const MAX_ORIGINAL_IMAGE_ZOOM = 2 const MIN_DOUBLE_TAP_SCALE = 2 @@ -35,6 +37,7 @@ type Props = { onZoom: (scaled: boolean) => void isScrollViewBeingDragged: boolean showControls: boolean + safeAreaRef: AnimatedRef<View> } const ImageItem = ({ @@ -43,20 +46,24 @@ const ImageItem = ({ onZoom, onRequestClose, showControls, + safeAreaRef, }: Props) => { const scrollViewRef = useAnimatedRef<Animated.ScrollView>() const translationY = useSharedValue(0) const [scaled, setScaled] = useState(false) + const screenSizeDelayedForJSThreadOnly = useSafeAreaFrame() const [imageAspect, imageDimensions] = useImageDimensions({ src: imageSrc.uri, knownDimensions: imageSrc.dimensions, }) const maxZoomScale = imageDimensions - ? (imageDimensions.width / SCREEN.width) * MAX_ORIGINAL_IMAGE_ZOOM + ? (imageDimensions.width / screenSizeDelayedForJSThreadOnly.width) * + MAX_ORIGINAL_IMAGE_ZOOM : 1 const animatedStyle = useAnimatedStyle(() => { return { + flex: 1, opacity: interpolate( translationY.value, [-SWIPE_CLOSE_OFFSET, 0, SWIPE_CLOSE_OFFSET], @@ -90,24 +97,13 @@ const ImageItem = ({ setScaled(nextIsScaled) } - function handleDoubleTap(absoluteX: number, absoluteY: number) { + function zoomTo(nextZoomRect: { + x: number + y: number + width: number + height: number + }) { const scrollResponderRef = scrollViewRef?.current?.getScrollResponder() - let nextZoomRect = { - x: 0, - y: 0, - width: SCREEN.width, - height: SCREEN.height, - } - - const willZoom = !scaled - if (willZoom) { - nextZoomRect = getZoomRectAfterDoubleTap( - imageAspect, - absoluteX, - absoluteY, - ) - } - // @ts-ignore scrollResponderRef?.scrollResponderZoomTo({ ...nextZoomRect, // This rect is in screen coordinates @@ -124,8 +120,27 @@ const ImageItem = ({ .numberOfTaps(2) .onEnd(e => { 'worklet' + const screenSize = measure(safeAreaRef) + if (!screenSize) { + return + } const {absoluteX, absoluteY} = e - runOnJS(handleDoubleTap)(absoluteX, absoluteY) + let nextZoomRect = { + x: 0, + y: 0, + width: screenSize.width, + height: screenSize.height, + } + const willZoom = !scaled + if (willZoom) { + nextZoomRect = getZoomRectAfterDoubleTap( + imageAspect, + absoluteX, + absoluteY, + screenSize, + ) + } + runOnJS(zoomTo)(nextZoomRect) }) const composedGesture = Gesture.Exclusive(doubleTap, singleTap) @@ -135,13 +150,13 @@ const ImageItem = ({ <Animated.ScrollView // @ts-ignore Something's up with the types here ref={scrollViewRef} - style={styles.listItem} pinchGestureEnabled showsHorizontalScrollIndicator={false} showsVerticalScrollIndicator={false} maximumZoomScale={maxZoomScale} - onScroll={scrollHandler}> - <Animated.View style={[styles.imageScrollContainer, animatedStyle]}> + onScroll={scrollHandler} + contentContainerStyle={styles.scrollContainer}> + <Animated.View style={animatedStyle}> <ActivityIndicator size="small" color="#FFF" style={styles.loading} /> <Image contentFit="contain" @@ -161,17 +176,6 @@ const ImageItem = ({ } const styles = StyleSheet.create({ - imageScrollContainer: { - height: SCREEN.height, - }, - listItem: { - width: SCREEN.width, - height: SCREEN.height, - }, - image: { - width: SCREEN.width, - height: SCREEN.height, - }, loading: { position: 'absolute', top: 0, @@ -179,30 +183,38 @@ const styles = StyleSheet.create({ right: 0, bottom: 0, }, + scrollContainer: { + flex: 1, + }, + image: { + flex: 1, + }, }) const getZoomRectAfterDoubleTap = ( imageAspect: number | undefined, touchX: number, touchY: number, + screenSize: {width: number; height: number}, ): { x: number y: number width: number height: number } => { + 'worklet' if (!imageAspect) { return { x: 0, y: 0, - width: SCREEN.width, - height: SCREEN.height, + width: screenSize.width, + height: screenSize.height, } } // First, let's figure out how much we want to zoom in. // We want to try to zoom in at least close enough to get rid of black bars. - const screenAspect = SCREEN.width / SCREEN.height + const screenAspect = screenSize.width / screenSize.height const zoom = Math.max( imageAspect / screenAspect, screenAspect / imageAspect, @@ -213,25 +225,25 @@ const getZoomRectAfterDoubleTap = ( // Next, we'll be calculating the rectangle to "zoom into" in screen coordinates. // We already know the zoom level, so this gives us the rectangle size. - let rectWidth = SCREEN.width / zoom - let rectHeight = SCREEN.height / zoom + let rectWidth = screenSize.width / zoom + let rectHeight = screenSize.height / zoom // Before we settle on the zoomed rect, figure out the safe area it has to be inside. // We don't want to introduce new black bars or make existing black bars unbalanced. let minX = 0 let minY = 0 - let maxX = SCREEN.width - rectWidth - let maxY = SCREEN.height - rectHeight + let maxX = screenSize.width - rectWidth + let maxY = screenSize.height - rectHeight if (imageAspect >= screenAspect) { // The image has horizontal black bars. Exclude them from the safe area. - const renderedHeight = SCREEN.width / imageAspect - const horizontalBarHeight = (SCREEN.height - renderedHeight) / 2 + const renderedHeight = screenSize.width / imageAspect + const horizontalBarHeight = (screenSize.height - renderedHeight) / 2 minY += horizontalBarHeight maxY -= horizontalBarHeight } else { // The image has vertical black bars. Exclude them from the safe area. - const renderedWidth = SCREEN.height * imageAspect - const verticalBarWidth = (SCREEN.width - renderedWidth) / 2 + const renderedWidth = screenSize.height * imageAspect + const verticalBarWidth = (screenSize.width - renderedWidth) / 2 minX += verticalBarWidth maxX -= verticalBarWidth } @@ -246,7 +258,7 @@ const getZoomRectAfterDoubleTap = ( rectX = Math.max(rectX, minX) } else { // Keep the rect centered on the screen so that black bars are balanced. - rectX = SCREEN.width / 2 - rectWidth / 2 + rectX = screenSize.width / 2 - rectWidth / 2 } let rectY if (maxY >= minY) { @@ -257,7 +269,7 @@ const getZoomRectAfterDoubleTap = ( rectY = Math.max(rectY, minY) } else { // Keep the rect centered on the screen so that black bars are balanced. - rectY = SCREEN.height / 2 - rectHeight / 2 + rectY = screenSize.height / 2 - rectHeight / 2 } return { |