[APP-539] Rework lightbox and alt-image behaviors (#573)
* Replace the long press on the lightbox with footer controls * Remove long-press from images in the feed * Tune the lightbox footer control ui * Replace the AltImageRead modal with the ability to view all alt text in the lightbox footer * Tune lightbox footer for iOS * Add alt text to the web lightbox * Fix lint * a11y slight changes --------- Co-authored-by: renahlee <renahlee@outlook.com>zio/stable
parent
4ef853ef6c
commit
d97e75c62f
|
@ -48,11 +48,6 @@ export interface AltTextImageModal {
|
||||||
image: ImageModel
|
image: ImageModel
|
||||||
}
|
}
|
||||||
|
|
||||||
export interface AltTextImageReadModal {
|
|
||||||
name: 'alt-text-image-read'
|
|
||||||
altText: string
|
|
||||||
}
|
|
||||||
|
|
||||||
export interface DeleteAccountModal {
|
export interface DeleteAccountModal {
|
||||||
name: 'delete-account'
|
name: 'delete-account'
|
||||||
}
|
}
|
||||||
|
@ -106,7 +101,6 @@ export type Modal =
|
||||||
|
|
||||||
// Posts
|
// Posts
|
||||||
| AltTextImageModal
|
| AltTextImageModal
|
||||||
| AltTextImageReadModal
|
|
||||||
| CropImageModal
|
| CropImageModal
|
||||||
| ServerInputModal
|
| ServerInputModal
|
||||||
| RepostModal
|
| RepostModal
|
||||||
|
@ -127,9 +121,14 @@ export class ProfileImageLightbox implements LightboxModel {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
interface ImagesLightboxItem {
|
||||||
|
uri: string
|
||||||
|
alt?: string
|
||||||
|
}
|
||||||
|
|
||||||
export class ImagesLightbox implements LightboxModel {
|
export class ImagesLightbox implements LightboxModel {
|
||||||
name = 'images'
|
name = 'images'
|
||||||
constructor(public uris: string[], public index: number) {
|
constructor(public images: ImagesLightboxItem[], public index: number) {
|
||||||
makeAutoObservable(this)
|
makeAutoObservable(this)
|
||||||
}
|
}
|
||||||
setIndex(index: number) {
|
setIndex(index: number) {
|
||||||
|
@ -173,7 +172,7 @@ export class ShellUiModel {
|
||||||
isModalActive = false
|
isModalActive = false
|
||||||
activeModals: Modal[] = []
|
activeModals: Modal[] = []
|
||||||
isLightboxActive = false
|
isLightboxActive = false
|
||||||
activeLightbox: ProfileImageLightbox | ImagesLightbox | undefined
|
activeLightbox: ProfileImageLightbox | ImagesLightbox | null = null
|
||||||
isComposerActive = false
|
isComposerActive = false
|
||||||
composerOpts: ComposerOpts | undefined
|
composerOpts: ComposerOpts | undefined
|
||||||
|
|
||||||
|
@ -262,7 +261,7 @@ export class ShellUiModel {
|
||||||
|
|
||||||
closeLightbox() {
|
closeLightbox() {
|
||||||
this.isLightboxActive = false
|
this.isLightboxActive = false
|
||||||
this.activeLightbox = undefined
|
this.activeLightbox = null
|
||||||
}
|
}
|
||||||
|
|
||||||
openComposer(opts: ComposerOpts) {
|
openComposer(opts: ComposerOpts) {
|
||||||
|
|
|
@ -1,31 +1,75 @@
|
||||||
import React from 'react'
|
import React from 'react'
|
||||||
|
import {Pressable, StyleSheet, View} from 'react-native'
|
||||||
import {observer} from 'mobx-react-lite'
|
import {observer} from 'mobx-react-lite'
|
||||||
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import ImageView from './ImageViewing'
|
import ImageView from './ImageViewing'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import * as models from 'state/models/ui/shell'
|
import * as models from 'state/models/ui/shell'
|
||||||
import {saveImageModal} from 'lib/media/manip'
|
import {saveImageModal} from 'lib/media/manip'
|
||||||
import {ImageSource} from './ImageViewing/@types'
|
import {Text} from '../util/text/Text'
|
||||||
|
import {s, colors} from 'lib/styles'
|
||||||
|
import {Button} from '../util/forms/Button'
|
||||||
|
import {isIOS} from 'platform/detection'
|
||||||
|
|
||||||
export const Lightbox = observer(function Lightbox() {
|
export const Lightbox = observer(function Lightbox() {
|
||||||
const store = useStores()
|
const store = useStores()
|
||||||
if (!store.shell.isLightboxActive) {
|
const [isAltExpanded, setAltExpanded] = React.useState(false)
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
const onClose = () => {
|
const onClose = React.useCallback(() => {
|
||||||
store.shell.closeLightbox()
|
store.shell.closeLightbox()
|
||||||
}
|
}, [store])
|
||||||
const onLongPress = (image: ImageSource) => {
|
|
||||||
if (
|
|
||||||
typeof image === 'object' &&
|
|
||||||
'uri' in image &&
|
|
||||||
typeof image.uri === 'string'
|
|
||||||
) {
|
|
||||||
saveImageModal({uri: image.uri})
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (store.shell.activeLightbox?.name === 'profile-image') {
|
const LightboxFooter = React.useCallback(
|
||||||
|
({imageIndex}: {imageIndex: number}) => {
|
||||||
|
const lightbox = store.shell.activeLightbox
|
||||||
|
if (!lightbox) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
let altText = ''
|
||||||
|
let uri
|
||||||
|
if (lightbox.name === 'images') {
|
||||||
|
const opts = store.shell.activeLightbox as models.ImagesLightbox
|
||||||
|
uri = opts.images[imageIndex].uri
|
||||||
|
altText = opts.images[imageIndex].alt
|
||||||
|
} else if (store.shell.activeLightbox.name === 'profile-image') {
|
||||||
|
const opts = store.shell.activeLightbox as models.ProfileImageLightbox
|
||||||
|
uri = opts.profileView.avatar
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[styles.footer]}>
|
||||||
|
{altText ? (
|
||||||
|
<Pressable
|
||||||
|
onPress={() => setAltExpanded(!isAltExpanded)}
|
||||||
|
accessibilityRole="button">
|
||||||
|
<Text
|
||||||
|
style={[s.gray3, styles.footerText]}
|
||||||
|
numberOfLines={isAltExpanded ? undefined : 3}>
|
||||||
|
{altText}
|
||||||
|
</Text>
|
||||||
|
</Pressable>
|
||||||
|
) : null}
|
||||||
|
<View style={styles.footerBtns}>
|
||||||
|
<Button
|
||||||
|
type="primary-outline"
|
||||||
|
style={styles.footerBtn}
|
||||||
|
onPress={() => saveImageModal({uri})}>
|
||||||
|
<FontAwesomeIcon icon="arrow-up-from-bracket" style={s.white} />
|
||||||
|
<Text type="xl" style={s.white}>
|
||||||
|
Share
|
||||||
|
</Text>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
},
|
||||||
|
[store.shell.activeLightbox, isAltExpanded, setAltExpanded],
|
||||||
|
)
|
||||||
|
|
||||||
|
if (!store.shell.activeLightbox) {
|
||||||
|
return null
|
||||||
|
} else if (store.shell.activeLightbox.name === 'profile-image') {
|
||||||
const opts = store.shell.activeLightbox as models.ProfileImageLightbox
|
const opts = store.shell.activeLightbox as models.ProfileImageLightbox
|
||||||
return (
|
return (
|
||||||
<ImageView
|
<ImageView
|
||||||
|
@ -33,20 +77,44 @@ export const Lightbox = observer(function Lightbox() {
|
||||||
imageIndex={0}
|
imageIndex={0}
|
||||||
visible
|
visible
|
||||||
onRequestClose={onClose}
|
onRequestClose={onClose}
|
||||||
|
FooterComponent={LightboxFooter}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if (store.shell.activeLightbox?.name === 'images') {
|
} else if (store.shell.activeLightbox.name === 'images') {
|
||||||
const opts = store.shell.activeLightbox as models.ImagesLightbox
|
const opts = store.shell.activeLightbox as models.ImagesLightbox
|
||||||
return (
|
return (
|
||||||
<ImageView
|
<ImageView
|
||||||
images={opts.uris.map(uri => ({uri}))}
|
images={opts.images.map(({uri}) => ({uri}))}
|
||||||
imageIndex={opts.index}
|
imageIndex={opts.index}
|
||||||
visible
|
visible
|
||||||
onRequestClose={onClose}
|
onRequestClose={onClose}
|
||||||
onLongPress={onLongPress}
|
FooterComponent={LightboxFooter}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
return null
|
return null
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|
||||||
|
const styles = StyleSheet.create({
|
||||||
|
footer: {
|
||||||
|
paddingTop: 16,
|
||||||
|
paddingBottom: isIOS ? 40 : 24,
|
||||||
|
paddingHorizontal: 24,
|
||||||
|
backgroundColor: '#000d',
|
||||||
|
},
|
||||||
|
footerText: {
|
||||||
|
paddingBottom: isIOS ? 20 : 16,
|
||||||
|
},
|
||||||
|
footerBtns: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
justifyContent: 'center',
|
||||||
|
},
|
||||||
|
footerBtn: {
|
||||||
|
flexDirection: 'row',
|
||||||
|
alignItems: 'center',
|
||||||
|
gap: 8,
|
||||||
|
backgroundColor: 'transparent',
|
||||||
|
borderColor: colors.white,
|
||||||
|
},
|
||||||
|
})
|
||||||
|
|
|
@ -10,11 +10,13 @@ import {observer} from 'mobx-react-lite'
|
||||||
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
|
||||||
import {useStores} from 'state/index'
|
import {useStores} from 'state/index'
|
||||||
import * as models from 'state/models/ui/shell'
|
import * as models from 'state/models/ui/shell'
|
||||||
import {colors} from 'lib/styles'
|
import {colors, s} from 'lib/styles'
|
||||||
import ImageDefaultHeader from './ImageViewing/components/ImageDefaultHeader'
|
import ImageDefaultHeader from './ImageViewing/components/ImageDefaultHeader'
|
||||||
|
import {Text} from '../util/text/Text'
|
||||||
|
|
||||||
interface Img {
|
interface Img {
|
||||||
uri: string
|
uri: string
|
||||||
|
alt?: string
|
||||||
}
|
}
|
||||||
|
|
||||||
export const Lightbox = observer(function Lightbox() {
|
export const Lightbox = observer(function Lightbox() {
|
||||||
|
@ -37,7 +39,7 @@ export const Lightbox = observer(function Lightbox() {
|
||||||
}
|
}
|
||||||
} else if (activeLightbox instanceof models.ImagesLightbox) {
|
} else if (activeLightbox instanceof models.ImagesLightbox) {
|
||||||
const opts = activeLightbox
|
const opts = activeLightbox
|
||||||
imgs = opts.uris.map(uri => ({uri}))
|
imgs = opts.images
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!imgs) {
|
if (!imgs) {
|
||||||
|
@ -131,6 +133,11 @@ function LightboxInner({
|
||||||
)}
|
)}
|
||||||
</View>
|
</View>
|
||||||
</TouchableWithoutFeedback>
|
</TouchableWithoutFeedback>
|
||||||
|
{imgs[index].alt ? (
|
||||||
|
<View style={styles.footer}>
|
||||||
|
<Text style={s.white}>{imgs[index].alt}</Text>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
<View style={styles.closeBtn}>
|
<View style={styles.closeBtn}>
|
||||||
<ImageDefaultHeader onRequestClose={onClose} />
|
<ImageDefaultHeader onRequestClose={onClose} />
|
||||||
</View>
|
</View>
|
||||||
|
@ -183,4 +190,9 @@ const styles = StyleSheet.create({
|
||||||
right: 30,
|
right: 30,
|
||||||
top: '50%',
|
top: '50%',
|
||||||
},
|
},
|
||||||
|
footer: {
|
||||||
|
paddingHorizontal: 32,
|
||||||
|
paddingVertical: 24,
|
||||||
|
backgroundColor: colors.black,
|
||||||
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,80 +0,0 @@
|
||||||
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}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel="Done"
|
|
||||||
accessibilityHint="Closes alt text modal">
|
|
||||||
<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,7 +13,6 @@ 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'
|
||||||
|
@ -76,9 +75,6 @@ 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,7 +15,6 @@ 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'
|
||||||
|
@ -89,8 +88,6 @@ function Modal({modal}: {modal: ModalIface}) {
|
||||||
element = <ContentLanguagesSettingsModal.Component />
|
element = <ContentLanguagesSettingsModal.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
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
import {AppBskyEmbedImages} from '@atproto/api'
|
import {AppBskyEmbedImages} from '@atproto/api'
|
||||||
import React, {ComponentProps, FC, useCallback} from 'react'
|
import React, {ComponentProps, FC} from 'react'
|
||||||
import {Pressable, StyleSheet, Text, TouchableOpacity, View} from 'react-native'
|
import {StyleSheet, Text, TouchableOpacity, View} from 'react-native'
|
||||||
import {Image} from 'expo-image'
|
import {Image} from 'expo-image'
|
||||||
import {useStores} from 'state/index'
|
|
||||||
|
|
||||||
type EventFunction = (index: number) => void
|
type EventFunction = (index: number) => void
|
||||||
|
|
||||||
|
@ -26,22 +25,14 @@ export const GalleryItem: FC<GalleryItemProps> = ({
|
||||||
onLongPress,
|
onLongPress,
|
||||||
}) => {
|
}) => {
|
||||||
const image = images[index]
|
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 (
|
return (
|
||||||
<View>
|
<View>
|
||||||
<TouchableOpacity
|
<TouchableOpacity
|
||||||
delayPressIn={DELAY_PRESS_IN}
|
delayPressIn={DELAY_PRESS_IN}
|
||||||
onPress={() => onPress?.(index)}
|
onPress={onPress ? () => onPress(index) : undefined}
|
||||||
onPressIn={() => onPressIn?.(index)}
|
onPressIn={onPressIn ? () => onPressIn(index) : undefined}
|
||||||
onLongPress={() => onLongPress?.(index)}
|
onLongPress={onLongPress ? () => onLongPress(index) : undefined}
|
||||||
accessibilityRole="button"
|
accessibilityRole="button"
|
||||||
accessibilityLabel="View image"
|
accessibilityLabel="View image"
|
||||||
accessibilityHint="">
|
accessibilityHint="">
|
||||||
|
@ -54,15 +45,7 @@ export const GalleryItem: FC<GalleryItemProps> = ({
|
||||||
accessibilityIgnoresInvertColors
|
accessibilityIgnoresInvertColors
|
||||||
/>
|
/>
|
||||||
</TouchableOpacity>
|
</TouchableOpacity>
|
||||||
{image.alt === '' ? null : (
|
{image.alt === '' ? null : <Text style={styles.alt}>ALT</Text>}
|
||||||
<Pressable
|
|
||||||
onPress={onPressAltText}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel="View alt text"
|
|
||||||
accessibilityHint="Opens modal with alt text">
|
|
||||||
<Text style={styles.alt}>ALT</Text>
|
|
||||||
</Pressable>
|
|
||||||
)}
|
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -78,8 +61,8 @@ const styles = StyleSheet.create({
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
paddingVertical: 3,
|
paddingVertical: 3,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: 10,
|
left: 6,
|
||||||
top: -26,
|
bottom: 6,
|
||||||
width: 46,
|
width: 46,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
|
@ -1,11 +1,10 @@
|
||||||
import React, {useCallback} from 'react'
|
import React from 'react'
|
||||||
import {
|
import {
|
||||||
StyleSheet,
|
StyleSheet,
|
||||||
StyleProp,
|
StyleProp,
|
||||||
View,
|
View,
|
||||||
ViewStyle,
|
ViewStyle,
|
||||||
Image as RNImage,
|
Image as RNImage,
|
||||||
Pressable,
|
|
||||||
Text,
|
Text,
|
||||||
} from 'react-native'
|
} from 'react-native'
|
||||||
import {
|
import {
|
||||||
|
@ -20,7 +19,6 @@ 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'
|
||||||
import {usePalette} from 'lib/hooks/usePalette'
|
import {usePalette} from 'lib/hooks/usePalette'
|
||||||
import {saveImageModal} from 'lib/media/manip'
|
|
||||||
import {YoutubeEmbed} from './YoutubeEmbed'
|
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'
|
||||||
|
@ -44,16 +42,6 @@ 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) &&
|
||||||
|
@ -103,20 +91,17 @@ export function PostEmbeds({
|
||||||
const {images} = embed
|
const {images} = embed
|
||||||
|
|
||||||
if (images.length > 0) {
|
if (images.length > 0) {
|
||||||
const uris = embed.images.map(img => img.fullsize)
|
const items = embed.images.map(img => ({uri: img.fullsize, alt: img.alt}))
|
||||||
const openLightbox = (index: number) => {
|
const openLightbox = (index: number) => {
|
||||||
store.shell.openLightbox(new ImagesLightbox(uris, index))
|
store.shell.openLightbox(new ImagesLightbox(items, index))
|
||||||
}
|
|
||||||
const onLongPress = (index: number) => {
|
|
||||||
saveImageModal({uri: uris[index]})
|
|
||||||
}
|
}
|
||||||
const onPressIn = (index: number) => {
|
const onPressIn = (index: number) => {
|
||||||
const firstImageToShow = uris[index]
|
const firstImageToShow = items[index].uri
|
||||||
RNImage.prefetch(firstImageToShow)
|
RNImage.prefetch(firstImageToShow)
|
||||||
uris.forEach(uri => {
|
items.forEach(item => {
|
||||||
if (firstImageToShow !== uri) {
|
if (firstImageToShow !== item.uri) {
|
||||||
// First image already prefeched above
|
// First image already prefeched above
|
||||||
RNImage.prefetch(uri)
|
RNImage.prefetch(item.uri)
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
@ -129,20 +114,9 @@ export function PostEmbeds({
|
||||||
alt={alt}
|
alt={alt}
|
||||||
uri={thumb}
|
uri={thumb}
|
||||||
onPress={() => openLightbox(0)}
|
onPress={() => openLightbox(0)}
|
||||||
onLongPress={() => onLongPress(0)}
|
|
||||||
onPressIn={() => onPressIn(0)}
|
onPressIn={() => onPressIn(0)}
|
||||||
style={styles.singleImage}>
|
style={styles.singleImage}>
|
||||||
{alt === '' ? null : (
|
{alt === '' ? null : <Text style={styles.alt}>ALT</Text>}
|
||||||
<Pressable
|
|
||||||
onPress={() => {
|
|
||||||
onPressAltText(alt)
|
|
||||||
}}
|
|
||||||
accessibilityRole="button"
|
|
||||||
accessibilityLabel="View alt text"
|
|
||||||
accessibilityHint="Opens modal with alt text">
|
|
||||||
<Text style={styles.alt}>ALT</Text>
|
|
||||||
</Pressable>
|
|
||||||
)}
|
|
||||||
</AutoSizedImage>
|
</AutoSizedImage>
|
||||||
</View>
|
</View>
|
||||||
)
|
)
|
||||||
|
@ -153,7 +127,6 @@ export function PostEmbeds({
|
||||||
<ImageLayoutGrid
|
<ImageLayoutGrid
|
||||||
images={embed.images}
|
images={embed.images}
|
||||||
onPress={openLightbox}
|
onPress={openLightbox}
|
||||||
onLongPress={onLongPress}
|
|
||||||
onPressIn={onPressIn}
|
onPressIn={onPressIn}
|
||||||
style={embed.images.length === 1 ? styles.singleImage : undefined}
|
style={embed.images.length === 1 ? styles.singleImage : undefined}
|
||||||
/>
|
/>
|
||||||
|
@ -209,8 +182,8 @@ const styles = StyleSheet.create({
|
||||||
paddingHorizontal: 10,
|
paddingHorizontal: 10,
|
||||||
paddingVertical: 3,
|
paddingVertical: 3,
|
||||||
position: 'absolute',
|
position: 'absolute',
|
||||||
left: 10,
|
left: 6,
|
||||||
top: -26,
|
bottom: 6,
|
||||||
width: 46,
|
width: 46,
|
||||||
},
|
},
|
||||||
})
|
})
|
||||||
|
|
Loading…
Reference in New Issue