diff options
Diffstat (limited to 'src/view/com/lightbox/ImageViewing/hooks')
7 files changed, 46 insertions, 314 deletions
diff --git a/src/view/com/lightbox/ImageViewing/hooks/useAnimatedComponents.ts b/src/view/com/lightbox/ImageViewing/hooks/useAnimatedComponents.ts deleted file mode 100644 index c21cd7f2c..000000000 --- a/src/view/com/lightbox/ImageViewing/hooks/useAnimatedComponents.ts +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) JOB TODAY S.A. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import {Animated} from 'react-native' - -const INITIAL_POSITION = {x: 0, y: 0} -const ANIMATION_CONFIG = { - duration: 200, - useNativeDriver: true, -} - -const useAnimatedComponents = () => { - const headerTranslate = new Animated.ValueXY(INITIAL_POSITION) - const footerTranslate = new Animated.ValueXY(INITIAL_POSITION) - - const toggleVisible = (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 headerTransform = headerTranslate.getTranslateTransform() - const footerTransform = footerTranslate.getTranslateTransform() - - return [headerTransform, footerTransform, toggleVisible] as const -} - -export default useAnimatedComponents diff --git a/src/view/com/lightbox/ImageViewing/hooks/useDoubleTapToZoom.ts b/src/view/com/lightbox/ImageViewing/hooks/useDoubleTapToZoom.ts deleted file mode 100644 index ea81d9f1c..000000000 --- a/src/view/com/lightbox/ImageViewing/hooks/useDoubleTapToZoom.ts +++ /dev/null @@ -1,150 +0,0 @@ -/** - * Copyright (c) JOB TODAY S.A. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import React, {useCallback} from 'react' -import {ScrollView, NativeTouchEvent, NativeSyntheticEvent} from 'react-native' - -import {Dimensions} from '../@types' - -const DOUBLE_TAP_DELAY = 300 -const MIN_ZOOM = 2 - -let lastTapTS: number | null = null - -/** - * This is iOS only. - * Same functionality for Android implemented inside usePanResponder hook. - */ -function useDoubleTapToZoom( - scrollViewRef: React.RefObject<ScrollView>, - scaled: boolean, - screen: Dimensions, - imageDimensions: Dimensions | null, -) { - const handleDoubleTap = useCallback( - (event: NativeSyntheticEvent<NativeTouchEvent>) => { - const nowTS = new Date().getTime() - const scrollResponderRef = scrollViewRef?.current?.getScrollResponder() - - const getZoomRectAfterDoubleTap = ( - touchX: number, - touchY: number, - ): { - x: number - y: number - width: number - height: number - } => { - if (!imageDimensions) { - return { - x: 0, - y: 0, - width: screen.width, - height: screen.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 imageAspect = imageDimensions.width / imageDimensions.height - const screenAspect = screen.width / screen.height - const zoom = Math.max( - imageAspect / screenAspect, - screenAspect / imageAspect, - MIN_ZOOM, - ) - // Unlike in the Android version, we don't constrain the *max* zoom level here. - // Instead, this is done in the ScrollView props so that it constraints pinch too. - - // 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 - - // 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 - 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 - 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 - minX += verticalBarWidth - maxX -= verticalBarWidth - } - - // Finally, we can position the rect according to its size and the safe area. - let rectX - if (maxX >= minX) { - // Content fills the screen horizontally so we have horizontal wiggle room. - // Try to keep the tapped point under the finger after zoom. - rectX = touchX - touchX / zoom - rectX = Math.min(rectX, maxX) - 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 - } - let rectY - if (maxY >= minY) { - // Content fills the screen vertically so we have vertical wiggle room. - // Try to keep the tapped point under the finger after zoom. - rectY = touchY - touchY / zoom - rectY = Math.min(rectY, maxY) - 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 - } - - return { - x: rectX, - y: rectY, - height: rectHeight, - width: rectWidth, - } - } - - if (lastTapTS && nowTS - lastTapTS < DOUBLE_TAP_DELAY) { - let nextZoomRect = { - x: 0, - y: 0, - width: screen.width, - height: screen.height, - } - - const willZoom = !scaled - if (willZoom) { - const {pageX, pageY} = event.nativeEvent - nextZoomRect = getZoomRectAfterDoubleTap(pageX, pageY) - } - - // @ts-ignore - scrollResponderRef?.scrollResponderZoomTo({ - ...nextZoomRect, // This rect is in screen coordinates - animated: true, - }) - } else { - lastTapTS = nowTS - } - }, - [imageDimensions, scaled, screen.height, screen.width, scrollViewRef], - ) - - return handleDoubleTap -} - -export default useDoubleTapToZoom diff --git a/src/view/com/lightbox/ImageViewing/hooks/useImageDimensions.ts b/src/view/com/lightbox/ImageViewing/hooks/useImageDimensions.ts index a5b0b6bd4..7f0851af3 100644 --- a/src/view/com/lightbox/ImageViewing/hooks/useImageDimensions.ts +++ b/src/view/com/lightbox/ImageViewing/hooks/useImageDimensions.ts @@ -8,11 +8,29 @@ import {useEffect, useState} from 'react' import {Image, ImageURISource} from 'react-native' - -import {createCache} from '../utils' import {Dimensions, ImageSource} from '../@types' const CACHE_SIZE = 50 + +type CacheStorageItem = {key: string; value: any} + +const createCache = (cacheSize: number) => ({ + _storage: [] as CacheStorageItem[], + get(key: string): any { + const {value} = + this._storage.find(({key: storageKey}) => storageKey === key) || {} + + return value + }, + set(key: string, value: any) { + if (this._storage.length >= cacheSize) { + this._storage.shift() + } + + this._storage.push({key, value}) + }, +}) + const imageDimensionsCache = createCache(CACHE_SIZE) const useImageDimensions = (image: ImageSource): Dimensions | null => { diff --git a/src/view/com/lightbox/ImageViewing/hooks/useImageIndexChange.ts b/src/view/com/lightbox/ImageViewing/hooks/useImageIndexChange.ts deleted file mode 100644 index 16430f3aa..000000000 --- a/src/view/com/lightbox/ImageViewing/hooks/useImageIndexChange.ts +++ /dev/null @@ -1,32 +0,0 @@ -/** - * Copyright (c) JOB TODAY S.A. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import {useState} from 'react' -import {NativeSyntheticEvent, NativeScrollEvent} from 'react-native' - -import {Dimensions} from '../@types' - -const useImageIndexChange = (imageIndex: number, screen: Dimensions) => { - const [currentImageIndex, setImageIndex] = useState(imageIndex) - 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) - } - } - - return [currentImageIndex, onScroll] as const -} - -export default useImageIndexChange diff --git a/src/view/com/lightbox/ImageViewing/hooks/useImagePrefetch.ts b/src/view/com/lightbox/ImageViewing/hooks/useImagePrefetch.ts deleted file mode 100644 index 3969945bb..000000000 --- a/src/view/com/lightbox/ImageViewing/hooks/useImagePrefetch.ts +++ /dev/null @@ -1,25 +0,0 @@ -/** - * Copyright (c) JOB TODAY S.A. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import {useEffect} from 'react' -import {Image} from 'react-native' -import {ImageSource} from '../@types' - -const useImagePrefetch = (images: ImageSource[]) => { - useEffect(() => { - images.forEach(image => { - //@ts-ignore - if (image.uri) { - //@ts-ignore - return Image.prefetch(image.uri) - } - }) - }, [images]) -} - -export default useImagePrefetch diff --git a/src/view/com/lightbox/ImageViewing/hooks/usePanResponder.ts b/src/view/com/lightbox/ImageViewing/hooks/usePanResponder.ts index 7908504ea..85454e37e 100644 --- a/src/view/com/lightbox/ImageViewing/hooks/usePanResponder.ts +++ b/src/view/com/lightbox/ImageViewing/hooks/usePanResponder.ts @@ -18,16 +18,11 @@ import { } from 'react-native' import {Position} from '../@types' -import { - getDistanceBetweenTouches, - getImageTranslate, - getImageDimensionsByTranslate, -} from '../utils' +import {getImageTranslate} from '../utils' const SCREEN = Dimensions.get('window') const SCREEN_WIDTH = SCREEN.width const SCREEN_HEIGHT = SCREEN.height -const MIN_DIMENSION = Math.min(SCREEN_WIDTH, SCREEN_HEIGHT) const ANDROID_BAR_HEIGHT = 24 const MIN_ZOOM = 2 @@ -39,18 +34,12 @@ type Props = { initialScale: number initialTranslate: Position onZoom: (isZoomed: boolean) => void - doubleTapToZoomEnabled: boolean - onLongPress: () => void - delayLongPress: number } const usePanResponder = ({ initialScale, initialTranslate, onZoom, - doubleTapToZoomEnabled, - onLongPress, - delayLongPress, }: Props): Readonly< [GestureResponderHandlers, Animated.Value, Animated.ValueXY] > => { @@ -62,9 +51,9 @@ const usePanResponder = ({ let tmpTranslate: Position | null = null let isDoubleTapPerformed = false let lastTapTS: number | null = null - let longPressHandlerRef: NodeJS.Timeout | null = null - const meaningfulShift = MIN_DIMENSION * 0.01 + // TODO: It's not valid to reinitialize Animated values during render. + // This is a bug. const scaleValue = new Animated.Value(initialScale) const translateValue = new Animated.ValueXY(initialTranslate) @@ -155,10 +144,6 @@ const usePanResponder = ({ return () => scaleValue.removeAllListeners() }) - const cancelLongPressHandle = () => { - longPressHandlerRef && clearTimeout(longPressHandlerRef) - } - const panResponder = PanResponder.create({ onStartShouldSetPanResponder: () => true, onStartShouldSetPanResponderCapture: () => true, @@ -173,8 +158,6 @@ const usePanResponder = ({ if (gestureState.numberActiveTouches > 1) { return } - - longPressHandlerRef = setTimeout(onLongPress, delayLongPress) }, onPanResponderStart: ( event: GestureResponderEvent, @@ -194,7 +177,7 @@ const usePanResponder = ({ lastTapTS && tapTS - lastTapTS < DOUBLE_TAP_DELAY, ) - if (doubleTapToZoomEnabled && isDoubleTapPerformed) { + if (isDoubleTapPerformed) { let nextScale = initialScale let nextTranslate = initialTranslate @@ -241,15 +224,8 @@ const usePanResponder = ({ event: GestureResponderEvent, gestureState: PanResponderGestureState, ) => { - const {dx, dy} = gestureState - - if (Math.abs(dx) >= meaningfulShift || Math.abs(dy) >= meaningfulShift) { - cancelLongPressHandle() - } - // Don't need to handle move because double tap in progress (was handled in onStart) - if (doubleTapToZoomEnabled && isDoubleTapPerformed) { - cancelLongPressHandle() + if (isDoubleTapPerformed) { return } @@ -267,8 +243,6 @@ const usePanResponder = ({ numberInitialTouches === 2 && gestureState.numberActiveTouches === 2 if (isPinchGesture) { - cancelLongPressHandle() - const initialDistance = getDistanceBetweenTouches(initialTouches) const currentDistance = getDistanceBetweenTouches( event.nativeEvent.touches, @@ -315,7 +289,7 @@ const usePanResponder = ({ if (isTapGesture && currentScale > initialScale) { const {x, y} = currentTranslate - // eslint-disable-next-line @typescript-eslint/no-shadow + const {dx, dy} = gestureState const [topBound, leftBound, bottomBound, rightBound] = getBounds(currentScale) @@ -360,8 +334,6 @@ const usePanResponder = ({ } }, onPanResponderRelease: () => { - cancelLongPressHandle() - if (isDoubleTapPerformed) { isDoubleTapPerformed = false } @@ -428,4 +400,24 @@ const usePanResponder = ({ return [panResponder.panHandlers, scaleValue, translateValue] } +const getImageDimensionsByTranslate = ( + translate: Position, + screen: {width: number; height: number}, +): {width: number; height: number} => ({ + width: screen.width - translate.x * 2, + height: screen.height - translate.y * 2, +}) + +const getDistanceBetweenTouches = (touches: NativeTouchEvent[]): number => { + const [a, b] = touches + + if (a == null || b == null) { + return 0 + } + + return Math.sqrt( + Math.pow(a.pageX - b.pageX, 2) + Math.pow(a.pageY - b.pageY, 2), + ) +} + export default usePanResponder diff --git a/src/view/com/lightbox/ImageViewing/hooks/useRequestClose.ts b/src/view/com/lightbox/ImageViewing/hooks/useRequestClose.ts deleted file mode 100644 index 4cd03fe71..000000000 --- a/src/view/com/lightbox/ImageViewing/hooks/useRequestClose.ts +++ /dev/null @@ -1,24 +0,0 @@ -/** - * Copyright (c) JOB TODAY S.A. and its affiliates. - * - * This source code is licensed under the MIT license found in the - * LICENSE file in the root directory of this source tree. - * - */ - -import {useState} from 'react' - -const useRequestClose = (onRequestClose: () => void) => { - const [opacity, setOpacity] = useState(1) - - return [ - opacity, - () => { - setOpacity(0) - onRequestClose() - setTimeout(() => setOpacity(1), 0) - }, - ] as const -} - -export default useRequestClose |