diff --git a/src/state/models/ui/shell.ts b/src/state/models/ui/shell.ts
index 797d53f816..98e98ef8eb 100644
--- a/src/state/models/ui/shell.ts
+++ b/src/state/models/ui/shell.ts
@@ -47,6 +47,11 @@ export interface AltTextImageModal {
onAltTextSet: (altText?: string) => void
}
+export interface AltTextImageReadModal {
+ name: 'alt-text-image-read'
+ altText: string
+}
+
export interface DeleteAccountModal {
name: 'delete-account'
}
@@ -93,8 +98,9 @@ export type Modal =
| ReportAccountModal
| ReportPostModal
- // Posting
+ // Posts
| AltTextImageModal
+ | AltTextImageReadModal
| CropImageModal
| ServerInputModal
| RepostModal
diff --git a/src/view/com/modals/AltImageRead.tsx b/src/view/com/modals/AltImageRead.tsx
new file mode 100644
index 0000000000..e7b4797eee
--- /dev/null
+++ b/src/view/com/modals/AltImageRead.tsx
@@ -0,0 +1,75 @@
+import React, {useCallback} from 'react'
+import {StyleSheet, View} from 'react-native'
+import {usePalette} from 'lib/hooks/usePalette'
+import {gradients, s} from 'lib/styles'
+import {Text} from '../util/text/Text'
+import {TouchableOpacity} from 'react-native-gesture-handler'
+import LinearGradient from 'react-native-linear-gradient'
+import {useStores} from 'state/index'
+import {isDesktopWeb} from 'platform/detection'
+
+export const snapPoints = ['70%']
+
+interface Props {
+ altText: string
+}
+
+export function Component({altText}: Props) {
+ const pal = usePalette('default')
+ const store = useStores()
+
+ const onPress = useCallback(() => {
+ store.shell.closeModal()
+ }, [store])
+
+ return (
+
+ Image description
+
+ {altText}
+
+
+
+
+ Done
+
+
+
+
+ )
+}
+
+const styles = StyleSheet.create({
+ container: {
+ gap: 18,
+ paddingVertical: isDesktopWeb ? 0 : 18,
+ paddingHorizontal: isDesktopWeb ? 0 : 12,
+ height: '100%',
+ width: '100%',
+ },
+ title: {
+ textAlign: 'center',
+ fontWeight: 'bold',
+ fontSize: 24,
+ },
+ text: {
+ borderRadius: 5,
+ marginVertical: 18,
+ paddingHorizontal: 18,
+ paddingVertical: 16,
+ },
+ button: {
+ flexDirection: 'row',
+ alignItems: 'center',
+ justifyContent: 'center',
+ width: '100%',
+ borderRadius: 32,
+ padding: 10,
+ },
+})
diff --git a/src/view/com/modals/Modal.tsx b/src/view/com/modals/Modal.tsx
index df7d7f0420..2e053e3add 100644
--- a/src/view/com/modals/Modal.tsx
+++ b/src/view/com/modals/Modal.tsx
@@ -13,6 +13,7 @@ import * as ServerInputModal from './ServerInput'
import * as ReportPostModal from './ReportPost'
import * as RepostModal from './Repost'
import * as AltImageModal from './AltImage'
+import * as AltImageReadModal from './AltImageRead'
import * as ReportAccountModal from './ReportAccount'
import * as DeleteAccountModal from './DeleteAccount'
import * as ChangeHandleModal from './ChangeHandle'
@@ -74,6 +75,9 @@ export const ModalsContainer = observer(function ModalsContainer() {
} else if (activeModal?.name === 'alt-text-image') {
snapPoints = AltImageModal.snapPoints
element =
+ } else if (activeModal?.name === 'alt-text-image-read') {
+ snapPoints = AltImageReadModal.snapPoints
+ element =
} else if (activeModal?.name === 'change-handle') {
snapPoints = ChangeHandleModal.snapPoints
element =
diff --git a/src/view/com/modals/Modal.web.tsx b/src/view/com/modals/Modal.web.tsx
index 07d5168eda..de748b3a8b 100644
--- a/src/view/com/modals/Modal.web.tsx
+++ b/src/view/com/modals/Modal.web.tsx
@@ -15,6 +15,7 @@ import * as DeleteAccountModal from './DeleteAccount'
import * as RepostModal from './Repost'
import * as CropImageModal from './crop-image/CropImage.web'
import * as AltTextImageModal from './AltImage'
+import * as AltTextImageReadModal from './AltImageRead'
import * as ChangeHandleModal from './ChangeHandle'
import * as WaitlistModal from './Waitlist'
import * as InviteCodesModal from './InviteCodes'
@@ -84,6 +85,8 @@ function Modal({modal}: {modal: ModalIface}) {
element =
} else if (modal.name === 'alt-text-image') {
element =
+ } else if (modal.name === 'alt-text-image-read') {
+ element =
} else {
return null
}
diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx
new file mode 100644
index 0000000000..78ced0668e
--- /dev/null
+++ b/src/view/com/util/images/Gallery.tsx
@@ -0,0 +1,76 @@
+import {AppBskyEmbedImages} from '@atproto/api'
+import React, {ComponentProps, FC, useCallback} from 'react'
+import {Pressable, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
+import {Image} from 'expo-image'
+import {useStores} from 'state/index'
+
+type EventFunction = (index: number) => void
+
+interface GalleryItemProps {
+ images: AppBskyEmbedImages.ViewImage[]
+ index: number
+ onPress?: EventFunction
+ onLongPress?: EventFunction
+ onPressIn?: EventFunction
+ imageStyle: ComponentProps['style']
+}
+
+const DELAY_PRESS_IN = 500
+
+export const GalleryItem: FC = ({
+ images,
+ index,
+ imageStyle,
+ onPress,
+ onPressIn,
+ onLongPress,
+}) => {
+ const image = images[index]
+ const store = useStores()
+
+ const onPressAltText = useCallback(() => {
+ store.shell.openModal({
+ name: 'alt-text-image-read',
+ altText: image.alt,
+ })
+ }, [image.alt, store.shell])
+
+ return (
+
+ onPress?.(index)}
+ onPressIn={() => onPressIn?.(index)}
+ onLongPress={() => onLongPress?.(index)}>
+
+
+ {image.alt === '' ? null : (
+
+ ALT
+
+ )}
+
+ )
+}
+
+const styles = StyleSheet.create({
+ alt: {
+ backgroundColor: 'rgba(0, 0, 0, 0.75)',
+ borderRadius: 6,
+ color: 'white',
+ fontSize: 12,
+ fontWeight: 'bold',
+ letterSpacing: 1,
+ paddingHorizontal: 10,
+ paddingVertical: 3,
+ position: 'absolute',
+ left: 10,
+ top: -26,
+ width: 46,
+ },
+})
diff --git a/src/view/com/util/images/ImageLayoutGrid.tsx b/src/view/com/util/images/ImageLayoutGrid.tsx
index 51bb04fe94..4c09013043 100644
--- a/src/view/com/util/images/ImageLayoutGrid.tsx
+++ b/src/view/com/util/images/ImageLayoutGrid.tsx
@@ -3,15 +3,13 @@ import {
LayoutChangeEvent,
StyleProp,
StyleSheet,
- TouchableOpacity,
View,
ViewStyle,
} from 'react-native'
-import {Image, ImageStyle} from 'expo-image'
+import {ImageStyle} from 'expo-image'
import {Dimensions} from 'lib/media/types'
import {AppBskyEmbedImages} from '@atproto/api'
-
-export const DELAY_PRESS_IN = 500
+import {GalleryItem} from './Gallery'
interface ImageLayoutGridProps {
images: AppBskyEmbedImages.ViewImage[]
@@ -21,32 +19,21 @@ interface ImageLayoutGridProps {
style?: StyleProp
}
-export function ImageLayoutGrid({
- images,
- onPress,
- onLongPress,
- onPressIn,
- style,
-}: ImageLayoutGridProps) {
+export function ImageLayoutGrid({style, ...props}: ImageLayoutGridProps) {
const [containerInfo, setContainerInfo] = useState()
const onLayout = (evt: LayoutChangeEvent) => {
+ const {width, height} = evt.nativeEvent.layout
setContainerInfo({
- width: evt.nativeEvent.layout.width,
- height: evt.nativeEvent.layout.height,
+ width,
+ height,
})
}
return (
{containerInfo ? (
-
+
) : undefined}
)
@@ -61,13 +48,10 @@ interface ImageLayoutGridInnerProps {
}
function ImageLayoutGridInner({
- images,
- onPress,
- onLongPress,
- onPressIn,
containerInfo,
+ ...props
}: ImageLayoutGridInnerProps) {
- const count = images.length
+ const count = props.images.length
const size1 = useMemo(() => {
if (count === 3) {
const size = (containerInfo.width - 10) / 3
@@ -87,149 +71,43 @@ function ImageLayoutGridInner({
}
}, [count, containerInfo])
- if (count === 2) {
- return (
-
- onPress?.(0)}
- onPressIn={() => onPressIn?.(0)}
- onLongPress={() => onLongPress?.(0)}>
-
-
-
- onPress?.(1)}
- onPressIn={() => onPressIn?.(1)}
- onLongPress={() => onLongPress?.(1)}>
-
-
-
- )
- }
- if (count === 3) {
- return (
-
- onPress?.(0)}
- onPressIn={() => onPressIn?.(0)}
- onLongPress={() => onLongPress?.(0)}>
-
-
-
-
- onPress?.(1)}
- onPressIn={() => onPressIn?.(1)}
- onLongPress={() => onLongPress?.(1)}>
-
-
-
- onPress?.(2)}
- onPressIn={() => onPressIn?.(2)}
- onLongPress={() => onLongPress?.(2)}>
-
-
+ switch (count) {
+ case 2:
+ return (
+
+
+
-
- )
- }
- if (count === 4) {
- return (
-
-
- onPress?.(0)}
- onPressIn={() => onPressIn?.(0)}
- onLongPress={() => onLongPress?.(0)}>
-
-
-
- onPress?.(2)}
- onPressIn={() => onPressIn?.(2)}
- onLongPress={() => onLongPress?.(2)}>
-
-
+ )
+ case 3:
+ return (
+
+
+
+
+
+
-
-
- onPress?.(1)}
- onPressIn={() => onPressIn?.(1)}
- onLongPress={() => onLongPress?.(1)}>
-
-
-
- onPress?.(3)}
- onPressIn={() => onPressIn?.(3)}
- onLongPress={() => onLongPress?.(3)}>
-
-
+ )
+ case 4:
+ return (
+
+
+
+
+
+
+
+
+
-
- )
+ )
+ default:
+ return null
}
- return
}
const styles = StyleSheet.create({
- flexRow: {flexDirection: 'row'},
- wSpace: {width: 5},
- hSpace: {height: 5},
+ flexRow: {flexDirection: 'row', gap: 5},
+ flexColumn: {flexDirection: 'column', gap: 5},
})
diff --git a/src/view/com/util/post-embeds/index.tsx b/src/view/com/util/post-embeds/index.tsx
index f37fba342d..6a77598408 100644
--- a/src/view/com/util/post-embeds/index.tsx
+++ b/src/view/com/util/post-embeds/index.tsx
@@ -1,10 +1,12 @@
-import React from 'react'
+import React, {useCallback} from 'react'
import {
StyleSheet,
StyleProp,
View,
ViewStyle,
Image as RNImage,
+ Pressable,
+ Text,
} from 'react-native'
import {
AppBskyEmbedImages,
@@ -14,7 +16,6 @@ import {
AppBskyFeedPost,
} from '@atproto/api'
import {Link} from '../Link'
-import {AutoSizedImage} from '../images/AutoSizedImage'
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
import {ImagesLightbox} from 'state/models/ui/shell'
import {useStores} from 'state/index'
@@ -24,6 +25,7 @@ import {YoutubeEmbed} from './YoutubeEmbed'
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
import {getYoutubeVideoId} from 'lib/strings/url-helpers'
import QuoteEmbed from './QuoteEmbed'
+import {AutoSizedImage} from '../images/AutoSizedImage'
type Embed =
| AppBskyEmbedRecord.View
@@ -42,6 +44,16 @@ export function PostEmbeds({
const pal = usePalette('default')
const store = useStores()
+ const onPressAltText = useCallback(
+ (alt: string) => {
+ store.shell.openModal({
+ name: 'alt-text-image-read',
+ altText: alt,
+ })
+ },
+ [store.shell],
+ )
+
if (
AppBskyEmbedRecordWithMedia.isView(embed) &&
AppBskyEmbedRecord.isViewRecord(embed.record.record) &&
@@ -88,7 +100,9 @@ export function PostEmbeds({
}
if (AppBskyEmbedImages.isView(embed)) {
- if (embed.images.length > 0) {
+ const {images} = embed
+
+ if (images.length > 0) {
const uris = embed.images.map(img => img.fullsize)
const openLightbox = (index: number) => {
store.shell.openLightbox(new ImagesLightbox(uris, index))
@@ -107,32 +121,42 @@ export function PostEmbeds({
})
}
- switch (embed.images.length) {
- case 1:
- return (
-
- openLightbox(0)}
- onLongPress={() => onLongPress(0)}
- onPressIn={() => onPressIn(0)}
- style={styles.singleImage}
- />
-
- )
- default:
- return (
-
-
-
- )
+ if (images.length === 1) {
+ const {alt, thumb} = images[0]
+ return (
+
+ openLightbox(0)}
+ onLongPress={() => onLongPress(0)}
+ onPressIn={() => onPressIn(0)}
+ style={styles.singleImage}>
+ {alt === '' ? null : (
+ {
+ onPressAltText(alt)
+ }}>
+ ALT
+
+ )}
+
+
+ )
}
+
+ return (
+
+
+
+ )
+ // }
}
}
@@ -172,4 +196,18 @@ const styles = StyleSheet.create({
borderRadius: 8,
marginTop: 4,
},
+ alt: {
+ backgroundColor: 'rgba(0, 0, 0, 0.75)',
+ borderRadius: 6,
+ color: 'white',
+ fontSize: 12,
+ fontWeight: 'bold',
+ letterSpacing: 1,
+ paddingHorizontal: 10,
+ paddingVertical: 3,
+ position: 'absolute',
+ left: 10,
+ top: -26,
+ width: 46,
+ },
})