import {useCallback, useEffect, useMemo, useState} from 'react' import {type LayoutChangeEvent, View} from 'react-native' import {useSafeAreaInsets} from 'react-native-safe-area-context' import { ScrollEdgeEffect, ScrollEdgeEffectProvider, } from '@bsky.app/expo-scroll-edge-effect' import {moderateProfile} from '@bsky.app/sdk/moderation' import {Trans, useLingui} from '@lingui/react/macro' import { type RouteProp, useFocusEffect, useIsFocused, useNavigation, useRoute, } from '@react-navigation/native' import {type NativeStackScreenProps} from '@react-navigation/native-stack' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' import {useViewportZoomLock} from '#/lib/hooks/useViewportZoomLock' import { type CommonNavigatorParams, type NavigationProp, } from '#/lib/routes/types' import {useMaybeProfileShadow} from '#/state/cache/profile-shadow' import {useEmail} from '#/state/email-verification' import {ConvoProvider, isConvoActive, useConvo} from '#/state/messages/convo' import {ConvoStatus} from '#/state/messages/convo/types' import {useCurrentConvoId} from '#/state/messages/current-convo-id' import {useModerationOpts} from '#/state/preferences/moderation-opts' import {useConvoQuery} from '#/state/queries/messages/conversation' import {useMarkJoinRequestsRead} from '#/state/queries/messages/mark-join-request-read' import {useSession} from '#/state/session' import {MessagesList} from '#/screens/Messages/components/MessagesList' import {atoms as a, web} from '#/alf' import {AgeRestrictedScreen} from '#/components/ageAssurance/AgeRestrictedScreen' import {useAgeAssuranceCopy} from '#/components/ageAssurance/useAgeAssuranceCopy' import { EmailDialogScreenID, useEmailDialogControl, } from '#/components/dialogs/EmailDialog' import {MessagesListBlockedFooter} from '#/components/dms/MessagesListBlockedFooter' import {MessagesListHeader} from '#/components/dms/MessagesListHeader' import {type ConvoWithDetails, parseConvoView} from '#/components/dms/util' import {Error} from '#/components/Error' import * as Layout from '#/components/Layout' import {IS_LIQUID_GLASS} from '#/env' import {chat} from '#/lexicons' import {isType} from '#/types/bsky' import {ChatDisabled} from './components/ChatDisabled' import {ChatEnded} from './components/ChatEnded' import {ChatLocked} from './components/ChatLocked' import {RequestStatus} from './components/RequestStatus' type Props = NativeStackScreenProps< CommonNavigatorParams, 'MessagesConversation' > export function MessagesConversationScreen(props: Props) { const {t: l} = useLingui() const aaCopy = useAgeAssuranceCopy() return ( ) } export function MessagesConversationScreenInner({route}: Props) { const convoId = route.params.conversation const {setCurrentConvoId} = useCurrentConvoId() useFocusEffect( useCallback(() => { setCurrentConvoId(convoId) return () => { setCurrentConvoId(undefined) } }, [convoId, setCurrentConvoId]), ) return ( ) } function Inner({convoId}: {convoId: string}) { const convoState = useConvo() const {t: l} = useLingui() const {currentAccount} = useSession() const isFocused = useIsFocused() const {top: topInset} = useSafeAreaInsets() const {data: convoData} = useConvoQuery({convoId}) useViewportZoomLock({enabled: isFocused}) const convo = convoData ? parseConvoView(convoData, currentAccount?.did) : null const [hasScrolled, setHasScrolled] = useState(false) // Any time that we re-render the `Initializing` state, we have to reset `hasScrolled` to false. After entering this // state, we know that we're resetting the list of messages and need to re-scroll to the bottom when they get added. const [prevState, setPrevState] = useState(convoState.status) if (prevState !== convoState.status) { setPrevState(convoState.status) if (convoState.status === ConvoStatus.Initializing) { setHasScrolled(false) } } if (convoState.status === ConvoStatus.Error) { return ( <> convoState.error.retry()} /> ) } return ( ) } function InnerReady({ hasScrolled, setHasScrolled, convo, isActive, isDisabled, }: { hasScrolled: boolean setHasScrolled: React.Dispatch> convo: ConvoWithDetails | null isActive: boolean isDisabled: boolean }) { const navigation = useNavigation() const {top: topInset} = useSafeAreaInsets() const [headerHeight, setHeaderHeight] = useState(0) const onHeaderLayout = (e: LayoutChangeEvent) => { setHeaderHeight(e.nativeEvent.layout.height) } const {params} = useRoute>() const {needsEmailVerification} = useEmail() const emailDialogControl = useEmailDialogControl() const unreadRequestCount = convo?.kind === 'group' && isType(chat.bsky.convo.defs.groupConvo, convo.view.kind) ? (convo.view.kind.unreadJoinRequestCount ?? 0) : 0 const {mutate: markJoinRequestsRead} = useMarkJoinRequestsRead(convo?.view.id) /** * Must be non-reactive, otherwise the update to open the global dialog will * cause a re-render loop. */ const maybeBlockForEmailVerification = useNonReactiveCallback(() => { if (needsEmailVerification) { /* * HACKFIX * * Load bearing timeout, to bump this state update until the after the * `navigator.addListener('state')` handler closes elements from * `shell/index.*.tsx` - sfn & esb */ setTimeout(() => emailDialogControl.open({ id: EmailDialogScreenID.Verify, instructions: [ Before you can message another user, you must first verify your email. , ], onCloseWithoutVerifying: () => { if (navigation.canGoBack()) { navigation.goBack() } else { navigation.navigate('Messages', {animation: 'pop'}) } }, }), ) } }) useEffect(() => { maybeBlockForEmailVerification() }, [maybeBlockForEmailVerification]) const primaryMember = useMaybeProfileShadow(convo?.primaryMember) const moderationOpts = useModerationOpts() const primaryMemberModeration = useMemo(() => { if (!primaryMember || !moderationOpts) return null return moderateProfile(primaryMember, moderationOpts) }, [primaryMember, moderationOpts]) const header = let footer: React.ReactNode = null if (isDisabled) { footer = } else if ( convo && primaryMember && primaryMemberModeration && (convo.kind === 'group' ? primaryMemberModeration?.blockCause?.type === 'blocking' : primaryMemberModeration?.blocked) ) { footer = ( ) } else if (convo?.kind === 'group') { if (convo.details.lockStatus === 'locked') { footer = } else if (convo.details.lockStatus === 'locked-permanently') { footer = } } return ( <> {IS_LIQUID_GLASS ? ( {header} ) : ( {header} )} {isActive && convo?.kind === 'group' && unreadRequestCount > 0 ? ( { markJoinRequestsRead() }} onPress={() => { markJoinRequestsRead() navigation.navigate('MessagesJoinRequests', { conversation: convo.view.id, }) }} /> ) : null} {isActive && ( )} ) }