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