import { useAuth, useUser } from "@clerk/expo";import * as Notifications from "expo-notifications";import { Link, Stack, useRouter } from "expo-router";import { SymbolView } from "expo-symbols";import * as Effect from "effect/Effect";import { useCallback, useEffect, useMemo, useState } from "react";import type { ComponentProps, ReactNode } from "react";import { Alert, Linking, Pressable, ScrollView, Switch, View } from "react-native";import { useSafeAreaInsets } from "react-native-safe-area-context"; import { isAtomCommandInterrupted, reportAtomCommandResult, settleAsyncResult, settlePromise, squashAtomCommandFailure,} from "@t3tools/client-runtime/state/runtime";import { AppText as Text } from "../../components/AppText";import { setLiveActivityUpdatesEnabled } from "../../features/agent-awareness/liveActivityPreferences";import { requestAgentNotificationPermission } from "../../features/agent-awareness/notificationPermissions";import { refreshAgentAwarenessRegistration } from "../../features/agent-awareness/remoteRegistration";import { refreshManagedRelayEnvironments } from "../../features/cloud/managedRelayState";import { useClerkSettingsSheetDetent } from "../../features/cloud/ClerkSettingsSheetDetent";import { hasCloudPublicConfig, resolveRelayClerkTokenOptions,} from "../../features/cloud/publicConfig";import { runtime } from "../../lib/runtime";import { loadPreferences } from "../../lib/storage";import { useThemeColor } from "../../lib/useThemeColor";import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry"; type NotificationStatus = "checking" | "enabled" | "disabled" | "unsupported";type LiveActivityStatus = "checking" | "enabled" | "disabled" | "signed-out" | "linking"; export default function SettingsRouteScreen() { return hasCloudPublicConfig() ? <ConfiguredSettingsRouteScreen /> : <LocalSettingsRouteScreen />;} function LocalSettingsRouteScreen() { const insets = useSafeAreaInsets(); const { savedConnectionsById } = useSavedRemoteConnections(); const environmentCount = Object.keys(savedConnectionsById).length; return ( <View collapsable={false} className="flex-1 bg-sheet"> <Stack.Screen options={{ title: "Settings" }} /> <ScrollView contentInsetAdjustmentBehavior="automatic" showsVerticalScrollIndicator={false} style={{ flex: 1 }} contentContainerStyle={{ gap: 24, paddingBottom: Math.max(insets.bottom, 18) + 18, paddingHorizontal: 20, paddingTop: 16, }} > <SettingsSection title="Configuration"> <SettingsRow icon="desktopcomputer" label="Environments" value={`${environmentCount}`} href="/settings/environments" /> </SettingsSection> <ArchivedThreadsSettingsSection /> <AppSettingsSection /> </ScrollView> </View> );} function ConfiguredSettingsRouteScreen() { const insets = useSafeAreaInsets(); const { push } = useRouter(); const { expand: expandClerkSheet } = useClerkSettingsSheetDetent(); const { getToken, isLoaded, isSignedIn } = useAuth({ treatPendingAsSignedOut: false }); const { user } = useUser(); const { savedConnectionsById } = useSavedRemoteConnections(); const [notificationStatus, setNotificationStatus] = useState<NotificationStatus>("checking"); const [liveActivityStatus, setLiveActivityStatus] = useState<LiveActivityStatus>("checking"); const connections = useMemo(() => Object.values(savedConnectionsById), [savedConnectionsById]); const environmentCount = connections.length; const accountLabel = useMemo(() => { if (!isLoaded) return "Checking"; if (!isSignedIn) return "Request access"; return user?.primaryEmailAddress?.emailAddress ?? "Signed in"; }, [isLoaded, isSignedIn, user?.primaryEmailAddress?.emailAddress]); const refreshNotifications = useCallback(async () => { if (process.env.EXPO_OS !== "ios") { setNotificationStatus("unsupported"); return; } const result = await settlePromise(() => Notifications.getPermissionsAsync()); if (result._tag === "Failure") { reportAtomCommandResult(result, { label: "notification permission refresh" }); setNotificationStatus("disabled"); return; } setNotificationStatus(result.value.granted ? "enabled" : "disabled"); }, []); useEffect(() => { void refreshNotifications(); }, [refreshNotifications]); useEffect(() => { if (!isLoaded) { setLiveActivityStatus("checking"); return; } if (!isSignedIn) { setLiveActivityStatus("signed-out"); return; } void (async () => { const result = await settlePromise(() => loadPreferences()); if (result._tag === "Failure") { reportAtomCommandResult(result, { label: "live activity preference load" }); setLiveActivityStatus("enabled"); return; } setLiveActivityStatus(result.value.liveActivitiesEnabled === false ? "disabled" : "enabled"); })(); }, [isLoaded, isSignedIn]); const requestNotifications = useCallback(async () => { const result = await settleAsyncResult(() => runtime.runPromiseExit( requestAgentNotificationPermission.pipe( Effect.tap((permission) => permission.type === "granted" ? refreshAgentAwarenessRegistration() : Effect.void, ), ), ), ); if (result._tag === "Failure") { if (!isAtomCommandInterrupted(result)) { const error = squashAtomCommandFailure(result); Alert.alert( "Notifications unavailable", error instanceof Error ? error.message : "Could not request notification permission.", ); } return; } if (result.value.type === "granted") { setNotificationStatus("enabled"); Alert.alert( "Notifications enabled", "Live Activity notifications are enabled for this device.", ); return; } if (result.value.type === "unsupported") { setNotificationStatus("unsupported"); Alert.alert( "Notifications unavailable", "Live Activity notifications are only available on iOS.", ); return; } setNotificationStatus("disabled"); if (result.value.canAskAgain) { Alert.alert("Notifications disabled", "Notifications were not enabled."); return; } Alert.alert( "Notifications disabled", "Notifications were denied for this app. Open Settings to enable them.", [ { text: "Cancel", style: "cancel" }, { text: "Open Settings", onPress: () => void Linking.openSettings() }, ], ); }, []); const promptSignIn = useCallback(() => { Alert.alert( "Request T3 Cloud access", "Live Activity updates require approved T3 Cloud access so relay can deliver updates to this device.", [ { text: "Cancel", style: "cancel" }, { text: "Continue", onPress: () => push("/settings/waitlist") }, ], ); }, [push]); const linkEnvironments = useCallback(async () => { if (!isSignedIn) { promptSignIn(); return; } setLiveActivityStatus("linking"); const tokenResult = await settlePromise(() => getToken(resolveRelayClerkTokenOptions())); if (tokenResult._tag === "Failure") { setLiveActivityStatus("disabled"); const error = squashAtomCommandFailure(tokenResult); Alert.alert( "Live Activities unavailable", error instanceof Error ? error.message : "Could not enable Live Activity updates.", ); return; } if (!tokenResult.value) { promptSignIn(); setLiveActivityStatus("signed-out"); return; } const updateResult = await settleAsyncResult(() => runtime.runPromiseExit( setLiveActivityUpdatesEnabled({ enabled: true, clerkToken: tokenResult.value, connections, }), ), ); if (updateResult._tag === "Failure") { setLiveActivityStatus("disabled"); if (!isAtomCommandInterrupted(updateResult)) { const error = squashAtomCommandFailure(updateResult); Alert.alert( "Live Activities unavailable", error instanceof Error ? error.message : "Could not enable Live Activity updates.", ); } return; } refreshManagedRelayEnvironments(); setLiveActivityStatus("enabled"); Alert.alert( "Live Activities enabled", environmentCount > 0 ? `${environmentCount} environment${environmentCount === 1 ? "" : "s"} linked for Live Activity updates.` : "Live Activity updates are enabled. Add an environment to start receiving updates.", ); }, [connections, environmentCount, getToken, isSignedIn, promptSignIn]); const handleDeviceNotificationsChange = useCallback( (enabled: boolean) => { if (enabled) { void requestNotifications(); return; } Alert.alert( "Disable notifications", "Notification permission is controlled by iOS. Open Settings to disable notifications for T3 Code.", [ { text: "Cancel", style: "cancel" }, { text: "Open Settings", onPress: () => void Linking.openSettings() }, ], ); }, [requestNotifications], ); const handleLiveActivitiesChange = useCallback( (enabled: boolean) => { if (!enabled) { setLiveActivityStatus("disabled"); void (async () => { let token: string | null = null; if (isSignedIn) { const tokenResult = await settlePromise(() => getToken(resolveRelayClerkTokenOptions()), ); if (tokenResult._tag === "Failure") { reportAtomCommandResult(tokenResult, { label: "live activity disable token lookup", }); return; } token = tokenResult.value; } const updateResult = await settleAsyncResult(() => runtime.runPromiseExit( setLiveActivityUpdatesEnabled({ enabled: false, clerkToken: token, connections, }), ), ); if (updateResult._tag === "Failure") { reportAtomCommandResult(updateResult, { label: "live activity disable", }); return; } refreshManagedRelayEnvironments(); })(); return; } if (!isSignedIn) { promptSignIn(); return; } void linkEnvironments(); }, [connections, getToken, isSignedIn, linkEnvironments, promptSignIn], ); const openAccount = useCallback(() => { if (!isLoaded) return; if (!isSignedIn) { push("/settings/waitlist"); return; } expandClerkSheet(); push("/settings/auth"); }, [expandClerkSheet, isLoaded, isSignedIn, push]); return ( <View collapsable={false} className="flex-1 bg-sheet"> <Stack.Screen options={{ title: "Settings" }} /> <ScrollView contentInsetAdjustmentBehavior="automatic" showsVerticalScrollIndicator={false} style={{ flex: 1 }} contentContainerStyle={{ gap: 24, paddingBottom: Math.max(insets.bottom, 18) + 18, paddingHorizontal: 20, paddingTop: 16, }} > <View className="gap-3"> <SettingsSection title="Account"> <SettingsRow icon="person.crop.circle" label="T3 Account" value={accountLabel} onPress={openAccount} /> </SettingsSection> <Text className="px-2 text-sm leading-[18px] text-foreground-muted"> T3 Code works locally without signing in. Cloud features are optional. </Text> </View> <SettingsSection title="Configuration"> <SettingsRow icon="desktopcomputer" label="Environments" value={`${environmentCount}`} href="/settings/environments" /> <SettingsSwitchRow icon="bell.badge" label="Device Notifications" disabled={notificationStatus === "checking" || notificationStatus === "unsupported"} value={notificationStatus === "enabled"} onValueChange={handleDeviceNotificationsChange} /> <SettingsSwitchRow disabled={ !isLoaded || liveActivityStatus === "checking" || liveActivityStatus === "linking" } icon="bolt.circle" label="Live Activity Updates" value={liveActivityStatus === "enabled" || liveActivityStatus === "linking"} onValueChange={handleLiveActivitiesChange} /> </SettingsSection> <ArchivedThreadsSettingsSection /> <AppSettingsSection /> </ScrollView> </View> );} type SymbolName = ComponentProps<typeof SymbolView>["name"]; function SettingsSection(props: { readonly title: string; readonly children: ReactNode }) { return ( <View className="gap-2"> <Text className="px-2 text-sm font-t3-medium text-foreground-muted">{props.title}</Text> <View className="overflow-hidden rounded-[28px] bg-card" style={{ borderCurve: "continuous" }} > {props.children} </View> </View> );} function AppSettingsSection() { const icon = useThemeColor("--color-icon"); return ( <SettingsSection title="App"> <View className="flex-row items-center gap-4 p-4"> <SymbolView name="info.circle" size={22} tintColor={icon} type="monochrome" weight="regular" /> <Text className="flex-1 text-lg text-foreground">Version</Text> <Text className="text-lg text-foreground-muted">Alpha</Text> </View> </SettingsSection> );} function ArchivedThreadsSettingsSection() { return ( <SettingsSection title="Threads"> <SettingsRow icon="archivebox" label="Archived Threads" href="/settings/archive" /> </SettingsSection> );} function SettingsRow(props: { readonly disabled?: boolean; readonly icon: SymbolName; readonly label: string; readonly value?: string; readonly href?: "/settings/archive" | "/settings/environments"; readonly onPress?: () => void;}) { const icon = useThemeColor("--color-icon"); const chevron = useThemeColor("--color-chevron"); const content = ( <View className="flex-row items-center gap-4 p-4" style={{ opacity: props.disabled ? 0.45 : 1 }} > <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" /> <Text className="shrink-0 text-lg text-foreground" numberOfLines={1}> {props.label} </Text> <View className="min-w-0 flex-1 items-end"> {props.value ? ( <Text className="max-w-[180px] text-right text-base text-foreground-muted" ellipsizeMode="middle" numberOfLines={1} > {props.value} </Text> ) : null} </View> <SymbolView name="chevron.right" size={16} tintColor={chevron} type="monochrome" weight="semibold" /> </View> ); if (props.href) { return ( <Link href={props.href} asChild> <Pressable accessibilityLabel={props.label} accessibilityRole="button"> {content} </Pressable> </Link> ); } return ( <Pressable accessibilityRole="button" disabled={props.disabled} onPress={props.onPress}> {content} </Pressable> );} function SettingsSwitchRow(props: { readonly disabled?: boolean; readonly icon: SymbolName; readonly label: string; readonly value: boolean; readonly onValueChange: (value: boolean) => void;}) { const icon = useThemeColor("--color-icon"); const activeTrack = String(useThemeColor("--color-switch-active")); const track = String(useThemeColor("--color-secondary-border")); return ( <ViewLines 1–500 of 515. Load more
import { useAuth, useUser } from "@clerk/expo"; import * as Notifications from "expo-notifications"; import { Link, Stack, useRouter } from "expo-router"; import { SymbolView } from "expo-symbols"; import * as Effect from "effect/Effect"; import { useCallback, useEffect, useMemo, useState } from "react"; import type { ComponentProps, ReactNode } from "react"; import { Alert, Linking, Pressable, ScrollView, Switch, View } from "react-native"; import { useSafeAreaInsets } from "react-native-safe-area-context"; import { isAtomCommandInterrupted, reportAtomCommandResult, settleAsyncResult, settlePromise, squashAtomCommandFailure, } from "@t3tools/client-runtime/state/runtime"; import { AppText as Text } from "../../components/AppText"; import { setLiveActivityUpdatesEnabled } from "../../features/agent-awareness/liveActivityPreferences"; import { requestAgentNotificationPermission } from "../../features/agent-awareness/notificationPermissions"; import { refreshAgentAwarenessRegistration } from "../../features/agent-awareness/remoteRegistration"; import { refreshManagedRelayEnvironments } from "../../features/cloud/managedRelayState"; import { useClerkSettingsSheetDetent } from "../../features/cloud/ClerkSettingsSheetDetent"; import { hasCloudPublicConfig, resolveRelayClerkTokenOptions, } from "../../features/cloud/publicConfig"; import { runtime } from "../../lib/runtime"; import { loadPreferences } from "../../lib/storage"; import { useThemeColor } from "../../lib/useThemeColor"; import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry"; type NotificationStatus = "checking" | "enabled" | "disabled" | "unsupported"; type LiveActivityStatus = "checking" | "enabled" | "disabled" | "signed-out" | "linking"; export default function SettingsRouteScreen() { return hasCloudPublicConfig() ? <ConfiguredSettingsRouteScreen /> : <LocalSettingsRouteScreen />; } function LocalSettingsRouteScreen() { const insets = useSafeAreaInsets(); const { savedConnectionsById } = useSavedRemoteConnections(); const environmentCount = Object.keys(savedConnectionsById).length; return ( <View collapsable={false} className="flex-1 bg-sheet"> <Stack.Screen options={{ title: "Settings" }} /> <ScrollView contentInsetAdjustmentBehavior="automatic" showsVerticalScrollIndicator={false} style={{ flex: 1 }} contentContainerStyle={{ gap: 24, paddingBottom: Math.max(insets.bottom, 18) + 18, paddingHorizontal: 20, paddingTop: 16, }} > <SettingsSection title="Configuration"> <SettingsRow icon="desktopcomputer" label="Environments" value={`${environmentCount}`} href="/settings/environments" /> </SettingsSection> <ArchivedThreadsSettingsSection /> <AppSettingsSection /> </ScrollView> </View> ); } function ConfiguredSettingsRouteScreen() { const insets = useSafeAreaInsets(); const { push } = useRouter(); const { expand: expandClerkSheet } = useClerkSettingsSheetDetent(); const { getToken, isLoaded, isSignedIn } = useAuth({ treatPendingAsSignedOut: false }); const { user } = useUser(); const { savedConnectionsById } = useSavedRemoteConnections(); const [notificationStatus, setNotificationStatus] = useState<NotificationStatus>("checking"); const [liveActivityStatus, setLiveActivityStatus] = useState<LiveActivityStatus>("checking"); const connections = useMemo(() => Object.values(savedConnectionsById), [savedConnectionsById]); const environmentCount = connections.length; const accountLabel = useMemo(() => { if (!isLoaded) return "Checking"; if (!isSignedIn) return "Request access"; return user?.primaryEmailAddress?.emailAddress ?? "Signed in"; }, [isLoaded, isSignedIn, user?.primaryEmailAddress?.emailAddress]); const refreshNotifications = useCallback(async () => { if (process.env.EXPO_OS !== "ios") { setNotificationStatus("unsupported"); return; } const result = await settlePromise(() => Notifications.getPermissionsAsync()); if (result._tag === "Failure") { reportAtomCommandResult(result, { label: "notification permission refresh" }); setNotificationStatus("disabled"); return; } setNotificationStatus(result.value.granted ? "enabled" : "disabled"); }, []); useEffect(() => { void refreshNotifications(); }, [refreshNotifications]); useEffect(() => { if (!isLoaded) { setLiveActivityStatus("checking"); return; } if (!isSignedIn) { setLiveActivityStatus("signed-out"); return; } void (async () => { const result = await settlePromise(() => loadPreferences()); if (result._tag === "Failure") { reportAtomCommandResult(result, { label: "live activity preference load" }); setLiveActivityStatus("enabled"); return; } setLiveActivityStatus(result.value.liveActivitiesEnabled === false ? "disabled" : "enabled"); })(); }, [isLoaded, isSignedIn]); const requestNotifications = useCallback(async () => { const result = await settleAsyncResult(() => runtime.runPromiseExit( requestAgentNotificationPermission.pipe( Effect.tap((permission) => permission.type === "granted" ? refreshAgentAwarenessRegistration() : Effect.void, ), ), ), ); if (result._tag === "Failure") { if (!isAtomCommandInterrupted(result)) { const error = squashAtomCommandFailure(result); Alert.alert( "Notifications unavailable", error instanceof Error ? error.message : "Could not request notification permission.", ); } return; } if (result.value.type === "granted") { setNotificationStatus("enabled"); Alert.alert( "Notifications enabled", "Live Activity notifications are enabled for this device.", ); return; } if (result.value.type === "unsupported") { setNotificationStatus("unsupported"); Alert.alert( "Notifications unavailable", "Live Activity notifications are only available on iOS.", ); return; } setNotificationStatus("disabled"); if (result.value.canAskAgain) { Alert.alert("Notifications disabled", "Notifications were not enabled."); return; } Alert.alert( "Notifications disabled", "Notifications were denied for this app. Open Settings to enable them.", [ { text: "Cancel", style: "cancel" }, { text: "Open Settings", onPress: () => void Linking.openSettings() }, ], ); }, []); const promptSignIn = useCallback(() => { Alert.alert( "Request T3 Cloud access", "Live Activity updates require approved T3 Cloud access so relay can deliver updates to this device.", [ { text: "Cancel", style: "cancel" }, { text: "Continue", onPress: () => push("/settings/waitlist") }, ], ); }, [push]); const linkEnvironments = useCallback(async () => { if (!isSignedIn) { promptSignIn(); return; } setLiveActivityStatus("linking"); const tokenResult = await settlePromise(() => getToken(resolveRelayClerkTokenOptions())); if (tokenResult._tag === "Failure") { setLiveActivityStatus("disabled"); const error = squashAtomCommandFailure(tokenResult); Alert.alert( "Live Activities unavailable", error instanceof Error ? error.message : "Could not enable Live Activity updates.", ); return; } if (!tokenResult.value) { promptSignIn(); setLiveActivityStatus("signed-out"); return; } const updateResult = await settleAsyncResult(() => runtime.runPromiseExit( setLiveActivityUpdatesEnabled({ enabled: true, clerkToken: tokenResult.value, connections, }), ), ); if (updateResult._tag === "Failure") { setLiveActivityStatus("disabled"); if (!isAtomCommandInterrupted(updateResult)) { const error = squashAtomCommandFailure(updateResult); Alert.alert( "Live Activities unavailable", error instanceof Error ? error.message : "Could not enable Live Activity updates.", ); } return; } refreshManagedRelayEnvironments(); setLiveActivityStatus("enabled"); Alert.alert( "Live Activities enabled", environmentCount > 0 ? `${environmentCount} environment${environmentCount === 1 ? "" : "s"} linked for Live Activity updates.` : "Live Activity updates are enabled. Add an environment to start receiving updates.", ); }, [connections, environmentCount, getToken, isSignedIn, promptSignIn]); const handleDeviceNotificationsChange = useCallback( (enabled: boolean) => { if (enabled) { void requestNotifications(); return; } Alert.alert( "Disable notifications", "Notification permission is controlled by iOS. Open Settings to disable notifications for T3 Code.", [ { text: "Cancel", style: "cancel" }, { text: "Open Settings", onPress: () => void Linking.openSettings() }, ], ); }, [requestNotifications], ); const handleLiveActivitiesChange = useCallback( (enabled: boolean) => { if (!enabled) { setLiveActivityStatus("disabled"); void (async () => { let token: string | null = null; if (isSignedIn) { const tokenResult = await settlePromise(() => getToken(resolveRelayClerkTokenOptions()), ); if (tokenResult._tag === "Failure") { reportAtomCommandResult(tokenResult, { label: "live activity disable token lookup", }); return; } token = tokenResult.value; } const updateResult = await settleAsyncResult(() => runtime.runPromiseExit( setLiveActivityUpdatesEnabled({ enabled: false, clerkToken: token, connections, }), ), ); if (updateResult._tag === "Failure") { reportAtomCommandResult(updateResult, { label: "live activity disable", }); return; } refreshManagedRelayEnvironments(); })(); return; } if (!isSignedIn) { promptSignIn(); return; } void linkEnvironments(); }, [connections, getToken, isSignedIn, linkEnvironments, promptSignIn], ); const openAccount = useCallback(() => { if (!isLoaded) return; if (!isSignedIn) { push("/settings/waitlist"); return; } expandClerkSheet(); push("/settings/auth"); }, [expandClerkSheet, isLoaded, isSignedIn, push]); return ( <View collapsable={false} className="flex-1 bg-sheet"> <Stack.Screen options={{ title: "Settings" }} /> <ScrollView contentInsetAdjustmentBehavior="automatic" showsVerticalScrollIndicator={false} style={{ flex: 1 }} contentContainerStyle={{ gap: 24, paddingBottom: Math.max(insets.bottom, 18) + 18, paddingHorizontal: 20, paddingTop: 16, }} > <View className="gap-3"> <SettingsSection title="Account"> <SettingsRow icon="person.crop.circle" label="T3 Account" value={accountLabel} onPress={openAccount} /> </SettingsSection> <Text className="px-2 text-sm leading-[18px] text-foreground-muted"> T3 Code works locally without signing in. Cloud features are optional. </Text> </View> <SettingsSection title="Configuration"> <SettingsRow icon="desktopcomputer" label="Environments" value={`${environmentCount}`} href="/settings/environments" /> <SettingsSwitchRow icon="bell.badge" label="Device Notifications" disabled={notificationStatus === "checking" || notificationStatus === "unsupported"} value={notificationStatus === "enabled"} onValueChange={handleDeviceNotificationsChange} /> <SettingsSwitchRow disabled={ !isLoaded || liveActivityStatus === "checking" || liveActivityStatus === "linking" } icon="bolt.circle" label="Live Activity Updates" value={liveActivityStatus === "enabled" || liveActivityStatus === "linking"} onValueChange={handleLiveActivitiesChange} /> </SettingsSection> <ArchivedThreadsSettingsSection /> <AppSettingsSection /> </ScrollView> </View> ); } type SymbolName = ComponentProps<typeof SymbolView>["name"]; function SettingsSection(props: { readonly title: string; readonly children: ReactNode }) { return ( <View className="gap-2"> <Text className="px-2 text-sm font-t3-medium text-foreground-muted">{props.title}</Text> <View className="overflow-hidden rounded-[28px] bg-card" style={{ borderCurve: "continuous" }} > {props.children} </View> </View> ); } function AppSettingsSection() { const icon = useThemeColor("--color-icon"); return ( <SettingsSection title="App"> <View className="flex-row items-center gap-4 p-4"> <SymbolView name="info.circle" size={22} tintColor={icon} type="monochrome" weight="regular" /> <Text className="flex-1 text-lg text-foreground">Version</Text> <Text className="text-lg text-foreground-muted">Alpha</Text> </View> </SettingsSection> ); } function ArchivedThreadsSettingsSection() { return ( <SettingsSection title="Threads"> <SettingsRow icon="archivebox" label="Archived Threads" href="/settings/archive" /> </SettingsSection> ); } function SettingsRow(props: { readonly disabled?: boolean; readonly icon: SymbolName; readonly label: string; readonly value?: string; readonly href?: "/settings/archive" | "/settings/environments"; readonly onPress?: () => void; }) { const icon = useThemeColor("--color-icon"); const chevron = useThemeColor("--color-chevron"); const content = ( <View className="flex-row items-center gap-4 p-4" style={{ opacity: props.disabled ? 0.45 : 1 }} > <SymbolView name={props.icon} size={22} tintColor={icon} type="monochrome" weight="regular" /> <Text className="shrink-0 text-lg text-foreground" numberOfLines={1}> {props.label} </Text> <View className="min-w-0 flex-1 items-end"> {props.value ? ( <Text className="max-w-[180px] text-right text-base text-foreground-muted" ellipsizeMode="middle" numberOfLines={1} > {props.value} </Text> ) : null} </View> <SymbolView name="chevron.right" size={16} tintColor={chevron} type="monochrome" weight="semibold" /> </View> ); if (props.href) { return ( <Link href={props.href} asChild> <Pressable accessibilityLabel={props.label} accessibilityRole="button"> {content} </Pressable> </Link> ); } return ( <Pressable accessibilityRole="button" disabled={props.disabled} onPress={props.onPress}> {content} </Pressable> ); } function SettingsSwitchRow(props: { readonly disabled?: boolean; readonly icon: SymbolName; readonly label: string; readonly value: boolean; readonly onValueChange: (value: boolean) => void; }) { const icon = useThemeColor("--color-icon"); const activeTrack = String(useThemeColor("--color-switch-active")); const track = String(useThemeColor("--color-secondary-border")); return ( <View