/* eslint-disable react/prop-types */ import React, {useCallback, useMemo, useState} from 'react' import {View} from 'react-native' import {ChatBskyConvoDefs} from '@atproto-labs/api' import {msg, Trans} from '@lingui/macro' import {useLingui} from '@lingui/react' import {NativeStackScreenProps} from '@react-navigation/native-stack' import {sha256} from 'js-sha256' import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender' import {MessagesTabNavigatorParams} from '#/lib/routes/types' import {useGate} from '#/lib/statsig/statsig' import {cleanError} from '#/lib/strings/errors' import {logger} from '#/logger' import {isNative} from '#/platform/detection' import {useListConvos} from '#/state/queries/messages/list-converations' import {useSession} from '#/state/session' import {List} from '#/view/com/util/List' import {TimeElapsed} from '#/view/com/util/TimeElapsed' import {PreviewableUserAvatar} from '#/view/com/util/UserAvatar' import {ViewHeader} from '#/view/com/util/ViewHeader' import {CenteredView} from '#/view/com/util/Views' import {ScrollView} from '#/view/com/util/Views' import {atoms as a, useBreakpoints, useTheme, web} from '#/alf' import {Button, ButtonIcon, ButtonText} from '#/components/Button' import {DialogControlProps, useDialogControl} from '#/components/Dialog' import {ConvoMenu} from '#/components/dms/ConvoMenu' import {NewChat} from '#/components/dms/NewChat' import * as TextField from '#/components/forms/TextField' import {useRefreshOnFocus} from '#/components/hooks/useRefreshOnFocus' import {PlusLarge_Stroke2_Corner0_Rounded as Plus} from '#/components/icons/Plus' import {SettingsSliderVertical_Stroke2_Corner0_Rounded as SettingsSlider} from '#/components/icons/SettingsSlider' import {Link} from '#/components/Link' import {ListFooter, ListMaybePlaceholder} from '#/components/Lists' import {useMenuControl} from '#/components/Menu' import {Text} from '#/components/Typography' import {ClipClopGate} from '../gate' import {useDmServiceUrlStorage} from '../Temp/useDmServiceUrlStorage' type Props = NativeStackScreenProps export function MessagesScreen({navigation}: Props) { const {_} = useLingui() const t = useTheme() const newChatControl = useDialogControl() const {gtMobile} = useBreakpoints() // TEMP const {serviceUrl, setServiceUrl} = useDmServiceUrlStorage() const hasValidServiceUrl = useMemo(() => { const hash = sha256(serviceUrl) return ( hash === 'a32318b49dd3fe6aa6a35c66c13fcc4c1cb6202b24f5a852d9a2279acee4169f' ) }, [serviceUrl]) const renderButton = useCallback(() => { return ( ) }, [_, t.atoms.text]) const initialNumToRender = useInitialNumToRender() const [isPTRing, setIsPTRing] = useState(false) const { data, isLoading, isFetchingNextPage, hasNextPage, fetchNextPage, error, refetch, } = useListConvos({refetchInterval: 15_000}) useRefreshOnFocus(refetch) const isError = !!error const conversations = useMemo(() => { if (data?.pages) { return data.pages.flatMap(page => page.convos) } return [] }, [data]) 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]) const onNewChat = useCallback( (conversation: string) => navigation.navigate('MessagesConversation', {conversation}), [navigation], ) const onNavigateToSettings = useCallback(() => { navigation.navigate('MessagesSettings') }, [navigation]) const renderItem = useCallback( ({item}: {item: ChatBskyConvoDefs.ConvoView}) => { return }, [], ) const gate = useGate() if (!gate('dms')) return if (!hasValidServiceUrl) { return ( Service URL setServiceUrl(text)} autoCapitalize="none" keyboardType="url" label="https://" /> ) } if (conversations.length < 1) { return ( {gtMobile ? ( ) : ( )} {!isError && } ) } return ( {!gtMobile && ( )} item.id} refreshing={isPTRing} onRefresh={onRefresh} onEndReached={onEndReached} ListHeaderComponent={ } ListFooterComponent={ } onEndReachedThreshold={3} initialNumToRender={initialNumToRender} windowSize={11} // @ts-ignore our .web version only -sfn desktopFixedHeight /> ) } function ChatListItem({convo}: {convo: ChatBskyConvoDefs.ConvoView}) { const t = useTheme() const {_} = useLingui() const {currentAccount} = useSession() const menuControl = useMenuControl() let lastMessage = _(msg`No messages yet`) let lastMessageSentAt: string | null = null if (ChatBskyConvoDefs.isMessageView(convo.lastMessage)) { if (convo.lastMessage.sender?.did === currentAccount?.did) { lastMessage = _(msg`You: ${convo.lastMessage.text}`) } else { lastMessage = convo.lastMessage.text } lastMessageSentAt = convo.lastMessage.sentAt } if (ChatBskyConvoDefs.isDeletedMessageView(convo.lastMessage)) { lastMessage = _(msg`Message deleted`) } const otherUser = convo.members.find( member => member.did !== currentAccount?.did, ) if (!otherUser) { return null } return ( {({hovered, pressed}) => ( 0 && a.font_bold]}> {otherUser.displayName || otherUser.handle} {' '} {lastMessageSentAt ? ( {({timeElapsed}) => ( @{otherUser.handle} · {timeElapsed} )} ) : ( @{otherUser.handle} )} 0 ? a.font_bold : t.atoms.text_contrast_medium, ]}> {lastMessage} {convo.unreadCount > 0 && ( )} )} ) } function DesktopHeader({ newChatControl, onNavigateToSettings, }: { newChatControl: DialogControlProps onNavigateToSettings: () => void }) { const t = useTheme() const {_} = useLingui() const {gtMobile, gtTablet} = useBreakpoints() if (!gtMobile) { return null } return ( Messages {gtTablet && ( )} ) }