Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions apps/mobile/src/Stack.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -96,6 +96,7 @@ import { SettingsAuthRouteScreen } from "./features/settings/SettingsAuthRouteSc
import { SettingsEnvironmentDetailRouteScreen } from "./features/settings/SettingsEnvironmentDetailRouteScreen";
import { SettingsEnvironmentsRouteScreen } from "./features/settings/SettingsEnvironmentsRouteScreen";
import { SettingsFollowUpRouteScreen } from "./features/settings/SettingsFollowUpRouteScreen";
import { SettingsCloudRouteScreen } from "./features/settings/SettingsCloudRouteScreen";
import { SettingsLanguageRouteScreen } from "./features/settings/SettingsLanguageRouteScreen";
import {
SettingsEnvironmentMaintenanceRouteScreen,
Expand Down Expand Up @@ -290,6 +291,11 @@ const SETTINGS_SCREENS = {
linking: "settings/follow-ups",
options: settingsPage("Chat"),
}),
SettingsCloud: createNativeStackScreen({
screen: SettingsCloudRouteScreen,
linking: "settings/cloud",
options: settingsPage("Cloud"),
}),
SettingsScheduledTasks: createNativeStackScreen({
screen: SettingsScheduledTasksRouteScreen,
linking: "settings/scheduled-tasks",
Expand Down
22 changes: 22 additions & 0 deletions apps/mobile/src/components/BoatIcon.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
import Svg, { Path } from "react-native-svg";
import { withUniwind } from "uniwind";

const ThemedPath = withUniwind(Path);

/** The boat.dev mark. */
export function BoatIcon(props: { readonly size: number; readonly colorClassName: string }) {
return (
<Svg
accessibilityLabel="Boat"
width={props.size}
height={(props.size * 463) / 498}
viewBox="0 0 498 463"
>
<ThemedPath
d="M490.123 361.461C494.464 361.461 497.985 364.978 497.985 369.323V403.916C497.985 408.256 494.464 411.777 490.123 411.777H473.317C468.941 411.777 465.396 415.327 465.396 419.699V454.232C465.396 458.573 461.875 462.094 457.534 462.094H84.3579C83.1446 462.094 81.9773 461.511 81.2263 460.558C52.4607 424.054 51.9691 423.595 7.86426 411.777C3.67043 410.653 0 408.256 0 403.916V369.323C0 364.978 3.52235 361.461 7.86426 361.461H490.123ZM234.167 4.81756C238.589 -2.56834 249.679 -1.18229 252.148 7.06463L321.938 240.22L244.366 338.334H51.9617C44.2696 338.334 39.5154 329.947 43.4665 323.347L234.167 4.81756ZM453.056 132.26C451.192 124.166 440.791 121.826 435.64 128.341L282.295 322.292C277.163 328.784 281.786 338.334 290.063 338.334H488.08C494.445 338.334 499.157 332.414 497.728 326.21L453.056 132.26Z"
colorClassName={props.colorClassName}
fill="currentColor"
/>
</Svg>
);
}
19 changes: 19 additions & 0 deletions apps/mobile/src/connection/onboarding.ts
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,25 @@ export const connectPairingUrl = createRuntimeCommand(connectionAtomRuntime, {
),
});

/** Pairs with a server from its address and a one-time code, such as a Boat cloud VM. */
export const connectPairingCode = createRuntimeCommand(connectionAtomRuntime, {
label: "mobile:connection:connect-pairing-code",
scheduler: onboardingScheduler,
concurrency: {
mode: "singleFlight",
key: (input: { readonly host: string; readonly pairingCode: string }) =>
JSON.stringify([input.host, input.pairingCode]),
},
execute: (input: {
readonly host: string;
readonly pairingCode: string;
readonly expectedEnvironmentId: EnvironmentId;
}) =>
ConnectionOnboarding.ConnectionOnboarding.pipe(
Effect.flatMap((onboarding) => onboarding.registerPairing(input)),
),
});

export const updateBearerConnection = createRuntimeCommand(connectionAtomRuntime, {
label: "mobile:connection:update-bearer",
scheduler: onboardingScheduler,
Expand Down
53 changes: 53 additions & 0 deletions apps/mobile/src/features/boatCloud/BoatCloudSteps.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
import type { BoatCloudStartStep, BoatCloudStepProgress } from "@t3tools/contracts";
import { View } from "react-native";

import { SymbolView, type AppSymbolName } from "../../components/ui/AppSymbol";
import { ListRowLayout } from "../../components/ui/ListRowLayout";
import { cn } from "../../lib/cn";

export type StepState = BoatCloudStepProgress["state"];

// Static glyphs: a VM takes seconds to minutes, and a spinner would repaint the whole time.
const STEP_ICONS: Record<StepState, { readonly name: AppSymbolName; readonly tint: string }> = {
done: { name: "checkmark.circle.fill", tint: "accent-success-foreground" },
failed: { name: "xmark.circle.fill", tint: "accent-danger-foreground" },
active: { name: "circle.inset.filled", tint: "accent-primary-text" },
pending: { name: "circle.dashed", tint: "accent-icon-muted" },
};

export function StepGlyph(props: { readonly state: StepState }) {
const icon = STEP_ICONS[props.state];
return <SymbolView name={icon.name} size={20} tintColorClassName={icon.tint} />;
}

const START_STEP_LABELS: Record<BoatCloudStartStep, string> = {
machine: "Find a VM",
server: "Start T3 Code",
repository: "Clone or update the repository",
connect: "Connect",
project: "Open the project",
thread: "Move the draft",
};

/** Each step of starting on Boat cloud, as the holder and this app report it. */
export function BoatCloudStartSteps(props: {
readonly steps: ReadonlyArray<BoatCloudStepProgress>;
}) {
return (
<View>
{props.steps.map((entry, index) => (
<View
key={entry.step}
className={cn(index > 0 && "border-t border-border-subtle")}
accessibilityLabel={`${START_STEP_LABELS[entry.step]}, ${entry.state}`}
>
<ListRowLayout
leading={<StepGlyph state={entry.state} />}
label={START_STEP_LABELS[entry.step]}
description={entry.detail ?? undefined}
/>
</View>
))}
</View>
);
}
Loading
Loading