diff options
author | Aryan Goharzad <arrygoo@gmail.com> | 2023-01-25 18:25:34 -0500 |
---|---|---|
committer | GitHub <noreply@github.com> | 2023-01-25 17:25:34 -0600 |
commit | eb33c3fa812cc087db14a6b6ba743e982b26c462 (patch) | |
tree | d098f7a804c67755f39e95bbbfd56887bacf476c /src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx | |
parent | adf328b50ce98c5ebd3282fe897ddfdcd0de8011 (diff) | |
download | voidsky-eb33c3fa812cc087db14a6b6ba743e982b26c462.tar.zst |
Saves image on long press (#83)
* Saves image on long press * Adds save on long press * Forking lightbox * move to wrapper only to the bottom sheet to reduce impact of this change * lint * lint * lint * Use official `share` API * Clean up cache after download * comment * comment * Reduce swipe close velocity * Updates per feedback * lint * bugfix * Adds delayed press-in for TouchableOpacity
Diffstat (limited to 'src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx')
-rw-r--r-- | src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx | 152 |
1 files changed, 152 insertions, 0 deletions
diff --git a/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx new file mode 100644 index 000000000..01a53ff6f --- /dev/null +++ b/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx @@ -0,0 +1,152 @@ +/** + * 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, 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 + +type Props = { + imageSrc: ImageSource + onRequestClose: () => void + onZoom: (isZoomed: boolean) => void + onLongPress: (image: ImageSource) => void + delayLongPress: number + swipeToCloseEnabled?: boolean + doubleTapToZoomEnabled?: boolean +} + +const ImageItem = ({ + imageSrc, + onZoom, + onRequestClose, + onLongPress, + delayLongPress, + swipeToCloseEnabled = true, + doubleTapToZoomEnabled = true, +}: Props) => { + const imageContainer = useRef<ScrollView & NativeMethodsMixin>(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, + }) + } + }, + [onZoom], + ) + + 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<NativeScrollEvent>) => { + 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<NativeScrollEvent>) => { + const offsetY = nativeEvent?.contentOffset?.y ?? 0 + + scrollValueY.setValue(offsetY) + } + + return ( + <ScrollView + ref={imageContainer} + style={styles.listItem} + pagingEnabled + nestedScrollEnabled + showsHorizontalScrollIndicator={false} + showsVerticalScrollIndicator={false} + contentContainerStyle={styles.imageScrollContainer} + scrollEnabled={swipeToCloseEnabled} + {...(swipeToCloseEnabled && { + onScroll, + onScrollEndDrag, + })}> + <Animated.Image + {...panHandlers} + source={imageSrc} + style={imageStylesWithOpacity} + onLoad={onLoaded} + /> + {(!isLoaded || !imageDimensions) && <ImageLoading />} + </ScrollView> + ) +} + +const styles = StyleSheet.create({ + listItem: { + width: SCREEN_WIDTH, + height: SCREEN_HEIGHT, + }, + imageScrollContainer: { + height: SCREEN_HEIGHT * 2, + }, +}) + +export default React.memo(ImageItem) |