this repo has no description
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

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