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