forked from
jollywhoppers.com/witchsky.app
Bluesky app fork with some witchin' additions 馃挮
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}