Bluesky app fork with some witchin' additions 💫
0
fork

Configure Feed

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

Add header to group clip clops (#10254)

Co-authored-by: Samuel Newman <mozzius@protonmail.com>

authored by

DS Boyce
Samuel Newman
and committed by
GitHub
9f3c21e2 e10c05d7

+147 -12
+4 -4
src/screens/Messages/ChatList.tsx
··· 242 242 if (!isScreenFocused) { 243 243 return 244 244 } 245 - return listenSoftReset(onSoftReset) 245 + return listenSoftReset(() => void onSoftReset()) 246 246 }, [onSoftReset, isScreenFocused]) 247 247 248 248 // NOTE(APiligrim) ··· 292 292 size="small" 293 293 color="secondary_inverted" 294 294 variant="solid" 295 - onPress={() => refetch()}> 295 + onPress={() => void refetch()}> 296 296 <ButtonText> 297 297 <Trans>Retry</Trans> 298 298 </ButtonText> ··· 342 342 renderItem={renderItem} 343 343 keyExtractor={keyExtractor} 344 344 refreshing={isPTRing} 345 - onRefresh={onRefresh} 346 - onEndReached={onEndReached} 345 + onRefresh={() => void onRefresh()} 346 + onEndReached={() => void onEndReached()} 347 347 ListFooterComponent={ 348 348 <ListFooter 349 349 isFetchingNextPage={isFetchingNextPage}
+1 -1
src/screens/Messages/Conversation.tsx
··· 142 142 return ( 143 143 <> 144 144 <Layout.Center 145 - style={[a.flex_1, IS_LIQUID_GLASS && {paddingTop: topInset}]}> 145 + style={[a.w_full, IS_LIQUID_GLASS && {paddingTop: topInset}]}> 146 146 {moderation ? ( 147 147 <MessagesListHeader profile={recipient} moderation={moderation} /> 148 148 ) : (
+24 -7
src/screens/Messages/components/MessagesList.tsx
··· 1 - import {useCallback, useEffect, useId, useRef, useState} from 'react' 1 + import { 2 + useCallback, 3 + useEffect, 4 + useId, 5 + useLayoutEffect, 6 + useRef, 7 + useState, 8 + } from 'react' 2 9 import {type LayoutChangeEvent, type ScrollViewProps, View} from 'react-native' 3 10 import { 4 11 KeyboardChatScrollView, ··· 62 69 import {IS_ANDROID, IS_NATIVE, IS_WEB} from '#/env' 63 70 import {ChatStatusInfo} from './ChatStatusInfo' 64 71 import {MessageInputEmbed, useMessageEmbed} from './MessageInputEmbed' 72 + import {MessagesListInfoPanel} from './MessagesListInfoPanel' 65 73 import {KeyboardStickyView} from './vendor/KeyboardStickyView' 66 74 67 75 function MaybeLoader({isLoading}: {isLoading: boolean}) { ··· 151 159 // Reset when hasScrolled goes back to false (e.g. convo re-initialization after backgrounding). 152 160 const hasInitiallyScrolled = useRef(false) 153 161 const prevHasScrolled = useRef(hasScrolled) 154 - if (prevHasScrolled.current && !hasScrolled) { 155 - hasInitiallyScrolled.current = false 156 - } 157 - prevHasScrolled.current = hasScrolled 162 + useLayoutEffect(() => { 163 + if (prevHasScrolled.current && !hasScrolled) { 164 + hasInitiallyScrolled.current = false 165 + } 166 + prevHasScrolled.current = hasScrolled 167 + }, [hasScrolled]) 158 168 159 169 // -- Keep track of background state and positioning for new pill 160 170 const layoutHeight = useSharedValue(0) ··· 384 394 profile={convoState.convo.members.find( 385 395 member => member.did === item.message.sender.did, 386 396 )} 387 - isGroupChat={convoState.getGroupInfo?.() != null} 397 + isGroupChat={convoState.isGroup()} 388 398 /> 389 399 ) 390 400 } else if (item.type === 'deleted-message') { ··· 417 427 [inputHeightUI], 418 428 ) 419 429 430 + console.log('DEBUG >>>', 'convoState.hasAllHistory', convoState.hasAllHistory) 431 + 420 432 return ( 421 433 <DateDividerToggleProvider> 422 434 <KeyboardGestureArea ··· 449 461 showsVerticalScrollIndicator={!IS_ANDROID} 450 462 scrollEventThrottle={100} 451 463 ListHeaderComponent={ 452 - <MaybeLoader isLoading={convoState.isFetchingHistory} /> 464 + <> 465 + <MaybeLoader isLoading={convoState.isFetchingHistory} /> 466 + {convoState.isGroup() && convoState.hasAllHistory ? ( 467 + <MessagesListInfoPanel convoState={convoState} /> 468 + ) : null} 469 + </> 453 470 } 454 471 // native only (prop is not supported on web) 455 472 renderScrollComponent={renderScrollComponent}
+95
src/screens/Messages/components/MessagesListInfoPanel.tsx
··· 1 + import {View} from 'react-native' 2 + import {Plural, Trans, useLingui} from '@lingui/react/macro' 3 + 4 + import {type ConvoState} from '#/state/messages/convo/types' 5 + import {useSession} from '#/state/session' 6 + import {atoms as a, useTheme} from '#/alf' 7 + import {AvatarBubbles} from '#/components/AvatarBubbles' 8 + import {Button, ButtonIcon, ButtonText} from '#/components/Button' 9 + import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink' 10 + import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person' 11 + import {Text} from '#/components/Typography' 12 + 13 + export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) { 14 + const t = useTheme() 15 + const {t: l} = useLingui() 16 + 17 + const {currentAccount} = useSession() 18 + 19 + const groupName = convoState.getGroupInfo?.()?.name 20 + 21 + const members = (convoState?.convo?.members ?? []).filter( 22 + profile => profile.did !== currentAccount?.did, 23 + ) 24 + 25 + let names: React.ReactNode | null = null 26 + if (members.length === 1) { 27 + names = <Trans>New chat with {members[0].displayName}</Trans> 28 + } 29 + if (members.length === 2) { 30 + names = ( 31 + <Trans> 32 + New chat with {members[0].displayName} and {members[1].displayName} 33 + </Trans> 34 + ) 35 + } 36 + if (members.length > 2) { 37 + names = ( 38 + <Trans> 39 + New chat with {members[0].displayName}, {members[1].displayName}, and{' '} 40 + <Plural 41 + value={members.length - 2} 42 + one={`${members.length - 2} more`} 43 + other={`${members.length - 2} more`} 44 + /> 45 + . 46 + </Trans> 47 + ) 48 + } 49 + 50 + return ( 51 + <View style={[a.align_center, a.justify_center]}> 52 + <AvatarBubbles animate={true} profiles={members} /> 53 + {groupName ? ( 54 + <Text style={[a.text_2xl, a.font_bold, a.mt_lg, t.atoms.text]}> 55 + {groupName} 56 + </Text> 57 + ) : null} 58 + {names ? ( 59 + <Text style={[a.text_sm, a.mt_xs, t.atoms.text_contrast_high]}> 60 + {names} 61 + </Text> 62 + ) : null} 63 + <View 64 + style={[ 65 + a.flex_row, 66 + a.align_center, 67 + a.justify_center, 68 + a.gap_sm, 69 + a.mt_lg, 70 + a.mb_4xl, 71 + ]}> 72 + <Button 73 + color="secondary" 74 + size="small" 75 + label={l`Click here to add people to this group chat`} 76 + onPress={() => {}}> 77 + <ButtonIcon icon={PersonPlusIcon} /> 78 + <ButtonText> 79 + <Trans>Add people</Trans> 80 + </ButtonText> 81 + </Button> 82 + <Button 83 + color="secondary" 84 + size="small" 85 + label={l`Click here to view or create an invite link for this group chat`} 86 + onPress={() => {}}> 87 + <ButtonIcon icon={ChainLinkIcon} /> 88 + <ButtonText> 89 + <Trans>Invite link</Trans> 90 + </ButtonText> 91 + </Button> 92 + </View> 93 + </View> 94 + ) 95 + }
+16
src/state/messages/convo/agent.ts
··· 153 153 sender: this.sender, 154 154 recipients: this.recipients, 155 155 isFetchingHistory: this.isFetchingHistory, 156 + // Explicit null check since the value is initially undefined. 157 + hasAllHistory: this.oldestRev === null, 156 158 deleteMessage: undefined, 157 159 sendMessage: undefined, 158 160 fetchMessageHistory: undefined, ··· 176 178 sender: this.sender!, 177 179 recipients: this.recipients!, 178 180 isFetchingHistory: this.isFetchingHistory, 181 + // Explicit null check since the value is initially undefined. 182 + hasAllHistory: this.oldestRev === null, 179 183 deleteMessage: this.deleteMessage, 180 184 sendMessage: this.sendMessage, 181 185 fetchMessageHistory: this.fetchMessageHistory, ··· 196 200 sender: undefined, 197 201 recipients: undefined, 198 202 isFetchingHistory: false, 203 + hasAllHistory: false, 199 204 deleteMessage: undefined, 200 205 sendMessage: undefined, 201 206 fetchMessageHistory: undefined, ··· 216 221 sender: this.sender, 217 222 recipients: this.recipients, 218 223 isFetchingHistory: false, 224 + // Explicit null check since the value is initially undefined. 225 + hasAllHistory: this.oldestRev === null, 219 226 deleteMessage: undefined, 220 227 sendMessage: undefined, 221 228 fetchMessageHistory: undefined, ··· 627 634 628 635 /* 629 636 * If oldestRev is null, we've fetched all history. 637 + * Needs to explicitly check for `null` since this is initially `undefined`. 630 638 */ 631 639 if (this.oldestRev === null) return 632 640 ··· 659 667 const {cursor, messages} = response.data 660 668 661 669 this.oldestRev = cursor ?? null 670 + 671 + /* 672 + * If the response contained fewer messages than the limit, we know 673 + * there are no more pages, regardless of whether a cursor was returned. 674 + */ 675 + if (messages.length < (IS_NATIVE ? 30 : 60)) { 676 + this.oldestRev = null 677 + } 662 678 663 679 for (const message of messages) { 664 680 if (
+7
src/state/messages/convo/types.ts
··· 156 156 sender: ChatBskyActorDefs.ProfileViewBasic | undefined 157 157 recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined 158 158 isFetchingHistory: false 159 + hasAllHistory: boolean 159 160 deleteMessage: undefined 160 161 sendMessage: undefined 161 162 fetchMessageHistory: undefined ··· 174 175 sender: ChatBskyActorDefs.ProfileViewBasic | undefined 175 176 recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined 176 177 isFetchingHistory: boolean 178 + hasAllHistory: boolean 177 179 deleteMessage: undefined 178 180 sendMessage: undefined 179 181 fetchMessageHistory: undefined ··· 192 194 sender: ChatBskyActorDefs.ProfileViewBasic 193 195 recipients: ChatBskyActorDefs.ProfileViewBasic[] 194 196 isFetchingHistory: boolean 197 + hasAllHistory: boolean 195 198 deleteMessage: DeleteMessage 196 199 sendMessage: SendMessage 197 200 fetchMessageHistory: FetchMessageHistory ··· 210 213 sender: ChatBskyActorDefs.ProfileViewBasic 211 214 recipients: ChatBskyActorDefs.ProfileViewBasic[] 212 215 isFetchingHistory: boolean 216 + hasAllHistory: boolean 213 217 deleteMessage: DeleteMessage 214 218 sendMessage: SendMessage 215 219 fetchMessageHistory: FetchMessageHistory ··· 228 232 sender: ChatBskyActorDefs.ProfileViewBasic 229 233 recipients: ChatBskyActorDefs.ProfileViewBasic[] 230 234 isFetchingHistory: boolean 235 + hasAllHistory: boolean 231 236 deleteMessage: DeleteMessage 232 237 sendMessage: SendMessage 233 238 fetchMessageHistory: FetchMessageHistory ··· 246 251 sender: undefined 247 252 recipients: undefined 248 253 isFetchingHistory: false 254 + hasAllHistory: false 249 255 deleteMessage: undefined 250 256 sendMessage: undefined 251 257 fetchMessageHistory: undefined ··· 264 270 sender: ChatBskyActorDefs.ProfileViewBasic 265 271 recipients: ChatBskyActorDefs.ProfileViewBasic[] 266 272 isFetchingHistory: boolean 273 + hasAllHistory: boolean 267 274 deleteMessage: DeleteMessage 268 275 sendMessage: SendMessage 269 276 fetchMessageHistory: FetchMessageHistory