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