From 1a2a03f44b7ef6e3159a67a262c1586dbd3d3d5b Mon Sep 17 00:00:00 2001 From: Julius Marminge Date: Thu, 17 Sep 2026 20:20:12 -0700 Subject: [PATCH] refactor(mobile): share local environment lists (#12365) --- .../connection/ConnectionsRouteScreen.tsx | 51 ++++------------ .../connection/LocalEnvironmentList.tsx | 60 +++++++++++++++++++ .../SettingsEnvironmentsRouteScreen.tsx | 53 ++++------------ 3 files changed, 81 insertions(+), 83 deletions(-) create mode 100644 apps/mobile/src/features/connection/LocalEnvironmentList.tsx diff --git a/apps/mobile/src/features/connection/ConnectionsRouteScreen.tsx b/apps/mobile/src/features/connection/ConnectionsRouteScreen.tsx index 28bb5327043455896905f991c67bbfa28faae415..1e7f128ed43b929ee399953f78eacfc62b4d398c 100644 --- a/apps/mobile/src/features/connection/ConnectionsRouteScreen.tsx +++ b/apps/mobile/src/features/connection/ConnectionsRouteScreen.tsx @@ -1,17 +1,14 @@ import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView"; import { NativeHeaderToolbar } from "../../native/StackHeader"; import { useNavigation } from "@react-navigation/native"; -import { SymbolView } from "../../components/AppSymbol"; import type { EnvironmentId } from "@t3tools/contracts"; import { useCallback, useState } from "react"; import { Platform, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { AndroidScreenHeader } from "../../components/AndroidScreenHeader"; -import { AppText as Text } from "../../components/AppText"; -import { cn } from "../../lib/cn"; import { useRemoteConnections } from "../../state/use-remote-environment-registry"; -import { ConnectionEnvironmentRow } from "./ConnectionEnvironmentRow"; +import { LocalEnvironmentList } from "./LocalEnvironmentList"; import { GitHubRoutingSettings } from "./GitHubRoutingSettings"; export function ConnectionsRouteScreen() { @@ -24,7 +21,6 @@ export function ConnectionsRouteScreen() { } = useRemoteConnections(); const navigation = useNavigation(); const insets = useSafeAreaInsets(); - const hasEnvironments = connectedEnvironments.length > 0; const [expandedId, setExpandedId] = useState(null); const handleToggle = useCallback((environmentId: EnvironmentId) => { setExpandedId((prev) => (prev === environmentId ? null : environmentId)); @@ -63,42 +59,15 @@ export function ConnectionsRouteScreen() { paddingTop: 16, }} > - {hasEnvironments ? ( - - {connectedEnvironments.map((environment, index) => ( - - handleToggle(environment.environmentId)} - onReconnect={onReconnectEnvironment} - onRemove={onRemoveEnvironmentPress} - onSetEnabled={onSetEnvironmentEnabled} - onUpdate={onUpdateEnvironment} - /> - - ))} - - ) : ( - - - - - - No environments connected yet.{"\n"}Tap{" "} - + to add one. - - - )} + diff --git a/apps/mobile/src/features/connection/LocalEnvironmentList.tsx b/apps/mobile/src/features/connection/LocalEnvironmentList.tsx new file mode 100644 index 0000000000000000000000000000000000000000..dca29e092f64eb4c099e1c80426e5d3b5670292b --- /dev/null +++ b/apps/mobile/src/features/connection/LocalEnvironmentList.tsx @@ -0,0 +1,60 @@ +import type { EnvironmentId } from "@t3tools/contracts"; +import type { ComponentProps } from "react"; +import { View } from "react-native"; + +import { SymbolView } from "../../components/AppSymbol"; +import { AppText as Text } from "../../components/AppText"; +import { cn } from "../../lib/cn"; +import { ConnectionEnvironmentRow } from "./ConnectionEnvironmentRow"; + +type EnvironmentRowProps = ComponentProps; + +/** Shared list and empty state for environment management entry points. */ +export function LocalEnvironmentList({ + environments, + expandedId, + onToggle, + ...rowActions +}: Pick & { + readonly environments: ReadonlyArray; + readonly expandedId: EnvironmentId | null; + readonly onToggle: (environmentId: EnvironmentId) => void; +}) { + if (environments.length === 0) { + return ( + + + + + + No environments connected yet.{"\n"}Tap{" "} + + to add one. + + + ); + } + + return ( + + {environments.map((environment, index) => ( + + onToggle(environment.environmentId)} + {...rowActions} + /> + + ))} + + ); +} diff --git a/apps/mobile/src/features/settings/SettingsEnvironmentsRouteScreen.tsx b/apps/mobile/src/features/settings/SettingsEnvironmentsRouteScreen.tsx index 1ca43b5a73cfa3d920bb97da917c7423a32196f5..90c4d0ef73d934ce3a0fcaa7cee4afa23a9b6e16 100644 --- a/apps/mobile/src/features/settings/SettingsEnvironmentsRouteScreen.tsx +++ b/apps/mobile/src/features/settings/SettingsEnvironmentsRouteScreen.tsx @@ -1,19 +1,16 @@ import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView"; import { NativeHeaderToolbar } from "../../native/StackHeader"; import { useNavigation } from "@react-navigation/native"; -import { SymbolView } from "../../components/AppSymbol"; import type { EnvironmentId } from "@t3tools/contracts"; import { useCallback, useState } from "react"; -import { Platform, View } from "react-native"; +import { Platform } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; -import { AppText as Text } from "../../components/AppText"; import { SettingsScreen } from "./components/SettingsScreen"; import { CloudEnvironmentRows } from "../connection/CloudEnvironmentRows"; -import { ConnectionEnvironmentRow } from "../connection/ConnectionEnvironmentRow"; +import { LocalEnvironmentList } from "../connection/LocalEnvironmentList"; import { GitHubRoutingSettings } from "../connection/GitHubRoutingSettings"; import { splitEnvironmentSections } from "../connection/environmentSections"; -import { cn } from "../../lib/cn"; import { useUniwindTheme } from "../../lib/useUniwindTheme"; import { useRemoteConnections } from "../../state/use-remote-environment-registry"; import { @@ -45,7 +42,6 @@ export function SettingsEnvironmentsRouteScreen() { const connectedCloudEnvironments = SHOWCASE_ENABLED ? SHOWCASE_CONNECTED_CLOUD_ENVIRONMENTS : environmentSections.connectedCloudEnvironments; - const hasLocalEnvironments = localEnvironments.length > 0; const [expandedId, setExpandedId] = useState(null); const headerIconColor = useUniwindTheme()["--color-icon"]; @@ -118,42 +114,15 @@ export function SettingsEnvironmentsRouteScreen() { paddingBottom: Math.max(insets.bottom, 18) + 18, }} > - {hasLocalEnvironments ? ( - - {localEnvironments.map((environment, index) => ( - - handleToggle(environment.environmentId)} - onReconnect={onReconnectEnvironment} - onRemove={onRemoveEnvironmentPress} - onSetEnabled={onSetEnvironmentEnabled} - onUpdate={handleUpdateEnvironment} - /> - - ))} - - ) : ( - - - - - - No environments connected yet.{"\n"}Tap{" "} - + to add one. - - - )} + {/* Always mounted: already-connected relay environments must stay visible (and removable) even when cloud config is missing or the -- 2.54.0