forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
1import {useState} from 'react'
2import {Image, View} from 'react-native'
3import {
4 FontAwesomeIcon,
5 type FontAwesomeIconStyle,
6} from '@fortawesome/react-native-fontawesome'
7import {msg} from '@lingui/core/macro'
8import {useLingui} from '@lingui/react'
9import {Trans} from '@lingui/react/macro'
10
11import {isBridgedPdsUrl, isBskyPdsUrl} from '#/state/queries/pds-label'
12
13const failedFaviconUrls = new Set<string>()
14import Svg, {G, Path, Rect} from 'react-native-svg'
15
16import {atoms as a, useBreakpoints, useTheme} from '#/alf'
17import {Button, ButtonText} from '#/components/Button'
18import * as Dialog from '#/components/Dialog'
19import {InlineLinkText} from '#/components/Link'
20import {Text} from '#/components/Typography'
21
22function formatBskyPdsDisplayName(hostname: string): string {
23 const match = hostname.match(/^([^.]+)\.([^.]+)\.host\.bsky\.network$/)
24 if (match) {
25 const name = match[1].charAt(0).toUpperCase() + match[1].slice(1)
26 const rawRegion = match[2]
27 const region = rawRegion
28 .replace(/^us-east$/, 'US East')
29 .replace(/^us-west$/, 'US West')
30 .replace(/^eu-west$/, 'EU West')
31 .replace(
32 /^ap-(.+)$/,
33 (_match: string, r: string) =>
34 `AP ${r.charAt(0).toUpperCase()}${r.slice(1)}`,
35 )
36 return `${name} (${region})`
37 }
38 if (hostname === 'bsky.social') return 'Bluesky Social'
39 return hostname
40}
41
42export function PdsDialog({
43 control,
44 pdsUrl,
45 faviconUrl,
46}: {
47 control: Dialog.DialogControlProps
48 pdsUrl: string
49 faviconUrl: string | undefined
50}) {
51 const {_} = useLingui()
52 const {gtMobile} = useBreakpoints()
53
54 let hostname = pdsUrl
55 try {
56 hostname = new URL(pdsUrl).hostname
57 } catch {}
58
59 const isBsky = isBskyPdsUrl(pdsUrl)
60 const isBridged = isBridgedPdsUrl(pdsUrl)
61 const displayName = isBsky ? formatBskyPdsDisplayName(hostname) : hostname
62
63 return (
64 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}>
65 <Dialog.Handle />
66 <Dialog.ScrollableInner
67 label={_(msg`PDS Information`)}
68 style={[
69 gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full,
70 ]}>
71 <View style={[a.gap_md, a.pb_lg]}>
72 <View style={[a.flex_row, a.align_center, a.gap_md]}>
73 <PdsBadgeIcon
74 faviconUrl={faviconUrl}
75 isBsky={isBsky}
76 isBridged={isBridged}
77 size={36}
78 />
79 <View style={[a.flex_1]}>
80 {
81 <Text
82 style={[a.text_2xl, a.font_semi_bold, a.leading_tight]}
83 numberOfLines={1}>
84 {isBridged ? <Trans>Fediverse</Trans> : displayName}
85 </Text>
86 }
87 {isBsky &&
88 <Text style={[a.text_sm, a.leading_tight]}>
89 <Trans>Bluesky Social</Trans>
90 </Text>
91 }
92 </View>
93 </View>
94
95 <Text style={[a.text_md, a.leading_snug]}>
96 <Trans>
97 This badge represents the Personal Data Server this account is
98 stored on:{' '}
99 <InlineLinkText
100 to={pdsUrl}
101 label={displayName}
102 style={[a.text_md, a.font_semi_bold]}>
103 {displayName}
104 </InlineLinkText>
105 . A PDS is where posts, follows, and other data live on the AT
106 Protocol network.
107 </Trans>
108 </Text>
109
110 {isBridged && (
111 <Text style={[a.text_md, a.leading_snug]}>
112 <Trans>
113 This account is bridged from the Fediverse via{' '}
114 <InlineLinkText
115 to="https://witchsky.app/profile/ap.brid.gy"
116 label="Bridgy Fed"
117 style={[a.text_md, a.font_semi_bold]}>
118 Bridgy Fed
119 </InlineLinkText>
120 .{' '}
121 {/* Their original account is avaiable at:{' '}
122 <InlineLinkText
123 to={BridgedUrl}
124 label="Federated account address"
125 style={[a.text_md, a.font_semi_bold]}>
126 {BridgedUrl}
127 </InlineLinkText> */}
128 </Trans>
129 </Text>
130 )}
131
132 {!isBridged && (
133 <Text style={[a.text_md, a.leading_snug]}>
134 <Trans>
135 <InlineLinkText
136 to="https://atproto.com/guides/glossary#pds-personal-data-server"
137 label="PDS Glossary definition"
138 style={[a.text_md, a.font_semi_bold]}>
139 Learn more
140 </InlineLinkText>{' '}
141 about what a PDS is and how to{' '}
142 <InlineLinkText
143 to="https://atproto.com/guides/self-hosting#pds"
144 label="Self-hosting PDS documentation"
145 style={[a.text_md, a.font_semi_bold]}>
146 self-host
147 </InlineLinkText>{' '}
148 your own.
149 </Trans>
150 </Text>
151 )}
152 </View>
153
154 <View
155 style={[
156 a.w_full,
157 a.gap_sm,
158 gtMobile
159 ? [a.flex_row, a.flex_row_reverse, a.justify_start]
160 : [a.flex_col],
161 ]}>
162 <Button
163 label={_(msg`Close dialog`)}
164 size="small"
165 variant="solid"
166 color="primary"
167 onPress={() => control.close()}>
168 <ButtonText>
169 <Trans>Close</Trans>
170 </ButtonText>
171 </Button>
172 </View>
173
174 <Dialog.Close />
175 </Dialog.ScrollableInner>
176 </Dialog.Outer>
177 )
178}
179
180function BskyBadgeSVG({size}: {size: number}) {
181 return (
182 <Svg width={size} height={size} viewBox="0 0 24 24">
183 <Rect width={24} height={24} rx={6} fill="#0085ff" />
184 <G transform="translate(2.4 2.4) scale(0.8)">
185 <Path
186 fill="#fff"
187 fillRule="evenodd"
188 clipRule="evenodd"
189 d="M6.335 4.212c2.293 1.76 4.76 5.327 5.665 7.241.906-1.914 3.372-5.482 5.665-7.241C19.319 2.942 22 1.96 22 5.086c0 .624-.35 5.244-.556 5.994-.713 2.608-3.315 3.273-5.629 2.87 4.045.704 5.074 3.035 2.852 5.366-4.22 4.426-6.066-1.111-6.54-2.53-.086-.26-.126-.382-.127-.278 0-.104-.041.018-.128.278-.473 1.419-2.318 6.956-6.539 2.53-2.222-2.331-1.193-4.662 2.852-5.366-2.314.403-4.916-.262-5.63-2.87C2.35 10.33 2 5.71 2 5.086c0-3.126 2.68-2.144 4.335-.874Z"
190 />
191 </G>
192 </Svg>
193 )
194}
195
196function FediverseBadgeSVG({size}: {size: number}) {
197 return (
198 <Svg width={size} height={size} viewBox="0 0 24 24">
199 <Rect width={24} height={24} rx={6} fill="#6364FF" />
200 <G transform="translate(2.4 2.4) scale(0.03)">
201 <Path
202 fill="#fff"
203 fillRule="evenodd"
204 clipRule="evenodd"
205 d="M426.8 590.9C407.1 590.4 389.3 579.3 380.2 561.8C371.2 544.4 372.3 523.4 383.2 507C394.1 490.6 413 481.5 432.6 483.1C452.3 483.6 470.1 494.7 479.2 512.2C488.2 529.6 487.1 550.6 476.2 567C465.3 583.4 446.4 592.5 426.8 590.9zM376.7 510.3C371.2 521.2 369.3 533.6 371.1 545.7L200.7 518.4C206.2 507.5 208.2 495.1 206.4 483L376.7 510.3zM144.7 545.6C125.1 545.1 107.3 533.9 98.3 516.5C89.2 499 90.4 478.1 101.3 461.7C112.1 445.4 131 436.2 150.6 437.8C170.2 438.3 188 449.5 197 466.9C206.1 484.4 204.9 505.3 194 521.7C183.2 538 164.3 547.2 144.7 545.6zM402.4 484.2C391.5 489.8 382.7 498.6 377 509.5L306.4 438.6L340 421.6L402.4 484.3zM518.1 325C526.8 333.6 537.9 339.3 550 341.4L471.4 494.8C462.7 486.2 451.6 480.5 439.5 478.4L518.1 325zM408.7 283.3L439.2 478.4C427.1 476.5 414.7 478.3 403.8 483.7L371.6 277.4L408.8 283.4zM382.4 392.9L206.2 482.2C204.2 470.1 198.6 459 190 450.2L376.6 355.6L382.4 392.8zM229.7 370.9L189.4 449.6C180.7 441 169.6 435.3 157.5 433.3L203.1 344.3L229.7 371zM156.7 433C144.6 431.2 132.3 433.2 121.3 438.6L94.7 268.3C106.8 270.1 119.2 268.2 130.1 262.7L156.7 433zM303.8 385.2L270.2 402.2L130.8 262.3C141.7 256.7 150.5 247.9 156.2 237L303.8 385.2zM501.3 292.4C503.3 304.5 508.9 315.6 517.5 324.3L428.2 369.5L422.4 332.3L501.3 292.3zM556.9 336.7C537.3 336.2 519.5 325 510.5 307.6C501.4 290.1 502.6 269.2 513.5 252.8C524.3 236.5 543.2 227.3 562.8 228.9C582.4 229.4 600.2 240.6 609.2 258C618.3 275.5 617.1 296.4 606.2 312.8C595.4 329.1 576.5 338.3 556.9 336.7zM316.6 122.7C325.3 131.3 336.4 137 348.4 139L253.1 325.1L226.5 298.4L316.5 122.6zM506.9 256.1C501.4 267 499.4 279.4 501.2 291.4L294.8 258.3L312 224.8L507 256.1zM100.7 263.6C81.1 263.1 63.3 251.9 54.3 234.5C45.2 217 46.4 196.1 57.3 179.7C68.1 163.4 87 154.2 106.6 155.8C126.2 156.3 144 167.5 153 184.9C162.1 202.4 160.9 223.3 150 239.7C139.2 256 120.3 265.2 100.7 263.6zM532.7 230.2C521.8 235.8 513 244.6 507.3 255.5L385.5 133.3C396.4 127.7 405.2 118.9 410.9 108L532.6 230.2zM261.3 216.6L244.1 250.1L156.7 236.1C162.1 225.2 164.1 212.8 162.2 200.7L261.2 216.6zM400.8 232.5L363.6 226.5L350 139.3C362.1 141 374.5 139 385.3 133.4L400.8 232.5zM299.8 90.2C301.8 102.3 307.4 113.4 316 122.1L162.1 200.1C160.1 188 154.5 176.9 145.9 168.2L299.8 90.2zM355.4 134.5C335.7 134 317.9 122.9 308.8 105.4C299.8 88 300.9 67 311.8 50.6C322.7 34.2 341.6 25.1 361.2 26.7C380.9 27.2 398.7 38.3 407.8 55.8C416.8 73.2 415.7 94.2 404.8 110.6C393.9 127 375 136.1 355.4 134.5z"
206 />
207 </G>
208 </Svg>
209 )
210}
211
212function DbBadgeIcon({
213 size,
214 borderRadius,
215}: {
216 size: number
217 borderRadius: number
218}) {
219 const t = useTheme()
220 return (
221 <View
222 style={[
223 a.align_center,
224 a.justify_center,
225 {
226 width: size,
227 height: size,
228 borderRadius,
229 backgroundColor: t.atoms.bg_contrast_100.backgroundColor,
230 },
231 ]}>
232 <FontAwesomeIcon
233 icon="database"
234 size={Math.round(size * 0.7)}
235 style={
236 {color: t.atoms.text_contrast_medium.color} as FontAwesomeIconStyle
237 }
238 />
239 </View>
240 )
241}
242
243function FaviconBadgeIcon({
244 size,
245 borderRadius,
246 faviconUrl,
247}: {
248 size: number
249 borderRadius: number
250 faviconUrl: string
251}) {
252 const t = useTheme()
253 const [imgError, setImgError] = useState(() =>
254 failedFaviconUrls.has(faviconUrl),
255 )
256
257 if (imgError) {
258 return <DbBadgeIcon size={size} borderRadius={borderRadius} />
259 }
260
261 return (
262 <View
263 style={[
264 a.overflow_hidden,
265 a.align_center,
266 a.justify_center,
267 {
268 width: size,
269 height: size,
270 borderRadius,
271 backgroundColor: t.atoms.bg_contrast_100.backgroundColor,
272 },
273 ]}>
274 <Image
275 source={{uri: faviconUrl}}
276 style={{width: size, height: size}}
277 accessibilityIgnoresInvertColors
278 onError={() => {
279 failedFaviconUrls.add(faviconUrl)
280 setImgError(true)
281 }}
282 />
283 </View>
284 )
285}
286
287export function PdsBadgeIcon({
288 faviconUrl,
289 isBsky,
290 isBridged,
291 size,
292 borderRadius,
293}: {
294 faviconUrl?: string
295 isBsky: boolean
296 isBridged: boolean
297 size: number
298 borderRadius?: number
299}) {
300 const r = borderRadius ?? size / 5
301 if (isBsky) return <BskyBadgeSVG size={size} />
302 if (isBridged) return <FediverseBadgeSVG size={size} />
303 if (faviconUrl)
304 return (
305 <FaviconBadgeIcon size={size} borderRadius={r} faviconUrl={faviconUrl} />
306 )
307 return <DbBadgeIcon size={size} borderRadius={r} />
308}