forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {type StyleProp, View, type ViewStyle} from 'react-native'
2import {AtUri} from '@atproto/api'
3import {msg, Trans} from '@lingui/macro'
4import {useLingui} from '@lingui/react'
5
6import {cleanError} from '#/lib/strings/errors'
7import {isNative, isWeb} from '#/platform/detection'
8import {useModerationOpts} from '#/state/preferences/moderation-opts'
9import {getFeedTypeFromUri} from '#/state/queries/feed'
10import {useProfileQuery} from '#/state/queries/profile'
11import {atoms as a, useTheme, web} from '#/alf'
12import {Button, ButtonText} from '#/components/Button'
13import * as Dialog from '#/components/Dialog'
14import {Divider} from '#/components/Divider'
15import {Warning_Stroke2_Corner0_Rounded as WarningIcon} from '#/components/icons/Warning'
16import * as ProfileCard from '#/components/ProfileCard'
17import {Text} from '#/components/Typography'
18
19export function MissingFeed({
20 style,
21 hideTopBorder,
22 uri,
23 error,
24}: {
25 style?: StyleProp<ViewStyle>
26 hideTopBorder?: boolean
27 uri: string
28 error?: unknown
29}) {
30 const t = useTheme()
31 const {_} = useLingui()
32 const control = Dialog.useDialogControl()
33
34 const type = getFeedTypeFromUri(uri)
35
36 return (
37 <>
38 <Button
39 label={
40 type === 'feed'
41 ? _(msg`Could not connect to custom feed`)
42 : _(msg`Deleted list`)
43 }
44 accessibilityHint={_(msg`Tap for more information`)}
45 onPress={control.open}
46 style={[
47 a.flex_1,
48 a.p_lg,
49 a.gap_md,
50 !hideTopBorder && !a.border_t,
51 t.atoms.border_contrast_low,
52 a.justify_start,
53 style,
54 ]}>
55 <View style={[a.flex_row, a.align_center]}>
56 <View
57 style={[
58 {width: 36, height: 36},
59 t.atoms.bg_contrast_25,
60 a.rounded_sm,
61 a.mr_md,
62 a.align_center,
63 a.justify_center,
64 ]}>
65 <WarningIcon size="lg" />
66 </View>
67 <View style={[a.flex_1]}>
68 <Text
69 emoji
70 style={[a.text_sm, a.font_semi_bold, a.leading_snug, a.italic]}
71 numberOfLines={1}>
72 {type === 'feed' ? (
73 <Trans>Feed unavailable</Trans>
74 ) : (
75 <Trans>Deleted list</Trans>
76 )}
77 </Text>
78 <Text
79 style={[
80 a.text_sm,
81 t.atoms.text_contrast_medium,
82 a.leading_snug,
83 a.italic,
84 ]}
85 numberOfLines={1}>
86 {isWeb ? (
87 <Trans>Click for information</Trans>
88 ) : (
89 <Trans>Tap for information</Trans>
90 )}
91 </Text>
92 </View>
93 </View>
94 </Button>
95
96 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
97 <Dialog.Handle />
98 <DialogInner uri={uri} type={type} error={error} />
99 </Dialog.Outer>
100 </>
101 )
102}
103
104function DialogInner({
105 uri,
106 type,
107 error,
108}: {
109 uri: string
110 type: 'feed' | 'list'
111 error: unknown
112}) {
113 const control = Dialog.useDialogContext()
114 const t = useTheme()
115 const {_} = useLingui()
116 const atUri = new AtUri(uri)
117 const {data: profile, isError: isProfileError} = useProfileQuery({
118 did: atUri.host,
119 })
120 const moderationOpts = useModerationOpts()
121
122 return (
123 <Dialog.ScrollableInner
124 label={
125 type === 'feed'
126 ? _(msg`Unavailable feed information`)
127 : _(msg`Deleted list`)
128 }
129 style={web({maxWidth: 500})}>
130 <View style={[a.gap_sm]}>
131 <Text style={[a.font_bold, a.text_2xl]}>
132 {type === 'feed' ? (
133 <Trans>Could not connect to feed service</Trans>
134 ) : (
135 <Trans>Deleted list</Trans>
136 )}
137 </Text>
138 <Text style={[t.atoms.text_contrast_high, a.leading_snug]}>
139 {type === 'feed' ? (
140 <Trans>
141 We could not connect to the service that provides this custom
142 feed. It may be temporarily unavailable and experiencing issues,
143 or permanently unavailable.
144 </Trans>
145 ) : (
146 <Trans>We could not find this list. It was probably deleted.</Trans>
147 )}
148 </Text>
149 <Divider style={[a.my_md]} />
150 <Text style={[a.font_semi_bold, t.atoms.text_contrast_high]}>
151 {type === 'feed' ? (
152 <Trans>Feed creator</Trans>
153 ) : (
154 <Trans>List creator</Trans>
155 )}
156 </Text>
157 {profile && moderationOpts && (
158 <View style={[a.w_full, a.align_start]}>
159 <ProfileCard.Link profile={profile} onPress={() => control.close()}>
160 <ProfileCard.Header>
161 <ProfileCard.Avatar
162 profile={profile}
163 moderationOpts={moderationOpts}
164 disabledPreview
165 />
166 <ProfileCard.NameAndHandle
167 profile={profile}
168 moderationOpts={moderationOpts}
169 />
170 </ProfileCard.Header>
171 </ProfileCard.Link>
172 </View>
173 )}
174 {isProfileError && (
175 <Text
176 style={[
177 t.atoms.text_contrast_high,
178 a.italic,
179 a.text_center,
180 a.w_full,
181 ]}>
182 <Trans>Could not find profile</Trans>
183 </Text>
184 )}
185 {type === 'feed' && (
186 <>
187 <Text
188 style={[a.font_semi_bold, t.atoms.text_contrast_high, a.mt_md]}>
189 <Trans>Feed identifier</Trans>
190 </Text>
191 <Text style={[a.text_md, t.atoms.text_contrast_high, a.italic]}>
192 {atUri.rkey}
193 </Text>
194 </>
195 )}
196 {error instanceof Error && (
197 <>
198 <Text
199 style={[a.font_semi_bold, t.atoms.text_contrast_high, a.mt_md]}>
200 <Trans>Error message</Trans>
201 </Text>
202 <Text style={[a.text_md, t.atoms.text_contrast_high, a.italic]}>
203 {cleanError(error.message)}
204 </Text>
205 </>
206 )}
207 </View>
208 {isNative && (
209 <Button
210 label={_(msg`Close`)}
211 onPress={() => control.close()}
212 size="small"
213 variant="solid"
214 color="secondary"
215 style={[a.mt_5xl]}>
216 <ButtonText>
217 <Trans>Close</Trans>
218 </ButtonText>
219 </Button>
220 )}
221 <Dialog.Close />
222 </Dialog.ScrollableInner>
223 )
224}