@@ -9,100 +9,44 @@ struct SettingsMenuView: View {
99
1010 var body : some View {
1111 List {
12- dataSection
13-
14- Section ( " Display Settings " ) {
15- NavigationRow ( title: " General " ,
16- icon: " gearshape " ,
17- value: SettingsRoute . general)
18- NavigationRow ( title: " Graph " ,
19- icon: " chart.xyaxis.line " ,
20- value: SettingsRoute . graph)
21-
22- if !nightscoutURL. value. isEmpty {
23- NavigationRow ( title: " Information Display " ,
24- icon: " info.circle " ,
25- value: SettingsRoute . infoDisplay)
26- }
27-
28- NavigationRow ( title: " Units and Metrics " ,
29- icon: " scalemass " ,
30- value: SettingsRoute . units)
31-
32- NavigationRow ( title: " Tabs " ,
33- icon: " rectangle.3.group " ,
34- value: SettingsRoute . tabSettings)
35- }
36-
37- Section ( " App Settings " ) {
38- NavigationRow ( title: " Background Refresh " ,
39- icon: " arrow.clockwise " ,
40- value: SettingsRoute . backgroundRefresh)
41-
42- NavigationRow ( title: " Import/Export " ,
43- icon: " square.and.arrow.down " ,
44- value: SettingsRoute . importExport)
45-
46- NavigationRow ( title: " APN " ,
47- icon: " bell.and.waves.left.and.right " ,
48- value: SettingsRoute . apn)
49-
50- #if !targetEnvironment(macCatalyst)
51- NavigationRow ( title: " Live Activity " ,
52- icon: " dot.radiowaves.left.and.right " ,
53- value: SettingsRoute . liveActivity)
54- #endif
55-
56- if !nightscoutURL. value. isEmpty {
57- NavigationRow ( title: " Remote " ,
58- icon: " antenna.radiowaves.left.and.right " ,
59- value: SettingsRoute . remote)
12+ ForEach ( SettingsRoute . menuSections ( nightscoutConfigured: !nightscoutURL. value. isEmpty) , id: \. 0 ) { section, routes in
13+ Section ( section. rawValue) {
14+ ForEach ( routes) { route in
15+ NavigationRow ( title: route. title,
16+ icon: route. icon,
17+ value: route)
18+ }
6019 }
6120 }
62-
63- Section ( " Alarms " ) {
64- NavigationRow ( title: " Alarms " ,
65- icon: " bell.badge " ,
66- value: SettingsRoute . alarmSettings)
67- }
68-
69- Section ( " Integrations " ) {
70- NavigationRow ( title: " Calendar " ,
71- icon: " calendar " ,
72- value: SettingsRoute . calendar)
73-
74- NavigationRow ( title: " Contact " ,
75- icon: " person.circle " ,
76- value: SettingsRoute . contact)
77- }
78-
79- Section ( " Advanced Settings " ) {
80- NavigationRow ( title: " Advanced " ,
81- icon: " exclamationmark.shield " ,
82- value: SettingsRoute . advanced)
83- }
8421 }
8522 . navigationTitle ( " Settings " )
8623 . navigationBarTitleDisplayMode ( . large)
8724 }
25+ }
8826
89- // MARK: – Section builders
27+ // MARK: – Sheet routing
9028
91- @ViewBuilder
92- private var dataSection : some View {
93- Section ( " Data Settings " ) {
94- NavigationRow ( title: " Nightscout " ,
95- icon: " network " ,
96- value: SettingsRoute . nightscout)
97-
98- NavigationRow ( title: " Dexcom " ,
99- icon: " sensor.tag.radiowaves.forward " ,
100- value: SettingsRoute . dexcom)
101- }
102- }
29+ enum SettingsSection : String , CaseIterable , Hashable {
30+ case data = " Data Settings "
31+ case display = " Display Settings "
32+ case app = " App Settings "
33+ case alarms = " Alarms "
34+ case integrations = " Integrations "
35+ case advanced = " Advanced Settings "
10336}
10437
105- // MARK: – Sheet routing
38+ /// A single setting inside a settings screen, exposed to Menu search. Leaves
39+ /// are not navigable on their own — a search hit opens the screen
40+ /// (`SettingsRoute`) that contains it.
41+ struct SettingsLeaf : Hashable {
42+ let title : String
43+ let keywords : [ String ]
44+
45+ init ( _ title: String , _ keywords: [ String ] = [ ] ) {
46+ self . title = title
47+ self . keywords = keywords
48+ }
49+ }
10650
10751enum SettingsRoute : Hashable , Identifiable {
10852 case settings
@@ -125,6 +69,224 @@ enum SettingsRoute: Hashable, Identifiable {
12569
12670 var id : Self { self }
12771
72+ // MARK: – Row presentation (single source of truth)
73+
74+ /// Title shown in the Settings list and used for search matching.
75+ /// Non-row cases (`.settings`, `.aggregatedStats`) return "" and are never
76+ /// included in `menuSections`.
77+ var title : String {
78+ switch self {
79+ case . nightscout: return " Nightscout "
80+ case . dexcom: return " Dexcom "
81+ case . general: return " General "
82+ case . graph: return " Graph "
83+ case . infoDisplay: return " Information Display "
84+ case . units: return " Units and Metrics "
85+ case . tabSettings: return " Tabs "
86+ case . backgroundRefresh: return " Background Refresh "
87+ case . importExport: return " Import/Export "
88+ case . apn: return " APN "
89+ #if !targetEnvironment(macCatalyst)
90+ case . liveActivity: return " Live Activity "
91+ #endif
92+ case . remote: return " Remote "
93+ case . alarmSettings: return " Alarms "
94+ case . calendar: return " Calendar "
95+ case . contact: return " Contact "
96+ case . advanced: return " Advanced "
97+ case . settings, . aggregatedStats: return " "
98+ }
99+ }
100+
101+ var icon : String {
102+ switch self {
103+ case . nightscout: return " network "
104+ case . dexcom: return " sensor.tag.radiowaves.forward "
105+ case . general: return " gearshape "
106+ case . graph: return " chart.xyaxis.line "
107+ case . infoDisplay: return " info.circle "
108+ case . units: return " scalemass "
109+ case . tabSettings: return " rectangle.3.group "
110+ case . backgroundRefresh: return " arrow.clockwise "
111+ case . importExport: return " square.and.arrow.down "
112+ case . apn: return " bell.and.waves.left.and.right "
113+ #if !targetEnvironment(macCatalyst)
114+ case . liveActivity: return " dot.radiowaves.left.and.right "
115+ #endif
116+ case . remote: return " antenna.radiowaves.left.and.right "
117+ case . alarmSettings: return " bell.badge "
118+ case . calendar: return " calendar "
119+ case . contact: return " person.circle "
120+ case . advanced: return " exclamationmark.shield "
121+ case . settings, . aggregatedStats: return " "
122+ }
123+ }
124+
125+ /// Extra synonyms so search finds a page by related terms, not just its title.
126+ var keywords : [ String ] {
127+ switch self {
128+ case . graph: return [ " chart " ]
129+ case . units: return [ " mmol " , " mgdl " , " metrics " ]
130+ case . infoDisplay: return [ " info " ]
131+ case . apn: return [ " push " , " notification " ]
132+ case . liveActivity: return [ " dynamic island " , " lock screen " ]
133+ case . importExport: return [ " import " , " export " , " backup " ]
134+ default : return [ ]
135+ }
136+ }
137+
138+ /// The individual settings inside this screen, so Menu search can find a
139+ /// bottom-level setting (e.g. "Graph Basal") and open the screen containing
140+ /// it. Titles mirror the row (or section) titles in each screen's view —
141+ /// keep them in sync when adding or renaming rows.
142+ var leaves : [ SettingsLeaf ] {
143+ switch self {
144+ case . nightscout: return [
145+ SettingsLeaf ( " URL " ) ,
146+ SettingsLeaf ( " Access Token " , [ " token " ] ) ,
147+ SettingsLeaf ( " Enable WebSocket " , [ " websocket " , " real-time " ] ) ,
148+ ]
149+ case . dexcom: return [
150+ SettingsLeaf ( " User Name " , [ " username " ] ) ,
151+ SettingsLeaf ( " Password " ) ,
152+ SettingsLeaf ( " Server " ) ,
153+ ]
154+ case . general: return [
155+ SettingsLeaf ( " Display App Badge " , [ " badge " ] ) ,
156+ SettingsLeaf ( " Persistent Notification " ) ,
157+ SettingsLeaf ( " Appearance " , [ " dark mode " , " light mode " , " theme " ] ) ,
158+ SettingsLeaf ( " Display Stats " ) ,
159+ SettingsLeaf ( " Display Small Graph " ) ,
160+ SettingsLeaf ( " Color BG Text " ) ,
161+ SettingsLeaf ( " Keep Screen Active " , [ " screen lock " , " screenlock " ] ) ,
162+ SettingsLeaf ( " Show Display Name " ) ,
163+ SettingsLeaf ( " Snoozer emoji " ) ,
164+ SettingsLeaf ( " Force portrait mode " , [ " orientation " , " landscape " ] ) ,
165+ SettingsLeaf ( " Time Zone Override " , [ " timezone " ] ) ,
166+ SettingsLeaf ( " Speak BG " , [ " voice " , " speech " ] ) ,
167+ SettingsLeaf ( " Send anonymous usage stats " , [ " telemetry " , " diagnostics " ] ) ,
168+ ]
169+ case . graph: return [
170+ SettingsLeaf ( " Display Dots " ) ,
171+ SettingsLeaf ( " Display Lines " ) ,
172+ SettingsLeaf ( " Show DIA Lines " , [ " dia " ] ) ,
173+ SettingsLeaf ( " Show −30 min Line " , [ " -30 " ] ) ,
174+ SettingsLeaf ( " Show −90 min Line " , [ " -90 " ] ) ,
175+ SettingsLeaf ( " Show Yesterday's BG " , [ " yesterday " ] ) ,
176+ SettingsLeaf ( " Show Midnight Lines " ) ,
177+ SettingsLeaf ( " Show Carb/Bolus Values " , [ " carbs " ] ) ,
178+ SettingsLeaf ( " Show Carb Absorption " ) ,
179+ SettingsLeaf ( " Treatments on Small Graph " ) ,
180+ SettingsLeaf ( " Small Graph Height " , [ " height " ] ) ,
181+ SettingsLeaf ( " Hours of Prediction " , [ " prediction " ] ) ,
182+ SettingsLeaf ( " Prediction Style " ) ,
183+ SettingsLeaf ( " Min Basal " , [ " basal scale " ] ) ,
184+ SettingsLeaf ( " Min BG Scale " ) ,
185+ SettingsLeaf ( " Show Days Back " , [ " history " , " days back " ] ) ,
186+ ]
187+ case . infoDisplay:
188+ return [ SettingsLeaf ( " Hide Information Table " ) ]
189+ + InfoType. allCases. map { SettingsLeaf ( $0. name) }
190+ case . units: return [
191+ SettingsLeaf ( " Glucose Unit " ) ,
192+ SettingsLeaf ( " Range Mode " , [ " tir " , " titr " , " time in range " ] ) ,
193+ SettingsLeaf ( " Glycemic Metrics " , [ " hba1c " , " ehba1c " , " gmi " ] ) ,
194+ SettingsLeaf ( " Variability " , [ " standard deviation " , " cv " ] ) ,
195+ ]
196+ case . backgroundRefresh: return [
197+ SettingsLeaf ( " Background Refresh Type " , [ " silent tune " , " bluetooth " , " rileylink " , " omnipod " , " heartbeat " ] ) ,
198+ ]
199+ case . importExport: return [
200+ SettingsLeaf ( " Scan QR Code to Import Settings " , [ " qr " ] ) ,
201+ SettingsLeaf ( " Export Settings To QR Code " , [ " qr " ] ) ,
202+ ]
203+ case . apn: return [
204+ SettingsLeaf ( " APNS Key ID " , [ " apns " ] ) ,
205+ SettingsLeaf ( " APNS Key " , [ " apns " , " p8 " ] ) ,
206+ ]
207+ #if !targetEnvironment(macCatalyst)
208+ case . liveActivity: return [
209+ SettingsLeaf ( " Enable Live Activity " ) ,
210+ SettingsLeaf ( " Restart Live Activity " ) ,
211+ SettingsLeaf ( " Grid Slots " , [ " carplay " , " watch " ] ) ,
212+ ]
213+ #endif
214+ case . remote: return [
215+ SettingsLeaf ( " Loop Remote Control " ) ,
216+ SettingsLeaf ( " Trio Remote Control " , [ " trc " ] ) ,
217+ SettingsLeaf ( " Meal with Bolus " ) ,
218+ SettingsLeaf ( " Meal with Fat/Protein " ) ,
219+ SettingsLeaf ( " Guardrails " , [ " max bolus " , " max carbs " , " max fat " , " max protein " ] ) ,
220+ SettingsLeaf ( " Bolus Increment " ) ,
221+ SettingsLeaf ( " Shared Secret " ) ,
222+ SettingsLeaf ( " QR Code URL " , [ " qr " ] ) ,
223+ ]
224+ case . alarmSettings: return [
225+ SettingsLeaf ( " All Alerts Snoozed " , [ " snooze all " ] ) ,
226+ SettingsLeaf ( " All Sounds Muted " , [ " mute all " ] ) ,
227+ SettingsLeaf ( " Day starts " , [ " schedule " , " day/night " ] ) ,
228+ SettingsLeaf ( " Night starts " , [ " schedule " , " day/night " ] ) ,
229+ SettingsLeaf ( " Override System Volume " , [ " volume " ] ) ,
230+ SettingsLeaf ( " Audio During Calls " ) ,
231+ SettingsLeaf ( " Ignore Zero BG " ) ,
232+ SettingsLeaf ( " Auto-Snooze CGM Start " , [ " autosnooze " ] ) ,
233+ SettingsLeaf ( " Volume Buttons Snooze Alarms " ) ,
234+ ]
235+ case . calendar: return [
236+ SettingsLeaf ( " Save BG to Calendar " , [ " watch " , " carplay " ] ) ,
237+ SettingsLeaf ( " Calendar Text " ) ,
238+ ]
239+ case . contact: return [
240+ SettingsLeaf ( " Enable Contact BG Updates " , [ " watch face " ] ) ,
241+ SettingsLeaf ( " Background Color " ) ,
242+ SettingsLeaf ( " Color Mode " ) ,
243+ SettingsLeaf ( " Text Color " ) ,
244+ SettingsLeaf ( " Show Trend " ) ,
245+ SettingsLeaf ( " Show Delta " ) ,
246+ SettingsLeaf ( " Show IOB " ) ,
247+ ]
248+ case . advanced: return [
249+ SettingsLeaf ( " Download Treatments " ) ,
250+ SettingsLeaf ( " Download Prediction " ) ,
251+ SettingsLeaf ( " Graph Basal " ) ,
252+ SettingsLeaf ( " Graph Bolus " ) ,
253+ SettingsLeaf ( " Graph Carbs " ) ,
254+ SettingsLeaf ( " Graph Other Treatments " ) ,
255+ SettingsLeaf ( " BG Update Delay " , [ " delay " ] ) ,
256+ SettingsLeaf ( " Debug Log Level " , [ " logging " ] ) ,
257+ ]
258+ case . tabSettings, . settings, . aggregatedStats: return [ ]
259+ }
260+ }
261+
262+ /// Ordered, grouped list of the routes shown as rows in the Settings menu.
263+ /// Encodes the conditional visibility in one place so both the list and the
264+ /// Menu search stay in sync.
265+ static func menuSections( nightscoutConfigured: Bool ) -> [ ( SettingsSection , [ SettingsRoute ] ) ] {
266+ var display : [ SettingsRoute ] = [ . general, . graph]
267+ if nightscoutConfigured {
268+ display. append ( . infoDisplay)
269+ }
270+ display += [ . units, . tabSettings]
271+
272+ var app : [ SettingsRoute ] = [ . backgroundRefresh, . importExport, . apn]
273+ #if !targetEnvironment(macCatalyst)
274+ app. append ( . liveActivity)
275+ #endif
276+ if nightscoutConfigured {
277+ app. append ( . remote)
278+ }
279+
280+ return [
281+ ( . data, [ . nightscout, . dexcom] ) ,
282+ ( . display, display) ,
283+ ( . app, app) ,
284+ ( . alarms, [ . alarmSettings] ) ,
285+ ( . integrations, [ . calendar, . contact] ) ,
286+ ( . advanced, [ . advanced] ) ,
287+ ]
288+ }
289+
128290 @ViewBuilder
129291 var destination : some View {
130292 switch self {
0 commit comments