Bluesky app fork with some witchin' additions 馃挮
at readme-update 220 lines 7.1 kB view raw
1import React, {useCallback} from 'react' 2import {View} from 'react-native' 3import {type AppBskyActorDefs} from '@atproto/api' 4import {msg, Trans} from '@lingui/macro' 5import {useLingui} from '@lingui/react' 6 7import {useActorStatus} from '#/lib/actor-status' 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 {Text} from '#/components/Typography' 21import {useSimpleVerificationState} from '#/components/verification' 22import {VerificationCheck} from '#/components/verification/VerificationCheck' 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 <React.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 </React.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 verification = useSimpleVerificationState({profile}) 121 const {isActive: live} = useActorStatus(profile) 122 const enableSquareButtons = useEnableSquareButtons() 123 124 const onPress = useCallback(() => { 125 onSelect(account) 126 }, [account, onSelect]) 127 128 const isLoggedOut = !account.refreshJwt || isJwtExpired(account.refreshJwt) 129 130 return ( 131 <Button 132 testID={`chooseAccountBtn-${account.handle}`} 133 key={account.did} 134 style={[a.w_full]} 135 onPress={onPress} 136 label={ 137 isCurrentAccount 138 ? _(msg`Continue as ${account.handle} (currently signed in)`) 139 : _(msg`Sign in as ${account.handle}`) 140 }> 141 {({hovered, pressed}) => ( 142 <View 143 style={[ 144 a.flex_1, 145 a.flex_row, 146 a.align_center, 147 a.p_lg, 148 a.gap_sm, 149 (hovered || pressed || isPendingAccount) && t.atoms.bg_contrast_25, 150 ]}> 151 <UserAvatar 152 avatar={profile?.avatar} 153 size={48} 154 type={profile?.associated?.labeler ? 'labeler' : 'user'} 155 live={live} 156 hideLiveBadge 157 /> 158 159 <View style={[a.flex_1, a.gap_2xs, a.pr_2xl]}> 160 <View style={[a.flex_row, a.align_center, a.gap_xs]}> 161 <Text 162 emoji 163 style={[a.font_medium, a.leading_tight, a.text_md]} 164 numberOfLines={1}> 165 {sanitizeDisplayName( 166 profile?.displayName || profile?.handle || account.handle, 167 )} 168 </Text> 169 {verification.showBadge && ( 170 <View> 171 <VerificationCheck 172 width={12} 173 verifier={verification.role === 'verifier'} 174 /> 175 </View> 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}