about summary refs log tree commit diff
diff options
context:
space:
mode:
-rw-r--r--src/screens/Profile/Header/Shell.tsx14
-rw-r--r--src/state/lightbox.tsx23
-rw-r--r--src/view/com/lightbox/ImageViewing/index.tsx146
-rw-r--r--src/view/com/lightbox/Lightbox.tsx164
-rw-r--r--src/view/com/lightbox/Lightbox.web.tsx20
-rw-r--r--src/view/com/profile/ProfileSubpageHeader.tsx1
-rw-r--r--src/view/com/util/post-embeds/index.tsx1
7 files changed, 155 insertions, 214 deletions
diff --git a/src/screens/Profile/Header/Shell.tsx b/src/screens/Profile/Header/Shell.tsx
index 26e940688..925066d72 100644
--- a/src/screens/Profile/Header/Shell.tsx
+++ b/src/screens/Profile/Header/Shell.tsx
@@ -55,8 +55,18 @@ let ProfileHeaderShell = ({
     const modui = moderation.ui('avatar')
     if (profile.avatar && !(modui.blur && modui.noOverride)) {
       openLightbox({
-        type: 'profile-image',
-        profile: profile,
+        images: [
+          {
+            uri: profile.avatar,
+            thumbUri: profile.avatar,
+            dimensions: {
+              // It's fine if it's actually smaller but we know it's 1:1.
+              height: 1000,
+              width: 1000,
+            },
+          },
+        ],
+        index: 0,
         thumbDims: null,
       })
     }
diff --git a/src/state/lightbox.tsx b/src/state/lightbox.tsx
index 53409e3ec..1dae67932 100644
--- a/src/state/lightbox.tsx
+++ b/src/state/lightbox.tsx
@@ -1,32 +1,15 @@
 import React from 'react'
 import type {MeasuredDimensions} from 'react-native-reanimated'
-import {AppBskyActorDefs} from '@atproto/api'
 
 import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
-import {Dimensions} from '#/lib/media/types'
+import {ImageSource} from '#/view/com/lightbox/ImageViewing/@types'
 
-type ProfileImageLightbox = {
-  type: 'profile-image'
-  profile: AppBskyActorDefs.ProfileViewDetailed
-  thumbDims: null
-}
-
-type ImagesLightboxItem = {
-  uri: string
-  thumbUri: string
-  alt?: string
-  dimensions: Dimensions | null
-}
-
-type ImagesLightbox = {
-  type: 'images'
-  images: ImagesLightboxItem[]
+type Lightbox = {
+  images: ImageSource[]
   thumbDims: MeasuredDimensions | null
   index: number
 }
 
-type Lightbox = ProfileImageLightbox | ImagesLightbox
-
 const LightboxContext = React.createContext<{
   activeLightbox: Lightbox | null
 }>({
diff --git a/src/view/com/lightbox/ImageViewing/index.tsx b/src/view/com/lightbox/ImageViewing/index.tsx
index 0d0ac4df1..40df4c819 100644
--- a/src/view/com/lightbox/ImageViewing/index.tsx
+++ b/src/view/com/lightbox/ImageViewing/index.tsx
@@ -8,13 +8,27 @@
 // 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, useMemo, useState} from 'react'
-import {Platform, StyleSheet, View} from 'react-native'
+import React, {useCallback, useMemo, useState} from 'react'
+import {
+  Dimensions,
+  LayoutAnimation,
+  Platform,
+  StyleSheet,
+  View,
+} from 'react-native'
 import PagerView from 'react-native-pager-view'
 import {MeasuredDimensions} from 'react-native-reanimated'
 import Animated, {useAnimatedStyle, withSpring} from 'react-native-reanimated'
+import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import {Edge, SafeAreaView} from 'react-native-safe-area-context'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {Trans} from '@lingui/macro'
 
+import {colors, s} from '#/lib/styles'
+import {isIOS} from '#/platform/detection'
+import {Button} from '#/view/com/util/forms/Button'
+import {Text} from '#/view/com/util/text/Text'
+import {ScrollView} from '#/view/com/util/Views'
 import {ImageSource} from './@types'
 import ImageDefaultHeader from './components/ImageDefaultHeader'
 import ImageItem from './components/ImageItem/ImageItem'
@@ -26,10 +40,11 @@ type Props = {
   visible: boolean
   onRequestClose: () => void
   backgroundColor?: string
-  HeaderComponent?: ComponentType<{imageIndex: number}>
-  FooterComponent?: ComponentType<{imageIndex: number}>
+  onPressSave: (uri: string) => void
+  onPressShare: (uri: string) => void
 }
 
+const SCREEN_HEIGHT = Dimensions.get('window').height
 const DEFAULT_BG_COLOR = '#000'
 
 function ImageViewing({
@@ -39,8 +54,8 @@ function ImageViewing({
   visible,
   onRequestClose,
   backgroundColor = DEFAULT_BG_COLOR,
-  HeaderComponent,
-  FooterComponent,
+  onPressSave,
+  onPressShare,
 }: Props) {
   const [isScaled, setIsScaled] = useState(false)
   const [isDragging, setIsDragging] = useState(false)
@@ -96,13 +111,7 @@ function ImageViewing({
       accessibilityViewIsModal>
       <View style={[styles.container, {backgroundColor}]}>
         <Animated.View style={[styles.header, animatedHeaderStyle]}>
-          {typeof HeaderComponent !== 'undefined' ? (
-            React.createElement(HeaderComponent, {
-              imageIndex,
-            })
-          ) : (
-            <ImageDefaultHeader onRequestClose={onRequestClose} />
-          )}
+          <ImageDefaultHeader onRequestClose={onRequestClose} />
         </Animated.View>
         <PagerView
           scrollEnabled={!isScaled}
@@ -129,18 +138,100 @@ function ImageViewing({
             </View>
           ))}
         </PagerView>
-        {typeof FooterComponent !== 'undefined' && (
-          <Animated.View style={[styles.footer, animatedFooterStyle]}>
-            {React.createElement(FooterComponent, {
-              imageIndex,
-            })}
-          </Animated.View>
-        )}
+        <Animated.View style={[styles.footer, animatedFooterStyle]}>
+          <LightboxFooter
+            images={images}
+            index={imageIndex}
+            onPressSave={onPressSave}
+            onPressShare={onPressShare}
+          />
+        </Animated.View>
       </View>
     </SafeAreaView>
   )
 }
 
+function LightboxFooter({
+  images,
+  index,
+  onPressSave,
+  onPressShare,
+}: {
+  images: ImageSource[]
+  index: number
+  onPressSave: (uri: string) => void
+  onPressShare: (uri: string) => void
+}) {
+  const {alt: altText, uri} = images[index]
+  const [isAltExpanded, setAltExpanded] = React.useState(false)
+  const insets = useSafeAreaInsets()
+  const svMaxHeight = SCREEN_HEIGHT - insets.top - 50
+  const isMomentumScrolling = React.useRef(false)
+  return (
+    <ScrollView
+      style={[
+        {
+          backgroundColor: '#000d',
+        },
+        {maxHeight: svMaxHeight},
+      ]}
+      scrollEnabled={isAltExpanded}
+      onMomentumScrollBegin={() => {
+        isMomentumScrolling.current = true
+      }}
+      onMomentumScrollEnd={() => {
+        isMomentumScrolling.current = false
+      }}
+      contentContainerStyle={{
+        paddingTop: 16,
+        paddingBottom: insets.bottom + 10,
+        paddingHorizontal: 24,
+      }}>
+      {altText ? (
+        <View accessibilityRole="button" style={styles.footerText}>
+          <Text
+            style={[s.gray3]}
+            numberOfLines={isAltExpanded ? undefined : 3}
+            selectable
+            onPress={() => {
+              if (isMomentumScrolling.current) {
+                return
+              }
+              LayoutAnimation.configureNext({
+                duration: 450,
+                update: {type: 'spring', springDamping: 1},
+              })
+              setAltExpanded(prev => !prev)
+            }}
+            onLongPress={() => {}}>
+            {altText}
+          </Text>
+        </View>
+      ) : null}
+      <View style={styles.footerBtns}>
+        <Button
+          type="primary-outline"
+          style={styles.footerBtn}
+          onPress={() => onPressSave(uri)}>
+          <FontAwesomeIcon icon={['far', 'floppy-disk']} style={s.white} />
+          <Text type="xl" style={s.white}>
+            <Trans context="action">Save</Trans>
+          </Text>
+        </Button>
+        <Button
+          type="primary-outline"
+          style={styles.footerBtn}
+          onPress={() => onPressShare(uri)}>
+          <FontAwesomeIcon icon="arrow-up-from-bracket" style={s.white} />
+          <Text type="xl" style={s.white}>
+            <Trans context="action">Share</Trans>
+          </Text>
+        </Button>
+      </View>
+    </ScrollView>
+  )
+}
+
 const styles = StyleSheet.create({
   screen: {
     position: 'absolute',
@@ -169,6 +260,21 @@ const styles = StyleSheet.create({
     zIndex: 1,
     bottom: 0,
   },
+  footerText: {
+    paddingBottom: isIOS ? 20 : 16,
+  },
+  footerBtns: {
+    flexDirection: 'row',
+    justifyContent: 'center',
+    gap: 8,
+  },
+  footerBtn: {
+    flexDirection: 'row',
+    alignItems: 'center',
+    gap: 8,
+    backgroundColor: 'transparent',
+    borderColor: colors.white,
+  },
 })
 
 const EnhancedImageViewing = (props: Props) => (
diff --git a/src/view/com/lightbox/Lightbox.tsx b/src/view/com/lightbox/Lightbox.tsx
index 83ea2e941..ed570d5a7 100644
--- a/src/view/com/lightbox/Lightbox.tsx
+++ b/src/view/com/lightbox/Lightbox.tsx
@@ -1,82 +1,25 @@
 import React from 'react'
-import {Dimensions, LayoutAnimation, StyleSheet, View} from 'react-native'
-import {useSafeAreaInsets} from 'react-native-safe-area-context'
 import * as MediaLibrary from 'expo-media-library'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {msg, Trans} from '@lingui/macro'
+import {msg} from '@lingui/macro'
 import {useLingui} from '@lingui/react'
 
 import {saveImageToMediaLibrary, shareImageModal} from '#/lib/media/manip'
-import {colors, s} from '#/lib/styles'
-import {isIOS} from '#/platform/detection'
 import {useLightbox, useLightboxControls} from '#/state/lightbox'
-import {ScrollView} from '#/view/com/util/Views'
-import {Button} from '../util/forms/Button'
-import {Text} from '../util/text/Text'
 import * as Toast from '../util/Toast'
 import ImageView from './ImageViewing'
 
-const SCREEN_HEIGHT = Dimensions.get('window').height
-
 export function Lightbox() {
   const {activeLightbox} = useLightbox()
   const {closeLightbox} = useLightboxControls()
+
   const onClose = React.useCallback(() => {
     closeLightbox()
   }, [closeLightbox])
 
-  if (!activeLightbox) {
-    return null
-  } else if (activeLightbox.type === 'profile-image') {
-    const opts = activeLightbox
-    return (
-      <ImageView
-        images={[
-          {
-            uri: opts.profile.avatar || '',
-            thumbUri: opts.profile.avatar || '',
-            dimensions: {
-              // It's fine if it's actually smaller but we know it's 1:1.
-              height: 1000,
-              width: 1000,
-            },
-          },
-        ]}
-        initialImageIndex={0}
-        thumbDims={opts.thumbDims}
-        visible
-        onRequestClose={onClose}
-        FooterComponent={LightboxFooter}
-      />
-    )
-  } else if (activeLightbox.type === 'images') {
-    const opts = activeLightbox
-    return (
-      <ImageView
-        images={opts.images.map(img => ({...img}))}
-        initialImageIndex={opts.index}
-        thumbDims={opts.thumbDims}
-        visible
-        onRequestClose={onClose}
-        FooterComponent={LightboxFooter}
-      />
-    )
-  } else {
-    return null
-  }
-}
-
-function LightboxFooter({imageIndex}: {imageIndex: number}) {
   const {_} = useLingui()
-  const {activeLightbox} = useLightbox()
-  const [isAltExpanded, setAltExpanded] = React.useState(false)
   const [permissionResponse, requestPermission] = MediaLibrary.usePermissions({
     granularPermissions: ['photo'],
   })
-  const insets = useSafeAreaInsets()
-  const svMaxHeight = SCREEN_HEIGHT - insets.top - 50
-  const isMomentumScrolling = React.useRef(false)
-
   const saveImageToAlbumWithToasts = React.useCallback(
     async (uri: string) => {
       if (!permissionResponse || permissionResponse.granted === false) {
@@ -96,7 +39,6 @@ function LightboxFooter({imageIndex}: {imageIndex: number}) {
         }
         return
       }
-
       try {
         await saveImageToMediaLibrary({uri})
         Toast.show(_(msg`Saved to your camera roll`))
@@ -107,101 +49,19 @@ function LightboxFooter({imageIndex}: {imageIndex: number}) {
     [permissionResponse, requestPermission, _],
   )
 
-  const lightbox = activeLightbox
-  if (!lightbox) {
+  if (!activeLightbox) {
     return null
   }
 
-  let altText = ''
-  let uri = ''
-  if (lightbox.type === 'images') {
-    const opts = lightbox
-    uri = opts.images[imageIndex].uri
-    altText = opts.images[imageIndex].alt || ''
-  } else if (lightbox.type === 'profile-image') {
-    const opts = lightbox
-    uri = opts.profile.avatar || ''
-  }
-
   return (
-    <ScrollView
-      style={[
-        {
-          backgroundColor: '#000d',
-        },
-        {maxHeight: svMaxHeight},
-      ]}
-      scrollEnabled={isAltExpanded}
-      onMomentumScrollBegin={() => {
-        isMomentumScrolling.current = true
-      }}
-      onMomentumScrollEnd={() => {
-        isMomentumScrolling.current = false
-      }}
-      contentContainerStyle={{
-        paddingTop: 16,
-        paddingBottom: insets.bottom + 10,
-        paddingHorizontal: 24,
-      }}>
-      {altText ? (
-        <View accessibilityRole="button" style={styles.footerText}>
-          <Text
-            style={[s.gray3]}
-            numberOfLines={isAltExpanded ? undefined : 3}
-            selectable
-            onPress={() => {
-              if (isMomentumScrolling.current) {
-                return
-              }
-              LayoutAnimation.configureNext({
-                duration: 450,
-                update: {type: 'spring', springDamping: 1},
-              })
-              setAltExpanded(prev => !prev)
-            }}
-            onLongPress={() => {}}>
-            {altText}
-          </Text>
-        </View>
-      ) : null}
-      <View style={styles.footerBtns}>
-        <Button
-          type="primary-outline"
-          style={styles.footerBtn}
-          onPress={() => saveImageToAlbumWithToasts(uri)}>
-          <FontAwesomeIcon icon={['far', 'floppy-disk']} style={s.white} />
-          <Text type="xl" style={s.white}>
-            <Trans context="action">Save</Trans>
-          </Text>
-        </Button>
-        <Button
-          type="primary-outline"
-          style={styles.footerBtn}
-          onPress={() => shareImageModal({uri})}>
-          <FontAwesomeIcon icon="arrow-up-from-bracket" style={s.white} />
-          <Text type="xl" style={s.white}>
-            <Trans context="action">Share</Trans>
-          </Text>
-        </Button>
-      </View>
-    </ScrollView>
+    <ImageView
+      images={activeLightbox.images}
+      initialImageIndex={activeLightbox.index}
+      thumbDims={activeLightbox.thumbDims}
+      visible
+      onRequestClose={onClose}
+      onPressSave={saveImageToAlbumWithToasts}
+      onPressShare={uri => shareImageModal({uri})}
+    />
   )
 }
-
-const styles = StyleSheet.create({
-  footerText: {
-    paddingBottom: isIOS ? 20 : 16,
-  },
-  footerBtns: {
-    flexDirection: 'row',
-    justifyContent: 'center',
-    gap: 8,
-  },
-  footerBtn: {
-    flexDirection: 'row',
-    alignItems: 'center',
-    gap: 8,
-    backgroundColor: 'transparent',
-    borderColor: colors.white,
-  },
-})
diff --git a/src/view/com/lightbox/Lightbox.web.tsx b/src/view/com/lightbox/Lightbox.web.tsx
index 9a0fa5d33..2ba7d06cc 100644
--- a/src/view/com/lightbox/Lightbox.web.tsx
+++ b/src/view/com/lightbox/Lightbox.web.tsx
@@ -38,24 +38,8 @@ export function Lightbox() {
     return null
   }
 
-  const initialIndex =
-    activeLightbox.type === 'images' ? activeLightbox.index : 0
-
-  let imgs: Img[] | undefined
-  if (activeLightbox.type === 'profile-image') {
-    const opts = activeLightbox
-    if (opts.profile.avatar) {
-      imgs = [{uri: opts.profile.avatar}]
-    }
-  } else if (activeLightbox.type === 'images') {
-    const opts = activeLightbox
-    imgs = opts.images
-  }
-
-  if (!imgs) {
-    return null
-  }
-
+  const initialIndex = activeLightbox.index
+  const imgs = activeLightbox.images
   return (
     <LightboxInner
       imgs={imgs}
diff --git a/src/view/com/profile/ProfileSubpageHeader.tsx b/src/view/com/profile/ProfileSubpageHeader.tsx
index d5b7c2b68..b712b346b 100644
--- a/src/view/com/profile/ProfileSubpageHeader.tsx
+++ b/src/view/com/profile/ProfileSubpageHeader.tsx
@@ -71,7 +71,6 @@ export function ProfileSubpageHeader({
       avatar // TODO && !(view.moderation.avatar.blur && view.moderation.avatar.noOverride)
     ) {
       openLightbox({
-        type: 'images',
         images: [
           {
             uri: avatar,
diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx
index 13e4d12e0..d686d2bd3 100644
--- a/src/view/com/util/post-embeds/index.tsx
+++ b/src/view/com/util/post-embeds/index.tsx
@@ -152,7 +152,6 @@ export function PostEmbeds({
         thumbDims: MeasuredDimensions | null,
       ) => {
         openLightbox({
-          type: 'images',
           images: items,
           index,
           thumbDims,