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

Configure Feed

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

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