about summary refs log tree commit diff
path: root/src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.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/components/ImageItem/ImageItem.android.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/components/ImageItem/ImageItem.android.tsx')
-rw-r--r--src/view/com/lightbox/ImageViewing/components/ImageItem/ImageItem.android.tsx152
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)