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/index.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/index.tsx')
-rw-r--r-- | src/view/com/lightbox/ImageViewing/index.tsx | 183 |
1 files changed, 183 insertions, 0 deletions
diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx new file mode 100644 index 000000000..fdaafe737 --- /dev/null +++ b/src/view/com/lightbox/ImageViewing/index.tsx @@ -0,0 +1,183 @@ +/** + * 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. + * + */ +// 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, useCallback, useRef, useEffect} from 'react' +import { + Animated, + Dimensions, + StyleSheet, + View, + VirtualizedList, + ModalProps, +} from 'react-native' +import {Modal} from '../../modals/Modal' + +import ImageItem from './components/ImageItem/ImageItem' +import ImageDefaultHeader from './components/ImageDefaultHeader' + +import useAnimatedComponents from './hooks/useAnimatedComponents' +import useImageIndexChange from './hooks/useImageIndexChange' +import useRequestClose from './hooks/useRequestClose' +import {ImageSource} from './@types' + +type Props = { + images: ImageSource[] + keyExtractor?: (imageSrc: ImageSource, index: number) => string + imageIndex: number + visible: boolean + onRequestClose: () => void + onLongPress?: (image: ImageSource) => void + onImageIndexChange?: (imageIndex: number) => void + presentationStyle?: ModalProps['presentationStyle'] + animationType?: ModalProps['animationType'] + backgroundColor?: string + swipeToCloseEnabled?: boolean + doubleTapToZoomEnabled?: boolean + delayLongPress?: number + HeaderComponent?: ComponentType<{imageIndex: number}> + FooterComponent?: ComponentType<{imageIndex: number}> +} + +const DEFAULT_BG_COLOR = '#000' +const DEFAULT_DELAY_LONG_PRESS = 800 +const SCREEN = Dimensions.get('screen') +const SCREEN_WIDTH = SCREEN.width + +function ImageViewing({ + images, + keyExtractor, + imageIndex, + visible, + onRequestClose, + onLongPress = () => {}, + onImageIndexChange, + backgroundColor = DEFAULT_BG_COLOR, + swipeToCloseEnabled, + doubleTapToZoomEnabled, + delayLongPress = DEFAULT_DELAY_LONG_PRESS, + HeaderComponent, + FooterComponent, +}: Props) { + const imageList = useRef<VirtualizedList<ImageSource>>(null) + const [opacity, onRequestCloseEnhanced] = useRequestClose(onRequestClose) + const [currentImageIndex, onScroll] = useImageIndexChange(imageIndex, SCREEN) + const [headerTransform, footerTransform, toggleBarsVisible] = + useAnimatedComponents() + + useEffect(() => { + if (onImageIndexChange) { + onImageIndexChange(currentImageIndex) + } + }, [currentImageIndex, onImageIndexChange]) + + const onZoom = useCallback( + (isScaled: boolean) => { + // @ts-ignore + imageList?.current?.setNativeProps({scrollEnabled: !isScaled}) + toggleBarsVisible(!isScaled) + }, + [toggleBarsVisible], + ) + + if (!visible) { + return null + } + + return ( + <View style={styles.screen}> + <Modal /> + <View style={[styles.container, {opacity, backgroundColor}]}> + <Animated.View style={[styles.header, {transform: headerTransform}]}> + {typeof HeaderComponent !== 'undefined' ? ( + React.createElement(HeaderComponent, { + imageIndex: currentImageIndex, + }) + ) : ( + <ImageDefaultHeader onRequestClose={onRequestCloseEnhanced} /> + )} + </Animated.View> + <VirtualizedList + ref={imageList} + data={images} + horizontal + pagingEnabled + windowSize={2} + initialNumToRender={1} + maxToRenderPerBatch={1} + showsHorizontalScrollIndicator={false} + showsVerticalScrollIndicator={false} + initialScrollIndex={imageIndex} + 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} + onLongPress={onLongPress} + delayLongPress={delayLongPress} + swipeToCloseEnabled={swipeToCloseEnabled} + doubleTapToZoomEnabled={doubleTapToZoomEnabled} + /> + )} + onMomentumScrollEnd={onScroll} + //@ts-ignore + keyExtractor={(imageSrc, index) => + keyExtractor + ? keyExtractor(imageSrc, index) + : typeof imageSrc === 'number' + ? `${imageSrc}` + : imageSrc.uri + } + /> + {typeof FooterComponent !== 'undefined' && ( + <Animated.View style={[styles.footer, {transform: footerTransform}]}> + {React.createElement(FooterComponent, { + imageIndex: currentImageIndex, + })} + </Animated.View> + )} + </View> + </View> + ) +} + +const styles = StyleSheet.create({ + screen: { + position: 'absolute', + }, + container: { + flex: 1, + backgroundColor: '#000', + }, + header: { + position: 'absolute', + width: '100%', + zIndex: 1, + top: 0, + }, + footer: { + position: 'absolute', + width: '100%', + zIndex: 1, + bottom: 0, + }, +}) + +const EnhancedImageViewing = (props: Props) => ( + <ImageViewing key={props.imageIndex} {...props} /> +) + +export default EnhancedImageViewing |