Bluesky app fork with some witchin' additions ๐Ÿ’ซ
0
fork

Configure Feed

Select the types of activity you want to include in your feed.

[๐Ÿด] Handle deleted accounts, restructure ChatListItem (#4114)

* Handle deleted accounts, restructure ChatListItem

* Remove triggerOpacity option

* account for handle change in screen reader

* simplify the check

---------

Co-authored-by: Hailey <me@haileyok.com>

authored by

Eric Bailey
Hailey
and committed by
GitHub
31a716d2 becf373e

+201 -165
+81 -66
src/components/dms/ConvoMenu.tsx
··· 18 18 import {useMuteConvo} from '#/state/queries/messages/mute-conversation' 19 19 import {useProfileBlockMutationQueue} from '#/state/queries/profile' 20 20 import * as Toast from '#/view/com/util/Toast' 21 - import {atoms as a, useTheme} from '#/alf' 21 + import {atoms as a, useTheme, ViewStyleProp} from '#/alf' 22 22 import {BlockedByListDialog} from '#/components/dms/BlockedByListDialog' 23 23 import {LeaveConvoPrompt} from '#/components/dms/LeaveConvoPrompt' 24 24 import {ReportConversationPrompt} from '#/components/dms/ReportConversationPrompt' ··· 41 41 currentScreen, 42 42 showMarkAsRead, 43 43 hideTrigger, 44 - triggerOpacity, 45 44 blockInfo, 45 + style, 46 46 }: { 47 47 convo: ChatBskyConvoDefs.ConvoView 48 48 profile: Shadow<AppBskyActorDefs.ProfileViewBasic> ··· 50 50 currentScreen: 'list' | 'conversation' 51 51 showMarkAsRead?: boolean 52 52 hideTrigger?: boolean 53 - triggerOpacity?: number 54 53 blockInfo: { 55 54 listBlocks: ModerationCause[] 56 55 userBlock?: ModerationCause 57 56 } 57 + style?: ViewStyleProp['style'] 58 58 }): React.ReactNode => { 59 59 const navigation = useNavigation<NavigationProp>() 60 60 const {_} = useLingui() ··· 66 66 67 67 const {listBlocks, userBlock} = blockInfo 68 68 const isBlocking = userBlock || !!listBlocks.length 69 + const isDeletedAccount = profile.handle === 'missing.invalid' 69 70 70 71 const {data: convo} = useConvoQuery(initialConvo) 71 72 ··· 105 106 <> 106 107 <Menu.Root control={control}> 107 108 {!hideTrigger && ( 108 - <View style={{opacity: triggerOpacity}}> 109 + <View style={[style]}> 109 110 <Menu.Trigger label={_(msg`Chat settings`)}> 110 111 {({props, state}) => ( 111 112 <Pressable ··· 128 129 </Menu.Trigger> 129 130 </View> 130 131 )} 131 - <Menu.Outer> 132 - <Menu.Group> 133 - {showMarkAsRead && ( 134 - <Menu.Item 135 - label={_(msg`Mark as read`)} 136 - onPress={() => 137 - markAsRead({ 138 - convoId: convo?.id, 139 - }) 140 - }> 141 - <Menu.ItemText> 142 - <Trans>Mark as read</Trans> 143 - </Menu.ItemText> 144 - <Menu.ItemIcon icon={Bubble} /> 145 - </Menu.Item> 146 - )} 147 - <Menu.Item 148 - label={_(msg`Go to user's profile`)} 149 - onPress={onNavigateToProfile}> 150 - <Menu.ItemText> 151 - <Trans>Go to profile</Trans> 152 - </Menu.ItemText> 153 - <Menu.ItemIcon icon={Person} /> 154 - </Menu.Item> 155 - <Menu.Item 156 - label={_(msg`Mute conversation`)} 157 - onPress={() => muteConvo({mute: !convo?.muted})}> 158 - <Menu.ItemText> 159 - {convo?.muted ? ( 160 - <Trans>Unmute conversation</Trans> 161 - ) : ( 162 - <Trans>Mute conversation</Trans> 163 - )} 164 - </Menu.ItemText> 165 - <Menu.ItemIcon icon={convo?.muted ? Unmute : Mute} /> 166 - </Menu.Item> 167 - </Menu.Group> 168 - <Menu.Divider /> 169 - <Menu.Group> 170 - <Menu.Item 171 - label={ 172 - isBlocking ? _(msg`Unblock account`) : _(msg`Block account`) 173 - } 174 - onPress={toggleBlock}> 175 - <Menu.ItemText> 176 - {isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)} 177 - </Menu.ItemText> 178 - <Menu.ItemIcon icon={isBlocking ? PersonX : PersonCheck} /> 179 - </Menu.Item> 180 - <Menu.Item 181 - label={_(msg`Report conversation`)} 182 - onPress={reportControl.open}> 183 - <Menu.ItemText> 184 - <Trans>Report conversation</Trans> 185 - </Menu.ItemText> 186 - <Menu.ItemIcon icon={Flag} /> 187 - </Menu.Item> 188 - </Menu.Group> 189 - <Menu.Divider /> 190 - <Menu.Group> 132 + 133 + {isDeletedAccount ? ( 134 + <Menu.Outer> 191 135 <Menu.Item 192 136 label={_(msg`Leave conversation`)} 193 137 onPress={leaveConvoControl.open}> ··· 196 140 </Menu.ItemText> 197 141 <Menu.ItemIcon icon={ArrowBoxLeft} /> 198 142 </Menu.Item> 199 - </Menu.Group> 200 - </Menu.Outer> 143 + </Menu.Outer> 144 + ) : ( 145 + <Menu.Outer> 146 + <Menu.Group> 147 + {showMarkAsRead && ( 148 + <Menu.Item 149 + label={_(msg`Mark as read`)} 150 + onPress={() => 151 + markAsRead({ 152 + convoId: convo?.id, 153 + }) 154 + }> 155 + <Menu.ItemText> 156 + <Trans>Mark as read</Trans> 157 + </Menu.ItemText> 158 + <Menu.ItemIcon icon={Bubble} /> 159 + </Menu.Item> 160 + )} 161 + <Menu.Item 162 + label={_(msg`Go to user's profile`)} 163 + onPress={onNavigateToProfile}> 164 + <Menu.ItemText> 165 + <Trans>Go to profile</Trans> 166 + </Menu.ItemText> 167 + <Menu.ItemIcon icon={Person} /> 168 + </Menu.Item> 169 + <Menu.Item 170 + label={_(msg`Mute conversation`)} 171 + onPress={() => muteConvo({mute: !convo?.muted})}> 172 + <Menu.ItemText> 173 + {convo?.muted ? ( 174 + <Trans>Unmute conversation</Trans> 175 + ) : ( 176 + <Trans>Mute conversation</Trans> 177 + )} 178 + </Menu.ItemText> 179 + <Menu.ItemIcon icon={convo?.muted ? Unmute : Mute} /> 180 + </Menu.Item> 181 + </Menu.Group> 182 + <Menu.Divider /> 183 + <Menu.Group> 184 + <Menu.Item 185 + label={ 186 + isBlocking ? _(msg`Unblock account`) : _(msg`Block account`) 187 + } 188 + onPress={toggleBlock}> 189 + <Menu.ItemText> 190 + {isBlocking ? _(msg`Unblock account`) : _(msg`Block account`)} 191 + </Menu.ItemText> 192 + <Menu.ItemIcon icon={isBlocking ? PersonX : PersonCheck} /> 193 + </Menu.Item> 194 + <Menu.Item 195 + label={_(msg`Report conversation`)} 196 + onPress={reportControl.open}> 197 + <Menu.ItemText> 198 + <Trans>Report conversation</Trans> 199 + </Menu.ItemText> 200 + <Menu.ItemIcon icon={Flag} /> 201 + </Menu.Item> 202 + </Menu.Group> 203 + <Menu.Divider /> 204 + <Menu.Group> 205 + <Menu.Item 206 + label={_(msg`Leave conversation`)} 207 + onPress={leaveConvoControl.open}> 208 + <Menu.ItemText> 209 + <Trans>Leave conversation</Trans> 210 + </Menu.ItemText> 211 + <Menu.ItemIcon icon={ArrowBoxLeft} /> 212 + </Menu.Item> 213 + </Menu.Group> 214 + </Menu.Outer> 215 + )} 201 216 </Menu.Root> 202 217 203 218 <LeaveConvoPrompt
+120 -99
src/screens/Messages/List/ChatListItem.tsx
··· 8 8 } from '@atproto/api' 9 9 import {msg} from '@lingui/macro' 10 10 import {useLingui} from '@lingui/react' 11 - import {useNavigation} from '@react-navigation/native' 12 11 13 - import {NavigationProp} from '#/lib/routes/types' 14 12 import {isNative} from '#/platform/detection' 15 13 import {useProfileShadow} from '#/state/cache/profile-shadow' 16 14 import {useModerationOpts} from '#/state/preferences/moderation-opts' ··· 19 17 import {TimeElapsed} from '#/view/com/util/TimeElapsed' 20 18 import {UserAvatar} from '#/view/com/util/UserAvatar' 21 19 import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' 22 - import {Button} from '#/components/Button' 23 20 import {ConvoMenu} from '#/components/dms/ConvoMenu' 24 21 import {Bell2Off_Filled_Corner0_Rounded as BellStroke} from '#/components/icons/Bell2' 22 + import {Link} from '#/components/Link' 25 23 import {useMenuControl} from '#/components/Menu' 26 24 import {Text} from '#/components/Typography' 27 25 ··· 91 89 moderation.ui('displayName'), 92 90 ) 93 91 92 + const isDimStyle = convo.muted || moderation.blocked || isDeletedAccount 93 + 94 94 let lastMessage = _(msg`No messages yet`) 95 95 let lastMessageSentAt: string | null = null 96 96 if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) { ··· 102 102 lastMessageSentAt = convo.lastMessage.sentAt 103 103 } 104 104 if (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage)) { 105 - lastMessage = _(msg`Message deleted`) 105 + lastMessage = _(msg`Conversation deleted`) 106 106 } 107 107 108 - const navigation = useNavigation<NavigationProp>() 109 108 const [showActions, setShowActions] = useState(false) 110 109 111 110 const onMouseEnter = useCallback(() => { ··· 121 120 setShowActions(true) 122 121 }, []) 123 122 124 - const onPress = useCallback(() => { 125 - navigation.push('MessagesConversation', { 126 - conversation: convo.id, 127 - }) 128 - }, [convo.id, navigation]) 129 - 130 123 const onLongPress = useCallback(() => { 131 124 menuControl.open() 132 125 }, [menuControl]) ··· 137 130 onMouseEnter={onMouseEnter} 138 131 onMouseLeave={onMouseLeave} 139 132 onFocus={onFocus} 140 - onBlur={onMouseLeave}> 141 - <Button 142 - onPress={onPress} 143 - style={[a.flex_1]} 144 - onLongPress={isNative ? onLongPress : undefined} 145 - label={profile.displayName || profile.handle} 146 - accessibilityHint={_(msg`Go to conversation with ${profile.handle}`)} 133 + onBlur={onMouseLeave} 134 + style={[a.relative]}> 135 + <Link 136 + to={`/messages/${convo.id}`} 137 + label={displayName} 138 + accessibilityHint={ 139 + !isDeletedAccount 140 + ? _(msg`Go to conversation with ${profile.handle}`) 141 + : undefined 142 + } 147 143 accessibilityActions={ 148 144 isNative 149 145 ? [ ··· 152 148 ] 153 149 : undefined 154 150 } 155 - onAccessibilityAction={onLongPress}> 151 + onAccessibilityAction={onLongPress} 152 + onPress={ 153 + isDeletedAccount 154 + ? e => { 155 + e.preventDefault() 156 + return false 157 + } 158 + : undefined 159 + } 160 + style={[ 161 + web({ 162 + cursor: isDeletedAccount ? 'default' : 'pointer', 163 + }), 164 + ]} 165 + onLongPress={isNative ? menuControl.open : undefined}> 156 166 {({hovered, pressed, focused}) => ( 157 167 <View 158 168 style={[ 159 169 a.flex_row, 170 + isDeletedAccount ? a.align_center : a.align_start, 160 171 a.flex_1, 161 172 a.px_lg, 162 173 a.py_md, 163 174 a.gap_md, 164 - (hovered || pressed || focused) && t.atoms.bg_contrast_25, 175 + (hovered || pressed || focused) && 176 + !isDeletedAccount && 177 + t.atoms.bg_contrast_25, 165 178 t.atoms.border_contrast_low, 166 179 ]}> 167 180 <UserAvatar ··· 169 182 size={52} 170 183 moderation={moderation.ui('avatar')} 171 184 /> 172 - <View style={[a.flex_1, a.flex_row, a.align_center]}> 173 - <View style={[a.flex_1]}> 174 - <View 175 - style={[ 176 - a.flex_1, 177 - a.flex_row, 178 - a.align_end, 179 - a.pb_2xs, 180 - web([{marginTop: -2}]), 181 - ]}> 185 + 186 + <View style={[a.flex_1, a.justify_center]}> 187 + <View style={[a.w_full, a.flex_row, a.align_end, a.pb_2xs]}> 188 + <Text 189 + numberOfLines={1} 190 + style={[{maxWidth: '85%'}, web([a.leading_normal])]}> 182 191 <Text 183 - numberOfLines={1} 184 - style={[{maxWidth: '85%'}, web([a.leading_normal])]}> 185 - <Text style={[a.text_md, t.atoms.text, a.font_bold]}> 186 - {displayName} 187 - </Text> 192 + style={[ 193 + a.text_md, 194 + t.atoms.text, 195 + a.font_bold, 196 + {lineHeight: 21}, 197 + isDimStyle && t.atoms.text_contrast_medium, 198 + ]}> 199 + {displayName} 188 200 </Text> 189 - {lastMessageSentAt && ( 190 - <TimeElapsed timestamp={lastMessageSentAt}> 191 - {({timeElapsed}) => ( 192 - <Text 193 - style={[ 194 - a.text_sm, 195 - web([a.leading_normal, {whiteSpace: 'pre'}]), 196 - t.atoms.text_contrast_medium, 197 - ]}> 198 - {' '} 199 - &middot; {timeElapsed} 200 - </Text> 201 - )} 202 - </TimeElapsed> 203 - )} 204 - {(convo.muted || moderation.blocked) && ( 205 - <Text 206 - style={[ 207 - a.text_sm, 208 - web([a.leading_normal, {whiteSpace: 'pre'}]), 209 - t.atoms.text_contrast_medium, 210 - ]}> 211 - {' '} 212 - &middot;{' '} 213 - <BellStroke 214 - size="xs" 215 - style={t.atoms.text_contrast_medium} 216 - /> 217 - </Text> 218 - )} 219 - </View> 220 - {!isDeletedAccount && ( 201 + </Text> 202 + {lastMessageSentAt && ( 203 + <TimeElapsed timestamp={lastMessageSentAt}> 204 + {({timeElapsed}) => ( 205 + <Text 206 + style={[ 207 + a.text_sm, 208 + {lineHeight: 21}, 209 + t.atoms.text_contrast_medium, 210 + ]}> 211 + {' '} 212 + &middot; {timeElapsed} 213 + </Text> 214 + )} 215 + </TimeElapsed> 216 + )} 217 + {(convo.muted || moderation.blocked) && ( 221 218 <Text 222 - numberOfLines={1} 223 - style={[a.text_sm, t.atoms.text_contrast_medium, a.pb_xs]}> 224 - @{profile.handle} 219 + style={[ 220 + a.text_sm, 221 + {lineHeight: 21}, 222 + t.atoms.text_contrast_medium, 223 + ]}> 224 + {' '} 225 + &middot;{' '} 226 + <BellStroke 227 + size="xs" 228 + style={[t.atoms.text_contrast_medium]} 229 + /> 225 230 </Text> 226 231 )} 232 + </View> 233 + 234 + {!isDeletedAccount && ( 227 235 <Text 228 - numberOfLines={2} 229 - style={[ 230 - a.text_sm, 231 - a.leading_snug, 232 - convo.unreadCount > 0 233 - ? a.font_bold 234 - : t.atoms.text_contrast_high, 235 - (convo.muted || moderation.blocked) && 236 - t.atoms.text_contrast_medium, 237 - ]}> 238 - {lastMessage} 236 + numberOfLines={1} 237 + style={[a.text_sm, t.atoms.text_contrast_medium, a.pb_xs]}> 238 + @{profile.handle} 239 239 </Text> 240 - </View> 240 + )} 241 + 242 + <Text 243 + numberOfLines={2} 244 + style={[ 245 + a.text_sm, 246 + a.leading_snug, 247 + convo.unreadCount > 0 248 + ? a.font_bold 249 + : t.atoms.text_contrast_high, 250 + isDimStyle && t.atoms.text_contrast_medium, 251 + ]}> 252 + {lastMessage} 253 + </Text> 254 + 241 255 {convo.unreadCount > 0 && ( 242 256 <View 243 257 style={[ 244 258 a.absolute, 245 259 a.rounded_full, 246 260 { 247 - backgroundColor: 248 - convo.muted || moderation.blocked 249 - ? t.palette.contrast_200 250 - : t.palette.primary_500, 261 + backgroundColor: isDimStyle 262 + ? t.palette.contrast_200 263 + : t.palette.primary_500, 251 264 height: 7, 252 265 width: 7, 253 266 }, ··· 263 276 ]} 264 277 /> 265 278 )} 266 - <ConvoMenu 267 - convo={convo} 268 - profile={profile} 269 - control={menuControl} 270 - currentScreen="list" 271 - showMarkAsRead={convo.unreadCount > 0} 272 - hideTrigger={isNative} 273 - triggerOpacity={ 274 - !gtMobile || showActions || menuControl.isOpen ? 1 : 0 275 - } 276 - blockInfo={blockInfo} 277 - /> 278 279 </View> 279 280 </View> 280 281 )} 281 - </Button> 282 + </Link> 283 + 284 + <ConvoMenu 285 + convo={convo} 286 + profile={profile} 287 + control={menuControl} 288 + currentScreen="list" 289 + showMarkAsRead={convo.unreadCount > 0} 290 + hideTrigger={isNative} 291 + blockInfo={blockInfo} 292 + style={[ 293 + a.absolute, 294 + a.h_full, 295 + a.self_end, 296 + a.justify_center, 297 + { 298 + right: a.px_lg.paddingRight, 299 + opacity: !gtMobile || showActions || menuControl.isOpen ? 1 : 0, 300 + }, 301 + ]} 302 + /> 282 303 </View> 283 304 ) 284 305 }