1import { ScreenScrollView as ScrollView } from "../../components/ScreenScrollView";
2import { useAuth, useUser } from "@clerk/expo";
3import { useNavigation } from "@react-navigation/native";
4import { Platform, View } from "react-native";
5import { deriveProjectGroupLabel } from "@t3tools/client-runtime/state/project-grouping";
6import { useSafeAreaInsets } from "react-native-safe-area-context";
7
8import { useCloudAuthAvailable } from "../cloud/cloudAvailability";
9import { useAdaptiveWorkspaceLayout } from "../layout/AdaptiveWorkspaceLayout";
10import { NativeHeaderToolbar, NativeStackScreenOptions } from "../../native/StackHeader";
11import { useSavedRemoteConnections } from "../../state/use-remote-environment-registry";
12import { SettingsRow } from "./components/SettingsRow";
13import { SettingsSection } from "./components/SettingsSection";
14import { SettingsScreen } from "./components/SettingsScreen";
15import {
16 AndroidSettingsEnvironmentFilter,
17 SettingsEnvironmentFilterHeader,
18} from "./components/SettingsEnvironmentFilterHeader";
19import { useSettingsEnvironmentFilter } from "./settings-environment-filter";
20
21export function SettingsRouteScreen() {
22 const navigation = useNavigation();
23 const { layout } = useAdaptiveWorkspaceLayout();
24 const content = useCloudAuthAvailable() ? (
25 <ConfiguredSettingsRouteScreen />
26 ) : (
27 <LocalSettingsRouteScreen />
28 );
29
30 return (
31 <>
32 {Platform.OS === "ios" ? (
33 <NativeStackScreenOptions options={{ headerBackVisible: !layout.usesSplitView }} />
34 ) : null}
35 {Platform.OS === "ios" && layout.usesSplitView ? (
36 <>
37 <NativeHeaderToolbar placement="left">
38 <NativeHeaderToolbar.Button
39 accessibilityLabel="Go back"
40 icon="chevron.left"
41 onPress={() => navigation.goBack()}
42 />
43 </NativeHeaderToolbar>
44 </>
45 ) : null}
46 <SettingsEnvironmentFilterHeader closeSettings />
47 {Platform.OS === "android" ? (
48 <SettingsScreen title="Settings" trailing={<AndroidSettingsEnvironmentFilter />}>
49 {content}
50 </SettingsScreen>
51 ) : (
52 content
53 )}
54 </>
55 );
56}
57
58function ConfiguredSettingsRouteScreen() {
59 const insets = useSafeAreaInsets();
60 const navigation = useNavigation();
61 const { isLoaded, isSignedIn } = useAuth({ treatPendingAsSignedOut: false });
62 const { user } = useUser();
63 const { savedConnectionsById } = useSavedRemoteConnections();
64 const accountLabel = !isLoaded
65 ? "Checking"
66 : !isSignedIn
67 ? "Sign in"
68 : (user?.primaryEmailAddress?.emailAddress ?? "Signed in");
69
70 return (
71 <View collapsable={false} className="flex-1 bg-sheet">
72 <ScrollView
73 contentInsetAdjustmentBehavior="automatic"
74 showsVerticalScrollIndicator={false}
75 className="flex-1"
76 contentContainerClassName="gap-4 px-5 pt-4"
77 contentContainerStyle={{ paddingBottom: Math.max(insets.bottom, 18) + 18 }}
78 >
79 <SettingsSection title="Connections">
80 <SettingsRow
81 icon="person.crop.circle"
82 label="T3 Account"
83 value={accountLabel}
84 disabled={!isLoaded}
85 onPress={() => navigation.navigate("SettingsSheet", { screen: "SettingsAuth" })}
86 />
87 <SettingsRow
88 icon="desktopcomputer"
89 label="Environments"
90 value={`${Object.keys(savedConnectionsById).length}`}
91 valuePosition="trailing"
92 target="SettingsEnvironments"
93 />
94 <SettingsRow icon="bell.badge" label="Notifications" target="SettingsNotifications" />
95 </SettingsSection>
96
97 <SettingsIndexSections />
98 </ScrollView>
99 </View>
100 );
101}
102
103function LocalSettingsRouteScreen() {
104 const insets = useSafeAreaInsets();
105 const { savedConnectionsById } = useSavedRemoteConnections();
106 const environmentCount = Object.keys(savedConnectionsById).length;
107
108 return (
109 <View collapsable={false} className="flex-1 bg-sheet">
110 <ScrollView
111 contentInsetAdjustmentBehavior="automatic"
112 showsVerticalScrollIndicator={false}
113 className="flex-1"
114 contentContainerClassName="gap-4 px-5 pt-4"
115 contentContainerStyle={{
116 paddingBottom: Math.max(insets.bottom, 18) + 18,
117 }}
118 >
119 <SettingsSection title="Connections">
120 <SettingsRow
121 icon="desktopcomputer"
122 label="Environments"
123 value={`${environmentCount}`}
124 valuePosition="trailing"
125 target="SettingsEnvironments"
126 />
127 </SettingsSection>
128
129 <SettingsIndexSections />
130 </ScrollView>
131 </View>
132 );
133}
134
135function SettingsIndexSections() {
136 const { selectedTargets, projectGroups, selectedProjectKey } = useSettingsEnvironmentFilter();
137 const noServerTargets = selectedTargets.length === 0;
138 const selectedProject = projectGroups.find((group) => group.key === selectedProjectKey);
139 const scopedProjectMembers =
140 selectedProject?.members
141 .map((member) => member.project)
142 .filter((project) =>
143 selectedTargets.some((target) => target.environmentId === project.environmentId),
144 ) ?? [];
145 const projectLabel =
146 scopedProjectMembers.length > 0
147 ? deriveProjectGroupLabel({
148 representative: scopedProjectMembers[0]!,
149 members: scopedProjectMembers,
150 })
151 : (selectedProject?.label ?? "Unavailable project");
152 return (
153 <>
154 <SettingsSection title="Interface">
155 <SettingsRow icon="paintbrush" label="Appearance" target="SettingsAppearance" />
156 {Platform.OS === "ios" ? (
157 <>
158 <SettingsRow icon="keyboard" label="Keyboard" target="SettingsKeyboard" />
159 <SettingsRow icon="mic" label="Microphone" target="SettingsMicrophone" />
160 </>
161 ) : null}
162 </SettingsSection>
163
164 <SettingsSection title="Automations">
165 <SettingsRow icon="clock" label="Scheduled tasks" target="SettingsScheduledTasks" />
166 </SettingsSection>
167
168 <SettingsSection title="Projects & threads">
169 {selectedProjectKey !== null ? (
170 <SettingsRow
171 icon="folder"
172 label="Overview"
173 value={projectLabel}
174 target="SettingsProjectOverview"
175 />
176 ) : null}
177 <SettingsRow icon="folder" label="Organization" target="SettingsOrganization" />
178 <SettingsRow icon="text.bubble" label="Thread behavior" target="SettingsThreads" />
179 <SettingsRow icon="arrow.turn.left.up" label="Follow-ups" target="SettingsFollowUp" />
180 <SettingsRow icon="archivebox" label="Archived Threads" target="SettingsArchive" />
181 </SettingsSection>
182
183 <SettingsSection title="Server settings">
184 <SettingsRow
185 icon="person.crop.circle"
186 label="Provider accounts"
187 target="SettingsProviderAccounts"
188 disabled={noServerTargets}
189 />
190 <SettingsRow
191 icon="text.bubble"
192 label="New threads"
193 target="SettingsEnvironmentNewThreads"
194 disabled={noServerTargets}
195 />
196 <SettingsRow
197 icon="arrow.triangle.branch"
198 label="Source control"
199 target="SettingsEnvironmentSourceControl"
200 disabled={noServerTargets}
201 />
202 <SettingsRow
203 icon="text.alignleft"
204 label="Agent behavior"
205 target="SettingsEnvironmentAgentBehavior"
206 disabled={noServerTargets}
207 />
208 <SettingsRow
209 icon="arrow.clockwise"
210 label="Maintenance"
211 target="SettingsEnvironmentMaintenance"
212 disabled={noServerTargets}
213 />
214 </SettingsSection>
215
216 <SettingsSection title="App">
217 <SettingsRow icon="chart.bar.xaxis" label="Usage" target="SettingsUsage" />
218 <SettingsRow icon="info.circle" label="About The Knife" target="SettingsAbout" />
219 </SettingsSection>
220 </>
221 );
222}