[Chat] NUX (#10848)

Co-authored-by: DS Boyce <260543580+ds-boyce@users.noreply.github.com>
Co-authored-by: Eric Bailey <git@esb.lol>
This commit is contained in:
Samuel Newman
2026-06-10 22:35:35 +03:00
committed by GitHub
parent 2d896983ab
commit 2efdc3fe35
14 changed files with 404 additions and 60 deletions
+65 -3
View File
@@ -8,6 +8,7 @@ import {type NativeStackScreenProps} from '@react-navigation/native-stack'
import {useAppState} from '#/lib/appState'
import {useInitialNumToRender} from '#/lib/hooks/useInitialNumToRender'
import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback'
import {useRequireEmailVerification} from '#/lib/hooks/useRequireEmailVerification'
import {type MessagesTabNavigatorParams} from '#/lib/routes/types'
import {cleanError} from '#/lib/strings/errors'
@@ -41,7 +42,7 @@ import {Link} from '#/components/Link'
import {ListFooter} from '#/components/Lists'
import {Text} from '#/components/Typography'
import {useAgeAssurance} from '#/ageAssurance'
import {IS_NATIVE} from '#/env'
import {IS_NATIVE, IS_WEB} from '#/env'
import {ChatDisabled} from './components/ChatDisabled'
import {ChatListItem} from './components/ChatListItem'
import {InboxRequests} from './components/InboxRequests'
@@ -100,6 +101,12 @@ export function MessagesScreenInner({navigation, route}: Props) {
const newChatControl = useDialogControl()
const {data: chatStatus} = useChatActorStatusQuery()
const pushToConversation = route.params?.pushToConversation
const pushToNewGroupChat = route.params?.pushToNewGroupChat
// Tracks whether the next new-chat dialog open should start directly on the
// group-chat creation step. Set when deep-linked via `pushToNewGroupChat`,
// and reset to `false` whenever the dialog is opened through the normal FAB/
// button path so it never gets stuck in group mode.
const [startNewChatInGroupChat, setStartNewChatInGroupChat] = useState(false)
// Whenever we have `pushToConversation` set, it means we pressed a notification for a chat without being on
// this tab. We should immediately push to the conversation after pressing the notification.
@@ -137,6 +144,7 @@ export function MessagesScreenInner({navigation, route}: Props) {
)
const openChatControl = useCallback(() => {
setStartNewChatInGroupChat(false)
newChatControl.open()
}, [newChatControl])
@@ -149,6 +157,50 @@ export function MessagesScreenInner({navigation, route}: Props) {
],
})
// Deep link into the group-chat creation step of the new-chat dialog. Mirrors
// the `pushToConversation` pattern: open the dialog (respecting the same
// email-verification gating as the normal new-chat button) starting directly
// in group mode, then clear the param so it can fire again later.
const openGroupChatControl = useCallback(() => {
setStartNewChatInGroupChat(true)
newChatControl.open()
}, [newChatControl])
const wrappedOpenGroupChatControl = requireEmailVerification(
openGroupChatControl,
{
instructions: [
<Trans key="new-group-chat">
Before you can message another user, you must first verify your email.
</Trans>,
],
},
)
// Stable reference to the (otherwise per-render) opener so the effect below
// doesn't list it as a dependency - if it did, clearing the param would
// re-run the effect and its cleanup would cancel the pending open.
const openGroupChat = useNonReactiveCallback(wrappedOpenGroupChatControl)
// Deep link into the group-chat creation step of the new-chat dialog. The
// dialog control isn't attached synchronously when navigating onto this
// screen, so defer the open by a tick. We clear the param *after* opening
// (inside the timeout) so clearing doesn't cancel the pending open.
useEffect(() => {
if (!pushToNewGroupChat) return
const timeout = setTimeout(() => {
openGroupChat()
if (IS_WEB) {
// `navigation.setParams({pushToNewGroupChat: undefined})` serializes the
// literal string "undefined" into the query on web (see router build()),
// so strip the param with the history API instead.
const url = new URL(window.location.href)
url.searchParams.delete('pushToNewGroupChat')
history.replaceState(null, '', url.pathname + url.search + url.hash)
} else {
navigation.setParams({pushToNewGroupChat: undefined})
}
}, 100)
return () => clearTimeout(timeout)
}, [navigation, pushToNewGroupChat, openGroupChat])
if (isWithinSplitView) {
return (
<>
@@ -172,7 +224,12 @@ export function MessagesScreenInner({navigation, route}: Props) {
}
style={[a.h_full, a.justify_center, a.pb_5xl]}
/>
<NewChat onNewChat={onNewChat} control={newChatControl} />
<NewChat
onNewChat={onNewChat}
control={newChatControl}
startInGroupChat={startNewChatInGroupChat}
onClose={() => setStartNewChatInGroupChat(false)}
/>
</>
)
}
@@ -181,7 +238,12 @@ export function MessagesScreenInner({navigation, route}: Props) {
<Layout.Screen testID="messagesScreen">
<Header newChatControl={newChatControl} chatStatus={chatStatus} />
<ChatList newChatControl={newChatControl} chatStatus={chatStatus} />
<NewChat onNewChat={onNewChat} control={newChatControl} />
<NewChat
onNewChat={onNewChat}
control={newChatControl}
startInGroupChat={startNewChatInGroupChat}
onClose={() => setStartNewChatInGroupChat(false)}
/>
</Layout.Screen>
)
}