diff --git a/src/components/Prompt.tsx b/src/components/Prompt.tsx
index 0a171674de..327bbbaa23 100644
--- a/src/components/Prompt.tsx
+++ b/src/components/Prompt.tsx
@@ -43,7 +43,9 @@ export function Outer({
+ style={[
+ gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full,
+ ]}>
{children}
diff --git a/src/components/dialogs/MutedWords.tsx b/src/components/dialogs/MutedWords.tsx
index 0eced11e3d..534263422d 100644
--- a/src/components/dialogs/MutedWords.tsx
+++ b/src/components/dialogs/MutedWords.tsx
@@ -37,12 +37,12 @@ export function MutedWordsDialog() {
return (
-
+
)
}
-function MutedWordsInner({}: {control: Dialog.DialogOuterProps['control']}) {
+function MutedWordsInner() {
const t = useTheme()
const {_} = useLingui()
const {gtMobile} = useBreakpoints()
diff --git a/src/view/com/composer/Composer.tsx b/src/view/com/composer/Composer.tsx
index 0ac4ac56e3..f472bb2e2c 100644
--- a/src/view/com/composer/Composer.tsx
+++ b/src/view/com/composer/Composer.tsx
@@ -59,6 +59,7 @@ import * as Toast from '../util/Toast'
import {UserAvatar} from '../util/UserAvatar'
import {CharProgress} from './char-progress/CharProgress'
import {ExternalEmbed} from './ExternalEmbed'
+import {GifAltText} from './GifAltText'
import {LabelsBtn} from './labels/LabelsBtn'
import {Gallery} from './photos/Gallery'
import {OpenCameraBtn} from './photos/OpenCameraBtn'
@@ -327,7 +328,7 @@ export const ComposePost = observer(function ComposePost({
image: gif.media_formats.preview.url,
likelyType: LikelyType.HTML,
title: gif.content_description,
- description: `ALT: ${gif.content_description}`,
+ description: '',
},
})
setExtGif(gif)
@@ -335,6 +336,26 @@ export const ComposePost = observer(function ComposePost({
[setExtLink],
)
+ const handleChangeGifAltText = useCallback(
+ (altText: string) => {
+ setExtLink(ext =>
+ ext && ext.meta
+ ? {
+ ...ext,
+ meta: {
+ ...ext?.meta,
+ description:
+ altText.trim().length === 0
+ ? ''
+ : `Alt text: ${altText.trim()}`,
+ },
+ }
+ : ext,
+ )
+ },
+ [setExtLink],
+ )
+
return (
{gallery.isEmpty && extLink && (
- {
- setExtLink(undefined)
- setExtGif(undefined)
- }}
- />
+
+ {
+ setExtLink(undefined)
+ setExtGif(undefined)
+ }}
+ />
+
+
)}
{quote ? (
diff --git a/src/view/com/composer/ExternalEmbed.tsx b/src/view/com/composer/ExternalEmbed.tsx
index 321e29b30a..b81065e99d 100644
--- a/src/view/com/composer/ExternalEmbed.tsx
+++ b/src/view/com/composer/ExternalEmbed.tsx
@@ -46,7 +46,12 @@ export const ExternalEmbed = ({
: undefined
return (
-
+
{link.isLoading ? (
@@ -62,7 +67,7 @@ export const ExternalEmbed = ({
) : linkInfo ? (
-
+
) : null}
void
+}) {
+ const control = Dialog.useDialogControl()
+ const {_} = useLingui()
+ const t = useTheme()
+
+ const {link, params} = React.useMemo(() => {
+ return {
+ link: {
+ title: linkProp.meta?.title ?? linkProp.uri,
+ uri: linkProp.uri,
+ description: linkProp.meta?.description ?? '',
+ thumb: linkProp.localThumb?.path,
+ },
+ params: parseEmbedPlayerFromUrl(linkProp.uri),
+ }
+ }, [linkProp])
+
+ const onPressSubmit = useCallback(
+ (alt: string) => {
+ control.close(() => {
+ onSubmit(alt)
+ })
+ },
+ [onSubmit, control],
+ )
+
+ if (!gif || !params) return null
+
+ return (
+ <>
+
+ {link.description ? (
+
+ ) : (
+
+ )}
+
+ ALT
+
+
+
+
+
+
+
+
+
+ >
+ )
+}
+
+function AltTextInner({
+ onSubmit,
+ link,
+ params,
+ initalValue,
+}: {
+ onSubmit: (text: string) => void
+ link: AppBskyEmbedExternal.ViewExternal
+ params: EmbedPlayerParams
+ initalValue: string
+}) {
+ const {_} = useLingui()
+ const [altText, setAltText] = useState(initalValue)
+
+ const onPressSubmit = useCallback(() => {
+ onSubmit(altText)
+ }, [onSubmit, altText])
+
+ return (
+
+
+
+
+
+ Descriptive alt text
+
+
+
+ setAltText(enforceLen(text, MAX_ALT_TEXT))
+ }
+ value={altText}
+ multiline
+ numberOfLines={3}
+ autoFocus
+ />
+
+
+
+
+ {/* below the text input to force tab order */}
+
+
+ Add ALT text
+
+
+
+
+
+
+
+
+ )
+}
diff --git a/src/view/com/composer/photos/Gallery.tsx b/src/view/com/composer/photos/Gallery.tsx
index 69c8debb02..7ff1b7b9ab 100644
--- a/src/view/com/composer/photos/Gallery.tsx
+++ b/src/view/com/composer/photos/Gallery.tsx
@@ -1,19 +1,20 @@
import React, {useState} from 'react'
import {ImageStyle, Keyboard, LayoutChangeEvent} from 'react-native'
-import {GalleryModel} from 'state/models/media/gallery'
-import {observer} from 'mobx-react-lite'
-import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {s, colors} from 'lib/styles'
import {StyleSheet, TouchableOpacity, View} from 'react-native'
import {Image} from 'expo-image'
-import {Text} from 'view/com/util/text/Text'
-import {Dimensions} from 'lib/media/types'
-import {usePalette} from 'lib/hooks/usePalette'
-import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
-import {Trans, msg} from '@lingui/macro'
+import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
+import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import {observer} from 'mobx-react-lite'
+
import {useModalControls} from '#/state/modals'
+import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries'
+import {Dimensions} from 'lib/media/types'
+import {colors, s} from 'lib/styles'
import {isNative} from 'platform/detection'
+import {GalleryModel} from 'state/models/media/gallery'
+import {Text} from 'view/com/util/text/Text'
+import {useTheme} from '#/alf'
const IMAGE_GAP = 8
@@ -49,10 +50,10 @@ const GalleryInner = observer(function GalleryImpl({
gallery,
containerInfo,
}: GalleryInnerProps) {
- const pal = usePalette('default')
const {_} = useLingui()
const {isMobile} = useWebMediaQueries()
const {openModal} = useModalControls()
+ const t = useTheme()
let side: number
@@ -126,16 +127,22 @@ const GalleryInner = observer(function GalleryImpl({
})
}}
style={[styles.altTextControl, altTextControlStyle]}>
-
- ALT
-
{image.altText.length > 0 ? (
+ ) : (
+
- ) : undefined}
+ )}
+
+ ALT
+
))}
-
-
-
-
-
-
- Alt text describes images for blind and low-vision users, and helps
- give context to everyone.
-
-
-
+
>
) : null
})
+export function AltTextReminder() {
+ const t = useTheme()
+ return (
+
+
+
+
+
+
+ Alt text describes images for blind and low-vision users, and helps
+ give context to everyone.
+
+
+
+ )
+}
+
const styles = StyleSheet.create({
gallery: {
flex: 1,
@@ -244,6 +258,7 @@ const styles = StyleSheet.create({
paddingVertical: 3,
flexDirection: 'row',
alignItems: 'center',
+ gap: 4,
},
altTextControlLabel: {
color: 'white',
diff --git a/src/view/com/util/images/Gallery.tsx b/src/view/com/util/images/Gallery.tsx
index 7de3b093ae..f6d2c7a1b0 100644
--- a/src/view/com/util/images/Gallery.tsx
+++ b/src/view/com/util/images/Gallery.tsx
@@ -1,9 +1,10 @@
-import {AppBskyEmbedImages} from '@atproto/api'
import React, {ComponentProps, FC} from 'react'
-import {StyleSheet, Text, Pressable, View} from 'react-native'
+import {Pressable, StyleSheet, Text, View} from 'react-native'
import {Image} from 'expo-image'
+import {AppBskyEmbedImages} from '@atproto/api'
import {msg} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+
import {isWeb} from 'platform/detection'
type EventFunction = (index: number) => void
diff --git a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
index 1fe75c44ea..b84c04b831 100644
--- a/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
+++ b/src/view/com/util/post-embeds/ExternalLinkEmbed.tsx
@@ -20,9 +20,11 @@ import {Text} from '../text/Text'
export const ExternalLinkEmbed = ({
link,
style,
+ hideAlt,
}: {
link: AppBskyEmbedExternal.ViewExternal
style?: StyleProp
+ hideAlt?: boolean
}) => {
const pal = usePalette('default')
const {isMobile} = useWebMediaQueries()
@@ -37,7 +39,7 @@ export const ExternalLinkEmbed = ({
}, [link.uri, externalEmbedPrefs])
if (embedPlayerParams?.source === 'tenor') {
- return
+ return
}
return (
diff --git a/src/view/com/util/post-embeds/GifEmbed.tsx b/src/view/com/util/post-embeds/GifEmbed.tsx
index 5d21ce0642..dde6efe348 100644
--- a/src/view/com/util/post-embeds/GifEmbed.tsx
+++ b/src/view/com/util/post-embeds/GifEmbed.tsx
@@ -1,14 +1,18 @@
import React from 'react'
-import {Pressable, View} from 'react-native'
+import {Pressable, StyleSheet, TouchableOpacity, View} from 'react-native'
import {AppBskyEmbedExternal} from '@atproto/api'
import {FontAwesomeIcon} from '@fortawesome/react-native-fontawesome'
-import {msg} from '@lingui/macro'
+import {msg, Trans} from '@lingui/macro'
import {useLingui} from '@lingui/react'
+import {HITSLOP_10} from '#/lib/constants'
+import {isWeb} from '#/platform/detection'
import {EmbedPlayerParams} from 'lib/strings/embed-player'
import {useAutoplayDisabled} from 'state/preferences'
import {atoms as a, useTheme} from '#/alf'
import {Loader} from '#/components/Loader'
+import * as Prompt from '#/components/Prompt'
+import {Text} from '#/components/Typography'
import {GifView} from '../../../../../modules/expo-bluesky-gif-view'
import {GifViewStateChangeEvent} from '../../../../../modules/expo-bluesky-gif-view/src/GifView.types'
@@ -82,9 +86,11 @@ function PlaybackControls({
export function GifEmbed({
params,
link,
+ hideAlt,
}: {
params: EmbedPlayerParams
link: AppBskyEmbedExternal.ViewExternal
+ hideAlt?: boolean
}) {
const {_} = useLingui()
const autoplayDisabled = useAutoplayDisabled()
@@ -111,7 +117,8 @@ export function GifEmbed({
}, [])
return (
-
+
+
+ {!hideAlt && link.description.startsWith('Alt text: ') && (
+
+ )}
)
}
+
+function AltText({text}: {text: string}) {
+ const control = Prompt.usePromptControl()
+
+ const {_} = useLingui()
+ return (
+ <>
+
+
+ ALT
+
+
+
+
+
+ Alt Text
+
+ {text}
+
+
+
+
+ >
+ )
+}
+
+const styles = StyleSheet.create({
+ altContainer: {
+ backgroundColor: 'rgba(0, 0, 0, 0.75)',
+ borderRadius: 6,
+ paddingHorizontal: 6,
+ paddingVertical: 3,
+ position: 'absolute',
+ // Related to margin/gap hack. This keeps the alt label in the same position
+ // on all platforms
+ left: isWeb ? 8 : 5,
+ bottom: isWeb ? 8 : 5,
+ zIndex: 2,
+ },
+ alt: {
+ color: 'white',
+ fontSize: 10,
+ fontWeight: 'bold',
+ },
+})