forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import React from 'react'
2import {type ImageStyle, useWindowDimensions, View} from 'react-native'
3import {Image} from 'expo-image'
4import {msg, Plural, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6
7import {MAX_ALT_TEXT} from '#/lib/constants'
8import {enforceLen} from '#/lib/strings/helpers'
9import {type ComposerImage} from '#/state/gallery'
10import {AltTextCounterWrapper} from '#/view/com/composer/AltTextCounterWrapper'
11import {atoms as a, useTheme} from '#/alf'
12import {Button, ButtonText} from '#/components/Button'
13import * as Dialog from '#/components/Dialog'
14import {type DialogControlProps} from '#/components/Dialog'
15import * as TextField from '#/components/forms/TextField'
16import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfo} from '#/components/icons/CircleInfo'
17import {Text} from '#/components/Typography'
18import {IS_ANDROID, IS_WEB} from '#/env'
19
20type Props = {
21 control: Dialog.DialogOuterProps['control']
22 image: ComposerImage
23 onChange: (next: ComposerImage) => void
24}
25
26export const ImageAltTextDialog = ({
27 control,
28 image,
29 onChange,
30}: Props): React.ReactNode => {
31 const [altText, setAltText] = React.useState(image.alt)
32
33 return (
34 <Dialog.Outer
35 control={control}
36 onClose={() => {
37 onChange({
38 ...image,
39 alt: enforceLen(altText, MAX_ALT_TEXT, true),
40 })
41 }}>
42 <Dialog.Handle />
43 <ImageAltTextInner
44 control={control}
45 image={image}
46 altText={altText}
47 setAltText={setAltText}
48 />
49 </Dialog.Outer>
50 )
51}
52
53const ImageAltTextInner = ({
54 altText,
55 setAltText,
56 control,
57 image,
58}: {
59 altText: string
60 setAltText: (text: string) => void
61 control: DialogControlProps
62 image: Props['image']
63}): React.ReactNode => {
64 const {_, i18n} = useLingui()
65 const t = useTheme()
66 const windim = useWindowDimensions()
67
68 const imageStyle = React.useMemo<ImageStyle>(() => {
69 const maxWidth = IS_WEB ? 450 : windim.width
70 const source = image.transformed ?? image.source
71
72 if (source.height > source.width) {
73 return {
74 resizeMode: 'contain',
75 width: '100%',
76 aspectRatio: 1,
77 borderRadius: 8,
78 }
79 }
80 return {
81 width: '100%',
82 height: (maxWidth / source.width) * source.height,
83 borderRadius: 8,
84 }
85 }, [image, windim])
86
87 return (
88 <Dialog.ScrollableInner label={_(msg`Add alt text`)}>
89 <Dialog.Close />
90
91 <View>
92 <Text style={[a.text_2xl, a.font_semi_bold, a.leading_tight, a.pb_sm]}>
93 <Trans>Add alt text</Trans>
94 </Text>
95
96 <View style={[t.atoms.bg_contrast_50, a.rounded_sm, a.overflow_hidden]}>
97 <Image
98 style={imageStyle}
99 source={{uri: (image.transformed ?? image.source).path}}
100 contentFit="contain"
101 accessible={true}
102 accessibilityIgnoresInvertColors
103 enableLiveTextInteraction
104 autoplay={false}
105 />
106 </View>
107 </View>
108
109 <View style={[a.mt_md, a.gap_md]}>
110 <View style={[a.gap_sm]}>
111 <View style={[a.relative, {width: '100%'}]}>
112 <TextField.LabelText>
113 <Trans>Descriptive alt text</Trans>
114 </TextField.LabelText>
115 <TextField.Root>
116 <Dialog.Input
117 label={_(msg`Alt text`)}
118 onChangeText={text => {
119 setAltText(text)
120 }}
121 defaultValue={altText}
122 multiline
123 numberOfLines={3}
124 autoFocus
125 />
126 </TextField.Root>
127 </View>
128
129 {altText.length > MAX_ALT_TEXT && (
130 <View style={[a.pb_sm, a.flex_row, a.gap_xs]}>
131 <CircleInfo fill={t.palette.negative_500} />
132 <Text
133 style={[
134 a.italic,
135 a.leading_snug,
136 t.atoms.text_contrast_medium,
137 ]}>
138 <Trans>
139 Alt text will be truncated.{' '}
140 <Plural
141 value={MAX_ALT_TEXT}
142 other={`Limit: ${i18n.number(MAX_ALT_TEXT)} characters.`}
143 />
144 </Trans>
145 </Text>
146 </View>
147 )}
148 </View>
149
150 <AltTextCounterWrapper altText={altText}>
151 <Button
152 label={_(msg`Save`)}
153 disabled={altText === image.alt}
154 size="large"
155 color="primary"
156 variant="solid"
157 onPress={() => {
158 control.close()
159 }}
160 style={[a.flex_grow]}>
161 <ButtonText>
162 <Trans>Save</Trans>
163 </ButtonText>
164 </Button>
165 </AltTextCounterWrapper>
166 </View>
167 {/* Maybe fix this later -h */}
168 {IS_ANDROID ? <View style={{height: 300}} /> : null}
169 </Dialog.ScrollableInner>
170 )
171}