this repo has no description
1import {useMemo} from 'react'
2import {View} from 'react-native'
3import {
4 type AppBskyActorDefs,
5 type ModerationCause,
6 type ModerationDecision,
7} from '@atproto/api'
8import {msg} from '@lingui/core/macro'
9import {useLingui} from '@lingui/react'
10
11import {makeProfileLink} from '#/lib/routes/links'
12import {sanitizeDisplayName} from '#/lib/strings/display-names'
13import {type Shadow} from '#/state/cache/profile-shadow'
14import {isConvoActive, useConvo} from '#/state/messages/convo'
15import {type ConvoItem} from '#/state/messages/convo/types'
16import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar'
17import {atoms as a, useTheme, web} from '#/alf'
18import {ConvoMenu} from '#/components/dms/ConvoMenu'
19import {Bell2Off_Filled_Corner0_Rounded as BellStroke} from '#/components/icons/Bell2'
20import * as Layout from '#/components/Layout'
21import {Link} from '#/components/Link'
22import {PostAlerts} from '#/components/moderation/PostAlerts'
23import {PdsBadge} from '#/components/PdsBadge'
24import {ProfileBadges} from '#/components/ProfileBadges'
25import {Text} from '#/components/Typography'
26import {IS_WEB} from '#/env'
27
28const PFP_SIZE = IS_WEB ? 40 : Layout.HEADER_SLOT_SIZE
29
30export function MessagesListHeader({
31 profile,
32 moderation,
33}: {
34 profile?: Shadow<AppBskyActorDefs.ProfileViewDetailed>
35 moderation?: ModerationDecision
36}) {
37 const t = useTheme()
38
39 const blockInfo = useMemo(() => {
40 if (!moderation) return
41 const modui = moderation.ui('profileView')
42 const blocks = modui.alerts.filter(alert => alert.type === 'blocking')
43 const listBlocks = blocks.filter(alert => alert.source.type === 'list')
44 const userBlock = blocks.find(alert => alert.source.type === 'user')
45 return {
46 listBlocks,
47 userBlock,
48 }
49 }, [moderation])
50
51 return (
52 <Layout.Header.Outer>
53 <View style={[a.w_full, a.flex_row, a.gap_xs, a.align_start]}>
54 <View style={[{minHeight: PFP_SIZE}, a.justify_center]}>
55 <Layout.Header.BackButton />
56 </View>
57 {profile && moderation && blockInfo ? (
58 <HeaderReady
59 profile={profile}
60 moderation={moderation}
61 blockInfo={blockInfo}
62 />
63 ) : (
64 <>
65 <View style={[a.flex_row, a.align_center, a.gap_md, a.flex_1]}>
66 <View
67 style={[
68 {width: PFP_SIZE, height: PFP_SIZE},
69 a.rounded_full,
70 t.atoms.bg_contrast_25,
71 ]}
72 />
73 <View style={a.gap_xs}>
74 <View
75 style={[
76 {width: 120, height: 16},
77 a.rounded_xs,
78 t.atoms.bg_contrast_25,
79 a.mt_xs,
80 ]}
81 />
82 <View
83 style={[
84 {width: 175, height: 12},
85 a.rounded_xs,
86 t.atoms.bg_contrast_25,
87 ]}
88 />
89 </View>
90 </View>
91
92 <Layout.Header.Slot />
93 </>
94 )}
95 </View>
96 </Layout.Header.Outer>
97 )
98}
99
100function HeaderReady({
101 profile,
102 moderation,
103 blockInfo,
104}: {
105 profile: Shadow<AppBskyActorDefs.ProfileViewDetailed>
106 moderation: ModerationDecision
107 blockInfo: {
108 listBlocks: ModerationCause[]
109 userBlock?: ModerationCause
110 }
111}) {
112 const {_} = useLingui()
113 const t = useTheme()
114 const convoState = useConvo()
115
116 const isDeletedAccount = profile?.handle === 'missing.invalid'
117 const displayName = isDeletedAccount
118 ? _(msg`Deleted Account`)
119 : sanitizeDisplayName(
120 profile.displayName || profile.handle,
121 moderation.ui('displayName'),
122 )
123
124 // @ts-ignore findLast is polyfilled - esb
125 const latestMessageFromOther = convoState.items.findLast(
126 (item: ConvoItem) =>
127 item.type === 'message' && item.message.sender.did === profile.did,
128 )
129
130 const latestReportableMessage =
131 latestMessageFromOther?.type === 'message'
132 ? latestMessageFromOther.message
133 : undefined
134
135 return (
136 <View style={[a.flex_1]}>
137 <View style={[a.w_full, a.flex_row, a.align_center, a.justify_between]}>
138 <Link
139 label={_(msg`View ${displayName}'s profile`)}
140 style={[a.flex_row, a.align_start, a.gap_md, a.flex_1, a.pr_md]}
141 to={makeProfileLink(profile)}>
142 <PreviewableUserAvatar
143 size={PFP_SIZE}
144 profile={profile}
145 moderation={moderation.ui('avatar')}
146 disableHoverCard={moderation.blocked}
147 />
148 <View style={[a.flex_1]}>
149 <View style={[a.flex_row, a.align_center]}>
150 <Text
151 emoji
152 style={[
153 a.text_md,
154 a.font_semi_bold,
155 a.self_start,
156 web(a.leading_normal),
157 ]}
158 numberOfLines={1}>
159 {displayName}
160 </Text>
161 <View style={[a.pl_xs]}>
162 <PdsBadge did={profile.did} size="sm" />
163 </View>
164 <ProfileBadges profile={profile} size="md" style={[a.pl_xs]} />
165 </View>
166 {!isDeletedAccount && (
167 <Text
168 style={[
169 t.atoms.text_contrast_medium,
170 a.text_xs,
171 web([a.leading_normal, {marginTop: -2}]),
172 ]}
173 numberOfLines={1}>
174 @{profile.handle}
175 {convoState.convo?.muted && (
176 <>
177 {' '}
178 ·{' '}
179 <BellStroke
180 size="xs"
181 style={t.atoms.text_contrast_medium}
182 />
183 </>
184 )}
185 </Text>
186 )}
187 </View>
188 </Link>
189
190 <View style={[{minHeight: PFP_SIZE}, a.justify_center]}>
191 <Layout.Header.Slot>
192 {isConvoActive(convoState) && (
193 <ConvoMenu
194 convo={convoState.convo}
195 profile={profile}
196 currentScreen="conversation"
197 blockInfo={blockInfo}
198 latestReportableMessage={latestReportableMessage}
199 />
200 )}
201 </Layout.Header.Slot>
202 </View>
203 </View>
204
205 <View
206 style={[
207 {
208 paddingLeft: PFP_SIZE + a.gap_md.gap,
209 },
210 ]}>
211 <PostAlerts
212 modui={moderation.ui('contentList')}
213 size="lg"
214 style={[a.pt_xs]}
215 />
216 </View>
217 </View>
218 )
219}