Bluesky app fork with some witchin' additions 馃挮
at main 230 lines 7.0 kB view raw
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 {Text} from '#/components/Typography' 25import {useSimpleVerificationState} from '#/components/verification' 26import {VerificationCheck} from '#/components/verification/VerificationCheck' 27import {IS_WEB} from '#/env' 28 29const PFP_SIZE = IS_WEB ? 40 : Layout.HEADER_SLOT_SIZE 30 31export function MessagesListHeader({ 32 profile, 33 moderation, 34}: { 35 profile?: Shadow<AppBskyActorDefs.ProfileViewDetailed> 36 moderation?: ModerationDecision 37}) { 38 const t = useTheme() 39 40 const blockInfo = useMemo(() => { 41 if (!moderation) return 42 const modui = moderation.ui('profileView') 43 const blocks = modui.alerts.filter(alert => alert.type === 'blocking') 44 const listBlocks = blocks.filter(alert => alert.source.type === 'list') 45 const userBlock = blocks.find(alert => alert.source.type === 'user') 46 return { 47 listBlocks, 48 userBlock, 49 } 50 }, [moderation]) 51 52 return ( 53 <Layout.Header.Outer> 54 <View style={[a.w_full, a.flex_row, a.gap_xs, a.align_start]}> 55 <View style={[{minHeight: PFP_SIZE}, a.justify_center]}> 56 <Layout.Header.BackButton /> 57 </View> 58 {profile && moderation && blockInfo ? ( 59 <HeaderReady 60 profile={profile} 61 moderation={moderation} 62 blockInfo={blockInfo} 63 /> 64 ) : ( 65 <> 66 <View style={[a.flex_row, a.align_center, a.gap_md, a.flex_1]}> 67 <View 68 style={[ 69 {width: PFP_SIZE, height: PFP_SIZE}, 70 a.rounded_full, 71 t.atoms.bg_contrast_25, 72 ]} 73 /> 74 <View style={a.gap_xs}> 75 <View 76 style={[ 77 {width: 120, height: 16}, 78 a.rounded_xs, 79 t.atoms.bg_contrast_25, 80 a.mt_xs, 81 ]} 82 /> 83 <View 84 style={[ 85 {width: 175, height: 12}, 86 a.rounded_xs, 87 t.atoms.bg_contrast_25, 88 ]} 89 /> 90 </View> 91 </View> 92 93 <Layout.Header.Slot /> 94 </> 95 )} 96 </View> 97 </Layout.Header.Outer> 98 ) 99} 100 101function HeaderReady({ 102 profile, 103 moderation, 104 blockInfo, 105}: { 106 profile: Shadow<AppBskyActorDefs.ProfileViewDetailed> 107 moderation: ModerationDecision 108 blockInfo: { 109 listBlocks: ModerationCause[] 110 userBlock?: ModerationCause 111 } 112}) { 113 const {_} = useLingui() 114 const t = useTheme() 115 const convoState = useConvo() 116 const verification = useSimpleVerificationState({ 117 profile, 118 }) 119 120 const isDeletedAccount = profile?.handle === 'missing.invalid' 121 const displayName = isDeletedAccount 122 ? _(msg`Deleted Account`) 123 : sanitizeDisplayName( 124 profile.displayName || profile.handle, 125 moderation.ui('displayName'), 126 ) 127 128 // @ts-ignore findLast is polyfilled - esb 129 const latestMessageFromOther = convoState.items.findLast( 130 (item: ConvoItem) => 131 item.type === 'message' && item.message.sender.did === profile.did, 132 ) 133 134 const latestReportableMessage = 135 latestMessageFromOther?.type === 'message' 136 ? latestMessageFromOther.message 137 : undefined 138 139 return ( 140 <View style={[a.flex_1]}> 141 <View style={[a.w_full, a.flex_row, a.align_center, a.justify_between]}> 142 <Link 143 label={_(msg`View ${displayName}'s profile`)} 144 style={[a.flex_row, a.align_start, a.gap_md, a.flex_1, a.pr_md]} 145 to={makeProfileLink(profile)}> 146 <PreviewableUserAvatar 147 size={PFP_SIZE} 148 profile={profile} 149 moderation={moderation.ui('avatar')} 150 disableHoverCard={moderation.blocked} 151 /> 152 <View style={[a.flex_1]}> 153 <View style={[a.flex_row, a.align_center]}> 154 <Text 155 emoji 156 style={[ 157 a.text_md, 158 a.font_semi_bold, 159 a.self_start, 160 web(a.leading_normal), 161 ]} 162 numberOfLines={1}> 163 {displayName} 164 </Text> 165 <View style={[a.pl_xs]}> 166 <PdsBadge did={profile.did} size="sm" /> 167 </View> 168 {verification.showBadge && ( 169 <View style={[a.pl_xs]}> 170 <VerificationCheck 171 width={14} 172 verifier={verification.role === 'verifier'} 173 /> 174 </View> 175 )} 176 </View> 177 {!isDeletedAccount && ( 178 <Text 179 style={[ 180 t.atoms.text_contrast_medium, 181 a.text_xs, 182 web([a.leading_normal, {marginTop: -2}]), 183 ]} 184 numberOfLines={1}> 185 @{profile.handle} 186 {convoState.convo?.muted && ( 187 <> 188 {' '} 189 &middot;{' '} 190 <BellStroke 191 size="xs" 192 style={t.atoms.text_contrast_medium} 193 /> 194 </> 195 )} 196 </Text> 197 )} 198 </View> 199 </Link> 200 201 <View style={[{minHeight: PFP_SIZE}, a.justify_center]}> 202 <Layout.Header.Slot> 203 {isConvoActive(convoState) && ( 204 <ConvoMenu 205 convo={convoState.convo} 206 profile={profile} 207 currentScreen="conversation" 208 blockInfo={blockInfo} 209 latestReportableMessage={latestReportableMessage} 210 /> 211 )} 212 </Layout.Header.Slot> 213 </View> 214 </View> 215 216 <View 217 style={[ 218 { 219 paddingLeft: PFP_SIZE + a.gap_md.gap, 220 }, 221 ]}> 222 <PostAlerts 223 modui={moderation.ui('contentList')} 224 size="lg" 225 style={[a.pt_xs]} 226 /> 227 </View> 228 </View> 229 ) 230}