Image alt text view modal (#551)
* Image alt text view modal * Minor style tweaks --------- Co-authored-by: Paul Frazee <pfrazee@gmail.com>zio/stable
parent
0ec98c77ef
commit
dbb3c5c155
|
@ -47,6 +47,11 @@ export interface AltTextImageModal {
|
||||||
onAltTextSet: (altText?: string) => void
|
onAltTextSet: (altText?: string) => void
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export interface AltTextImageReadModal {
|
||||||
|
name: 'alt-text-image-read'
|
||||||
|
altText: string
|
||||||
|
}
|
||||||
|
|
||||||
export interface DeleteAccountModal {
|
export interface DeleteAccountModal {
|
||||||
name: 'delete-account'
|
name: 'delete-account'
|
||||||
}
|
}
|
||||||
|
@ -93,8 +98,9 @@ export type Modal =
|
||||||
| ReportAccountModal
|
| ReportAccountModal
|
||||||
| ReportPostModal
|
| ReportPostModal
|
||||||
|
|
||||||
// Posting
|
// Posts
|
||||||
| AltTextImageModal
|
| AltTextImageModal
|
||||||
|
| AltTextImageReadModal
|
||||||
| CropImageModal
|
| CropImageModal
|
||||||
| ServerInputModal
|
| ServerInputModal
|
||||||
| RepostModal
|
| RepostModal
|
||||||
|
|
|
@ -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 (
|
||||||
|
<View
|
||||||
|
testID="altTextImageModal"
|
||||||
|
style={[pal.view, styles.container, s.flex1]}>
|
||||||
|
<Text style={[styles.title, pal.text]}>Image description</Text>
|
||||||
|
<View style={[styles.text, pal.viewLight]}>
|
||||||
|
<Text style={pal.text}>{altText}</Text>
|
||||||
|
</View>
|
||||||
|
<TouchableOpacity testID="altTextImageSaveBtn" onPress={onPress}>
|
||||||
|
<LinearGradient
|
||||||
|
colors={[gradients.blueLight.start, gradients.blueLight.end]}
|
||||||
|
start={{x: 0, y: 0}}
|
||||||
|
end={{x: 1, y: 1}}
|
||||||
|
style={[styles.button]}>
|
||||||
|
<Text type="button-lg" style={[s.white, s.bold]}>
|
||||||
|
Done
|
||||||
|
</Text>
|
||||||
|
</LinearGradient>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
|
@ -13,6 +13,7 @@ import * as ServerInputModal from './ServerInput'
|
||||||
import * as ReportPostModal from './ReportPost'
|
import * as ReportPostModal from './ReportPost'
|
||||||
import * as RepostModal from './Repost'
|
import * as RepostModal from './Repost'
|
||||||
import * as AltImageModal from './AltImage'
|
import * as AltImageModal from './AltImage'
|
||||||
|
import * as AltImageReadModal from './AltImageRead'
|
||||||
import * as ReportAccountModal from './ReportAccount'
|
import * as ReportAccountModal from './ReportAccount'
|
||||||
import * as DeleteAccountModal from './DeleteAccount'
|
import * as DeleteAccountModal from './DeleteAccount'
|
||||||
import * as ChangeHandleModal from './ChangeHandle'
|
import * as ChangeHandleModal from './ChangeHandle'
|
||||||
|
@ -74,6 +75,9 @@ export const ModalsContainer = observer(function ModalsContainer() {
|
||||||
} else if (activeModal?.name === 'alt-text-image') {
|
} else if (activeModal?.name === 'alt-text-image') {
|
||||||
snapPoints = AltImageModal.snapPoints
|
snapPoints = AltImageModal.snapPoints
|
||||||
element = <AltImageModal.Component {...activeModal} />
|
element = <AltImageModal.Component {...activeModal} />
|
||||||
|
} else if (activeModal?.name === 'alt-text-image-read') {
|
||||||
|
snapPoints = AltImageReadModal.snapPoints
|
||||||
|
element = <AltImageReadModal.Component {...activeModal} />
|
||||||
} else if (activeModal?.name === 'change-handle') {
|
} else if (activeModal?.name === 'change-handle') {
|
||||||
snapPoints = ChangeHandleModal.snapPoints
|
snapPoints = ChangeHandleModal.snapPoints
|
||||||
element = <ChangeHandleModal.Component {...activeModal} />
|
element = <ChangeHandleModal.Component {...activeModal} />
|
||||||
|
|
|
@ -15,6 +15,7 @@ import * as DeleteAccountModal from './DeleteAccount'
|
||||||
import * as RepostModal from './Repost'
|
import * as RepostModal from './Repost'
|
||||||
import * as CropImageModal from './crop-image/CropImage.web'
|
import * as CropImageModal from './crop-image/CropImage.web'
|
||||||
import * as AltTextImageModal from './AltImage'
|
import * as AltTextImageModal from './AltImage'
|
||||||
|
import * as AltTextImageReadModal from './AltImageRead'
|
||||||
import * as ChangeHandleModal from './ChangeHandle'
|
import * as ChangeHandleModal from './ChangeHandle'
|
||||||
import * as WaitlistModal from './Waitlist'
|
import * as WaitlistModal from './Waitlist'
|
||||||
import * as InviteCodesModal from './InviteCodes'
|
import * as InviteCodesModal from './InviteCodes'
|
||||||
|
@ -84,6 +85,8 @@ function Modal({modal}: {modal: ModalIface}) {
|
||||||
element = <ContentFilteringSettingsModal.Component />
|
element = <ContentFilteringSettingsModal.Component />
|
||||||
} else if (modal.name === 'alt-text-image') {
|
} else if (modal.name === 'alt-text-image') {
|
||||||
element = <AltTextImageModal.Component {...modal} />
|
element = <AltTextImageModal.Component {...modal} />
|
||||||
|
} else if (modal.name === 'alt-text-image-read') {
|
||||||
|
element = <AltTextImageReadModal.Component {...modal} />
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
|
|
|
@ -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<typeof Image>['style']
|
||||||
|
}
|
||||||
|
|
||||||
|
const DELAY_PRESS_IN = 500
|
||||||
|
|
||||||
|
export const GalleryItem: FC<GalleryItemProps> = ({
|
||||||
|
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 (
|
||||||
|
<View>
|
||||||
|
<TouchableOpacity
|
||||||
|
delayPressIn={DELAY_PRESS_IN}
|
||||||
|
onPress={() => onPress?.(index)}
|
||||||
|
onPressIn={() => onPressIn?.(index)}
|
||||||
|
onLongPress={() => onLongPress?.(index)}>
|
||||||
|
<Image
|
||||||
|
source={{uri: image.thumb}}
|
||||||
|
style={imageStyle}
|
||||||
|
accessible={true}
|
||||||
|
accessibilityLabel={image.alt}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
{image.alt === '' ? null : (
|
||||||
|
<Pressable onPress={onPressAltText}>
|
||||||
|
<Text style={styles.alt}>ALT</Text>
|
||||||
|
</Pressable>
|
||||||
|
)}
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
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,
|
||||||
|
},
|
||||||
|
})
|
|
@ -3,15 +3,13 @@ import {
|
||||||
LayoutChangeEvent,
|
LayoutChangeEvent,
|
||||||
StyleProp,
|
StyleProp,
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
View,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {Image, ImageStyle} from 'expo-image'
|
import {ImageStyle} from 'expo-image'
|
||||||
import {Dimensions} from 'lib/media/types'
|
import {Dimensions} from 'lib/media/types'
|
||||||
import {AppBskyEmbedImages} from '@atproto/api'
|
import {AppBskyEmbedImages} from '@atproto/api'
|
||||||
|
import {GalleryItem} from './Gallery'
|
||||||
export const DELAY_PRESS_IN = 500
|
|
||||||
|
|
||||||
interface ImageLayoutGridProps {
|
interface ImageLayoutGridProps {
|
||||||
images: AppBskyEmbedImages.ViewImage[]
|
images: AppBskyEmbedImages.ViewImage[]
|
||||||
|
@ -21,32 +19,21 @@ interface ImageLayoutGridProps {
|
||||||
style?: StyleProp<ViewStyle>
|
style?: StyleProp<ViewStyle>
|
||||||
}
|
}
|
||||||
|
|
||||||
export function ImageLayoutGrid({
|
export function ImageLayoutGrid({style, ...props}: ImageLayoutGridProps) {
|
||||||
images,
|
|
||||||
onPress,
|
|
||||||
onLongPress,
|
|
||||||
onPressIn,
|
|
||||||
style,
|
|
||||||
}: ImageLayoutGridProps) {
|
|
||||||
const [containerInfo, setContainerInfo] = useState<Dimensions | undefined>()
|
const [containerInfo, setContainerInfo] = useState<Dimensions | undefined>()
|
||||||
|
|
||||||
const onLayout = (evt: LayoutChangeEvent) => {
|
const onLayout = (evt: LayoutChangeEvent) => {
|
||||||
|
const {width, height} = evt.nativeEvent.layout
|
||||||
setContainerInfo({
|
setContainerInfo({
|
||||||
width: evt.nativeEvent.layout.width,
|
width,
|
||||||
height: evt.nativeEvent.layout.height,
|
height,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={style} onLayout={onLayout}>
|
<View style={style} onLayout={onLayout}>
|
||||||
{containerInfo ? (
|
{containerInfo ? (
|
||||||
<ImageLayoutGridInner
|
<ImageLayoutGridInner {...props} containerInfo={containerInfo} />
|
||||||
images={images}
|
|
||||||
onPress={onPress}
|
|
||||||
onPressIn={onPressIn}
|
|
||||||
onLongPress={onLongPress}
|
|
||||||
containerInfo={containerInfo}
|
|
||||||
/>
|
|
||||||
) : undefined}
|
) : undefined}
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -61,13 +48,10 @@ interface ImageLayoutGridInnerProps {
|
||||||
}
|
}
|
||||||
|
|
||||||
function ImageLayoutGridInner({
|
function ImageLayoutGridInner({
|
||||||
images,
|
|
||||||
onPress,
|
|
||||||
onLongPress,
|
|
||||||
onPressIn,
|
|
||||||
containerInfo,
|
containerInfo,
|
||||||
|
...props
|
||||||
}: ImageLayoutGridInnerProps) {
|
}: ImageLayoutGridInnerProps) {
|
||||||
const count = images.length
|
const count = props.images.length
|
||||||
const size1 = useMemo<ImageStyle>(() => {
|
const size1 = useMemo<ImageStyle>(() => {
|
||||||
if (count === 3) {
|
if (count === 3) {
|
||||||
const size = (containerInfo.width - 10) / 3
|
const size = (containerInfo.width - 10) / 3
|
||||||
|
@ -87,149 +71,43 @@ function ImageLayoutGridInner({
|
||||||
}
|
}
|
||||||
}, [count, containerInfo])
|
}, [count, containerInfo])
|
||||||
|
|
||||||
if (count === 2) {
|
switch (count) {
|
||||||
|
case 2:
|
||||||
return (
|
return (
|
||||||
<View style={styles.flexRow}>
|
<View style={styles.flexRow}>
|
||||||
<TouchableOpacity
|
<GalleryItem index={0} {...props} imageStyle={size1} />
|
||||||
delayPressIn={DELAY_PRESS_IN}
|
<GalleryItem index={1} {...props} imageStyle={size1} />
|
||||||
onPress={() => onPress?.(0)}
|
|
||||||
onPressIn={() => onPressIn?.(0)}
|
|
||||||
onLongPress={() => onLongPress?.(0)}>
|
|
||||||
<Image
|
|
||||||
source={{uri: images[0].thumb}}
|
|
||||||
style={size1}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={images[0].alt}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={styles.wSpace} />
|
|
||||||
<TouchableOpacity
|
|
||||||
delayPressIn={DELAY_PRESS_IN}
|
|
||||||
onPress={() => onPress?.(1)}
|
|
||||||
onPressIn={() => onPressIn?.(1)}
|
|
||||||
onLongPress={() => onLongPress?.(1)}>
|
|
||||||
<Image
|
|
||||||
source={{uri: images[1].thumb}}
|
|
||||||
style={size1}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={images[1].alt}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
case 3:
|
||||||
if (count === 3) {
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.flexRow}>
|
<View style={styles.flexRow}>
|
||||||
<TouchableOpacity
|
<GalleryItem index={0} {...props} imageStyle={size2} />
|
||||||
delayPressIn={DELAY_PRESS_IN}
|
<View style={styles.flexColumn}>
|
||||||
onPress={() => onPress?.(0)}
|
<GalleryItem index={1} {...props} imageStyle={size1} />
|
||||||
onPressIn={() => onPressIn?.(0)}
|
<GalleryItem index={2} {...props} imageStyle={size1} />
|
||||||
onLongPress={() => onLongPress?.(0)}>
|
|
||||||
<Image
|
|
||||||
source={{uri: images[0].thumb}}
|
|
||||||
style={size2}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={images[0].alt}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={styles.wSpace} />
|
|
||||||
<View>
|
|
||||||
<TouchableOpacity
|
|
||||||
delayPressIn={DELAY_PRESS_IN}
|
|
||||||
onPress={() => onPress?.(1)}
|
|
||||||
onPressIn={() => onPressIn?.(1)}
|
|
||||||
onLongPress={() => onLongPress?.(1)}>
|
|
||||||
<Image
|
|
||||||
source={{uri: images[1].thumb}}
|
|
||||||
style={size1}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={images[1].alt}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={styles.hSpace} />
|
|
||||||
<TouchableOpacity
|
|
||||||
delayPressIn={DELAY_PRESS_IN}
|
|
||||||
onPress={() => onPress?.(2)}
|
|
||||||
onPressIn={() => onPressIn?.(2)}
|
|
||||||
onLongPress={() => onLongPress?.(2)}>
|
|
||||||
<Image
|
|
||||||
source={{uri: images[2].thumb}}
|
|
||||||
style={size1}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={images[2].alt}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
case 4:
|
||||||
if (count === 4) {
|
|
||||||
return (
|
return (
|
||||||
<View style={styles.flexRow}>
|
<View style={styles.flexRow}>
|
||||||
<View>
|
<View style={styles.flexColumn}>
|
||||||
<TouchableOpacity
|
<GalleryItem index={0} {...props} imageStyle={size1} />
|
||||||
delayPressIn={DELAY_PRESS_IN}
|
<GalleryItem index={2} {...props} imageStyle={size1} />
|
||||||
onPress={() => onPress?.(0)}
|
|
||||||
onPressIn={() => onPressIn?.(0)}
|
|
||||||
onLongPress={() => onLongPress?.(0)}>
|
|
||||||
<Image
|
|
||||||
source={{uri: images[0].thumb}}
|
|
||||||
style={size1}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={images[0].alt}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={styles.hSpace} />
|
|
||||||
<TouchableOpacity
|
|
||||||
delayPressIn={DELAY_PRESS_IN}
|
|
||||||
onPress={() => onPress?.(2)}
|
|
||||||
onPressIn={() => onPressIn?.(2)}
|
|
||||||
onLongPress={() => onLongPress?.(2)}>
|
|
||||||
<Image
|
|
||||||
source={{uri: images[2].thumb}}
|
|
||||||
style={size1}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={images[2].alt}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
<View style={styles.wSpace} />
|
<View style={styles.flexColumn}>
|
||||||
<View>
|
<GalleryItem index={1} {...props} imageStyle={size1} />
|
||||||
<TouchableOpacity
|
<GalleryItem index={3} {...props} imageStyle={size1} />
|
||||||
delayPressIn={DELAY_PRESS_IN}
|
|
||||||
onPress={() => onPress?.(1)}
|
|
||||||
onPressIn={() => onPressIn?.(1)}
|
|
||||||
onLongPress={() => onLongPress?.(1)}>
|
|
||||||
<Image
|
|
||||||
source={{uri: images[1].thumb}}
|
|
||||||
style={size1}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={images[1].alt}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
<View style={styles.hSpace} />
|
|
||||||
<TouchableOpacity
|
|
||||||
delayPressIn={DELAY_PRESS_IN}
|
|
||||||
onPress={() => onPress?.(3)}
|
|
||||||
onPressIn={() => onPressIn?.(3)}
|
|
||||||
onLongPress={() => onLongPress?.(3)}>
|
|
||||||
<Image
|
|
||||||
source={{uri: images[3].thumb}}
|
|
||||||
style={size1}
|
|
||||||
accessible={true}
|
|
||||||
accessibilityLabel={images[3].alt}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
</View>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
default:
|
||||||
|
return null
|
||||||
}
|
}
|
||||||
return <View />
|
|
||||||
}
|
}
|
||||||
|
|
||||||
const styles = StyleSheet.create({
|
const styles = StyleSheet.create({
|
||||||
flexRow: {flexDirection: 'row'},
|
flexRow: {flexDirection: 'row', gap: 5},
|
||||||
wSpace: {width: 5},
|
flexColumn: {flexDirection: 'column', gap: 5},
|
||||||
hSpace: {height: 5},
|
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,10 +1,12 @@
|
||||||
import React from 'react'
|
import React, {useCallback} from 'react'
|
||||||
import {
|
import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
StyleProp,
|
StyleProp,
|
||||||
View,
|
View,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
Image as RNImage,
|
Image as RNImage,
|
||||||
|
Pressable,
|
||||||
|
Text,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {
|
import {
|
||||||
AppBskyEmbedImages,
|
AppBskyEmbedImages,
|
||||||
|
@ -14,7 +16,6 @@ import {
|
||||||
AppBskyFeedPost,
|
AppBskyFeedPost,
|
||||||
} from '@atproto/api'
|
} from '@atproto/api'
|
||||||
import {Link} from '../Link'
|
import {Link} from '../Link'
|
||||||
import {AutoSizedImage} from '../images/AutoSizedImage'
|
|
||||||
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
|
import {ImageLayoutGrid} from '../images/ImageLayoutGrid'
|
||||||
import {ImagesLightbox} from 'state/models/ui/shell'
|
import {ImagesLightbox} from 'state/models/ui/shell'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
|
@ -24,6 +25,7 @@ import {YoutubeEmbed} from './YoutubeEmbed'
|
||||||
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
|
import {ExternalLinkEmbed} from './ExternalLinkEmbed'
|
||||||
import {getYoutubeVideoId} from 'lib/strings/url-helpers'
|
import {getYoutubeVideoId} from 'lib/strings/url-helpers'
|
||||||
import QuoteEmbed from './QuoteEmbed'
|
import QuoteEmbed from './QuoteEmbed'
|
||||||
|
import {AutoSizedImage} from '../images/AutoSizedImage'
|
||||||
|
|
||||||
type Embed =
|
type Embed =
|
||||||
| AppBskyEmbedRecord.View
|
| AppBskyEmbedRecord.View
|
||||||
|
@ -42,6 +44,16 @@ export function PostEmbeds({
|
||||||
const pal = usePalette('default')
|
const pal = usePalette('default')
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
|
|
||||||
|
const onPressAltText = useCallback(
|
||||||
|
(alt: string) => {
|
||||||
|
store.shell.openModal({
|
||||||
|
name: 'alt-text-image-read',
|
||||||
|
altText: alt,
|
||||||
|
})
|
||||||
|
},
|
||||||
|
[store.shell],
|
||||||
|
)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
AppBskyEmbedRecordWithMedia.isView(embed) &&
|
AppBskyEmbedRecordWithMedia.isView(embed) &&
|
||||||
AppBskyEmbedRecord.isViewRecord(embed.record.record) &&
|
AppBskyEmbedRecord.isViewRecord(embed.record.record) &&
|
||||||
|
@ -88,7 +100,9 @@ export function PostEmbeds({
|
||||||
}
|
}
|
||||||
|
|
||||||
if (AppBskyEmbedImages.isView(embed)) {
|
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 uris = embed.images.map(img => img.fullsize)
|
||||||
const openLightbox = (index: number) => {
|
const openLightbox = (index: number) => {
|
||||||
store.shell.openLightbox(new ImagesLightbox(uris, index))
|
store.shell.openLightbox(new ImagesLightbox(uris, index))
|
||||||
|
@ -107,21 +121,30 @@ export function PostEmbeds({
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
switch (embed.images.length) {
|
if (images.length === 1) {
|
||||||
case 1:
|
const {alt, thumb} = images[0]
|
||||||
return (
|
return (
|
||||||
<View style={[styles.imagesContainer, style]}>
|
<View style={[styles.imagesContainer, style]}>
|
||||||
<AutoSizedImage
|
<AutoSizedImage
|
||||||
alt={embed.images[0].alt}
|
alt={alt}
|
||||||
uri={embed.images[0].thumb}
|
uri={thumb}
|
||||||
onPress={() => openLightbox(0)}
|
onPress={() => openLightbox(0)}
|
||||||
onLongPress={() => onLongPress(0)}
|
onLongPress={() => onLongPress(0)}
|
||||||
onPressIn={() => onPressIn(0)}
|
onPressIn={() => onPressIn(0)}
|
||||||
style={styles.singleImage}
|
style={styles.singleImage}>
|
||||||
/>
|
{alt === '' ? null : (
|
||||||
|
<Pressable
|
||||||
|
onPress={() => {
|
||||||
|
onPressAltText(alt)
|
||||||
|
}}>
|
||||||
|
<Text style={styles.alt}>ALT</Text>
|
||||||
|
</Pressable>
|
||||||
|
)}
|
||||||
|
</AutoSizedImage>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
default:
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<View style={[styles.imagesContainer, style]}>
|
<View style={[styles.imagesContainer, style]}>
|
||||||
<ImageLayoutGrid
|
<ImageLayoutGrid
|
||||||
|
@ -129,10 +152,11 @@ export function PostEmbeds({
|
||||||
onPress={openLightbox}
|
onPress={openLightbox}
|
||||||
onLongPress={onLongPress}
|
onLongPress={onLongPress}
|
||||||
onPressIn={onPressIn}
|
onPressIn={onPressIn}
|
||||||
|
style={embed.images.length === 1 ? styles.singleImage : undefined}
|
||||||
/>
|
/>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -172,4 +196,18 @@ const styles = StyleSheet.create({
|
||||||
borderRadius: 8,
|
borderRadius: 8,
|
||||||
marginTop: 4,
|
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,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue