about summary refs log tree commit diff
path: root/src/view/com/lightbox/ImageViewing/index.tsx
diff options
context:
space:
mode:
authorAryan Goharzad <arrygoo@gmail.com>2023-01-25 18:25:34 -0500
committerGitHub <noreply@github.com>2023-01-25 17:25:34 -0600
commiteb33c3fa812cc087db14a6b6ba743e982b26c462 (patch)
treed098f7a804c67755f39e95bbbfd56887bacf476c /src/view/com/lightbox/ImageViewing/index.tsx
parentadf328b50ce98c5ebd3282fe897ddfdcd0de8011 (diff)
downloadvoidsky-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.tsx183
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