import { useCallback, useEffect, useState, type ComponentType } from "react"; import { View, Text, Pressable } from "react-native"; import { StyleSheet, useUnistyles } from "react-native-unistyles"; import { useRouter } from "expo-router"; import { FolderOpen, Inbox, Plug, Smartphone } from "lucide-react-native"; import { PaseoLogo } from "@/components/icons/paseo-logo"; import { CommunityLinks } from "@/components/community-links"; import { MenuHeader } from "@/components/headers/menu-header"; import { useOpenProjectPicker } from "@/hooks/use-open-project-picker"; import { usePanelStore } from "@/stores/panel-store"; import { useIsCompactFormFactor, HEADER_INNER_HEIGHT, HEADER_INNER_HEIGHT_MOBILE, HEADER_TOP_PADDING_MOBILE, } from "@/constants/layout"; import { TitlebarDragRegion } from "@/components/desktop/titlebar-drag-region"; import { useIsLocalDaemon } from "@/hooks/use-is-local-daemon"; import { PairDeviceModal } from "@/desktop/components/pair-device-modal"; import { buildHostAgentDetailRoute, buildSettingsHostSectionRoute } from "@/utils/host-routes"; import { ImportSessionSheet } from "@/components/import-session-sheet"; import { useHostRuntimeClient } from "@/runtime/host-runtime"; import { useOpenProject } from "@/hooks/use-open-project"; import type { Href } from "expo-router"; export function OpenProjectScreen({ serverId }: { serverId: string }) { const router = useRouter(); const openDesktopAgentList = usePanelStore((s) => s.openDesktopAgentList); const openProjectPicker = useOpenProjectPicker(serverId); const isLocalDaemon = useIsLocalDaemon(serverId); const client = useHostRuntimeClient(serverId); const openProject = useOpenProject(serverId); const [isPairDeviceOpen, setIsPairDeviceOpen] = useState(false); const [isImportSheetOpen, setIsImportSheetOpen] = useState(false); const isCompactLayout = useIsCompactFormFactor(); useEffect(() => { if (!isCompactLayout) { openDesktopAgentList(); } }, [isCompactLayout, openDesktopAgentList]); const handleOpenPicker = useCallback(() => { void openProjectPicker(); }, [openProjectPicker]); const handleOpenPairDevice = useCallback(() => setIsPairDeviceOpen(true), []); const handleClosePairDevice = useCallback(() => setIsPairDeviceOpen(false), []); const handleOpenImportSession = useCallback(() => setIsImportSheetOpen(true), []); const handleCloseImportSession = useCallback(() => setIsImportSheetOpen(false), []); const handleImported = useCallback( (agent: { id: string; cwd: string }) => { void (async () => { await openProject(agent.cwd); router.push(buildHostAgentDetailRoute(serverId, agent.id) as Href); })(); }, [openProject, router, serverId], ); const handleOpenProviders = useCallback(() => { router.push(buildSettingsHostSectionRoute(serverId, "providers")); }, [router, serverId]); return ( {isLocalDaemon ? ( ) : null} ); } interface HomeTileProps { icon: ComponentType<{ size: number; color: string }>; title: string; description: string; onPress: () => void; testID?: string; accent?: boolean; } function HomeTile({ icon: Icon, title, description, onPress, testID, accent }: HomeTileProps) { // useUnistyles is acceptable here: leaf component, off the hot path (home screen renders once). const { theme } = useUnistyles(); const [hovered, setHovered] = useState(false); const handleHoverIn = useCallback(() => setHovered(true), []); const handleHoverOut = useCallback(() => setHovered(false), []); const iconColor = accent ? theme.colors.accent : theme.colors.foregroundMuted; const pressableStyle = useCallback( ({ pressed }: { pressed: boolean }) => [ styles.tile, hovered && styles.tileHovered, pressed && styles.tilePressed, ], [hovered], ); return ( {title} {description} ); } const styles = StyleSheet.create((theme) => ({ container: { flex: 1, backgroundColor: theme.colors.surface0, userSelect: "none", }, content: { position: "relative", flex: 1, justifyContent: { xs: "flex-start", md: "center" }, alignItems: "center", gap: 0, padding: theme.spacing[6], paddingTop: { xs: theme.spacing[12], md: theme.spacing[6] }, paddingBottom: { xs: HEADER_INNER_HEIGHT_MOBILE + HEADER_TOP_PADDING_MOBILE + theme.spacing[6], md: HEADER_INNER_HEIGHT + theme.spacing[6], }, }, logo: { marginBottom: theme.spacing[8], }, tiles: { marginTop: { xs: theme.spacing[6], md: theme.spacing[12] }, width: "100%", maxWidth: 452, flexDirection: "row", flexWrap: "wrap", justifyContent: "flex-start", gap: theme.spacing[3], }, tile: { width: { xs: "100%", md: 220 }, minHeight: { xs: 0, md: 132 }, padding: theme.spacing[4], backgroundColor: theme.colors.surface1, borderWidth: 1, borderColor: theme.colors.border, borderRadius: theme.borderRadius.xl, gap: theme.spacing[3], }, tileHovered: { backgroundColor: theme.colors.surface2, borderColor: theme.colors.borderAccent, }, tilePressed: { opacity: 0.85, }, tileText: { gap: theme.spacing[1], }, tileTitle: { color: theme.colors.foreground, fontSize: theme.fontSize.base, fontWeight: theme.fontWeight.normal, }, tileDescription: { color: theme.colors.foregroundMuted, fontSize: theme.fontSize.sm, lineHeight: 18, }, communityRow: { position: "absolute", left: 0, right: 0, bottom: { xs: HEADER_INNER_HEIGHT_MOBILE + HEADER_TOP_PADDING_MOBILE + theme.spacing[2], md: HEADER_INNER_HEIGHT + theme.spacing[2], }, flexDirection: "row", justifyContent: "center", alignItems: "center", gap: 0, }, }));