Bluesky app fork with some witchin' additions 馃挮
at main 6.6 kB view raw
1import {useCallback, useImperativeHandle, useRef, useState} from 'react' 2import {View} from 'react-native' 3import {useWindowDimensions} from 'react-native' 4import {msg, Trans} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6 7import {BSKY_SERVICE} from '#/lib/constants' 8import {logger} from '#/logger' 9import * as persisted from '#/state/persisted' 10import {useSession} from '#/state/session' 11import {atoms as a, platform, useBreakpoints, useTheme, web} from '#/alf' 12import {Admonition} from '#/components/Admonition' 13import {Button, ButtonText} from '#/components/Button' 14import * as Dialog from '#/components/Dialog' 15import * as TextField from '#/components/forms/TextField' 16import {Globe_Stroke2_Corner0_Rounded as Globe} from '#/components/icons/Globe' 17import {InlineLinkText} from '#/components/Link' 18import {Text} from '#/components/Typography' 19 20export function ServerInputDialog({ 21 control, 22 onSelect, 23}: { 24 control: Dialog.DialogOuterProps['control'] 25 onSelect: (url: string) => void 26}) { 27 const {height} = useWindowDimensions() 28 const formRef = useRef<DialogInnerRef>(null) 29 30 // persist these options between dialog open/close 31 const [previousCustomAddress, setPreviousCustomAddress] = useState('') 32 33 const onClose = useCallback(() => { 34 const result = formRef.current?.getFormState() 35 if (result) { 36 onSelect(result) 37 if (result !== BSKY_SERVICE) { 38 setPreviousCustomAddress(result) 39 } 40 } 41 logger.metric('signin:hostingProviderPressed', { 42 hostingProviderDidChange: false, // stubbed for PDS auto-resolution 43 }) 44 }, [onSelect]) 45 46 return ( 47 <Dialog.Outer 48 control={control} 49 onClose={onClose} 50 nativeOptions={platform({ 51 android: {minHeight: height / 2}, 52 ios: {preventExpansion: true}, 53 })}> 54 <Dialog.Handle /> 55 <DialogInner 56 formRef={formRef} 57 initialCustomAddress={previousCustomAddress} 58 /> 59 </Dialog.Outer> 60 ) 61} 62 63type DialogInnerRef = {getFormState: () => string | null} 64 65function DialogInner({ 66 formRef, 67 initialCustomAddress, 68}: { 69 formRef: React.Ref<DialogInnerRef> 70 initialCustomAddress: string 71}) { 72 const control = Dialog.useDialogContext() 73 const {_} = useLingui() 74 const t = useTheme() 75 const {accounts} = useSession() 76 const {gtMobile} = useBreakpoints() 77 const [customAddress, setCustomAddress] = useState(initialCustomAddress) 78 const [pdsAddressHistory, setPdsAddressHistory] = useState<string[]>( 79 persisted.get('pdsAddressHistory') || [], 80 ) 81 82 useImperativeHandle( 83 formRef, 84 () => ({ 85 getFormState: () => { 86 let url = customAddress.trim().toLowerCase() 87 if (!url) { 88 return null 89 } 90 if (!url.startsWith('http://') && !url.startsWith('https://')) { 91 if (url === 'localhost' || url.startsWith('localhost:')) { 92 url = `http://${url}` 93 } else { 94 url = `https://${url}` 95 } 96 } 97 98 if (!pdsAddressHistory.includes(url)) { 99 const newHistory = [url, ...pdsAddressHistory.slice(0, 4)] 100 setPdsAddressHistory(newHistory) 101 persisted.write('pdsAddressHistory', newHistory) 102 } 103 104 return url 105 }, 106 }), 107 [customAddress, pdsAddressHistory], 108 ) 109 110 const isFirstTimeUser = accounts.length === 0 111 112 return ( 113 <Dialog.ScrollableInner 114 accessibilityDescribedBy="dialog-description" 115 accessibilityLabelledBy="dialog-title" 116 style={web({maxWidth: 500})}> 117 <View style={[a.relative, a.gap_md, a.w_full]}> 118 <Text nativeID="dialog-title" style={[a.text_2xl, a.font_bold]}> 119 <Trans>Choose your account provider</Trans> 120 </Text> 121 122 {isFirstTimeUser && ( 123 <Admonition type="tip"> 124 <Trans> 125 Bluesky is an open network where you can choose your own provider. 126 If you're new here, we recommend sticking with the default Bluesky 127 Social option. 128 </Trans> 129 </Admonition> 130 )} 131 132 <View 133 style={[ 134 a.border, 135 t.atoms.border_contrast_low, 136 a.rounded_sm, 137 a.px_md, 138 a.py_md, 139 ]}> 140 <TextField.LabelText nativeID="address-input-label"> 141 <Trans>Server address</Trans> 142 </TextField.LabelText> 143 <TextField.Root> 144 <TextField.Icon icon={Globe} /> 145 <Dialog.Input 146 testID="customServerTextInput" 147 value={customAddress} 148 onChangeText={setCustomAddress} 149 label="my-server.com" 150 accessibilityLabelledBy="address-input-label" 151 autoCapitalize="none" 152 keyboardType="url" 153 /> 154 </TextField.Root> 155 {pdsAddressHistory.length > 0 && ( 156 <View style={[a.flex_row, a.flex_wrap, a.mt_xs]}> 157 {pdsAddressHistory.map(uri => ( 158 <Button 159 key={uri} 160 variant="ghost" 161 color="primary" 162 label={uri} 163 style={[a.px_sm, a.py_xs, a.rounded_sm, a.gap_sm]} 164 onPress={() => setCustomAddress(uri)}> 165 <ButtonText>{uri}</ButtonText> 166 </Button> 167 ))} 168 </View> 169 )} 170 </View> 171 172 <View style={[a.py_xs]}> 173 <Text 174 style={[t.atoms.text_contrast_medium, a.text_sm, a.leading_snug]}> 175 {isFirstTimeUser ? ( 176 <Trans> 177 If you're a developer, you can host your own server. 178 </Trans> 179 ) : ( 180 <Trans> 181 Bluesky is an open network where you can choose your hosting 182 provider. If you're a developer, you can host your own server. 183 </Trans> 184 )}{' '} 185 <InlineLinkText 186 label={_(msg`Learn more about self hosting your PDS.`)} 187 to="https://atproto.com/guides/self-hosting"> 188 <Trans>Learn more.</Trans> 189 </InlineLinkText> 190 </Text> 191 </View> 192 193 <View style={gtMobile && [a.flex_row, a.justify_end]}> 194 <Button 195 testID="doneBtn" 196 variant="solid" 197 color="primary" 198 size={platform({ 199 native: 'large', 200 web: 'small', 201 })} 202 onPress={() => control.close()} 203 label={_(msg`Done`)}> 204 <ButtonText> 205 <Trans>Done</Trans> 206 </ButtonText> 207 </Button> 208 </View> 209 </View> 210 </Dialog.ScrollableInner> 211 ) 212}