Add header to group clip clops (#10254)
Co-authored-by: Samuel Newman <mozzius@protonmail.com>
This commit is contained in:
@@ -242,7 +242,7 @@ export function MessagesScreenInner({navigation, route}: Props) {
|
|||||||
if (!isScreenFocused) {
|
if (!isScreenFocused) {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
return listenSoftReset(onSoftReset)
|
return listenSoftReset(() => void onSoftReset())
|
||||||
}, [onSoftReset, isScreenFocused])
|
}, [onSoftReset, isScreenFocused])
|
||||||
|
|
||||||
// NOTE(APiligrim)
|
// NOTE(APiligrim)
|
||||||
@@ -292,7 +292,7 @@ export function MessagesScreenInner({navigation, route}: Props) {
|
|||||||
size="small"
|
size="small"
|
||||||
color="secondary_inverted"
|
color="secondary_inverted"
|
||||||
variant="solid"
|
variant="solid"
|
||||||
onPress={() => refetch()}>
|
onPress={() => void refetch()}>
|
||||||
<ButtonText>
|
<ButtonText>
|
||||||
<Trans>Retry</Trans>
|
<Trans>Retry</Trans>
|
||||||
</ButtonText>
|
</ButtonText>
|
||||||
@@ -342,8 +342,8 @@ export function MessagesScreenInner({navigation, route}: Props) {
|
|||||||
renderItem={renderItem}
|
renderItem={renderItem}
|
||||||
keyExtractor={keyExtractor}
|
keyExtractor={keyExtractor}
|
||||||
refreshing={isPTRing}
|
refreshing={isPTRing}
|
||||||
onRefresh={onRefresh}
|
onRefresh={() => void onRefresh()}
|
||||||
onEndReached={onEndReached}
|
onEndReached={() => void onEndReached()}
|
||||||
ListFooterComponent={
|
ListFooterComponent={
|
||||||
<ListFooter
|
<ListFooter
|
||||||
isFetchingNextPage={isFetchingNextPage}
|
isFetchingNextPage={isFetchingNextPage}
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ function Inner() {
|
|||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
<Layout.Center
|
<Layout.Center
|
||||||
style={[a.flex_1, IS_LIQUID_GLASS && {paddingTop: topInset}]}>
|
style={[a.w_full, IS_LIQUID_GLASS && {paddingTop: topInset}]}>
|
||||||
{moderation ? (
|
{moderation ? (
|
||||||
<MessagesListHeader profile={recipient} moderation={moderation} />
|
<MessagesListHeader profile={recipient} moderation={moderation} />
|
||||||
) : (
|
) : (
|
||||||
|
|||||||
@@ -1,4 +1,11 @@
|
|||||||
import {useCallback, useEffect, useId, useRef, useState} from 'react'
|
import {
|
||||||
|
useCallback,
|
||||||
|
useEffect,
|
||||||
|
useId,
|
||||||
|
useLayoutEffect,
|
||||||
|
useRef,
|
||||||
|
useState,
|
||||||
|
} from 'react'
|
||||||
import {type LayoutChangeEvent, type ScrollViewProps, View} from 'react-native'
|
import {type LayoutChangeEvent, type ScrollViewProps, View} from 'react-native'
|
||||||
import {
|
import {
|
||||||
KeyboardChatScrollView,
|
KeyboardChatScrollView,
|
||||||
@@ -62,6 +69,7 @@ import {useAnalytics} from '#/analytics'
|
|||||||
import {IS_ANDROID, IS_NATIVE, IS_WEB} from '#/env'
|
import {IS_ANDROID, IS_NATIVE, IS_WEB} from '#/env'
|
||||||
import {ChatStatusInfo} from './ChatStatusInfo'
|
import {ChatStatusInfo} from './ChatStatusInfo'
|
||||||
import {MessageInputEmbed, useMessageEmbed} from './MessageInputEmbed'
|
import {MessageInputEmbed, useMessageEmbed} from './MessageInputEmbed'
|
||||||
|
import {MessagesListInfoPanel} from './MessagesListInfoPanel'
|
||||||
import {KeyboardStickyView} from './vendor/KeyboardStickyView'
|
import {KeyboardStickyView} from './vendor/KeyboardStickyView'
|
||||||
|
|
||||||
function MaybeLoader({isLoading}: {isLoading: boolean}) {
|
function MaybeLoader({isLoading}: {isLoading: boolean}) {
|
||||||
@@ -151,10 +159,12 @@ export function MessagesList({
|
|||||||
// Reset when hasScrolled goes back to false (e.g. convo re-initialization after backgrounding).
|
// Reset when hasScrolled goes back to false (e.g. convo re-initialization after backgrounding).
|
||||||
const hasInitiallyScrolled = useRef(false)
|
const hasInitiallyScrolled = useRef(false)
|
||||||
const prevHasScrolled = useRef(hasScrolled)
|
const prevHasScrolled = useRef(hasScrolled)
|
||||||
if (prevHasScrolled.current && !hasScrolled) {
|
useLayoutEffect(() => {
|
||||||
hasInitiallyScrolled.current = false
|
if (prevHasScrolled.current && !hasScrolled) {
|
||||||
}
|
hasInitiallyScrolled.current = false
|
||||||
prevHasScrolled.current = hasScrolled
|
}
|
||||||
|
prevHasScrolled.current = hasScrolled
|
||||||
|
}, [hasScrolled])
|
||||||
|
|
||||||
// -- Keep track of background state and positioning for new pill
|
// -- Keep track of background state and positioning for new pill
|
||||||
const layoutHeight = useSharedValue(0)
|
const layoutHeight = useSharedValue(0)
|
||||||
@@ -384,7 +394,7 @@ export function MessagesList({
|
|||||||
profile={convoState.convo.members.find(
|
profile={convoState.convo.members.find(
|
||||||
member => member.did === item.message.sender.did,
|
member => member.did === item.message.sender.did,
|
||||||
)}
|
)}
|
||||||
isGroupChat={convoState.getGroupInfo?.() != null}
|
isGroupChat={convoState.isGroup()}
|
||||||
/>
|
/>
|
||||||
)
|
)
|
||||||
} else if (item.type === 'deleted-message') {
|
} else if (item.type === 'deleted-message') {
|
||||||
@@ -417,6 +427,8 @@ export function MessagesList({
|
|||||||
[inputHeightUI],
|
[inputHeightUI],
|
||||||
)
|
)
|
||||||
|
|
||||||
|
console.log('DEBUG >>>', 'convoState.hasAllHistory', convoState.hasAllHistory)
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<DateDividerToggleProvider>
|
<DateDividerToggleProvider>
|
||||||
<KeyboardGestureArea
|
<KeyboardGestureArea
|
||||||
@@ -449,7 +461,12 @@ export function MessagesList({
|
|||||||
showsVerticalScrollIndicator={!IS_ANDROID}
|
showsVerticalScrollIndicator={!IS_ANDROID}
|
||||||
scrollEventThrottle={100}
|
scrollEventThrottle={100}
|
||||||
ListHeaderComponent={
|
ListHeaderComponent={
|
||||||
<MaybeLoader isLoading={convoState.isFetchingHistory} />
|
<>
|
||||||
|
<MaybeLoader isLoading={convoState.isFetchingHistory} />
|
||||||
|
{convoState.isGroup() && convoState.hasAllHistory ? (
|
||||||
|
<MessagesListInfoPanel convoState={convoState} />
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
}
|
}
|
||||||
// native only (prop is not supported on web)
|
// native only (prop is not supported on web)
|
||||||
renderScrollComponent={renderScrollComponent}
|
renderScrollComponent={renderScrollComponent}
|
||||||
|
|||||||
@@ -0,0 +1,95 @@
|
|||||||
|
import {View} from 'react-native'
|
||||||
|
import {Plural, Trans, useLingui} from '@lingui/react/macro'
|
||||||
|
|
||||||
|
import {type ConvoState} from '#/state/messages/convo/types'
|
||||||
|
import {useSession} from '#/state/session'
|
||||||
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
|
import {AvatarBubbles} from '#/components/AvatarBubbles'
|
||||||
|
import {Button, ButtonIcon, ButtonText} from '#/components/Button'
|
||||||
|
import {ChainLink_Stroke2_Corner0_Rounded as ChainLinkIcon} from '#/components/icons/ChainLink'
|
||||||
|
import {PersonPlus_Stroke2_Corner0_Rounded as PersonPlusIcon} from '#/components/icons/Person'
|
||||||
|
import {Text} from '#/components/Typography'
|
||||||
|
|
||||||
|
export function MessagesListInfoPanel({convoState}: {convoState: ConvoState}) {
|
||||||
|
const t = useTheme()
|
||||||
|
const {t: l} = useLingui()
|
||||||
|
|
||||||
|
const {currentAccount} = useSession()
|
||||||
|
|
||||||
|
const groupName = convoState.getGroupInfo?.()?.name
|
||||||
|
|
||||||
|
const members = (convoState?.convo?.members ?? []).filter(
|
||||||
|
profile => profile.did !== currentAccount?.did,
|
||||||
|
)
|
||||||
|
|
||||||
|
let names: React.ReactNode | null = null
|
||||||
|
if (members.length === 1) {
|
||||||
|
names = <Trans>New chat with {members[0].displayName}</Trans>
|
||||||
|
}
|
||||||
|
if (members.length === 2) {
|
||||||
|
names = (
|
||||||
|
<Trans>
|
||||||
|
New chat with {members[0].displayName} and {members[1].displayName}
|
||||||
|
</Trans>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (members.length > 2) {
|
||||||
|
names = (
|
||||||
|
<Trans>
|
||||||
|
New chat with {members[0].displayName}, {members[1].displayName}, and{' '}
|
||||||
|
<Plural
|
||||||
|
value={members.length - 2}
|
||||||
|
one={`${members.length - 2} more`}
|
||||||
|
other={`${members.length - 2} more`}
|
||||||
|
/>
|
||||||
|
.
|
||||||
|
</Trans>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<View style={[a.align_center, a.justify_center]}>
|
||||||
|
<AvatarBubbles animate={true} profiles={members} />
|
||||||
|
{groupName ? (
|
||||||
|
<Text style={[a.text_2xl, a.font_bold, a.mt_lg, t.atoms.text]}>
|
||||||
|
{groupName}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
{names ? (
|
||||||
|
<Text style={[a.text_sm, a.mt_xs, t.atoms.text_contrast_high]}>
|
||||||
|
{names}
|
||||||
|
</Text>
|
||||||
|
) : null}
|
||||||
|
<View
|
||||||
|
style={[
|
||||||
|
a.flex_row,
|
||||||
|
a.align_center,
|
||||||
|
a.justify_center,
|
||||||
|
a.gap_sm,
|
||||||
|
a.mt_lg,
|
||||||
|
a.mb_4xl,
|
||||||
|
]}>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
label={l`Click here to add people to this group chat`}
|
||||||
|
onPress={() => {}}>
|
||||||
|
<ButtonIcon icon={PersonPlusIcon} />
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Add people</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
<Button
|
||||||
|
color="secondary"
|
||||||
|
size="small"
|
||||||
|
label={l`Click here to view or create an invite link for this group chat`}
|
||||||
|
onPress={() => {}}>
|
||||||
|
<ButtonIcon icon={ChainLinkIcon} />
|
||||||
|
<ButtonText>
|
||||||
|
<Trans>Invite link</Trans>
|
||||||
|
</ButtonText>
|
||||||
|
</Button>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
)
|
||||||
|
}
|
||||||
@@ -153,6 +153,8 @@ export class Convo {
|
|||||||
sender: this.sender,
|
sender: this.sender,
|
||||||
recipients: this.recipients,
|
recipients: this.recipients,
|
||||||
isFetchingHistory: this.isFetchingHistory,
|
isFetchingHistory: this.isFetchingHistory,
|
||||||
|
// Explicit null check since the value is initially undefined.
|
||||||
|
hasAllHistory: this.oldestRev === null,
|
||||||
deleteMessage: undefined,
|
deleteMessage: undefined,
|
||||||
sendMessage: undefined,
|
sendMessage: undefined,
|
||||||
fetchMessageHistory: undefined,
|
fetchMessageHistory: undefined,
|
||||||
@@ -176,6 +178,8 @@ export class Convo {
|
|||||||
sender: this.sender!,
|
sender: this.sender!,
|
||||||
recipients: this.recipients!,
|
recipients: this.recipients!,
|
||||||
isFetchingHistory: this.isFetchingHistory,
|
isFetchingHistory: this.isFetchingHistory,
|
||||||
|
// Explicit null check since the value is initially undefined.
|
||||||
|
hasAllHistory: this.oldestRev === null,
|
||||||
deleteMessage: this.deleteMessage,
|
deleteMessage: this.deleteMessage,
|
||||||
sendMessage: this.sendMessage,
|
sendMessage: this.sendMessage,
|
||||||
fetchMessageHistory: this.fetchMessageHistory,
|
fetchMessageHistory: this.fetchMessageHistory,
|
||||||
@@ -196,6 +200,7 @@ export class Convo {
|
|||||||
sender: undefined,
|
sender: undefined,
|
||||||
recipients: undefined,
|
recipients: undefined,
|
||||||
isFetchingHistory: false,
|
isFetchingHistory: false,
|
||||||
|
hasAllHistory: false,
|
||||||
deleteMessage: undefined,
|
deleteMessage: undefined,
|
||||||
sendMessage: undefined,
|
sendMessage: undefined,
|
||||||
fetchMessageHistory: undefined,
|
fetchMessageHistory: undefined,
|
||||||
@@ -216,6 +221,8 @@ export class Convo {
|
|||||||
sender: this.sender,
|
sender: this.sender,
|
||||||
recipients: this.recipients,
|
recipients: this.recipients,
|
||||||
isFetchingHistory: false,
|
isFetchingHistory: false,
|
||||||
|
// Explicit null check since the value is initially undefined.
|
||||||
|
hasAllHistory: this.oldestRev === null,
|
||||||
deleteMessage: undefined,
|
deleteMessage: undefined,
|
||||||
sendMessage: undefined,
|
sendMessage: undefined,
|
||||||
fetchMessageHistory: undefined,
|
fetchMessageHistory: undefined,
|
||||||
@@ -627,6 +634,7 @@ export class Convo {
|
|||||||
|
|
||||||
/*
|
/*
|
||||||
* If oldestRev is null, we've fetched all history.
|
* If oldestRev is null, we've fetched all history.
|
||||||
|
* Needs to explicitly check for `null` since this is initially `undefined`.
|
||||||
*/
|
*/
|
||||||
if (this.oldestRev === null) return
|
if (this.oldestRev === null) return
|
||||||
|
|
||||||
@@ -660,6 +668,14 @@ export class Convo {
|
|||||||
|
|
||||||
this.oldestRev = cursor ?? null
|
this.oldestRev = cursor ?? null
|
||||||
|
|
||||||
|
/*
|
||||||
|
* If the response contained fewer messages than the limit, we know
|
||||||
|
* there are no more pages, regardless of whether a cursor was returned.
|
||||||
|
*/
|
||||||
|
if (messages.length < (IS_NATIVE ? 30 : 60)) {
|
||||||
|
this.oldestRev = null
|
||||||
|
}
|
||||||
|
|
||||||
for (const message of messages) {
|
for (const message of messages) {
|
||||||
if (
|
if (
|
||||||
ChatBskyConvoDefs.isMessageView(message) ||
|
ChatBskyConvoDefs.isMessageView(message) ||
|
||||||
|
|||||||
@@ -156,6 +156,7 @@ export type ConvoStateUninitialized = {
|
|||||||
sender: ChatBskyActorDefs.ProfileViewBasic | undefined
|
sender: ChatBskyActorDefs.ProfileViewBasic | undefined
|
||||||
recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined
|
recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined
|
||||||
isFetchingHistory: false
|
isFetchingHistory: false
|
||||||
|
hasAllHistory: boolean
|
||||||
deleteMessage: undefined
|
deleteMessage: undefined
|
||||||
sendMessage: undefined
|
sendMessage: undefined
|
||||||
fetchMessageHistory: undefined
|
fetchMessageHistory: undefined
|
||||||
@@ -174,6 +175,7 @@ export type ConvoStateInitializing = {
|
|||||||
sender: ChatBskyActorDefs.ProfileViewBasic | undefined
|
sender: ChatBskyActorDefs.ProfileViewBasic | undefined
|
||||||
recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined
|
recipients: ChatBskyActorDefs.ProfileViewBasic[] | undefined
|
||||||
isFetchingHistory: boolean
|
isFetchingHistory: boolean
|
||||||
|
hasAllHistory: boolean
|
||||||
deleteMessage: undefined
|
deleteMessage: undefined
|
||||||
sendMessage: undefined
|
sendMessage: undefined
|
||||||
fetchMessageHistory: undefined
|
fetchMessageHistory: undefined
|
||||||
@@ -192,6 +194,7 @@ export type ConvoStateReady = {
|
|||||||
sender: ChatBskyActorDefs.ProfileViewBasic
|
sender: ChatBskyActorDefs.ProfileViewBasic
|
||||||
recipients: ChatBskyActorDefs.ProfileViewBasic[]
|
recipients: ChatBskyActorDefs.ProfileViewBasic[]
|
||||||
isFetchingHistory: boolean
|
isFetchingHistory: boolean
|
||||||
|
hasAllHistory: boolean
|
||||||
deleteMessage: DeleteMessage
|
deleteMessage: DeleteMessage
|
||||||
sendMessage: SendMessage
|
sendMessage: SendMessage
|
||||||
fetchMessageHistory: FetchMessageHistory
|
fetchMessageHistory: FetchMessageHistory
|
||||||
@@ -210,6 +213,7 @@ export type ConvoStateBackgrounded = {
|
|||||||
sender: ChatBskyActorDefs.ProfileViewBasic
|
sender: ChatBskyActorDefs.ProfileViewBasic
|
||||||
recipients: ChatBskyActorDefs.ProfileViewBasic[]
|
recipients: ChatBskyActorDefs.ProfileViewBasic[]
|
||||||
isFetchingHistory: boolean
|
isFetchingHistory: boolean
|
||||||
|
hasAllHistory: boolean
|
||||||
deleteMessage: DeleteMessage
|
deleteMessage: DeleteMessage
|
||||||
sendMessage: SendMessage
|
sendMessage: SendMessage
|
||||||
fetchMessageHistory: FetchMessageHistory
|
fetchMessageHistory: FetchMessageHistory
|
||||||
@@ -228,6 +232,7 @@ export type ConvoStateSuspended = {
|
|||||||
sender: ChatBskyActorDefs.ProfileViewBasic
|
sender: ChatBskyActorDefs.ProfileViewBasic
|
||||||
recipients: ChatBskyActorDefs.ProfileViewBasic[]
|
recipients: ChatBskyActorDefs.ProfileViewBasic[]
|
||||||
isFetchingHistory: boolean
|
isFetchingHistory: boolean
|
||||||
|
hasAllHistory: boolean
|
||||||
deleteMessage: DeleteMessage
|
deleteMessage: DeleteMessage
|
||||||
sendMessage: SendMessage
|
sendMessage: SendMessage
|
||||||
fetchMessageHistory: FetchMessageHistory
|
fetchMessageHistory: FetchMessageHistory
|
||||||
@@ -246,6 +251,7 @@ export type ConvoStateError = {
|
|||||||
sender: undefined
|
sender: undefined
|
||||||
recipients: undefined
|
recipients: undefined
|
||||||
isFetchingHistory: false
|
isFetchingHistory: false
|
||||||
|
hasAllHistory: false
|
||||||
deleteMessage: undefined
|
deleteMessage: undefined
|
||||||
sendMessage: undefined
|
sendMessage: undefined
|
||||||
fetchMessageHistory: undefined
|
fetchMessageHistory: undefined
|
||||||
@@ -264,6 +270,7 @@ export type ConvoStateDisabled = {
|
|||||||
sender: ChatBskyActorDefs.ProfileViewBasic
|
sender: ChatBskyActorDefs.ProfileViewBasic
|
||||||
recipients: ChatBskyActorDefs.ProfileViewBasic[]
|
recipients: ChatBskyActorDefs.ProfileViewBasic[]
|
||||||
isFetchingHistory: boolean
|
isFetchingHistory: boolean
|
||||||
|
hasAllHistory: boolean
|
||||||
deleteMessage: DeleteMessage
|
deleteMessage: DeleteMessage
|
||||||
sendMessage: SendMessage
|
sendMessage: SendMessage
|
||||||
fetchMessageHistory: FetchMessageHistory
|
fetchMessageHistory: FetchMessageHistory
|
||||||
|
|||||||
Reference in New Issue
Block a user