| 1 | import type { EnvironmentThreadShell } from "@t3tools/client-runtime/state/shell"; |
| 2 | import { |
| 3 | CommonActions, |
| 4 | StackActions, |
| 5 | useNavigation, |
| 6 | type NavigationState, |
| 7 | } from "@react-navigation/native"; |
| 8 | import type { NativeStackNavigationProp } from "@react-navigation/native-stack"; |
| 9 | import { useCallback, useEffect, useRef } from "react"; |
| 10 | import { Platform } from "react-native"; |
| 11 | import { useAndroidWorkspaceSelection } from "../workspace/useAndroidWorkspaceSelection"; |
| 12 | |
| 13 | type ThreadSelection = Pick<EnvironmentThreadShell, "environmentId" | "id">; |
| 14 | |
| 15 | export function createHomeThreadNavigationAction(input: { |
| 16 | readonly state: Pick<NavigationState, "index" | "routes">; |
| 17 | readonly dismissingRouteKey: string | null; |
| 18 | readonly thread: ThreadSelection; |
| 19 | }) { |
| 20 | const currentRoute = input.state.routes[input.state.index]; |
| 21 | const params = { |
| 22 | environmentId: input.thread.environmentId, |
| 23 | threadId: input.thread.id, |
| 24 | }; |
| 25 | |
| 26 | // Native swipe-back pops the outgoing route after its animation. Reusing |
| 27 | // that key would also discard this selection when the dismissal arrives. |
| 28 | if (input.dismissingRouteKey !== null && currentRoute?.key === input.dismissingRouteKey) { |
| 29 | return StackActions.push("Thread", params); |
| 30 | } |
| 31 | |
| 32 | return CommonActions.navigate("Thread", params); |
| 33 | } |
| 34 | |
| 35 | export function useHomeThreadSelection() { |
| 36 | const navigation = |
| 37 | useNavigation<NativeStackNavigationProp<ReactNavigation.RootParamList, "Home">>(); |
| 38 | const dismissingRouteKey = useRef<string | null>(null); |
| 39 | const selectAndroidWorkspace = useAndroidWorkspaceSelection(); |
| 40 | |
| 41 | useEffect(() => { |
| 42 | const clear = () => { |
| 43 | dismissingRouteKey.current = null; |
| 44 | }; |
| 45 | // This listener belongs to Home, so swipe-back is its opening transition. |
| 46 | // Thread's closing event is targeted at the outgoing Thread route. |
| 47 | const removeTransitionStart = navigation.addListener("transitionStart", ({ data }) => { |
| 48 | const state = navigation.getState(); |
| 49 | const currentRoute = state.routes[state.index]; |
| 50 | dismissingRouteKey.current = |
| 51 | !data.closing && currentRoute?.name === "Thread" ? currentRoute.key : null; |
| 52 | }); |
| 53 | const removeFocus = navigation.addListener("focus", clear); |
| 54 | |
| 55 | return () => { |
| 56 | clear(); |
| 57 | removeTransitionStart(); |
| 58 | removeFocus(); |
| 59 | }; |
| 60 | }, [navigation]); |
| 61 | |
| 62 | return useCallback( |
| 63 | (thread: ThreadSelection) => { |
| 64 | if (Platform.OS === "android") { |
| 65 | selectAndroidWorkspace({ |
| 66 | name: "Thread", |
| 67 | params: { environmentId: String(thread.environmentId), threadId: String(thread.id) }, |
| 68 | }); |
| 69 | return; |
| 70 | } |
| 71 | navigation.dispatch((state) => |
| 72 | createHomeThreadNavigationAction({ |
| 73 | state, |
| 74 | dismissingRouteKey: dismissingRouteKey.current, |
| 75 | thread, |
| 76 | }), |
| 77 | ); |
| 78 | }, |
| 79 | [navigation, selectAndroidWorkspace], |
| 80 | ); |
| 81 | } |