import type { DevicePlatform, DeviceServiceState, EnvironmentId } from "@t3tools/contracts"; import { Check, CircleAlert } from "lucide-react"; import { useState } from "react"; import { Button } from "~/components/ui/button"; import { DialogClose } from "~/components/ui/dialog"; import { WizardHeader, WizardPanel, WizardSteps, WizardFooter } from "~/components/ui/wizard"; import { Spinner } from "~/components/ui/spinner"; import { Switch } from "~/components/ui/switch"; import { deviceEnvironment } from "~/state/device"; import { useAtomCommand } from "~/state/use-atom-command"; import { cn } from "~/lib/utils"; const platformName = (platform: DevicePlatform) => (platform === "ios" ? "iOS" : "Android"); export const deviceHubDescription = "Enable this environment to open simulators and emulators, whether they run here or on a remote device host."; export const agentDeviceDescription = "Allow new agent sessions in this environment to start and control local and remote devices, with required tools set up automatically."; export function platformSetupStatus(state: DeviceServiceState, platform: DevicePlatform) { const availability = state.hosts .flatMap((host) => host.platforms) .find((candidate) => candidate.platform === platform); if (!availability?.available) { return { ready: false, message: availability?.reason ?? `${platformName(platform)} support was not detected.`, }; } if ( state.hostStatus === "ready" && !state.devices.some((device) => device.platform === platform) ) { return { ready: false, message: platform === "ios" ? "Xcode is installed, but no iOS Simulator is available. Install a runtime in Xcode Settings → Components." : "The Android SDK is installed, but no virtual device exists. Create one in Android Studio → Device Manager.", }; } return { ready: true, message: platform === "ios" ? "Xcode and iOS Simulator are available." : "The Android SDK and Emulator are available.", }; } export function DeviceSetup(props: { readonly environmentId: EnvironmentId; readonly state: DeviceServiceState; readonly onComplete?: () => void; }) { const configure = useAtomCommand(deviceEnvironment.configure); const list = useAtomCommand(deviceEnvironment.list, { reportFailure: false }); const [pending, setPending] = useState<"hub" | "check" | "agent" | "complete" | null>(null); const [step, setStep] = useState(0); const enabled = props.state.hostStatus !== "disabled"; const busy = props.state.hostStatus === "installing" || props.state.hostStatus === "starting"; const update = async ( kind: NonNullable, input: { enabled?: boolean; agentAccessEnabled?: boolean; onboardingCompleted?: boolean }, ) => { setPending(kind); try { const result = await configure({ environmentId: props.environmentId, input }); if (kind === "complete" && result._tag === "Success") props.onComplete?.(); } finally { setPending(null); } }; return ( <> busy || pending !== null || requested > step} /> {step === 0 ? (

Enable the device hub

{deviceHubDescription}

void update("hub", { enabled: Boolean(checked), ...(checked ? {} : { agentAccessEnabled: false }), }) } />
) : null} {step === 1 ? (

Check simulator support

{ setPending("check"); void list({ environmentId: props.environmentId, input: {} }).finally(() => setPending(null), ); }} />
) : null} {step === 2 ? (

Allow agent control

{agentDeviceDescription}

void update("agent", { agentAccessEnabled: Boolean(checked) }) } />

Leave this off to keep manual device controls without giving agents access.

) : null} {props.state.hostStatus === "failed" && props.state.hostStatusDetail ? (

{props.state.hostStatusDetail}

) : null}
{step === 0 ? ( }>Cancel ) : ( )} {step < 2 ? ( ) : ( )} ); } export function DeviceHubSetupStatus({ state, pending, compact = false, }: { readonly state: DeviceServiceState; readonly pending: boolean; readonly compact?: boolean; }) { if (!pending && state.hostStatus !== "ready") return null; return (

{pending ? : } {pending ? state.hostStatus === "installing" ? compact ? "Installing…" : "Installing device hub…" : state.hostStatus === "starting" ? compact ? "Starting…" : "Starting device hub…" : compact ? "Updating…" : "Updating device hub…" : "Device hub is ready."}

); } function DevicePlatformSetup(props: { readonly state: DeviceServiceState; readonly checking: boolean; readonly disabled: boolean; readonly onCheck: () => void; }) { return (

You can use either platform. Fixing a missing platform does not block the other one.

); } export function AgentDeviceSetupStatus(props: { readonly state: DeviceServiceState; readonly pending: boolean; readonly compact?: boolean; }) { if (props.pending) { const label = props.state.hostStatus === "installing" ? props.compact ? "Installing…" : "Installing agent tools…" : props.state.hostStatus === "starting" ? props.compact ? "Starting…" : "Starting agent tools…" : props.compact ? "Updating…" : "Updating agent access…"; return (

{label}

); } if ( props.state.agentAccessEnabled && props.state.hostStatus === "ready" && props.state.hosts.some((host) => host.agentDeviceInstalled) ) { return (

Agent tools are ready.

); } return null; } export function PlatformStatus(props: { readonly platform: string; readonly status: { readonly ready: boolean; readonly message: string }; readonly compact?: boolean; }) { const Icon = props.status.ready ? Check : CircleAlert; return (

{props.platform}

{props.compact && props.status.ready ? "Ready" : props.status.message}

); }