import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader"; import { useIsFocused, useNavigation, type StaticScreenProps } from "@react-navigation/native"; import { SymbolView } from "../../components/AppSymbol"; import type { EnvironmentProject } from "@t3tools/client-runtime/state/shell"; import { useEffect, useRef, useState } from "react"; import { ActivityIndicator, Alert, Platform, Pressable, ScrollView, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { useThemeColor } from "../../lib/useThemeColor"; import { cn } from "../../lib/cn"; import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; import { AppText as Text } from "../../components/AppText"; import { ProjectFavicon } from "../../components/ProjectFavicon"; import { useProjects } from "../../state/entities"; import type { WorkspaceState } from "../../state/workspaceModel"; import { useWorkspaceState } from "../../state/workspace"; import { scopedProjectKey } from "../../lib/scopedEntities"; import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout"; import { useIncomingShare } from "../sharing/IncomingShareProvider"; import { useNewTaskFlow } from "./new-task-flow-provider"; type NewTaskRouteParams = { readonly incomingShareId?: string | string[]; }; function deriveProjectEmptyState(catalogState: WorkspaceState): { readonly title: string; readonly detail: string; readonly loading: boolean; } { if (catalogState.isLoadingConnections) { return { title: "Loading environments", detail: "Checking saved environments on this device.", loading: true, }; } if (!catalogState.hasConnections) { return { title: "No environments connected", detail: "Add an environment before creating a task.", loading: false, }; } if ( (catalogState.connectionState === "available" || catalogState.connectionState === "offline" || catalogState.connectionState === "error") && !catalogState.hasLoadedShellSnapshot ) { return { title: "Environment unavailable", detail: catalogState.connectionError ?? "The saved environment is offline. Check the URL or start the environment, then retry.", loading: false, }; } if ( catalogState.hasConnectingEnvironment && !catalogState.hasLoadedShellSnapshot && catalogState.connectionError === null ) { return { title: "Connecting to environment", detail: "Loading projects from the saved environment.", loading: true, }; } return { title: "No projects found", detail: "The connected environment did not report any projects.", loading: false, }; } export function NewTaskRouteScreen({ route }: StaticScreenProps) { const projects = useProjects(); const { projectScopes } = useNewTaskFlow(); const { state: catalogState } = useWorkspaceState(); const navigation = useNavigation(); const isFocused = useIsFocused(); const { layout } = useAdaptiveWorkspaceLayout(); const insets = useSafeAreaInsets(); const chevronColor = useThemeColor("--color-chevron"); const accentColor = useThemeColor("--color-icon-muted"); const [expandedGroupKeys, setExpandedGroupKeys] = useState>(() => new Set()); const { getShare, releaseShareReservation } = useIncomingShare(); const routeShareId = Array.isArray(route.params?.incomingShareId) ? route.params.incomingShareId[0] : route.params?.incomingShareId; const incomingShare = routeShareId ? getShare(routeShareId) : null; const incomingShareSubtitle = incomingShare ? incomingShare.attachments.length === 0 ? "Choose a project for what you shared" : incomingShare.attachments.length === 1 ? "Choose a project for the image you shared" : `Choose a project for the ${incomingShare.attachments.length} images you shared` : null; const screenTitle = incomingShare ? "Start a task" : "Choose project"; const projectEmptyState = deriveProjectEmptyState(catalogState); const resumedDestinationKeyRef = useRef(null); const reservedDestinationProject = incomingShare?.destination ? (projects.find( (project) => project.environmentId === incomingShare.destination?.environmentId && project.id === incomingShare.destination?.projectId, ) ?? null) : null; async function selectProject(project: EnvironmentProject): Promise { if (incomingShare?.destination && !reservedDestinationProject) { try { await releaseShareReservation(incomingShare.id, incomingShare.destination); } catch (error) { Alert.alert( "Could not change project", error instanceof Error ? error.message : "The shared content reservation could not be updated.", ); return; } } navigation.navigate("NewTaskSheet", { screen: "NewTaskDraft", params: { environmentId: project.environmentId, projectId: project.id, title: project.title, incomingShareId: incomingShare?.id, }, }); } function toggleGroup(groupKey: string): void { setExpandedGroupKeys((current) => { const next = new Set(current); if (next.has(groupKey)) { next.delete(groupKey); } else { next.add(groupKey); } return next; }); } useEffect(() => { const destination = incomingShare?.destination; if (!destination) { resumedDestinationKeyRef.current = null; return; } if (!isFocused) { // Returning from the reserved draft is a fresh resume attempt. Keeping // this latch set would leave every project row disabled with no route. resumedDestinationKeyRef.current = null; return; } const destinationKey = `${incomingShare.id}:${destination.environmentId}:${destination.projectId}`; if (resumedDestinationKeyRef.current === destinationKey) { return; } if (!reservedDestinationProject) { return; } resumedDestinationKeyRef.current = destinationKey; navigation.navigate("NewTaskSheet", { screen: "NewTaskDraft", params: { environmentId: reservedDestinationProject.environmentId, projectId: reservedDestinationProject.id, title: reservedDestinationProject.title, incomingShareId: incomingShare.id, }, }); }, [incomingShare, isFocused, navigation, reservedDestinationProject]); return ( {Platform.OS === "android" ? ( <> {/* Android renders its own in-screen header instead of the native bar. */} navigation.goBack() : undefined} actions={ catalogState.hasReadyEnvironment ? [ { accessibilityLabel: "Add project", icon: "plus", onPress: () => navigation.navigate("NewTaskSheet", { screen: "AddProject" }), }, ] : [] } /> ) : ( <> {layout.usesSplitView ? ( navigation.goBack()} separateBackground /> ) : null} {catalogState.hasReadyEnvironment ? ( navigation.navigate("NewTaskSheet", { screen: "AddProject" })} separateBackground /> ) : null} )} {projectScopes.length === 0 ? ( {projectEmptyState.loading ? : null} {projectEmptyState.title} {projectEmptyState.detail} {!catalogState.hasReadyEnvironment ? ( navigation.navigate("ConnectionsNew")} > Add environment ) : ( navigation.navigate("NewTaskSheet", { screen: "AddProject" })} > Add new project )} ) : ( {projectScopes.map((scope, scopeIndex) => { const hasMultipleProjects = scope.projects.length > 1; const expanded = expandedGroupKeys.has(scope.key); const singleProject = hasMultipleProjects ? null : scope.projects[0]; return ( 0 && "border-t border-border-subtle")} > { if (singleProject) { void selectProject(singleProject); } else { toggleGroup(scope.key); } }} className="flex-row items-center gap-3 bg-card px-4 py-3.5" > {scope.title} {hasMultipleProjects ? `${scope.projects.length} workspaces` : singleProject?.workspaceRoot} {hasMultipleProjects && expanded ? scope.projects.map((project) => ( void selectProject(project)} className="flex-row items-center gap-3 border-t border-border-subtle bg-card py-3 pr-4 pl-10" > {project.title} {project.workspaceRoot} )) : null} ); })} )} ); }