فهرست منبع

Introduce searchable settings WIP

Co-Authored-By: polscm32 <marvin-polscheit@t-online.de>
Deniz Cengiz 1 سال پیش
والد
کامیت
b1da1f6063

+ 4 - 0
FreeAPS.xcodeproj/project.pbxproj

@@ -310,6 +310,7 @@
 		BDBAACFA2C2D439700370AAE /* OverrideData.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDBAACF92C2D439700370AAE /* OverrideData.swift */; };
 		BDC2EA452C3043B000E5BBD0 /* OverrideStorage.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC2EA442C3043B000E5BBD0 /* OverrideStorage.swift */; };
 		BDC2EA472C3045AD00E5BBD0 /* Override.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDC2EA462C3045AD00E5BBD0 /* Override.swift */; };
+		BDCAF2382C639F35002DC907 /* SettingItems.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCAF2372C639F35002DC907 /* SettingItems.swift */; };
 		BDCD47AF2C1F3F1700F8BCD5 /* OverrideStored+helper.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */; };
 		BDF34EBE2C0A31D100D51995 /* CustomNotification.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */; };
 		BDF34F832C10C5B600D51995 /* DataManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = BDF34F822C10C5B600D51995 /* DataManager.swift */; };
@@ -947,6 +948,7 @@
 		BDBAACF92C2D439700370AAE /* OverrideData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideData.swift; sourceTree = "<group>"; };
 		BDC2EA442C3043B000E5BBD0 /* OverrideStorage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OverrideStorage.swift; sourceTree = "<group>"; };
 		BDC2EA462C3045AD00E5BBD0 /* Override.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Override.swift; sourceTree = "<group>"; };
+		BDCAF2372C639F35002DC907 /* SettingItems.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SettingItems.swift; sourceTree = "<group>"; };
 		BDCD47AE2C1F3F1700F8BCD5 /* OverrideStored+helper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "OverrideStored+helper.swift"; sourceTree = "<group>"; };
 		BDF34EBD2C0A31D000D51995 /* CustomNotification.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomNotification.swift; sourceTree = "<group>"; };
 		BDF34F822C10C5B600D51995 /* DataManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DataManager.swift; sourceTree = "<group>"; };
@@ -1532,6 +1534,7 @@
 				3811DE3E25C9D4A100A708ED /* SettingsProvider.swift */,
 				3811DE3925C9D4A100A708ED /* SettingsStateModel.swift */,
 				3811DE3B25C9D4A100A708ED /* View */,
+				BDCAF2372C639F35002DC907 /* SettingItems.swift */,
 			);
 			path = Settings;
 			sourceTree = "<group>";
@@ -3364,6 +3367,7 @@
 				9825E5E923F0B8FA80C8C7C7 /* NightscoutConfigStateModel.swift in Sources */,
 				38A43598262E0E4900E80935 /* FetchAnnouncementsManager.swift in Sources */,
 				DD1745442C55C60E00211FAC /* AutosensSettingsDataFlow.swift in Sources */,
+				BDCAF2382C639F35002DC907 /* SettingItems.swift in Sources */,
 				642F76A05A4FF530463A9FD0 /* NightscoutConfigRootView.swift in Sources */,
 				BD7DA9AC2AE06EB900601B20 /* BolusCalculatorConfigRootView.swift in Sources */,
 				AD3D2CD42CD01B9EB8F26522 /* PumpConfigDataFlow.swift in Sources */,

+ 13 - 13
FreeAPS/Sources/Modules/AlgorithmAdvancedSettings/View/AlgorithmAdvancedSettingsRootView.swift

@@ -173,45 +173,45 @@ extension AlgorithmAdvancedSettings {
                 )
 
                 SettingInputSection(
-                    decimalValue: $state.min5mCarbimpact,
+                    decimalValue: $state.autotuneISFAdjustmentFraction,
                     booleanValue: $booleanPlaceholder,
                     shouldDisplayHint: $shouldDisplayHint,
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
                             selectedVerboseHint = $0
-                            hintLabel = NSLocalizedString("Min 5m Carbimpact", comment: "Min 5m Carbimpact")
+                            hintLabel = NSLocalizedString(
+                                "Autotune ISF Adjustment Fraction",
+                                comment: "Autotune ISF Adjustment Fraction"
+                            )
                         }
                     ),
                     type: .decimal,
-                    label: NSLocalizedString("Min 5m Carbimpact", comment: "Min 5m Carbimpact"),
+                    label: NSLocalizedString("Autotune ISF Adjustment Fraction", comment: "Autotune ISF Adjustment Fraction"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
-                        "This is a setting for default carb absorption impact per 5 minutes. The default is an expected 8 mg/dL/5min. This affects how fast COB is decayed in situations when carb absorption is not visible in BG deviations. The default of 8 mg/dL/5min corresponds to a minimum carb absorption rate of 24g/hr at a CSF of 4 mg/dL/g.",
-                        comment: "Min 5m Carbimpact"
+                        "The default of 0.5 for this value keeps autotune ISF closer to pump ISF via a weighted average of fullNewISF and pumpISF. 1.0 allows full adjustment, 0 is no adjustment from pump ISF.",
+                        comment: "Autotune ISF Adjustment Fraction"
                     )
                 )
 
                 SettingInputSection(
-                    decimalValue: $state.autotuneISFAdjustmentFraction,
+                    decimalValue: $state.min5mCarbimpact,
                     booleanValue: $booleanPlaceholder,
                     shouldDisplayHint: $shouldDisplayHint,
                     selectedVerboseHint: Binding(
                         get: { selectedVerboseHint },
                         set: {
                             selectedVerboseHint = $0
-                            hintLabel = NSLocalizedString(
-                                "Autotune ISF Adjustment Fraction",
-                                comment: "Autotune ISF Adjustment Fraction"
-                            )
+                            hintLabel = NSLocalizedString("Min 5m Carbimpact", comment: "Min 5m Carbimpact")
                         }
                     ),
                     type: .decimal,
-                    label: NSLocalizedString("Autotune ISF Adjustment Fraction", comment: "Autotune ISF Adjustment Fraction"),
+                    label: NSLocalizedString("Min 5m Carbimpact", comment: "Min 5m Carbimpact"),
                     miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
                     verboseHint: NSLocalizedString(
-                        "The default of 0.5 for this value keeps autotune ISF closer to pump ISF via a weighted average of fullNewISF and pumpISF. 1.0 allows full adjustment, 0 is no adjustment from pump ISF.",
-                        comment: "Autotune ISF Adjustment Fraction"
+                        "This is a setting for default carb absorption impact per 5 minutes. The default is an expected 8 mg/dL/5min. This affects how fast COB is decayed in situations when carb absorption is not visible in BG deviations. The default of 8 mg/dL/5min corresponds to a minimum carb absorption rate of 24g/hr at a CSF of 4 mg/dL/g.",
+                        comment: "Min 5m Carbimpact"
                     )
                 )
 

+ 54 - 54
FreeAPS/Sources/Modules/AutotuneConfig/View/AutotuneConfigRootView.swift

@@ -113,72 +113,72 @@ extension AutotuneConfig {
 
                 if let autotune = state.autotune {
                     if !state.onlyAutotuneBasals {
-                Section {
-                    HStack {
-                        Text("Carb ratio")
-                        Spacer()
-                        Text(isfFormatter.string(from: autotune.carbRatio as NSNumber) ?? "0")
-                        Text("g/U").foregroundColor(.secondary)
-                    }
-                    HStack {
-                        Text("Sensitivity")
-                        Spacer()
-                        if state.units == .mmolL {
-                            Text(isfFormatter.string(from: autotune.sensitivity.asMmolL as NSNumber) ?? "0")
-                        } else {
-                            Text(isfFormatter.string(from: autotune.sensitivity as NSNumber) ?? "0")
+                        Section {
+                            HStack {
+                                Text("Carb ratio")
+                                Spacer()
+                                Text(isfFormatter.string(from: autotune.carbRatio as NSNumber) ?? "0")
+                                Text("g/U").foregroundColor(.secondary)
+                            }
+                            HStack {
+                                Text("Sensitivity")
+                                Spacer()
+                                if state.units == .mmolL {
+                                    Text(isfFormatter.string(from: autotune.sensitivity.asMmolL as NSNumber) ?? "0")
+                                } else {
+                                    Text(isfFormatter.string(from: autotune.sensitivity as NSNumber) ?? "0")
+                                }
+                                Text(state.units.rawValue + "/U").foregroundColor(.secondary)
+                            }
                         }
-                        Text(state.units.rawValue + "/U").foregroundColor(.secondary)
-                    }
-                }
-                .listRowBackground(Color.chart)
+                        .listRowBackground(Color.chart)
                     }
 
-                Section(header: Text("Basal profile")) {
-                    ForEach(0 ..< autotune.basalProfile.count, id: \.self) { index in
+                    Section(header: Text("Basal profile")) {
+                        ForEach(0 ..< autotune.basalProfile.count, id: \.self) { index in
+                            HStack {
+                                Text(autotune.basalProfile[index].start).foregroundColor(.secondary)
+                                Spacer()
+                                Text(rateFormatter.string(from: autotune.basalProfile[index].rate as NSNumber) ?? "0")
+                                Text("U/hr").foregroundColor(.secondary)
+                            }
+                        }
                         HStack {
-                            Text(autotune.basalProfile[index].start).foregroundColor(.secondary)
+                            Text("Total")
+                                .bold()
+                                .foregroundColor(.primary)
                             Spacer()
-                            Text(rateFormatter.string(from: autotune.basalProfile[index].rate as NSNumber) ?? "0")
-                            Text("U/hr").foregroundColor(.secondary)
+                            Text(rateFormatter.string(from: autotune.basalProfile.reduce(0) { $0 + $1.rate } as NSNumber) ?? "0")
+                                .foregroundColor(.primary) +
+                                Text(" U/day")
+                                .foregroundColor(.secondary)
                         }
                     }
-                    HStack {
-                        Text("Total")
-                            .bold()
-                            .foregroundColor(.primary)
-                        Spacer()
-                        Text(rateFormatter.string(from: autotune.basalProfile.reduce(0) { $0 + $1.rate } as NSNumber) ?? "0")
-                            .foregroundColor(.primary) +
-                            Text(" U/day")
-                            .foregroundColor(.secondary)
-                    }
-                }
-                .listRowBackground(Color.chart)
+                    .listRowBackground(Color.chart)
 
-                Section {
-                    Button {
-                        Task {
-                            await state.delete()
+                    Section {
+                        Button {
+                            Task {
+                                await state.delete()
+                            }
+                        } label: {
+                            Text("Delete Autotune Data")
                         }
-                    } label: {
-                        Text("Delete Autotune Data")
+                        .frame(maxWidth: .infinity, alignment: .center)
+                        .listRowBackground(Color(.loopRed))
+                        .tint(.white)
                     }
-                    .frame(maxWidth: .infinity, alignment: .center)
-                    .listRowBackground(Color(.loopRed))
-                    .tint(.white)
-                }
 
-                Section {
-                    Button {
-                        replaceAlert = true
-                    } label: {
-                        Text("Save as Normal Basal Rates")
+                    Section {
+                        Button {
+                            replaceAlert = true
+                        } label: {
+                            Text("Save as Normal Basal Rates")
+                        }
+                        .frame(maxWidth: .infinity, alignment: .center)
+                        .listRowBackground(Color(.systemGray4))
+                        .tint(.white)
                     }
-                    .frame(maxWidth: .infinity, alignment: .center)
-                    .listRowBackground(Color(.systemGray4))
-                    .tint(.white)
-                }
                 }
             }
             .sheet(isPresented: $shouldDisplayHint) {

+ 1 - 1
FreeAPS/Sources/Modules/LiveActivitySettings/View/LiveActivitySettingsRootView.swift

@@ -69,7 +69,7 @@ extension LiveActivitySettings {
                             get: { selectedVerboseHint },
                             set: {
                                 selectedVerboseHint = $0
-                                hintLabel = "Show Live Activity"
+                                hintLabel = "Enable Live Activity"
                             }
                         ),
                         type: .boolean,

+ 69 - 73
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutConfigRootView.swift

@@ -56,91 +56,87 @@ extension NightscoutConfig {
                     content: {
                         NavigationLink("Connect", destination: NightscoutConnectView(state: state))
                         NavigationLink("Upload", destination: NightscoutUploadView(state: state))
-                        NavigationLink("Fetch and Remote Control", destination: NightscoutFetchView(state: state))
+                        NavigationLink("Fetch & Remote Control", destination: NightscoutFetchView(state: state))
                     }
                 ).listRowBackground(Color.chart)
 
-                Section(
-                    header: Text("Tidepool Integration"),
-                    content:
-                    {
-                        VStack {
-                            Button {
-                                importAlert = Alert(
-                                    title: Text("Import settings?"),
+                Section {
+                    VStack {
+                        Button {
+                            importAlert = Alert(
+                                title: Text("Import settings?"),
+                                message: Text(
+                                    "\n" +
+                                        NSLocalizedString(
+                                            "This will replace some or all of your current pump settings. Are you sure you want to import profile settings from Nightscout?",
+                                            comment: "Profile Import Alert"
+                                        ) +
+                                        "\n"
+                                ),
+                                primaryButton: .destructive(
+                                    Text("Yes, Import"),
+                                    action: {
+                                        state.importSettings()
+                                        importedHasRun = true
+                                    }
+                                ),
+                                secondaryButton: .cancel()
+                            )
+                            isImportAlertPresented.toggle()
+                        } label: {
+                            Text("Import Settings")
+                                .font(.title3) }
+                            .frame(maxWidth: .infinity, alignment: .center)
+                            .buttonStyle(.bordered)
+                            .disabled(state.url.isEmpty || state.connecting)
+                            .alert(isPresented: $importedHasRun) {
+                                Alert(
+                                    title: Text(
+                                        (fetchedErrors.first?.error ?? "")
+                                            .count < 4 ? "Settings imported" : "Import Error"
+                                    ),
                                     message: Text(
-                                        "\n" +
+                                        (fetchedErrors.first?.error ?? "").count < 4 ?
                                             NSLocalizedString(
-                                                "This will replace some or all of your current pump settings. Are you sure you want to import profile settings from Nightscout?",
-                                                comment: "Profile Import Alert"
-                                            ) +
-                                            "\n"
+                                                "\nNow please verify all of your new settings thoroughly: \n\n • DIA (Pump settings)\n • Basal Rates\n • Insulin Sensitivities\n • Carb Ratios\n • Target Glucose\n\n in Trio Settings -> Configuration.\n\nBad or invalid profile settings could have disastrous effects.",
+                                                comment: "Imported Profiles Alert"
+                                            ) :
+                                            NSLocalizedString(
+                                                fetchedErrors.first?.error ?? "",
+                                                comment: "Import Error"
+                                            )
                                     ),
                                     primaryButton: .destructive(
-                                        Text("Yes, Import"),
-                                        action: {
-                                            state.importSettings()
-                                            importedHasRun = true
-                                        }
+                                        Text("OK")
                                     ),
                                     secondaryButton: .cancel()
                                 )
-                                isImportAlertPresented.toggle()
-                            } label: {
-                                Text("Import Settings")
-                                    .font(.title3) }
-                                .frame(maxWidth: .infinity, alignment: .center)
-                                .buttonStyle(.bordered)
-                                .disabled(state.url.isEmpty || state.connecting)
-                                .alert(isPresented: $importedHasRun) {
-                                    Alert(
-                                        title: Text(
-                                            (fetchedErrors.first?.error ?? "")
-                                                .count < 4 ? "Settings imported" : "Import Error"
-                                        ),
-                                        message: Text(
-                                            (fetchedErrors.first?.error ?? "").count < 4 ?
-                                                NSLocalizedString(
-                                                    "\nNow please verify all of your new settings thoroughly: \n\n • DIA (Pump settings)\n • Basal Rates\n • Insulin Sensitivities\n • Carb Ratios\n • Target Glucose\n\n in Trio Settings -> Configuration.\n\nBad or invalid profile settings could have disastrous effects.",
-                                                    comment: "Imported Profiles Alert"
-                                                ) :
-                                                NSLocalizedString(
-                                                    fetchedErrors.first?.error ?? "",
-                                                    comment: "Import Error"
-                                                )
-                                        ),
-                                        primaryButton: .destructive(
-                                            Text("OK")
-                                        ),
-                                        secondaryButton: .cancel()
-                                    )
-                                }
+                            }
 
-                            HStack(alignment: .top) {
-                                Text(
-                                    "You can import therapy settings from Nightscout. See hint for more information which settings will be overwritten."
-                                )
-                                .font(.footnote)
-                                .foregroundColor(.secondary)
-                                .lineLimit(nil)
-                                Spacer()
-                                Button(
-                                    action: {
-                                        hintLabel = "Import Settings from Nightscout"
-                                        selectedVerboseHint =
-                                            "Importing settings from Nightscout will overwrite the following Trio therapy settings: \n • DIA (Pump settings) \n • Basal Profile \n • Insulin Sensitivities \n • Carb Ratios \n • Target Glucose"
-                                        shouldDisplayHint.toggle()
-                                    },
-                                    label: {
-                                        HStack {
-                                            Image(systemName: "questionmark.circle")
-                                        }
+                        HStack(alignment: .top) {
+                            Text(
+                                "You can import therapy settings from Nightscout. See hint for more information which settings will be overwritten."
+                            )
+                            .font(.footnote)
+                            .foregroundColor(.secondary)
+                            .lineLimit(nil)
+                            Spacer()
+                            Button(
+                                action: {
+                                    hintLabel = "Import Settings from Nightscout"
+                                    selectedVerboseHint =
+                                        "Importing settings from Nightscout will overwrite the following Trio therapy settings: \n • DIA (Pump settings) \n • Basal Profile \n • Insulin Sensitivities \n • Carb Ratios \n • Target Glucose"
+                                    shouldDisplayHint.toggle()
+                                },
+                                label: {
+                                    HStack {
+                                        Image(systemName: "questionmark.circle")
                                     }
-                                ).buttonStyle(BorderlessButtonStyle())
-                            }.padding(.top)
-                        }.padding(.vertical)
-                    }
-                ).listRowBackground(Color.chart)
+                                }
+                            ).buttonStyle(BorderlessButtonStyle())
+                        }.padding(.top)
+                    }.padding(.vertical)
+                }.listRowBackground(Color.chart)
 
                 Section(
                     content:

+ 1 - 1
FreeAPS/Sources/Modules/NightscoutConfig/View/NightscoutFetchView.swift

@@ -81,7 +81,7 @@ struct NightscoutFetchView: View {
                 sheetTitle: "Help"
             )
         }
-        .navigationTitle("Fetch and Remote")
+        .navigationTitle("Fetch & Remote")
         .navigationBarTitleDisplayMode(.automatic)
         .scrollContentBackground(.hidden).background(color)
     }

+ 1 - 1
FreeAPS/Sources/Modules/PumpSettingsEditor/View/PumpSettingsEditorRootView.swift

@@ -126,7 +126,7 @@ extension PumpSettingsEditor {
             }
             .scrollContentBackground(.hidden).background(color)
             .onAppear(perform: configureView)
-            .navigationTitle("Delivery Limits & DIA")
+            .navigationTitle("Delivery Limits")
             .navigationBarTitleDisplayMode(.automatic)
         }
     }

+ 304 - 0
FreeAPS/Sources/Modules/Settings/SettingItems.swift

@@ -0,0 +1,304 @@
+import Foundation
+import LoopKitUI
+import SwiftUI
+
+struct SettingItem: Identifiable {
+    let id = UUID()
+    let title: LocalizedStringKey
+    let view: Screen
+    let searchContents: [LocalizedStringKey]?
+    let path: [LocalizedStringKey]?
+
+    init(
+        title: LocalizedStringKey,
+        view: Screen,
+        searchContents: [LocalizedStringKey]? = nil,
+        path: [LocalizedStringKey]? = nil
+    ) {
+        self.title = title
+        self.view = view
+        self.searchContents = searchContents
+        self.path = path
+    }
+}
+
+struct FilteredSettingItem: Identifiable {
+    let id = UUID()
+    let settingItem: SettingItem
+    let matchedContent: LocalizedStringKey
+}
+
+// TODO: fill this shit here with content...
+
+enum SettingItems {
+    static let trioConfig = [
+        SettingItem(title: "Devices", view: .devices),
+        SettingItem(title: "Therapy", view: .therapySettings),
+        SettingItem(title: "Algorithm", view: .algorithmSettings),
+        SettingItem(title: "Features", view: .featureSettings),
+        SettingItem(title: "Notifications", view: .notificationSettings),
+        SettingItem(title: "Services", view: .serviceSettings)
+    ]
+
+    static let devicesItems = [
+        SettingItem(title: "Insulin Pump", view: .pumpConfig),
+        SettingItem(
+            title: "Delivery Limits & DIA",
+            view: .cgm,
+            searchContents: ["Max Basal", "Max Bolus", "Duration of Insulin Action", "DIA"],
+            path: ["Devices", "Insulin Pump", "Delivery Limits & DIA"]
+        ),
+        SettingItem(
+            title: "CGM",
+            view: .cgm,
+            searchContents: ["Smooth Glucose Value"],
+            path: ["Devices", "Continuous Glucose Monitor"]
+        ),
+        SettingItem(title: "Smart Watch", view: .watch),
+        SettingItem(
+            title: "Apple Watch",
+            view: .watch,
+            searchContents: ["Display on Watch", "Show Protein and Fat", "Confirm Bolus Faster"],
+            path: ["Devices", "Smart Watch", "Apple Watch"]
+        )
+    ]
+
+    static let therapyItems = [
+        SettingItem(
+            title: "Units and Limits",
+            view: .unitsAndLimits,
+            searchContents: ["Glucose Units", "Max IOB", "Max COB"],
+            path: ["Therapy Settings", "Units and Limits"]
+        ),
+        SettingItem(title: "Basal Rates", view: .basalProfileEditor),
+        SettingItem(title: "Insulin Sensitivities", view: .isfEditor),
+        SettingItem(title: "ISF", view: .isfEditor),
+        SettingItem(title: "Carb Ratios", view: .crEditor),
+        SettingItem(title: "CR", view: .crEditor),
+        SettingItem(title: "Target Glucose", view: .targetsEditor)
+    ]
+
+    static let algorithmItems = [
+        SettingItem(
+            title: "Autosens",
+            view: .autosensSettings,
+            searchContents: ["Autosens Max", "Autosens Min", "Rewind Resets Autosens"],
+            path: ["Algorithm", "Autosens"]
+        ),
+        SettingItem(
+            title: "Super Micro Bolus (SMB)",
+            view: .smbSettings,
+            searchContents: [
+                "Enable SMB Always",
+                "Max SMB Basal Minutes",
+                "Max UAM SMB Basal Minutes",
+                "Max Delta-BG Threshold SMB",
+                "SMB Delivery Ratio",
+                "SMB Interval"
+            ],
+            path: ["Algorithm", "Super Micro Bolus (SMB)"]
+        ),
+        SettingItem(
+            title: "Dynamic Sensitivity",
+            view: .dynamicISF,
+            searchContents: [
+                "Activate Dynamic Sensitivity (ISF)",
+                "Activate Dynamic Carb Ratio (CR)",
+                "Use Sigmoid Formula",
+                "Adjustment Factor",
+                "Sigmoid Adjustment Factor",
+                "Weighted Average of TDD",
+                "Adjust Basal",
+                "Threshold Setting"
+            ],
+            path: ["Algorithm", "Dynamic Sensitivity"]
+        ),
+        SettingItem(
+            title: "Target Behavior",
+            view: .targetBehavior,
+            searchContents: [
+                "High Temptarget Raises Sensitivity",
+                "Low Temptarget Lowers Sensitivity",
+                "Sensitivity Raises Target",
+                "Resistance Lowers Target",
+                "Half Basal Exercise Target"
+            ],
+            path: ["Algorithm", "Target Behavior"]
+        ),
+        SettingItem(
+            title: "Additionals",
+            view: .algorithmAdvancedSettings,
+            searchContents: [
+                "Max Daily Safety Multiplier",
+                "Current Basal Safety Multiplier",
+                "Use Custom Peak Time",
+                "Insulin Peak Time",
+                "Skip Neutral Temps",
+                "Unsuspend If No Temp",
+                "Suspend Zeros IOB",
+                "Min 5m Carbimpact",
+                "Autotune ISF Adjustment Fractio",
+                "Remaining Carbs Fraction",
+                "Remaining Carbs Cap",
+                "Noisy CGM Target Multiplier"
+            ],
+            path: ["Algorithm", "Additionals"]
+        )
+    ]
+
+    static let trioFeaturesItems = [
+        SettingItem(
+            title: "Bolus Calculator",
+            view: .bolusCalculatorConfig,
+            searchContents: [
+                "Display Meal Presets",
+                "Recommended Bolus Percentage",
+                "Enable Fatty Meal Factor",
+                "Fatty Meal Factor",
+                "Enable Super Bolus",
+                "Super Bolus Factor"
+            ],
+            path: ["Features", "Bolus Calculator"]
+        ),
+        SettingItem(
+            title: "Meal Settings",
+            view: .mealSettings,
+            searchContents: [
+                "Max Carbs",
+                "Max Fat",
+                "Max Protein",
+                "Display and Allow Fat and Protein Entries",
+                "Fat and Protein Delay",
+                "Maximum Duration (hours)",
+                "Spread Interval (minutes)",
+                "Fat and Protein Factor"
+            ],
+            path: ["Features", "Meal Settings"]
+        ),
+        SettingItem(
+            title: "Shortcuts",
+            view: .shortcutsConfig,
+            searchContents: ["Allow Bolusing with Shortcuts"],
+            path: ["Features", "Shortcuts"]
+        ),
+        SettingItem(
+            title: "User Interface",
+            view: .userInterfaceSettings,
+            searchContents: [
+                "Show X-Axis Grid Lines",
+                "Show Y-Axis Grid Line",
+                "Show Low and High Thresholds",
+                "Low Threshold",
+                "High Threshold",
+                "X-Axis Interval Step",
+                "Total Insulin Display Type",
+                "Total Daily Dose",
+                "Total Insulin in Scope",
+                "Override HbA1c Unit",
+                "Standing / Laying TIR Chart",
+                "Show Carbs Required Badge",
+                "Carbs Required Threshold"
+            ],
+            path: ["Features", "User Interface"]
+        ),
+        SettingItem(title: "App Icons", view: .iconConfig),
+        SettingItem(title: "Autotune", view: .autotuneConfig)
+    ]
+
+    static let notificationItems = [
+        SettingItem(
+            title: "Glucose Notifications",
+            view: .glucoseNotificationSettings,
+            searchContents: [
+                "Show Glucose App Badge",
+                "Always Notify Glucose",
+                "Play Alarm Sound",
+                "Add Glucose Source to Alarm",
+                "Low Glucose Alarm Limit",
+                "High Glucose Alarm Limit"
+            ],
+            path: ["Notifications", "Glucose Notifications"]
+        ),
+        SettingItem(
+            title: "Live Activity",
+            view: .liveActivitySettings,
+            searchContents: [
+                "Enable Live Activity",
+                "Lock Screen Widget Style"
+            ],
+            path: ["Notifications", "Live Activity"]
+        ),
+        SettingItem(
+            title: "Calendar Events",
+            view: .calendarEventSettings,
+            searchContents: [
+                "Create Calendar Events",
+                "Choose Calendar",
+                "Display Emojis as Labels",
+                "Display IOB and COB"
+            ],
+            path: ["Notifications", "Calendar Events"]
+        )
+    ]
+
+    static let serviceItems = [
+        SettingItem(
+            title: "Nightscout",
+            view: .nighscoutConfig,
+            searchContents: [
+                "Import Settings",
+                "Backfill Glucose"
+            ],
+            path: ["Services", "Nightscout"]
+        ),
+        SettingItem(
+            title: "Nightscout Upload",
+            view: .nighscoutConfig,
+            searchContents: [
+                "Allow Uploading to Nightscout",
+                "Upload Glucose"
+            ],
+            path: ["Services", "Nightscout", "Upload"]
+        ),
+        SettingItem(
+            title: "Nightscout Fetch & Remote Control",
+            view: .nighscoutConfig,
+            searchContents: [
+                "Allow Fetching From Nightscout"
+            ],
+            path: ["Services", "Nightscout", "Fetch and Remote Control"]
+        ),
+        SettingItem(title: "Tidepool", view: .serviceSettings),
+        SettingItem(title: "Apple Health", view: .healthkit)
+    ]
+
+    static var allItems: [SettingItem] {
+        trioConfig + devicesItems + therapyItems + algorithmItems + trioFeaturesItems + notificationItems + serviceItems
+    }
+
+    static func filteredItems(searchText: String) -> [FilteredSettingItem] {
+        allItems.compactMap { item in
+            if item.title.stringValue.localizedCaseInsensitiveContains(searchText) {
+                return FilteredSettingItem(settingItem: item, matchedContent: item.title)
+            }
+            if let matchedContent = item.searchContents?
+                .first(where: { $0.stringValue.localizedCaseInsensitiveContains(searchText) })
+            {
+                return FilteredSettingItem(settingItem: item, matchedContent: matchedContent)
+            }
+            return nil
+        }
+    }
+}
+
+extension LocalizedStringKey {
+    var stringValue: String {
+        let mirror = Mirror(reflecting: self)
+        let children = mirror.children
+        if let label = children.first(where: { $0.label == "key" })?.value as? String {
+            return NSLocalizedString(label, comment: "")
+        } else {
+            return ""
+        }
+    }
+}

+ 158 - 112
FreeAPS/Sources/Modules/Settings/View/SettingsRootView.swift

@@ -9,7 +9,6 @@ extension Settings {
         let resolver: Resolver
         @StateObject var state = StateModel()
         @State private var showShareSheet = false
-
         @State private var searchText: String = ""
 
         @Environment(\.colorScheme) var colorScheme
@@ -32,141 +31,188 @@ extension Settings {
                 )
         }
 
+        private var filteredItems: [FilteredSettingItem] {
+            SettingItems.filteredItems(searchText: searchText)
+        }
+
         var body: some View {
             Form {
-                let buildDetails = BuildDetails.default
+                if searchText.isEmpty {
+                    let buildDetails = BuildDetails.default
 
-                Section(
-                    header: Text("BRANCH: \(buildDetails.branchAndSha)").textCase(nil),
-                    content: {
-                        let versionNumber = Bundle.main.releaseVersionNumber ?? "Unknown"
-                        let buildNumber = Bundle.main.buildVersionNumber ?? "Unknown"
+                    Section(
+                        header: Text("BRANCH: \(buildDetails.branchAndSha)").textCase(nil),
+                        content: {
+                            let versionNumber = Bundle.main.releaseVersionNumber ?? "Unknown"
+                            let buildNumber = Bundle.main.buildVersionNumber ?? "Unknown"
 
-                        Group {
-                            HStack {
-                                Image(uiImage: UIImage(named: appIcons.appIcon.rawValue) ?? UIImage())
-                                    .resizable()
-                                    .aspectRatio(contentMode: .fit)
-                                    .frame(width: 50, height: 50)
-                                    .padding(.trailing, 10)
-                                VStack(alignment: .leading) {
-                                    Text("Trio v\(versionNumber) (\(buildNumber))")
-                                        .font(.headline)
-                                    if let expirationDate = buildDetails.calculateExpirationDate() {
-                                        let formattedDate = DateFormatter.localizedString(
-                                            from: expirationDate,
-                                            dateStyle: .medium,
-                                            timeStyle: .none
-                                        )
-                                        Text("\(buildDetails.expirationHeaderString): \(formattedDate)")
-                                            .font(.footnote)
-                                            .foregroundColor(.secondary)
-                                    } else {
-                                        Text("Simulator Build has no expiry")
-                                            .font(.footnote)
-                                            .foregroundColor(.secondary)
+                            Group {
+                                HStack {
+                                    Image(uiImage: UIImage(named: appIcons.appIcon.rawValue) ?? UIImage())
+                                        .resizable()
+                                        .aspectRatio(contentMode: .fit)
+                                        .frame(width: 50, height: 50)
+                                        .padding(.trailing, 10)
+                                    VStack(alignment: .leading) {
+                                        Text("Trio v\(versionNumber) (\(buildNumber))")
+                                            .font(.headline)
+                                        if let expirationDate = buildDetails.calculateExpirationDate() {
+                                            let formattedDate = DateFormatter.localizedString(
+                                                from: expirationDate,
+                                                dateStyle: .medium,
+                                                timeStyle: .none
+                                            )
+                                            Text("\(buildDetails.expirationHeaderString): \(formattedDate)")
+                                                .font(.footnote)
+                                                .foregroundColor(.secondary)
+                                        } else {
+                                            Text("Simulator Build has no expiry")
+                                                .font(.footnote)
+                                                .foregroundColor(.secondary)
+                                        }
                                     }
                                 }
-                            }
 
-                            Text("Statistics").navigationLink(to: .statistics, from: self)
+                                Text("Statistics").navigationLink(to: .statistics, from: self)
+                            }
                         }
-                    }
-                ).listRowBackground(Color.chart)
+                    ).listRowBackground(Color.chart)
 
-                Section(
-                    header: Text("Automated Insulin Delivery"),
-                    content: {
-                        VStack {
-                            Toggle("Closed Loop", isOn: $state.closedLoop)
+                    Section(
+                        header: Text("Automated Insulin Delivery"),
+                        content: {
+                            VStack {
+                                Toggle("Closed Loop", isOn: $state.closedLoop)
 
-                            Spacer()
+                                Spacer()
 
-                            (
-                                Text("Running Trio in")
-                                    +
-                                    Text(" closed loop mode ").bold()
-                                    +
-                                    Text("requires an active CGM sensor session and a connected pump.")
-                                    +
-                                    Text("This enables automated insulin delivery.").bold()
-                            )
-                            .foregroundColor(.secondary)
-                            .font(.footnote)
+                                (
+                                    Text("Running Trio in")
+                                        +
+                                        Text(" closed loop mode ").bold()
+                                        +
+                                        Text("requires an active CGM sensor session and a connected pump.")
+                                        +
+                                        Text("This enables automated insulin delivery.").bold()
+                                )
+                                .foregroundColor(.secondary)
+                                .font(.footnote)
 
-                        }.padding(.vertical)
-                    }
-                ).listRowBackground(Color.chart)
+                            }.padding(.vertical)
+                        }
+                    ).listRowBackground(Color.chart)
 
-                Section(
-                    header: Text("Trio Configuration"),
-                    content: {
-                        Text("Devices").navigationLink(to: .devices, from: self)
-                        Text("Therapy").navigationLink(to: .therapySettings, from: self)
-                        Text("Algorithm").navigationLink(to: .algorithmSettings, from: self)
-                        Text("Features").navigationLink(to: .featureSettings, from: self)
-                        Text("Notifications").navigationLink(to: .notificationSettings, from: self)
-                        Text("Services").navigationLink(to: .serviceSettings, from: self)
-                    }
-                ).listRowBackground(Color.chart)
+                    Section(
+                        header: Text("Trio Configuration"),
+                        content: {
+                            ForEach(SettingItems.trioConfig) { item in
+                                Text(item.title).navigationLink(to: item.view, from: self)
+                            }
+                        }
+                    )
+                    .listRowBackground(Color.chart)
 
-                Section(
-                    header: Text("Support & Community"),
-                    content: {
-                        HStack {
-                            Text("Share Logs")
-                                .onTapGesture {
-                                    showShareSheet.toggle()
+                    Section(
+                        header: Text("Support & Community"),
+                        content: {
+                            Button {
+                                showShareSheet.toggle()
+                            } label: {
+                                HStack {
+                                    Text("Share Logs")
+                                        .foregroundColor(.white)
+                                    Spacer()
+                                    Image(systemName: "chevron.right")
+                                        .foregroundColor(.secondary)
+                                        .font(.footnote)
                                 }
-                            Spacer()
-                            Image(systemName: "chevron.right").foregroundColor(.secondary)
-                        }
+                            }
+                            .frame(maxWidth: .infinity, alignment: .leading)
 
-                        HStack {
-                            Text("Submit Ticket on GitHub")
-                                .onTapGesture {
-                                    if let url = URL(string: "https://github.com/nightscout/Trio/issues/new/choose") {
-                                        UIApplication.shared.open(url)
-                                    }
+                            Button {
+                                if let url = URL(string: "https://github.com/nightscout/Trio/issues/new/choose") {
+                                    UIApplication.shared.open(url)
                                 }
-                            Spacer()
-                            Image(systemName: "chevron.right").foregroundColor(.secondary)
-                        }
+                            } label: {
+                                HStack {
+                                    Text("Submit Ticket on GitHub")
+                                        .foregroundColor(.white)
+                                    Spacer()
+                                    Image(systemName: "chevron.right")
+                                        .foregroundColor(.secondary)
+                                        .font(.footnote)
+                                }
+                            }
+                            .frame(maxWidth: .infinity, alignment: .leading)
 
-                        HStack {
-                            Text("Trio Discord")
-                                .onTapGesture {
-                                    if let url = URL(string: "https://discord.gg/FnwFEFUwXE") {
-                                        UIApplication.shared.open(url)
-                                    }
+                            Button {
+                                if let url = URL(string: "https://discord.gg/FnwFEFUwXE") {
+                                    UIApplication.shared.open(url)
                                 }
-                            Spacer()
-                            Image(systemName: "chevron.right").foregroundColor(.secondary)
-                        }
+                            } label: {
+                                HStack {
+                                    Text("Trio Discord")
+                                        .foregroundColor(.white)
+                                    Spacer()
+                                    Image(systemName: "chevron.right")
+                                        .foregroundColor(.secondary)
+                                        .font(.footnote)
+                                }
+                            }
+                            .frame(maxWidth: .infinity, alignment: .leading)
 
-                        HStack {
-                            Text("Trio Facebook")
-                                .onTapGesture {
-                                    if let url = URL(string: "https://m.facebook.com/groups/1351938092206709/") {
-                                        UIApplication.shared.open(url)
-                                    }
+                            Button {
+                                if let url = URL(string: "https://m.facebook.com/groups/1351938092206709/") {
+                                    UIApplication.shared.open(url)
+                                }
+                            } label: {
+                                HStack {
+                                    Text("Trio Facebook")
+                                        .foregroundColor(.white)
+                                    Spacer()
+                                    Image(systemName: "chevron.right")
+                                        .foregroundColor(.secondary)
+                                        .font(.footnote)
                                 }
-                            Spacer()
-                            Image(systemName: "chevron.right").foregroundColor(.secondary)
+                            }
+                            .frame(maxWidth: .infinity, alignment: .leading)
+
+                            Button {
+                                if let url = URL(string: "https://diy-trio.org/") {
+                                    UIApplication.shared.open(url)
+                                }
+                            } label: {
+                                HStack {
+                                    Text("Trio Website")
+                                        .foregroundColor(.white)
+                                    Spacer()
+                                    Image(systemName: "chevron.right")
+                                        .foregroundColor(.secondary)
+                                        .font(.footnote)
+                                }
+                            }
+                            .frame(maxWidth: .infinity, alignment: .leading)
                         }
+                    ).listRowBackground(Color.chart)
 
-                        HStack {
-                            Text("Trio Website")
-                                .onTapGesture {
-                                    if let url = URL(string: "https://diy-trio.org/") {
-                                        UIApplication.shared.open(url)
+                } else {
+                    Section(
+                        header: Text("Search Results"),
+                        content: {
+                            ForEach(filteredItems) { filteredItem in
+                                VStack(alignment: .leading) {
+                                    Text(filteredItem.matchedContent).bold()
+//                                    Text(filteredItem.settingItem.title).font(.caption).foregroundColor(.secondary)
+                                    if let path = filteredItem.settingItem.path {
+                                        Text(path.map(\.stringValue).joined(separator: " > "))
+                                            .font(.caption)
+                                            .foregroundColor(.secondary)
                                     }
-                                }
-                            Spacer()
-                            Image(systemName: "chevron.right").foregroundColor(.secondary)
+                                }.navigationLink(to: filteredItem.settingItem.view, from: self)
+                            }
                         }
-                    }
-                ).listRowBackground(Color.chart)
+                    ).listRowBackground(Color.chart)
+                }
 
                 // TODO: remove this more or less entirely; add build-time flag to enable Middleware; add settings export feature
 //                Section {
@@ -262,7 +308,7 @@ extension Settings {
                     }
                 }
                 // TODO: check how to implement intuitive search
-//                .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic))
+                .searchable(text: $searchText, placement: .navigationBarDrawer(displayMode: .automatic))
                 .onDisappear(perform: { state.uploadProfileAndSettings(false) })
                 .screenNavigation(self)
         }

+ 1 - 1
FreeAPS/Sources/Modules/UserInterfaceSettings/View/UserInterfaceSettingsRootView.swift

@@ -56,7 +56,7 @@ extension UserInterfaceSettings {
                     content: {
                         VStack {
                             Toggle("Show X-Axis Grid Lines", isOn: $state.xGridLines)
-                            Toggle("Show Y-Axis Grid Line", isOn: $state.yGridLines)
+                            Toggle("Show Y-Axis Grid Lines", isOn: $state.yGridLines)
 
                             HStack(alignment: .top) {
                                 Text(

+ 2 - 2
Trio.xcworkspace/xcshareddata/swiftpm/Package.resolved

@@ -1,5 +1,5 @@
 {
-  "originHash" : "f5c836c216c4ca7d356e3777e58d6d4f9502b03f3974891349eb775f4c4cf750",
+  "originHash" : "59ac7eba66375d6eb406e758cb0b9964f4b3b0ae45c5665596f00384c32262b9",
   "pins" : [
     {
       "identity" : "cryptoswift",
@@ -49,7 +49,7 @@
     {
       "identity" : "swiftcharts",
       "kind" : "remoteSourceControl",
-      "location" : "https://github.com/ivanschuetz/SwiftCharts",
+      "location" : "https://github.com/ivanschuetz/SwiftCharts.git",
       "state" : {
         "branch" : "master",
         "revision" : "c354c1945bb35a1f01b665b22474f6db28cba4a2"