import {useCallback, useMemo, useState} from 'react' import {View} from 'react-native' import { type ChatBskyConvoDefs, type ChatBskyConvoListConvos, } from '@atproto/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {useFocusEffect, useNavigation} from '@react-navigation/native' import { type InfiniteData, type UseInfiniteQueryResult, } from '@tanstack/react-query' import {useAppState} from '#/lib/hooks/useAppState' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import { type CommonNavigatorParams, type NativeStackScreenProps, type NavigationProp, } from '#/lib/routes/types' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' import {isNative} from '#/platform/detection' 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 {useUpdateAllRead} from '#/state/queries/messages/update-all-read' import {FAB} from '#/view/com/util/fab/FAB' import {List} from '#/view/com/util/List' import {ChatListLoadingPlaceholder} from '#/view/com/util/LoadingPlaceholder' import * as Toast from '#/view/com/util/Toast' import {atoms as a, useBreakpoints, useTheme} from '#/alf' import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen' import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {useRefreshOnFocus} from '#/components/hooks/useRefreshOnFocus' import {ArrowLeft_Stroke2_Corner0_Rounded as ArrowLeftIcon} from '#/components/icons/Arrow' import {ArrowRotateCounterClockwise_Stroke2_Corner0_Rounded as RetryIcon} from '#/components/icons/ArrowRotateCounterClockwise' import {Check_Stroke2_Corner0_Rounded as CheckIcon} from '#/components/icons/Check' import {CircleInfo_Stroke2_Corner0_Rounded as CircleInfoIcon} from '#/components/icons/CircleInfo' import {Message_Stroke2_Corner0_Rounded as MessageIcon} from '#/components/icons/Message' import * as Layout from '#/components/Layout' import {ListFooter} from '#/components/Lists' import {Text} from '#/components/Typography' import {RequestListItem} from './components/RequestListItem' type Props = NativeStackScreenProps export function MessagesInboxScreen(props: Props) { const {_} = useLingui() const aaCopy = useAgeAssuranceCopy() return ( ) } export function MessagesInboxScreenInner({}: Props) { const {gtTablet} = useBreakpoints() const listConvosQuery = useListConvosQuery({status: 'request'}) const {data} = listConvosQuery const leftConvos = useLeftConvos() const conversations = useMemo(() => { if (data?.pages) { const convos = data.pages .flatMap(page => page.convos) // filter out convos that are actively being left .filter(convo => !leftConvos.includes(convo.id)) return convos } return [] }, [data, leftConvos]) const hasUnreadConvos = useMemo(() => { return conversations.some( conversation => conversation.members.every( member => member.handle !== 'missing.invalid', ) && conversation.unreadCount > 0, ) }, [conversations]) return ( Chat requests {hasUnreadConvos && gtTablet ? ( ) : ( )} ) } function RequestList({ listConvosQuery, conversations, hasUnreadConvos, }: { listConvosQuery: UseInfiniteQueryResult< InfiniteData, Error > conversations: ChatBskyConvoDefs.ConvoView[] hasUnreadConvos: boolean }) { const {_} = useLingui() const t = useTheme() const navigation = useNavigation() // Request the poll interval to be 10s (or whatever the MESSAGE_SCREEN_POLL_INTERVAL is set to in the future) // but only when the screen is active const messagesBus = useMessagesEventBus() const state = useAppState() const isActive = state === 'active' useFocusEffect( useCallback(() => { if (isActive) { const unsub = messagesBus.requestPollInterval( MESSAGE_SCREEN_POLL_INTERVAL, ) return () => unsub() } }, [messagesBus, isActive]), ) const initialNumToRender = useInitialNumToRender({minItemHeight: 130}) const [isPTRing, setIsPTRing] = useState(false) const { isLoading, isFetchingNextPage, hasNextPage, fetchNextPage, isError, error, refetch, } = listConvosQuery useRefreshOnFocus(refetch) const onRefresh = useCallback(async () => { setIsPTRing(true) try { await refetch() } catch (err) { logger.error('Failed to refresh conversations', {message: err}) } setIsPTRing(false) }, [refetch, setIsPTRing]) const onEndReached = useCallback(async () => { if (isFetchingNextPage || !hasNextPage || isError) return try { await fetchNextPage() } catch (err) { logger.error('Failed to load more conversations', {message: err}) } }, [isFetchingNextPage, hasNextPage, isError, fetchNextPage]) if (conversations.length < 1) { return ( {isLoading ? ( ) : ( <> {isError ? ( <> Whoops! {cleanError(error) || _(msg`Failed to load conversations`)} ) : ( <> Inbox zero! You don't have any chat requests at the moment. )} )} ) } return ( <> } onEndReachedThreshold={isNative ? 1.5 : 0} initialNumToRender={initialNumToRender} windowSize={11} desktopFixedHeight sideBorders={false} /> {hasUnreadConvos && } ) } function keyExtractor(item: ChatBskyConvoDefs.ConvoView) { return item.id } function renderItem({item}: {item: ChatBskyConvoDefs.ConvoView}) { return } function MarkAllReadFAB() { const {_} = useLingui() const t = useTheme() const {mutate: markAllRead} = useUpdateAllRead('request', { onMutate: () => { Toast.show(_(msg`Marked all as read`), 'check') }, onError: () => { Toast.show(_(msg`Failed to mark all requests as read`), 'xmark') }, }) return ( markAllRead()} icon={} accessibilityRole="button" accessibilityLabel={_(msg`Mark all as read`)} accessibilityHint="" /> ) } function MarkAsReadHeaderButton() { const {_} = useLingui() const {mutate: markAllRead} = useUpdateAllRead('request', { onMutate: () => { Toast.show(_(msg`Marked all as read`), 'check') }, onError: () => { Toast.show(_(msg`Failed to mark all requests as read`), 'xmark') }, }) return ( ) }