SettingItems.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378
  1. import Foundation
  2. import LoopKitUI
  3. import SwiftUI
  4. struct SettingItem: Identifiable {
  5. let id = UUID()
  6. let title: String
  7. let view: Screen
  8. let searchContents: [String]?
  9. let path: [String]?
  10. init(
  11. title: String,
  12. view: Screen,
  13. searchContents: [String]? = nil,
  14. path: [String]? = nil
  15. ) {
  16. self.title = title
  17. self.view = view
  18. self.searchContents = searchContents
  19. self.path = path
  20. }
  21. }
  22. struct FilteredSettingItem: Identifiable {
  23. let id = UUID()
  24. let settingItem: SettingItem
  25. let matchedContent: String
  26. }
  27. enum SettingItems {
  28. static let trioConfig = [
  29. SettingItem(title: String(localized: "Devices", comment: "Devices menu item in the Settings main view."), view: .devices),
  30. SettingItem(
  31. title: String(localized: "Therapy", comment: "Therapy menu item in the Settings main view."),
  32. view: .therapySettings
  33. ),
  34. SettingItem(
  35. title: String(localized: "Algorithm", comment: "Algorithm menu item in the Settings main view."),
  36. view: .algorithmSettings
  37. ),
  38. SettingItem(
  39. title: String(localized: "Features", comment: "Features menu item in the Settings main view."),
  40. view: .featureSettings
  41. ),
  42. SettingItem(
  43. title: String(localized: "Notifications", comment: "Notifications menu item in the Settings main view."),
  44. view: .notificationSettings
  45. ),
  46. SettingItem(
  47. title: String(localized: "Services", comment: "Services menu item in the Settings main view."),
  48. view: .serviceSettings
  49. )
  50. ]
  51. static let devicesItems = [
  52. SettingItem(title: "Insulin Pump", view: .pumpConfig, path: ["Devices"]),
  53. SettingItem(
  54. title: "CGM",
  55. view: .cgm,
  56. searchContents: ["Smooth Glucose Value"],
  57. path: ["Devices", "Continuous Glucose Monitor"]
  58. ),
  59. SettingItem(title: "Smart Watch", view: .watch, path: ["Devices"]),
  60. SettingItem(
  61. title: "Apple Watch",
  62. view: .watch,
  63. searchContents: ["Display on Watch", "Show Protein and Fat", "Confirm Bolus Faster"],
  64. path: ["Devices", "Smart Watch", "Apple Watch"]
  65. ),
  66. SettingItem(
  67. title: "Contact Image",
  68. view: .watch,
  69. searchContents: ["Display on Watch", "Watch Complication"],
  70. path: ["Devices", "Smart Watch", "Apple Watch", "Contact Image"]
  71. )
  72. ]
  73. static let therapyItems = [
  74. SettingItem(
  75. title: "Units and Limits",
  76. view: .unitsAndLimits,
  77. searchContents: [
  78. "Glucose Units",
  79. "Max Basal",
  80. "Max Bolus",
  81. "Max IOB",
  82. "Max COB",
  83. "Minimum Safety Threshold",
  84. "Delivery Limits"
  85. ],
  86. path: ["Therapy Settings", "Units and Limits"]
  87. ),
  88. SettingItem(title: "Basal Rates", view: .basalProfileEditor, path: ["Therapy Settings"]),
  89. SettingItem(title: "Insulin Sensitivities", view: .isfEditor, path: ["Therapy Settings"]),
  90. SettingItem(title: "ISF", view: .isfEditor, path: ["Therapy Settings"]),
  91. SettingItem(title: "Carb Ratios", view: .crEditor, path: ["Therapy Settings"]),
  92. SettingItem(title: "CR", view: .crEditor, path: ["Therapy Settings"]),
  93. SettingItem(title: "Glucose Targets", view: .targetsEditor, path: ["Therapy Settings"])
  94. ]
  95. static let algorithmItems = [
  96. SettingItem(
  97. title: "Autosens",
  98. view: .autosensSettings,
  99. searchContents: ["Autosens Max", "Autosens Min", "Rewind Resets Autosens"],
  100. path: ["Algorithm", "Autosens"]
  101. ),
  102. SettingItem(
  103. title: "Super Micro Bolus (SMB)",
  104. view: .smbSettings,
  105. searchContents: [
  106. "Enable SMB Always",
  107. "Enable SMB With COB",
  108. "Enable SMB With Temporary Target",
  109. "Enable SMB After Carbs",
  110. "Enable SMB With High Glucose",
  111. "High Glucose Target",
  112. "Allow SMB With High Temporary Target",
  113. "Enable UAM",
  114. "Max SMB Basal Minutes",
  115. "Max UAM SMB Basal Minutes",
  116. "Max Allowed Glucose Rise for SMB"
  117. ],
  118. path: ["Algorithm", "Super Micro Bolus (SMB)"]
  119. ),
  120. SettingItem(
  121. title: "Dynamic Settings",
  122. view: .dynamicISF,
  123. searchContents: [
  124. "Dynamic ISF",
  125. "Sigmoid",
  126. "Logarithmic",
  127. "Adjustment Factor (AF)",
  128. "Sigmoid Adjustment Factor",
  129. "Weighted Average of TDD",
  130. "Adjust Basal"
  131. ],
  132. path: ["Algorithm", "Dynamic Sensitivity"]
  133. ),
  134. SettingItem(
  135. title: "Target Behavior",
  136. view: .targetBehavior,
  137. searchContents: [
  138. "High Temptarget Raises Sensitivity",
  139. "Low Temptarget Lowers Sensitivity",
  140. "Sensitivity Raises Target",
  141. "Resistance Lowers Target",
  142. "Half Basal Exercise Target"
  143. ],
  144. path: ["Algorithm", "Target Behavior"]
  145. ),
  146. SettingItem(
  147. title: "Additionals",
  148. view: .algorithmAdvancedSettings,
  149. searchContents: [
  150. "Max Daily Safety Multiplier",
  151. "Current Basal Safety Multiplier",
  152. "Use Custom Peak Time",
  153. "Duration of Insulin Action", "DIA",
  154. "Insulin Peak Time",
  155. "Skip Neutral Temps",
  156. "Unsuspend If No Temp",
  157. "SMB Delivery Ratio",
  158. "SMB Interval",
  159. "Min 5m Carbimpact",
  160. "Remaining Carbs Fraction",
  161. "Remaining Carbs Cap",
  162. "Noisy CGM Target Multiplier"
  163. ],
  164. path: ["Algorithm", "Additionals"]
  165. )
  166. ]
  167. static let trioFeaturesItems = [
  168. SettingItem(
  169. title: "Bolus Calculator",
  170. view: .bolusCalculatorConfig,
  171. searchContents: [
  172. "Display Meal Presets",
  173. "Recommended Bolus Percentage",
  174. "Enable Reduced Bolus Factor",
  175. "Reduced Bolus Factor",
  176. "Enable Super Bolus",
  177. "Super Bolus Factor",
  178. "Very Low Glucose Warning"
  179. ],
  180. path: ["Features", "Bolus Calculator"]
  181. ),
  182. SettingItem(
  183. title: "Meal Settings",
  184. view: .mealSettings,
  185. searchContents: [
  186. "Max Carbs",
  187. "Max Meal Absorption Time",
  188. "Max Fat",
  189. "Max Protein",
  190. "Display and Allow Fat and Protein Entries",
  191. "Fat and Protein Delay",
  192. "Maximum Duration (hours)",
  193. "Spread Interval (minutes)",
  194. "Fat and Protein Factor",
  195. "FPU"
  196. ],
  197. path: ["Features", "Meal Settings"]
  198. ),
  199. SettingItem(
  200. title: "Shortcuts",
  201. view: .shortcutsConfig,
  202. searchContents: ["Allow Bolusing with Shortcuts"],
  203. path: ["Features", "Shortcuts"]
  204. ),
  205. SettingItem(
  206. title: "Remote Control",
  207. view: .remoteControlConfig,
  208. searchContents: ["Remote Control"],
  209. path: ["Features", "Remote Control"]
  210. ),
  211. SettingItem(
  212. title: "User Interface",
  213. view: .userInterfaceSettings,
  214. searchContents: [
  215. "Show X-Axis Grid Lines",
  216. "Show Y-Axis Grid Lines",
  217. "Show Low and High Thresholds",
  218. "Low Threshold",
  219. "High Threshold",
  220. "X-Axis Interval Step",
  221. "eA1c/GMI Display Unit",
  222. "Show Carbs Required Badge",
  223. "Carbs Required Threshold",
  224. "Forecast Display Type",
  225. "Bolus Display Threshold",
  226. "Cone",
  227. "Lines",
  228. "Dark Mode",
  229. "Light Mode",
  230. "Appearance",
  231. "Dark Scheme",
  232. "Light Scheme",
  233. "Glucose Color Scheme",
  234. "Time in Range Type",
  235. "Time in Tight Range (TITR)",
  236. "Time in Normoglycemia (TING)"
  237. ],
  238. path: ["Features", "User Interface"]
  239. ),
  240. SettingItem(
  241. title: "App Icons",
  242. view: .iconConfig,
  243. searchContents: ["Trio Icon"],
  244. path: ["Features", "App Icons"]
  245. ),
  246. SettingItem(
  247. title: "App Diagnostics",
  248. view: .appDiagnostics,
  249. searchContents: ["Anonymized Data Sharing"],
  250. path: ["Features", "App Diagnostics"]
  251. )
  252. ]
  253. static let notificationItems = [
  254. SettingItem(title: "Manage iOS Preferences", view: .notificationSettings),
  255. SettingItem(
  256. title: "Trio Notifications",
  257. view: .glucoseNotificationSettings,
  258. searchContents: [
  259. "Always Notify Pump",
  260. "Always Notify CGM",
  261. "Always Notify Carb",
  262. "Always Notify Algorithm",
  263. "Show Glucose App Badge",
  264. "Glucose Notifications",
  265. "Add Glucose Source to Alarm",
  266. "Low Glucose Alarm Limit",
  267. "High Glucose Alarm Limit"
  268. ],
  269. path: ["Notifications", "Trio Notifications"] // Glucose
  270. ),
  271. SettingItem(
  272. title: "Live Activity",
  273. view: .liveActivitySettings,
  274. searchContents: [
  275. "Enable Live Activity",
  276. "Lock Screen Widget Style"
  277. ],
  278. path: ["Notifications", "Live Activity"]
  279. ),
  280. SettingItem(
  281. title: "Calendar Events",
  282. view: .calendarEventSettings,
  283. searchContents: [
  284. "Create Calendar Events",
  285. "Choose Calendar",
  286. "Display Emojis as Labels",
  287. "Display IOB and COB"
  288. ],
  289. path: ["Notifications", "Calendar Events"]
  290. )
  291. ]
  292. static let serviceItems = [
  293. SettingItem(
  294. title: "Nightscout",
  295. view: .nighscoutConfig,
  296. searchContents: [
  297. "Import Settings",
  298. "Backfill Glucose"
  299. ],
  300. path: ["Services", "Nightscout"]
  301. ),
  302. SettingItem(
  303. title: "Nightscout Upload",
  304. view: .nighscoutConfig,
  305. searchContents: [
  306. "Allow Uploading to Nightscout",
  307. "Upload Glucose"
  308. ],
  309. path: ["Services", "Nightscout", "Upload"]
  310. ),
  311. SettingItem(
  312. title: "Nightscout Fetch & Remote Control",
  313. view: .nighscoutConfig,
  314. searchContents: [
  315. "Allow Fetching From Nightscout"
  316. ],
  317. path: ["Services", "Nightscout", "Fetch and Remote Control"]
  318. ),
  319. SettingItem(title: "Tidepool", view: .serviceSettings, path: ["Services"]),
  320. SettingItem(title: "Apple Health", view: .healthkit, path: ["Services"])
  321. ]
  322. static var allItems: [SettingItem] {
  323. trioConfig + devicesItems + therapyItems + algorithmItems + trioFeaturesItems + notificationItems + serviceItems
  324. }
  325. static func filteredItems(searchText: String) -> [FilteredSettingItem] {
  326. allItems.flatMap { item in
  327. var results = [FilteredSettingItem]()
  328. let searchLower = searchText.lowercased()
  329. let titleLocalized = item.title.localized
  330. let titleEnglish = item.title.englishLocalized
  331. if titleLocalized.localizedCaseInsensitiveContains(searchLower) ||
  332. titleEnglish.localizedCaseInsensitiveContains(searchLower)
  333. {
  334. results.append(FilteredSettingItem(settingItem: item, matchedContent: item.title))
  335. }
  336. if let contents = item.searchContents {
  337. let matched = contents.filter {
  338. $0.localized.localizedCaseInsensitiveContains(searchLower) ||
  339. $0.englishLocalized.localizedCaseInsensitiveContains(searchLower)
  340. }
  341. results.append(contentsOf: matched.map { FilteredSettingItem(settingItem: item, matchedContent: $0) })
  342. }
  343. return results
  344. }
  345. }
  346. }
  347. extension String {
  348. func localizedString(locale: Locale = .current) -> String {
  349. if locale.identifier == "en",
  350. let path = Bundle.main.path(forResource: "en", ofType: "lproj"),
  351. let bundle = Bundle(path: path)
  352. {
  353. return NSLocalizedString(self, bundle: bundle, comment: "")
  354. }
  355. return NSLocalizedString(self, comment: "")
  356. }
  357. var localized: String { localizedString() }
  358. var englishLocalized: String { localizedString(locale: Locale(identifier: "en")) }
  359. }