Bluesky app fork with some witchin' additions 馃挮
at main 226 lines 6.8 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/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 {Text} from '#/components/Typography' 24import {useSimpleVerificationState} from '#/components/verification' 25import {VerificationCheck} from '#/components/verification/VerificationCheck' 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 const verification = useSimpleVerificationState({ 116 profile, 117 }) 118 119 const isDeletedAccount = profile?.handle === 'missing.invalid' 120 const displayName = isDeletedAccount 121 ? _(msg`Deleted Account`) 122 : sanitizeDisplayName( 123 profile.displayName || profile.handle, 124 moderation.ui('displayName'), 125 ) 126 127 // @ts-ignore findLast is polyfilled - esb 128 const latestMessageFromOther = convoState.items.findLast( 129 (item: ConvoItem) => 130 item.type === 'message' && item.message.sender.did === profile.did, 131 ) 132 133 const latestReportableMessage = 134 latestMessageFromOther?.type === 'message' 135 ? latestMessageFromOther.message 136 : undefined 137 138 return ( 139 <View style={[a.flex_1]}> 140 <View style={[a.w_full, a.flex_row, a.align_center, a.justify_between]}> 141 <Link 142 label={_(msg`View ${displayName}'s profile`)} 143 style={[a.flex_row, a.align_start, a.gap_md, a.flex_1, a.pr_md]} 144 to={makeProfileLink(profile)}> 145 <PreviewableUserAvatar 146 size={PFP_SIZE} 147 profile={profile} 148 moderation={moderation.ui('avatar')} 149 disableHoverCard={moderation.blocked} 150 /> 151 <View style={[a.flex_1]}> 152 <View style={[a.flex_row, a.align_center]}> 153 <Text 154 emoji 155 style={[ 156 a.text_md, 157 a.font_semi_bold, 158 a.self_start, 159 web(a.leading_normal), 160 ]} 161 numberOfLines={1}> 162 {displayName} 163 </Text> 164 {verification.showBadge && ( 165 <View style={[a.pl_xs]}> 166 <VerificationCheck 167 width={14} 168 verifier={verification.role === 'verifier'} 169 /> 170 </View> 171 )} 172 </View> 173 {!isDeletedAccount && ( 174 <Text 175 style={[ 176 t.atoms.text_contrast_medium, 177 a.text_xs, 178 web([a.leading_normal, {marginTop: -2}]), 179 ]} 180 numberOfLines={1}> 181 @{profile.handle} 182 {convoState.convo?.muted && ( 183 <> 184 {' '} 185 &middot;{' '} 186 <BellStroke 187 size="xs" 188 style={t.atoms.text_contrast_medium} 189 /> 190 </> 191 )} 192 </Text> 193 )} 194 </View> 195 </Link> 196 197 <View style={[{minHeight: PFP_SIZE}, a.justify_center]}> 198 <Layout.Header.Slot> 199 {isConvoActive(convoState) && ( 200 <ConvoMenu 201 convo={convoState.convo} 202 profile={profile} 203 currentScreen="conversation" 204 blockInfo={blockInfo} 205 latestReportableMessage={latestReportableMessage} 206 /> 207 )} 208 </Layout.Header.Slot> 209 </View> 210 </View> 211 212 <View 213 style={[ 214 { 215 paddingLeft: PFP_SIZE + a.gap_md.gap, 216 }, 217 ]}> 218 <PostAlerts 219 modui={moderation.ui('contentList')} 220 size="lg" 221 style={[a.pt_xs]} 222 /> 223 </View> 224 </View> 225 ) 226}