From c995eb2f2fa3e73dcc6943078c85cd6a68f5370b Mon Sep 17 00:00:00 2001 From: Samuel Newman Date: Tue, 4 Mar 2025 13:54:19 +0000 Subject: DMs inbox (#7778) * improve error screen * add chat request prompt * mock up inbox * bigger button * use two-button layout * get inbox working somewhat * fix type errors * fetch both pages for badge * don't include read convos in preview * in-chat ui for non-accepted convos (part 1) * add chatstatusinfo * fix status info not disappearing * get chat status info working * change min item height * move files around * add updated sdk * improve badge behaviour * mock up mark all as read * update sdk to 0.14.4 * hide chat status info if initiating convo * fix unread count for deleted accounts * add toasts after rejection * add prompt to delete * adjust badge on desktop * requests -> chat requests * fix height flicker * add mark as read button to header * add mark all as read APIs * separate avatarstack into two components (#7845) * fix messages being hidden behind chatstatusinfo * show inbox preview on empty state * fix empty state again * Use new convo availability API (#7812) * [Inbox] Accept button on convo screen (#7795) * accept button on convo screen * fix types * fix type error * improve spacing * [DMs] Implement new log types (#7835) * optimise badge state * add read message log * add isLogAcceptConvo * mute/unmute convo logs * use setqueriesdata * always show label on button * optimistically update badge * change incorrect unread count change * Update src/screens/Messages/Inbox.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/screens/Messages/components/RequestButtons.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/screens/Messages/components/RequestButtons.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * Update src/screens/Messages/components/RequestListItem.tsx Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> * fix race condition with accepting convo * fix back button on web * filter left convos from badge * update atproto to fix CI * Add accept override external to convo (#7891) * Add accept override external to convo * rm log --------- Co-authored-by: Samuel Newman --------- Co-authored-by: surfdude29 <149612116+surfdude29@users.noreply.github.com> Co-authored-by: Eric Bailey --- src/screens/Messages/ChatList.tsx | 102 ++++++++++++++++++++++++++++---------- 1 file changed, 77 insertions(+), 25 deletions(-) (limited to 'src/screens/Messages/ChatList.tsx') diff --git a/src/screens/Messages/ChatList.tsx b/src/screens/Messages/ChatList.tsx index 32b111def..b060b23e5 100644 --- a/src/screens/Messages/ChatList.tsx +++ b/src/screens/Messages/ChatList.tsx @@ -1,7 +1,7 @@ import {useCallback, useEffect, useMemo, useState} from 'react' import {View} from 'react-native' import {useAnimatedRef} from 'react-native-reanimated' -import {ChatBskyConvoDefs} from '@atproto/api' +import {ChatBskyActorDefs, ChatBskyConvoDefs} from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect, useIsFocused} from '@react-navigation/native' @@ -18,6 +18,7 @@ import {MESSAGE_SCREEN_POLL_INTERVAL} from '#/state/messages/convo/const' import {useMessagesEventBus} from '#/state/messages/events' import {useLeftConvos} from '#/state/queries/messages/leave-conversation' import {useListConvosQuery} from '#/state/queries/messages/list-conversations' +import {useSession} from '#/state/session' import {List, ListRef} from '#/view/com/util/List' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' @@ -35,20 +36,37 @@ import {ListFooter} from '#/components/Lists' import {Loader} from '#/components/Loader' import {Text} from '#/components/Typography' import {ChatListItem} from './components/ChatListItem' +import {InboxPreview} from './components/InboxPreview' -type Props = NativeStackScreenProps +type ListItem = + | { + type: 'INBOX' + count: number + profiles: ChatBskyActorDefs.ProfileViewBasic[] + } + | { + type: 'CONVERSATION' + conversation: ChatBskyConvoDefs.ConvoView + } -function renderItem({item}: {item: ChatBskyConvoDefs.ConvoView}) { - return +function renderItem({item}: {item: ListItem}) { + switch (item.type) { + case 'INBOX': + return + case 'CONVERSATION': + return + } } -function keyExtractor(item: ChatBskyConvoDefs.ConvoView) { - return item.id +function keyExtractor(item: ListItem) { + return item.type === 'INBOX' ? 'INBOX' : item.conversation.id } +type Props = NativeStackScreenProps export function MessagesScreen({navigation, route}: Props) { const {_} = useLingui() const t = useTheme() + const {currentAccount} = useSession() const newChatControl = useDialogControl() const scrollElRef: ListRef = useAnimatedRef() const pushToConversation = route.params?.pushToConversation @@ -94,33 +112,63 @@ export function MessagesScreen({navigation, route}: Props) { isError, error, refetch, - } = useListConvosQuery() + } = useListConvosQuery({status: 'accepted'}) + + const {data: inboxData, refetch: refetchInbox} = useListConvosQuery({ + status: 'request', + }) useRefreshOnFocus(refetch) + useRefreshOnFocus(refetchInbox) const leftConvos = useLeftConvos() + const inboxPreviewConvos = useMemo(() => { + const inbox = + inboxData?.pages + .flatMap(page => page.convos) + .filter( + convo => + !leftConvos.includes(convo.id) && + !convo.muted && + convo.unreadCount > 0, + ) ?? [] + + return inbox + .map(x => x.members.find(y => y.did !== currentAccount?.did)) + .filter(x => !!x) + }, [inboxData, leftConvos, currentAccount?.did]) + const conversations = useMemo(() => { if (data?.pages) { - return ( - data.pages - .flatMap(page => page.convos) - // filter out convos that are actively being left - .filter(convo => !leftConvos.includes(convo.id)) - ) + const conversations = data.pages + .flatMap(page => page.convos) + // filter out convos that are actively being left + .filter(convo => !leftConvos.includes(convo.id)) + + return [ + { + type: 'INBOX', + count: inboxPreviewConvos.length, + profiles: inboxPreviewConvos.slice(0, 3), + }, + ...conversations.map( + convo => ({type: 'CONVERSATION', conversation: convo} as const), + ), + ] satisfies ListItem[] } return [] - }, [data, leftConvos]) + }, [data, leftConvos, inboxPreviewConvos]) const onRefresh = useCallback(async () => { setIsPTRing(true) try { - await refetch() + await Promise.all([refetch(), refetchInbox()]) } catch (err) { logger.error('Failed to refresh conversations', {message: err}) } setIsPTRing(false) - }, [refetch, setIsPTRing]) + }, [refetch, refetchInbox, setIsPTRing]) const onEndReached = useCallback(async () => { if (isFetchingNextPage || !hasNextPage || isError) return @@ -157,7 +205,8 @@ export function MessagesScreen({navigation, route}: Props) { return listenSoftReset(onSoftReset) }, [onSoftReset, isScreenFocused]) - if (conversations.length < 1) { + // Will always have 1 item - the inbox button + if (conversations.length < 2) { return (
@@ -173,7 +222,7 @@ export function MessagesScreen({navigation, route}: Props) { Whoops! @@ -187,13 +236,14 @@ export function MessagesScreen({navigation, route}: Props) { t.atoms.text_contrast_medium, {maxWidth: 360}, ]}> - {cleanError(error)} + {cleanError(error) || + _(msg`Failed to load conversations`)}