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

Configure Feed

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

at fix-flake-devshell 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 {PdsBadge} from '#/components/PdsBadge' 21import {ProfileBadges} from '#/components/ProfileBadges' 22import {Text} from '#/components/Typography' 23import {useActorStatus} from '#/features/liveNow' 24 25export function AccountList({ 26 onSelectAccount, 27 onSelectOther, 28 otherLabel, 29 pendingDid, 30}: { 31 onSelectAccount: (account: SessionAccount) => void 32 onSelectOther: () => void 33 otherLabel?: string 34 pendingDid: string | null 35}) { 36 const {currentAccount, accounts} = useSession() 37 const t = useTheme() 38 const {_} = useLingui() 39 const enableSquareButtons = useEnableSquareButtons() 40 const {data: profiles} = useProfilesQuery({ 41 handles: accounts.map(acc => acc.did), 42 }) 43 44 const onPressAddAccount = useCallback(() => { 45 onSelectOther() 46 }, [onSelectOther]) 47 48 return ( 49 <View 50 pointerEvents={pendingDid ? 'none' : 'auto'} 51 style={[ 52 a.rounded_lg, 53 a.overflow_hidden, 54 a.border, 55 t.atoms.border_contrast_low, 56 ]}> 57 {accounts.map(account => ( 58 <Fragment key={account.did}> 59 <AccountItem 60 profile={profiles?.profiles.find(p => p.did === account.did)} 61 account={account} 62 onSelect={onSelectAccount} 63 isCurrentAccount={account.did === currentAccount?.did} 64 isPendingAccount={account.did === pendingDid} 65 /> 66 <View style={[a.border_b, t.atoms.border_contrast_low]} /> 67 </Fragment> 68 ))} 69 <Button 70 testID="chooseAddAccountBtn" 71 style={[a.flex_1]} 72 onPress={pendingDid ? undefined : onPressAddAccount} 73 label={_(msg`Sign in to account that is not listed`)}> 74 {({hovered, pressed}) => ( 75 <View 76 style={[ 77 a.flex_1, 78 a.flex_row, 79 a.align_center, 80 a.p_lg, 81 a.gap_sm, 82 (hovered || pressed) && t.atoms.bg_contrast_25, 83 ]}> 84 <View 85 style={[ 86 t.atoms.bg_contrast_25, 87 enableSquareButtons ? a.rounded_sm : a.rounded_full, 88 {width: 48, height: 48}, 89 a.justify_center, 90 a.align_center, 91 (hovered || pressed) && t.atoms.bg_contrast_50, 92 ]}> 93 <PlusIcon style={[t.atoms.text_contrast_low]} size="md" /> 94 </View> 95 <Text style={[a.flex_1, a.leading_tight, a.text_md, a.font_medium]}> 96 {otherLabel ?? <Trans>Other account</Trans>} 97 </Text> 98 <ChevronIcon size="md" style={[t.atoms.text_contrast_low]} /> 99 </View> 100 )} 101 </Button> 102 </View> 103 ) 104} 105 106function AccountItem({ 107 profile, 108 account, 109 onSelect, 110 isCurrentAccount, 111 isPendingAccount, 112}: { 113 profile?: AppBskyActorDefs.ProfileViewDetailed 114 account: SessionAccount 115 onSelect: (account: SessionAccount) => void 116 isCurrentAccount: boolean 117 isPendingAccount: boolean 118}) { 119 const t = useTheme() 120 const {_} = useLingui() 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 <PdsBadge did={account.did} size="sm" /> 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}