Bluesky app fork with some witchin' additions 馃挮
at main 231 lines 6.8 kB view raw
1import {useState} from 'react' 2import {TouchableOpacity, View} from 'react-native' 3import {msg} from '@lingui/core/macro' 4import {useLingui} from '@lingui/react' 5import {Plural, Trans} from '@lingui/react/macro' 6 7import {HITSLOP_10, MAX_ALT_TEXT} from '#/lib/constants' 8import {parseAltFromGIFDescription} from '#/lib/gif-alt-text' 9import { 10 type EmbedPlayerParams, 11 parseEmbedPlayerFromUrl, 12} from '#/lib/strings/embed-player' 13import {useResolveGifQuery} from '#/state/queries/resolve-link' 14import {type Gif} from '#/state/queries/tenor' 15import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper' 16import {atoms as a, useTheme} from '#/alf' 17import {Button, ButtonText} from '#/components/Button' 18import * as Dialog from '#/components/Dialog' 19import {type DialogControlProps} from '#/components/Dialog' 20import * as TextField from '#/components/forms/TextField' 21import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check' 22import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo' 23import {PlusSmall_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' 24import {GifEmbed} from '#/components/Post/Embed/ExternalEmbed/Gif' 25import {Text} from '#/components/Typography' 26import {IS_ANDROID} from '#/env' 27import {AltTextReminder} from './photos/Gallery' 28 29export function GifAltTextDialog({ 30 gif, 31 altText, 32 onSubmit, 33}: { 34 gif: Gif 35 altText: string 36 onSubmit: (alt: string) => void 37}) { 38 const {data} = useResolveGifQuery(gif) 39 const vendorAltText = parseAltFromGIFDescription(data?.description ?? '').alt 40 const params = data ? parseEmbedPlayerFromUrl(data.uri) : undefined 41 if (!data || !params) { 42 return null 43 } 44 return ( 45 <GifAltTextDialogLoaded 46 altText={altText} 47 vendorAltText={vendorAltText} 48 thumb={data.thumb?.source.path} 49 params={params} 50 onSubmit={onSubmit} 51 /> 52 ) 53} 54 55export function GifAltTextDialogLoaded({ 56 vendorAltText, 57 altText, 58 onSubmit, 59 params, 60 thumb, 61}: { 62 vendorAltText: string 63 altText: string 64 onSubmit: (alt: string) => void 65 params: EmbedPlayerParams 66 thumb: string | undefined 67}) { 68 const control = Dialog.useDialogControl() 69 const {_} = useLingui() 70 const t = useTheme() 71 const [altTextDraft, setAltTextDraft] = useState(altText || vendorAltText) 72 return ( 73 <> 74 <TouchableOpacity 75 testID="altTextButton" 76 accessibilityRole="button" 77 accessibilityLabel={_(msg`Add alt text`)} 78 accessibilityHint="" 79 hitSlop={HITSLOP_10} 80 onPress={control.open} 81 style={[ 82 a.absolute, 83 {top: 8, left: 8}, 84 {borderRadius: 6}, 85 a.pl_xs, 86 a.pr_sm, 87 a.py_2xs, 88 a.flex_row, 89 a.gap_xs, 90 a.align_center, 91 {backgroundColor: 'rgba(0, 0, 0, 0.75)'}, 92 ]}> 93 {altText ? ( 94 <Check size="xs" fill={t.palette.white} style={a.ml_xs} /> 95 ) : ( 96 <Plus size="sm" fill={t.palette.white} /> 97 )} 98 <Text 99 style={[a.font_semi_bold, {color: t.palette.white}]} 100 accessible={false}> 101 <Trans>ALT</Trans> 102 </Text> 103 </TouchableOpacity> 104 105 <AltTextReminder /> 106 107 <Dialog.Outer 108 control={control} 109 onClose={() => { 110 onSubmit(altTextDraft) 111 }}> 112 <Dialog.Handle /> 113 <AltTextInner 114 vendorAltText={vendorAltText} 115 altText={altTextDraft} 116 onChange={setAltTextDraft} 117 thumb={thumb} 118 control={control} 119 params={params} 120 /> 121 </Dialog.Outer> 122 </> 123 ) 124} 125 126function AltTextInner({ 127 vendorAltText, 128 altText, 129 onChange, 130 control, 131 params, 132 thumb, 133}: { 134 vendorAltText: string 135 altText: string 136 onChange: (text: string) => void 137 control: DialogControlProps 138 params: EmbedPlayerParams 139 thumb: string | undefined 140}) { 141 const t = useTheme() 142 const {_, i18n} = useLingui() 143 144 return ( 145 <Dialog.ScrollableInner label={_(msg`Add alt text`)}> 146 <View style={a.flex_col_reverse}> 147 <View style={[a.mt_md, a.gap_md]}> 148 <View style={[a.gap_sm]}> 149 <View style={[a.relative]}> 150 <TextField.LabelText> 151 <Trans>Descriptive alt text</Trans> 152 </TextField.LabelText> 153 <TextField.Root> 154 <Dialog.Input 155 label={_(msg`Alt text`)} 156 placeholder={vendorAltText} 157 onChangeText={onChange} 158 defaultValue={altText} 159 multiline 160 numberOfLines={3} 161 autoFocus 162 onKeyPress={({nativeEvent}) => { 163 if (nativeEvent.key === 'Escape') { 164 control.close() 165 } 166 }} 167 /> 168 </TextField.Root> 169 </View> 170 171 {altText.length > MAX_ALT_TEXT && ( 172 <View style={[a.pb_sm, a.flex_row, a.gap_xs]}> 173 <CircleInfo fill={t.palette.negative_500} /> 174 <Text 175 style={[ 176 a.italic, 177 a.leading_snug, 178 t.atoms.text_contrast_medium, 179 ]}> 180 <Trans> 181 Alt text will be truncated.{' '} 182 <Plural 183 value={MAX_ALT_TEXT} 184 other={`Limit: ${i18n.number(MAX_ALT_TEXT)} characters.`} 185 /> 186 </Trans> 187 </Text> 188 </View> 189 )} 190 </View> 191 192 <AltTextCounterWrapper altText={altText}> 193 <Button 194 label={_(msg`Save`)} 195 size="large" 196 color="primary" 197 variant="solid" 198 onPress={() => { 199 control.close() 200 }} 201 style={[a.flex_grow]}> 202 <ButtonText> 203 <Trans>Save</Trans> 204 </ButtonText> 205 </Button> 206 </AltTextCounterWrapper> 207 </View> 208 {/* below the text input to force tab order */} 209 <View> 210 <Text 211 style={[a.text_2xl, a.font_semi_bold, a.leading_tight, a.pb_sm]}> 212 <Trans>Add alt text</Trans> 213 </Text> 214 <View style={[a.align_center]}> 215 <GifEmbed 216 thumb={thumb} 217 altText={altText} 218 isPreferredAltText={true} 219 params={params} 220 hideAlt 221 style={[{height: 225}]} 222 /> 223 </View> 224 </View> 225 </View> 226 <Dialog.Close /> 227 {/* Maybe fix this later -h */} 228 {IS_ANDROID ? <View style={{height: 300}} /> : null} 229 </Dialog.ScrollableInner> 230 ) 231}