Bluesky app fork with some witchin' additions 馃挮
0
fork

Configure Feed

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

at main 220 lines 7.0 kB view raw
1import {Fragment, useCallback} from 'react' 2import {View} from 'react-native' 3import {type AppBskyActorDefs} from '@atproto/api' 4import {msg} from '@lingui/core/macro' 5import {useLingui} from '@lingui/react' 6import {Trans} from '@lingui/react/macro' 7 8import {isJwtExpired} from '#/lib/jwt' 9import {sanitizeDisplayName} from '#/lib/strings/display-names' 10import {sanitizeHandle} from '#/lib/strings/handles' 11import {useEnableSquareButtons} from '#/state/preferences/enable-square-buttons' 12import {useProfilesQuery} from '#/state/queries/profile' 13import {type SessionAccount, useSession} from '#/state/session' 14import {UserAvatar} from '#/view/com/util/UserAvatar' 15import {atoms as a, useTheme} from '#/alf' 16import {Button} from '#/components/Button' 17import {CheckThick_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' 18import {ChevronRight_Stroke2_Corner0_Rounded as ChevronIcon} from '#/components/icons/Chevron' 19import {PlusLarge_Stroke2_Corner0_Rounded as PlusIcon} from '#/components/icons/Plus' 20import {ProfileBadges} from '#/components/ProfileBadges' 21import {Text} from '#/components/Typography' 22import {useActorStatus} from '#/features/liveNow' 23 24export function AccountList({ 25 onSelectAccount, 26 onSelectOther, 27 otherLabel, 28 pendingDid, 29}: { 30 onSelectAccount: (account: SessionAccount) => void 31 onSelectOther: () => void 32 otherLabel?: string 33 pendingDid: string | null 34}) { 35 const {currentAccount, accounts} = useSession() 36 const t = useTheme() 37 const {_} = useLingui() 38 const enableSquareButtons = useEnableSquareButtons() 39 const {data: profiles} = useProfilesQuery({ 40 handles: accounts.map(acc => acc.did), 41 }) 42 43 const onPressAddAccount = useCallback(() => { 44 onSelectOther() 45 }, [onSelectOther]) 46 47 return ( 48 <View 49 pointerEvents={pendingDid ? 'none' : 'auto'} 50 style={[ 51 a.rounded_lg, 52 a.overflow_hidden, 53 a.border, 54 t.atoms.border_contrast_low, 55 ]}> 56 {accounts.map(account => ( 57 <Fragment key={account.did}> 58 <AccountItem 59 profile={profiles?.profiles.find(p => p.did === account.did)} 60 account={account} 61 onSelect={onSelectAccount} 62 isCurrentAccount={account.did === currentAccount?.did} 63 isPendingAccount={account.did === pendingDid} 64 /> 65 <View style={[a.border_b, t.atoms.border_contrast_low]} /> 66 </Fragment> 67 ))} 68 <Button 69 testID="chooseAddAccountBtn" 70 style={[a.flex_1]} 71 onPress={pendingDid ? undefined : onPressAddAccount} 72 label={_(msg`Sign in to account that is not listed`)}> 73 {({hovered, pressed}) => ( 74 <View 75 style={[ 76 a.flex_1, 77 a.flex_row, 78 a.align_center, 79 a.p_lg, 80 a.gap_sm, 81 (hovered || pressed) && t.atoms.bg_contrast_25, 82 ]}> 83 <View 84 style={[ 85 t.atoms.bg_contrast_25, 86 enableSquareButtons ? a.rounded_sm : a.rounded_full, 87 {width: 48, height: 48}, 88 a.justify_center, 89 a.align_center, 90 (hovered || pressed) && t.atoms.bg_contrast_50, 91 ]}> 92 <PlusIcon style={[t.atoms.text_contrast_low]} size="md" /> 93 </View> 94 <Text style={[a.flex_1, a.leading_tight, a.text_md, a.font_medium]}> 95 {otherLabel ?? <Trans>Other account</Trans>} 96 </Text> 97 <ChevronIcon size="md" style={[t.atoms.text_contrast_low]} /> 98 </View> 99 )} 100 </Button> 101 </View> 102 ) 103} 104 105function AccountItem({ 106 profile, 107 account, 108 onSelect, 109 isCurrentAccount, 110 isPendingAccount, 111}: { 112 profile?: AppBskyActorDefs.ProfileViewDetailed 113 account: SessionAccount 114 onSelect: (account: SessionAccount) => void 115 isCurrentAccount: boolean 116 isPendingAccount: boolean 117}) { 118 const t = useTheme() 119 const {_} = useLingui() 120 const {isActive: live} = useActorStatus(profile) 121 const enableSquareButtons = useEnableSquareButtons() 122 123 const onPress = useCallback(() => { 124 onSelect(account) 125 }, [account, onSelect]) 126 127 const isLoggedOut = account.isOauthSession 128 ? false // OAuth sessions are managed by the OAuth client, not refreshJwt 129 : !account.refreshJwt || isJwtExpired(account.refreshJwt) 130 131 return ( 132 <Button 133 testID={`chooseAccountBtn-${account.handle}`} 134 key={account.did} 135 style={[a.w_full]} 136 onPress={onPress} 137 label={ 138 isCurrentAccount 139 ? _(msg`Continue as ${account.handle} (currently signed in)`) 140 : _(msg`Sign in as ${account.handle}`) 141 }> 142 {({hovered, pressed}) => ( 143 <View 144 style={[ 145 a.flex_1, 146 a.flex_row, 147 a.align_center, 148 a.p_lg, 149 a.gap_sm, 150 (hovered || pressed || isPendingAccount) && t.atoms.bg_contrast_25, 151 ]}> 152 <UserAvatar 153 avatar={profile?.avatar} 154 size={48} 155 type={profile?.associated?.labeler ? 'labeler' : 'user'} 156 live={live} 157 hideLiveBadge 158 /> 159 160 <View style={[a.flex_1, a.gap_2xs, a.pr_2xl]}> 161 <View style={[a.flex_row, a.align_center, a.gap_xs]}> 162 <Text 163 emoji 164 style={[a.font_medium, a.leading_tight, a.text_md]} 165 numberOfLines={1}> 166 {sanitizeDisplayName( 167 profile?.displayName || profile?.handle || account.handle, 168 )} 169 </Text> 170 {profile && ( 171 <ProfileBadges 172 profile={profile} 173 size="sm" 174 style={[{marginTop: -2}]} 175 /> 176 )} 177 </View> 178 <Text 179 style={[ 180 a.leading_tight, 181 t.atoms.text_contrast_medium, 182 a.text_sm, 183 ]}> 184 {sanitizeHandle(account.handle, '@')} 185 </Text> 186 {isLoggedOut && ( 187 <Text 188 style={[ 189 a.leading_tight, 190 a.text_xs, 191 a.italic, 192 t.atoms.text_contrast_medium, 193 ]}> 194 <Trans>Logged out</Trans> 195 </Text> 196 )} 197 </View> 198 199 {isCurrentAccount ? ( 200 <View 201 style={[ 202 { 203 width: 20, 204 height: 20, 205 backgroundColor: t.palette.positive_500, 206 }, 207 enableSquareButtons ? a.rounded_sm : a.rounded_full, 208 a.justify_center, 209 a.align_center, 210 ]}> 211 <CheckIcon size="xs" style={[{color: t.palette.white}]} /> 212 </View> 213 ) : ( 214 <ChevronIcon size="md" style={[t.atoms.text_contrast_low]} /> 215 )} 216 </View> 217 )} 218 </Button> 219 ) 220}