my fork of the bluesky client
1import React, {useCallback} from 'react'
2import {View} from 'react-native'
3import {AppBskyActorDefs} from '@atproto/api'
4import {msg, Trans} from '@lingui/macro'
5import {useLingui} from '@lingui/react'
6
7import {sanitizeDisplayName} from '#/lib/strings/display-names'
8import {sanitizeHandle} from '#/lib/strings/handles'
9import {useProfilesQuery} from '#/state/queries/profile'
10import {type SessionAccount, useSession} from '#/state/session'
11import {UserAvatar} from '#/view/com/util/UserAvatar'
12import {atoms as a, useTheme} from '#/alf'
13import {Check_Stroke2_Corner0_Rounded as Check} from '#/components/icons/Check'
14import {ChevronRight_Stroke2_Corner0_Rounded as Chevron} from '#/components/icons/Chevron'
15import {Button} from './Button'
16import {Text} from './Typography'
17
18export function AccountList({
19 onSelectAccount,
20 onSelectOther,
21 otherLabel,
22 pendingDid,
23}: {
24 onSelectAccount: (account: SessionAccount) => void
25 onSelectOther: () => void
26 otherLabel?: string
27 pendingDid: string | null
28}) {
29 const {currentAccount, accounts} = useSession()
30 const t = useTheme()
31 const {_} = useLingui()
32 const {data: profiles} = useProfilesQuery({
33 handles: accounts.map(acc => acc.did),
34 })
35
36 const onPressAddAccount = useCallback(() => {
37 onSelectOther()
38 }, [onSelectOther])
39
40 return (
41 <View
42 pointerEvents={pendingDid ? 'none' : 'auto'}
43 style={[
44 a.rounded_md,
45 a.overflow_hidden,
46 {borderWidth: 1},
47 t.atoms.border_contrast_low,
48 ]}>
49 {accounts.map(account => (
50 <React.Fragment key={account.did}>
51 <AccountItem
52 profile={profiles?.profiles.find(p => p.did === account.did)}
53 account={account}
54 onSelect={onSelectAccount}
55 isCurrentAccount={account.did === currentAccount?.did}
56 isPendingAccount={account.did === pendingDid}
57 />
58 <View style={[{borderBottomWidth: 1}, t.atoms.border_contrast_low]} />
59 </React.Fragment>
60 ))}
61 <Button
62 testID="chooseAddAccountBtn"
63 style={[a.flex_1]}
64 onPress={pendingDid ? undefined : onPressAddAccount}
65 label={_(msg`Login to account that is not listed`)}>
66 {({hovered, pressed}) => (
67 <View
68 style={[
69 a.flex_1,
70 a.flex_row,
71 a.align_center,
72 {height: 48},
73 (hovered || pressed) && t.atoms.bg_contrast_25,
74 ]}>
75 <Text
76 style={[
77 a.align_baseline,
78 a.flex_1,
79 a.flex_row,
80 a.py_sm,
81 {paddingLeft: 48},
82 ]}>
83 {otherLabel ?? <Trans>Other account</Trans>}
84 </Text>
85 <Chevron size="sm" style={[t.atoms.text, a.mr_md]} />
86 </View>
87 )}
88 </Button>
89 </View>
90 )
91}
92
93function AccountItem({
94 profile,
95 account,
96 onSelect,
97 isCurrentAccount,
98 isPendingAccount,
99}: {
100 profile?: AppBskyActorDefs.ProfileViewDetailed
101 account: SessionAccount
102 onSelect: (account: SessionAccount) => void
103 isCurrentAccount: boolean
104 isPendingAccount: boolean
105}) {
106 const t = useTheme()
107 const {_} = useLingui()
108
109 const onPress = useCallback(() => {
110 onSelect(account)
111 }, [account, onSelect])
112
113 return (
114 <Button
115 testID={`chooseAccountBtn-${account.handle}`}
116 key={account.did}
117 style={[a.flex_1]}
118 onPress={onPress}
119 label={
120 isCurrentAccount
121 ? _(msg`Continue as ${account.handle} (currently signed in)`)
122 : _(msg`Sign in as ${account.handle}`)
123 }>
124 {({hovered, pressed}) => (
125 <View
126 style={[
127 a.flex_1,
128 a.flex_row,
129 a.align_center,
130 {height: 48},
131 (hovered || pressed || isPendingAccount) && t.atoms.bg_contrast_25,
132 ]}>
133 <View style={a.p_md}>
134 <UserAvatar avatar={profile?.avatar} size={24} />
135 </View>
136 <Text style={[a.align_baseline, a.flex_1, a.flex_row, a.py_sm]}>
137 <Text emoji style={[a.font_bold]}>
138 {sanitizeDisplayName(
139 profile?.displayName || profile?.handle || account.handle,
140 )}
141 </Text>{' '}
142 <Text emoji style={[t.atoms.text_contrast_medium]}>
143 {sanitizeHandle(account.handle)}
144 </Text>
145 </Text>
146 {isCurrentAccount ? (
147 <Check
148 size="sm"
149 style={[{color: t.palette.positive_600}, a.mr_md]}
150 />
151 ) : (
152 <Chevron size="sm" style={[t.atoms.text, a.mr_md]} />
153 )}
154 </View>
155 )}
156 </Button>
157 )
158}