Bluesky app fork with some witchin' additions 馃挮
at main 260 lines 7.2 kB view raw
1import {useState} from 'react' 2import {Image, View} from 'react-native' 3import { 4 FontAwesomeIcon, 5 type FontAwesomeIconStyle, 6} from '@fortawesome/react-native-fontawesome' 7import {msg, Trans} from '@lingui/macro' 8import {useLingui} from '@lingui/react' 9 10import {isBridgedPdsUrl, isBskyPdsUrl} from '#/state/queries/pds-label' 11import {atoms as a, useBreakpoints, useTheme} from '#/alf' 12import {Button, ButtonText} from '#/components/Button' 13import * as Dialog from '#/components/Dialog' 14import {Fediverse as FediverseIcon} from '#/components/icons/Fediverse' 15import {Mark as BskyMark} from '#/components/icons/Logo' 16import {InlineLinkText} from '#/components/Link' 17import {Text} from '#/components/Typography' 18 19function formatBskyPdsDisplayName(hostname: string): string { 20 const match = hostname.match(/^([^.]+)\.([^.]+)\.host\.bsky\.network$/) 21 if (match) { 22 const name = match[1].charAt(0).toUpperCase() + match[1].slice(1) 23 const rawRegion = match[2] 24 const region = rawRegion 25 .replace(/^us-east$/, 'US East') 26 .replace(/^us-west$/, 'US West') 27 .replace(/^eu-west$/, 'EU West') 28 .replace( 29 /^ap-(.+)$/, 30 (_match: string, r: string) => 31 `AP ${r.charAt(0).toUpperCase()}${r.slice(1)}`, 32 ) 33 return `${name} (${region})` 34 } 35 if (hostname === 'bsky.social') return 'Bluesky Social' 36 return hostname 37} 38 39export function PdsDialog({ 40 control, 41 pdsUrl, 42 faviconUrl, 43}: { 44 control: Dialog.DialogControlProps 45 pdsUrl: string 46 faviconUrl: string 47}) { 48 const {_} = useLingui() 49 const {gtMobile} = useBreakpoints() 50 51 let hostname = pdsUrl 52 try { 53 hostname = new URL(pdsUrl).hostname 54 } catch {} 55 56 const isBsky = isBskyPdsUrl(pdsUrl) 57 const isBridged = isBridgedPdsUrl(pdsUrl) 58 const displayName = isBsky ? formatBskyPdsDisplayName(hostname) : hostname 59 60 return ( 61 <Dialog.Outer control={control} nativeOptions={{preventExpansion: true}}> 62 <Dialog.Handle /> 63 <Dialog.ScrollableInner 64 label={_(msg`PDS Information`)} 65 style={[ 66 gtMobile ? {width: 'auto', maxWidth: 400, minWidth: 200} : a.w_full, 67 ]}> 68 <View style={[a.gap_md, a.pb_lg]}> 69 <View style={[a.flex_row, a.align_center, a.gap_md]}> 70 <FaviconOrGlobe 71 faviconUrl={faviconUrl} 72 isBsky={isBsky} 73 isBridged={isBridged} 74 size={36} 75 /> 76 <View style={[a.flex_1]}> 77 <Text 78 style={[a.text_2xl, a.font_semi_bold, a.leading_tight]} 79 numberOfLines={1}> 80 {displayName} 81 </Text> 82 {isBsky && ( 83 <Text style={[a.text_sm]}> 84 <Trans>Bluesky-hosted PDS</Trans> 85 </Text> 86 )} 87 {isBridged && ( 88 <Text style={[a.text_sm]}> 89 <Trans>Fediverse bridge</Trans> 90 </Text> 91 )} 92 </View> 93 </View> 94 95 <Text style={[a.text_md, a.leading_snug]}> 96 <Trans> 97 This account's data is stored on a Personal Data Server (PDS):{' '} 98 <InlineLinkText 99 to={pdsUrl} 100 label={displayName} 101 style={[a.text_md, a.font_semi_bold]}> 102 {displayName} 103 </InlineLinkText> 104 {'. '}A PDS is where your posts, follows, and other data live on 105 the AT Protocol network. 106 </Trans> 107 </Text> 108 109 {isBridged && ( 110 <Text style={[a.text_md, a.leading_snug]}> 111 <Trans> 112 This account is bridged from the Fediverse via{' '} 113 <InlineLinkText 114 to="https://fed.brid.gy" 115 label="Bridgy Fed" 116 style={[a.text_md, a.font_semi_bold]}> 117 Bridgy Fed 118 </InlineLinkText> 119 . Their original account lives on a Fediverse platform such as 120 Mastodon. 121 </Trans> 122 </Text> 123 )} 124 125 {!isBsky && !isBridged && ( 126 <Text style={[a.text_md, a.leading_snug]}> 127 <Trans> 128 This account is self-hosted or uses a third-party PDS provider. 129 </Trans> 130 </Text> 131 )} 132 </View> 133 134 <View 135 style={[ 136 a.w_full, 137 a.gap_sm, 138 gtMobile 139 ? [a.flex_row, a.flex_row_reverse, a.justify_start] 140 : [a.flex_col], 141 ]}> 142 <Button 143 label={_(msg`Close dialog`)} 144 size="small" 145 variant="solid" 146 color="primary" 147 onPress={() => control.close()}> 148 <ButtonText> 149 <Trans>Close</Trans> 150 </ButtonText> 151 </Button> 152 </View> 153 154 <Dialog.Close /> 155 </Dialog.ScrollableInner> 156 </Dialog.Outer> 157 ) 158} 159 160export function FaviconOrGlobe({ 161 faviconUrl, 162 isBsky, 163 isBridged, 164 size, 165 borderRadius, 166}: { 167 faviconUrl: string 168 isBsky: boolean 169 isBridged: boolean 170 size: number 171 borderRadius?: number 172}) { 173 const t = useTheme() 174 const [imgError, setImgError] = useState(false) 175 const resolvedBorderRadius = borderRadius ?? size / 5 176 177 if (isBsky) { 178 return ( 179 <View 180 style={[ 181 a.align_center, 182 a.justify_center, 183 a.overflow_hidden, 184 { 185 width: size, 186 height: size, 187 borderRadius: resolvedBorderRadius, 188 backgroundColor: '#0085ff', 189 }, 190 ]}> 191 <BskyMark width={Math.round(size * 0.8)} style={{color: '#fff'}} /> 192 </View> 193 ) 194 } 195 196 if (isBridged) { 197 return ( 198 <View 199 style={[ 200 a.align_center, 201 a.justify_center, 202 a.overflow_hidden, 203 { 204 width: size, 205 height: size, 206 borderRadius: resolvedBorderRadius, 207 backgroundColor: '#6364FF', 208 }, 209 ]}> 210 <FediverseIcon width={Math.round(size * 0.8)} style={{color: '#fff'}} /> 211 </View> 212 ) 213 } 214 215 if (!imgError && faviconUrl) { 216 return ( 217 <View 218 style={[ 219 a.overflow_hidden, 220 a.align_center, 221 a.justify_center, 222 { 223 width: size, 224 height: size, 225 borderRadius: resolvedBorderRadius, 226 backgroundColor: t.atoms.bg_contrast_100.backgroundColor, 227 }, 228 ]}> 229 <Image 230 source={{uri: faviconUrl}} 231 style={{width: size, height: size}} 232 onError={() => setImgError(true)} 233 accessibilityIgnoresInvertColors 234 /> 235 </View> 236 ) 237 } 238 239 return ( 240 <View 241 style={[ 242 a.align_center, 243 a.justify_center, 244 { 245 width: size, 246 height: size, 247 borderRadius: resolvedBorderRadius, 248 backgroundColor: t.atoms.bg_contrast_100.backgroundColor, 249 }, 250 ]}> 251 <FontAwesomeIcon 252 icon="database" 253 size={Math.round(size * 0.7)} 254 style={ 255 {color: t.atoms.text_contrast_medium.color} as FontAwesomeIconStyle 256 } 257 /> 258 </View> 259 ) 260}