OnboardingStateModel.swift 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524
  1. import Combine
  2. import FirebaseCrashlytics
  3. import Foundation
  4. import LoopKit
  5. import Observation
  6. import SwiftUI
  7. /// Model that holds the data collected during onboarding.
  8. extension Onboarding {
  9. @Observable final class StateModel: BaseStateModel<Provider> {
  10. @ObservationIgnored @Injected() var fileStorage: FileStorage!
  11. @ObservationIgnored @Injected() var deviceManager: DeviceDataManager!
  12. @ObservationIgnored @Injected() var broadcaster: Broadcaster!
  13. @ObservationIgnored @Injected() var keychain: Keychain!
  14. @ObservationIgnored @Injected() var nightscoutManager: NightscoutManager!
  15. private let settingsProvider = PickerSettingsProvider.shared
  16. // MARK: - App Diagnostics
  17. var diagnosticsSharingOption: DiagnosticsSharingOption = .enabled
  18. // MARK: - Nightscout Setup
  19. var nightscoutSetupOption: NightscoutSetupOption = .noSelection
  20. var nightscoutImportOption: NightscoutImportOption = .noSelection
  21. var nightscoutUrl = ""
  22. var nightscoutSecret = ""
  23. var nightscoutResponseMessage = ""
  24. var isValidNightscoutURL: Bool = false
  25. var isConnectingToNS: Bool = false
  26. var isConnectedToNS: Bool = false
  27. var nightscoutImportErrors: [String] = []
  28. var nightscoutImportStatus: ImportStatus = .finished
  29. // MARK: - Units and Pump Omboarding Option
  30. var units: GlucoseUnits = .mgdL
  31. var pumpOptionForOnboardingUnits: PumpOptionForOnboardingUnits = .omnipodDash
  32. // MARK: - Time Values (shared)
  33. let sharedTimeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }.sorted()
  34. // MARK: - Carb Ratio
  35. let carbRatioPickerSetting = PickerSetting(value: 3, step: 0.1, min: 3, max: 50, type: .gram)
  36. var carbRatioItems: [CarbRatioEditor.Item] = []
  37. var initialCarbRatioItems: [CarbRatioEditor.Item] = []
  38. var carbRatioTimeValues: [TimeInterval] { sharedTimeValues }
  39. var carbRatioRateValues: [Decimal] { settingsProvider.generatePickerValues(from: carbRatioPickerSetting, units: units) }
  40. // MARK: - Basal Profile
  41. var basalRatePickerSetting: PickerSetting {
  42. switch pumpOptionForOnboardingUnits {
  43. case .dana:
  44. return PickerSetting(value: 0.05, step: 0.05, min: 0, max: 3, type: .insulinUnitPerHour)
  45. case .minimed:
  46. return PickerSetting(value: 0.05, step: 0.05, min: 0, max: 35, type: .insulinUnitPerHour)
  47. case .omnipodDash:
  48. return PickerSetting(value: 0.05, step: 0.05, min: 0, max: 30, type: .insulinUnitPerHour)
  49. case .omnipodEros:
  50. return PickerSetting(value: 0.05, step: 0.05, min: 0.05, max: 30, type: .insulinUnitPerHour)
  51. }
  52. }
  53. var basalProfileItems: [BasalProfileEditor.Item] = []
  54. var initialBasalProfileItems: [BasalProfileEditor.Item] = []
  55. var basalProfileTimeValues: [TimeInterval] { sharedTimeValues }
  56. var basalProfileRateValues: [Decimal] { settingsProvider.generatePickerValues(from: basalRatePickerSetting, units: units)
  57. }
  58. // MARK: - Insulin Sensitivity Factor (ISF)
  59. var sensitivityPickerSetting = PickerSetting(value: 100, step: 1, min: 9, max: 540, type: .glucose)
  60. var isfItems: [ISFEditor.Item] = []
  61. var initialISFItems: [ISFEditor.Item] = []
  62. var isfTimeValues: [TimeInterval] { sharedTimeValues }
  63. var isfRateValues: [Decimal] { settingsProvider.generatePickerValues(from: sensitivityPickerSetting, units: units) }
  64. // MARK: - Glucose Targets
  65. let letTargetPickerSetting = PickerSetting(value: 100, step: 1, min: 72, max: 180, type: .glucose)
  66. var targetItems: [TargetsEditor.Item] = []
  67. var initialTargetItems: [TargetsEditor.Item] = []
  68. var targetTimeValues: [TimeInterval] { sharedTimeValues }
  69. var targetRateValues: [Decimal] { settingsProvider.generatePickerValues(from: letTargetPickerSetting, units: units) }
  70. // MARK: - Delivery Limit Defaults
  71. var maxBolus: Decimal = 10
  72. var maxBasal: Decimal = 2
  73. var maxIOB: Decimal = 0
  74. var maxCOB: Decimal = 120
  75. var minimumSafetyThreshold: Decimal = 60
  76. // MARK: - Subscribe
  77. override func subscribe() {
  78. // Keychain items are not removed, even after uninstalling the app. Attempt to read them initially.
  79. nightscoutUrl = keychain.getValue(String.self, forKey: NightscoutConfig.Config.urlKey) ?? ""
  80. nightscoutSecret = keychain.getValue(String.self, forKey: NightscoutConfig.Config.secretKey) ?? ""
  81. isConnectedToNS = false
  82. isConnectingToNS = false
  83. isValidNightscoutURL = false
  84. // Attempt to fetch existing units, therapy settings and delivery limits from file
  85. units = settingsManager.settings.units
  86. fetchExistingTherapySettingsFromFile()
  87. fetchExistingDeliveryLimtisFromFile()
  88. }
  89. // MARK: - Helpers
  90. /// Finds the index of the closest `Decimal` value in the given array.
  91. /// - Parameters:
  92. /// - value: The value to match.
  93. /// - array: The array to search in.
  94. /// - Returns: Closest index in array.
  95. func closestIndex(for value: Decimal, in array: [Decimal]) -> Int {
  96. array.enumerated().min(by: {
  97. abs($0.element - value) < abs($1.element - value)
  98. })?.offset ?? 0
  99. }
  100. /// Finds the index of the closest `TimeInterval` value in the given array.
  101. /// - Parameters:
  102. /// - value: The time value to match.
  103. /// - array: The array to search in.
  104. /// - Returns: Closest index in array.
  105. func closestIndex(for value: TimeInterval, in array: [TimeInterval]) -> Int {
  106. array.enumerated().min(by: {
  107. abs($0.element - value) < abs($1.element - value)
  108. })?.offset ?? 0
  109. }
  110. /// A date formatter for time strings used in saved settings.
  111. private var timeFormatter: DateFormatter {
  112. let formatter = DateFormatter()
  113. formatter.timeZone = TimeZone(secondsFromGMT: 0)
  114. formatter.dateFormat = "HH:mm:ss"
  115. return formatter
  116. }
  117. // MARK: - Fetch existing therapy settings from file
  118. /// Loads existing therapy settings from the provider and maps them into UI editor items.
  119. ///
  120. /// This function processes therapy-related configurations (glucose targets, basal rates,
  121. /// carb ratios, and insulin sensitivity factors) stored in file-backed models from the provider.
  122. /// It calculates the closest matching indices for time and rate values to map them to corresponding
  123. /// `Editor.Item` models for use in the UI.
  124. ///
  125. /// - Populates:
  126. /// - `targetItems` and `initialTargetItems` with glucose target entries.
  127. /// - `basalProfileItems` and `initialBasalProfileItems` with basal rate entries.
  128. /// - `carbRatioItems` and `initialCarbRatioItems` with carbohydrate ratio entries.
  129. /// - `isfItems` and `initialISFItems` with insulin sensitivity factor entries.
  130. func fetchExistingTherapySettingsFromFile() {
  131. targetItems = provider.glucoseTargetsOnFile.targets.map { value in
  132. let timeIndex = closestIndex(for: TimeInterval(Double(value.offset * 60)), in: targetTimeValues)
  133. let lowIndex = closestIndex(for: value.low, in: targetRateValues)
  134. let highIndex = closestIndex(for: value.high, in: targetRateValues)
  135. return TargetsEditor.Item(lowIndex: lowIndex, highIndex: highIndex, timeIndex: timeIndex)
  136. }
  137. initialTargetItems = targetItems
  138. .map { TargetsEditor.Item(lowIndex: $0.lowIndex, highIndex: $0.highIndex, timeIndex: $0.timeIndex) }
  139. basalProfileItems = provider.basalProfileOnFile.map { value in
  140. let timeIndex = closestIndex(for: TimeInterval(Double(value.minutes * 60)), in: basalProfileTimeValues)
  141. let rateIndex = closestIndex(for: value.rate, in: basalProfileRateValues)
  142. return BasalProfileEditor.Item(rateIndex: rateIndex, timeIndex: timeIndex)
  143. }
  144. initialBasalProfileItems = basalProfileItems
  145. .map { BasalProfileEditor.Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
  146. carbRatioItems = provider.carbRatiosOnFile.schedule.map { value in
  147. let timeIndex = closestIndex(for: TimeInterval(Double(value.offset * 60)), in: carbRatioTimeValues)
  148. let rateIndex = closestIndex(for: value.ratio, in: carbRatioRateValues)
  149. return CarbRatioEditor.Item(rateIndex: rateIndex, timeIndex: timeIndex)
  150. }
  151. initialCarbRatioItems = carbRatioItems.map { CarbRatioEditor.Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
  152. isfItems = provider.isfOnFile.sensitivities.map { value in
  153. let timeIndex = closestIndex(for: TimeInterval(Double(value.offset * 60)), in: isfTimeValues)
  154. let rateIndex = closestIndex(for: value.sensitivity, in: isfRateValues)
  155. return ISFEditor.Item(rateIndex: rateIndex, timeIndex: timeIndex)
  156. }
  157. initialISFItems = isfItems.map { ISFEditor.Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
  158. }
  159. /// Loads delivery limit settings (Units, Max IOB, Max COB, Max Bolus, Max Basal) from the provider.
  160. ///
  161. /// Retrieves pump-related safety and delivery limits from both the provider's
  162. /// file-backed pump settings and app-specific preferences. These values are used
  163. /// to pre-fill the delivery limits editor in the onboarding or settings UI.
  164. ///
  165. /// - Populates:
  166. /// - `maxBolus` and `maxBasal` from file-based pump settings.
  167. /// - `maxIOB`, `maxCOB`, and `minimumSafetyThreshold` from app preferences.
  168. /// - `units` from app settings.
  169. func fetchExistingDeliveryLimtisFromFile() {
  170. let pumpSettingsFromFile = provider.pumpSettingsFromFile
  171. if let pumpSettingsFromFile = pumpSettingsFromFile {
  172. maxBolus = pumpSettingsFromFile.maxBolus
  173. maxBasal = pumpSettingsFromFile.maxBasal
  174. }
  175. let preferences = settingsManager.preferences
  176. maxIOB = preferences.maxIOB
  177. maxCOB = preferences.maxCOB
  178. minimumSafetyThreshold = preferences.threshold_setting
  179. }
  180. // MARK: - Get Therapy Items
  181. /// Converts ISF editor items to a list of `TherapySettingItem`.
  182. /// - Returns: Sorted list of therapy setting items based on ISF.
  183. func getISFTherapyItems() -> [TherapySettingItem] {
  184. getTherapyItems(from: isfItems, rateValues: isfRateValues, timeValues: isfTimeValues)
  185. }
  186. /// Converts basal profile editor items to a list of `TherapySettingItem`.
  187. /// - Returns: Sorted list of therapy setting items based on basal rates.
  188. func getBasalTherapyItems() -> [TherapySettingItem] {
  189. getTherapyItems(
  190. from: basalProfileItems,
  191. rateValues: basalProfileRateValues,
  192. timeValues: basalProfileTimeValues
  193. )
  194. }
  195. /// Converts carb ratio editor items to a list of `TherapySettingItem`.
  196. /// - Returns: Sorted list of therapy setting items based on carb ratios.
  197. func getCarbRatioTherapyItems() -> [TherapySettingItem] {
  198. getTherapyItems(from: carbRatioItems, rateValues: carbRatioRateValues, timeValues: carbRatioTimeValues)
  199. }
  200. /// Converts glucose target editor items to a list of `TherapySettingItem`.
  201. /// - Returns: Sorted list of therapy setting items based on glucose targets.
  202. func getTargetTherapyItems() -> [TherapySettingItem] {
  203. targetItems.map {
  204. TherapySettingItem(
  205. time: targetTimeValues[$0.timeIndex],
  206. value: targetRateValues[$0.lowIndex]
  207. )
  208. }.sorted { $0.time < $1.time }
  209. }
  210. /// Generic helper to convert any type of editor item into therapy setting items.
  211. /// - Parameters:
  212. /// - items: An array of items conforming to `TherapyItemConvertible`.
  213. /// - rateValues: The rate values to be used.
  214. /// - timeValues: The time values to be used.
  215. /// - Returns: A sorted array of `TherapySettingItem`.
  216. private func getTherapyItems<T: TherapyItemConvertible>(
  217. from items: [T],
  218. rateValues: [Decimal],
  219. timeValues: [TimeInterval]
  220. ) -> [TherapySettingItem] {
  221. items.map {
  222. TherapySettingItem(
  223. time: timeValues[$0.timeIndex],
  224. value: rateValues[$0.rateIndex]
  225. )
  226. }.sorted { $0.time < $1.time }
  227. }
  228. // MARK: - Unified Update Methods
  229. /// Updates the ISF editor items based on the provided therapy setting items.
  230. /// - Parameter therapyItems: The list of therapy items to update from.
  231. func updateISF(from therapyItems: [TherapySettingItem]) {
  232. isfItems = therapyItems.map {
  233. ISFEditor.Item(
  234. rateIndex: closestIndex(for: $0.value, in: isfRateValues),
  235. timeIndex: closestIndex(for: $0.time, in: isfTimeValues)
  236. )
  237. }.sorted { $0.timeIndex < $1.timeIndex }
  238. }
  239. /// Updates the basal rate editor items based on the provided therapy setting items.
  240. /// - Parameter therapyItems: The list of therapy items to update from.
  241. func updateBasal(from therapyItems: [TherapySettingItem]) {
  242. basalProfileItems = therapyItems.map {
  243. BasalProfileEditor.Item(
  244. rateIndex: closestIndex(for: $0.value, in: basalProfileRateValues),
  245. timeIndex: closestIndex(for: $0.time, in: basalProfileTimeValues)
  246. )
  247. }.sorted { $0.timeIndex < $1.timeIndex }
  248. }
  249. /// Updates the carb ratio editor items based on the provided therapy setting items.
  250. /// - Parameter therapyItems: The list of therapy items to update from.
  251. func updateCarbRatio(from therapyItems: [TherapySettingItem]) {
  252. carbRatioItems = therapyItems.map {
  253. CarbRatioEditor.Item(
  254. rateIndex: closestIndex(for: $0.value, in: carbRatioRateValues),
  255. timeIndex: closestIndex(for: $0.time, in: carbRatioTimeValues)
  256. )
  257. }.sorted { $0.timeIndex < $1.timeIndex }
  258. }
  259. /// Updates the glucose target editor items based on the provided therapy setting items.
  260. /// - Parameter therapyItems: The list of therapy items to update from.
  261. func updateTargets(from therapyItems: [TherapySettingItem]) {
  262. targetItems = therapyItems.map {
  263. let rateIndex = closestIndex(for: $0.value, in: targetRateValues)
  264. let timeIndex = closestIndex(for: $0.time, in: targetTimeValues)
  265. return TargetsEditor.Item(
  266. lowIndex: rateIndex,
  267. highIndex: rateIndex,
  268. timeIndex: timeIndex
  269. )
  270. }.sorted { $0.timeIndex < $1.timeIndex }
  271. }
  272. // MARK: - Add Initials
  273. /// Adds a default ISF editor item at 00:00 with a standard sensitivity value.
  274. func addInitialISF() {
  275. addInitialItem(
  276. defaultValue: 50,
  277. rateValues: isfRateValues,
  278. assign: { isfItems = $0 },
  279. makeItem: ISFEditor.Item.init
  280. )
  281. }
  282. /// Adds a default basal rate editor item at 00:00 with a typical rate value.
  283. func addInitialBasalRate() {
  284. addInitialItem(
  285. defaultValue: 0.1,
  286. rateValues: basalProfileRateValues,
  287. assign: { basalProfileItems = $0 },
  288. makeItem: BasalProfileEditor.Item.init
  289. )
  290. }
  291. /// Adds a default carb ratio editor item at 00:00 with a standard ratio.
  292. func addInitialCarbRatio() {
  293. addInitialItem(
  294. defaultValue: 10,
  295. rateValues: carbRatioRateValues,
  296. assign: { carbRatioItems = $0 },
  297. makeItem: CarbRatioEditor.Item.init
  298. )
  299. }
  300. /// Adds a default glucose target item at 00:00 with a typical target value.
  301. func addInitialTarget() {
  302. let timeIndex = 0
  303. let rateIndex = closestIndex(for: 100, in: targetRateValues)
  304. targetItems = [TargetsEditor.Item(lowIndex: rateIndex, highIndex: rateIndex, timeIndex: timeIndex)]
  305. }
  306. /// Adds an initial therapy setting item for a given editor item type.
  307. /// - Parameters:
  308. /// - defaultValue: The expected default value to use.
  309. /// - rateValues: The array of rate values for the item.
  310. /// - assign: A closure that assigns the newly created array to the correct property.
  311. private func addInitialItem<ItemType>(
  312. defaultValue: Decimal,
  313. rateValues: [Decimal],
  314. assign: ([ItemType]) -> Void,
  315. makeItem: (Int, Int) -> ItemType
  316. ) {
  317. let timeIndex = 0
  318. let rateIndex = closestIndex(for: defaultValue, in: rateValues)
  319. assign([makeItem(rateIndex, timeIndex)])
  320. }
  321. // MARK: - Validate
  322. /// Removes duplicate entries from `carbRatioItems`, ensures sorting by time index,
  323. /// and forces the first entry to start at 00:00 (timeIndex 0).
  324. func validateCarbRatios() {
  325. carbRatioItems = validated(items: carbRatioItems, timeIndexKeyPath: \.timeIndex)
  326. }
  327. /// Removes duplicate entries from `basalProfileItems`, ensures sorting by time index,
  328. /// and forces the first entry to start at 00:00 (timeIndex 0).
  329. func validateBasal() {
  330. basalProfileItems = validated(items: basalProfileItems, timeIndexKeyPath: \.timeIndex)
  331. }
  332. /// Removes duplicate entries from `isfItems`, ensures sorting by time index,
  333. /// and forces the first entry to start at 00:00 (timeIndex 0).
  334. func validateISF() {
  335. isfItems = validated(items: isfItems, timeIndexKeyPath: \.timeIndex)
  336. }
  337. /// Removes duplicate entries from `targetItems`, ensures sorting by time index,
  338. /// and forces the first entry to start at 00:00 (timeIndex 0).
  339. func validateTarget() {
  340. targetItems = validated(items: targetItems, timeIndexKeyPath: \.timeIndex)
  341. }
  342. /// Removes duplicates, sorts by time, and ensures the first entry starts at 00:00.
  343. /// - Parameters:
  344. /// - items: The list of items to validate.
  345. /// - timeIndexKeyPath: A writable key path to the timeIndex property.
  346. /// - Returns: A validated and sorted list of items with the first entry at 00:00.
  347. private func validated<T: Hashable>(items: [T], timeIndexKeyPath: WritableKeyPath<T, Int>) -> [T] {
  348. var result = Array(Set(items)).sorted { $0[keyPath: timeIndexKeyPath] < $1[keyPath: timeIndexKeyPath] }
  349. if !result.isEmpty, result[0][keyPath: timeIndexKeyPath] != 0 {
  350. result[0][keyPath: timeIndexKeyPath] = 0
  351. }
  352. return result
  353. }
  354. // MARK: - Save
  355. /// Saves the carb ratio items to file storage and sets them as initial values.
  356. func saveCarbRatios() {
  357. let schedule = carbRatioItems.map { item in
  358. let time = timeFormatter.string(from: Date(timeIntervalSince1970: carbRatioTimeValues[item.timeIndex]))
  359. let offset = Int(carbRatioTimeValues[item.timeIndex] / 60)
  360. let value = carbRatioRateValues[item.rateIndex]
  361. return CarbRatioEntry(start: time, offset: offset, ratio: value)
  362. }
  363. fileStorage.save(CarbRatios(units: .grams, schedule: schedule), as: OpenAPS.Settings.carbRatios)
  364. initialCarbRatioItems = carbRatioItems
  365. }
  366. /// Saves the basal profile items to file storage and sets them as initial values.
  367. func saveBasalProfile() {
  368. let profile = basalProfileItems.map { item in
  369. let time = timeFormatter.string(from: Date(timeIntervalSince1970: basalProfileTimeValues[item.timeIndex]))
  370. let offset = Int(basalProfileTimeValues[item.timeIndex] / 60)
  371. let rate = basalProfileRateValues[item.rateIndex]
  372. return BasalProfileEntry(start: time, minutes: offset, rate: rate)
  373. }
  374. fileStorage.save(profile, as: OpenAPS.Settings.basalProfile)
  375. initialBasalProfileItems = basalProfileItems
  376. }
  377. /// Saves the insulin sensitivity (ISF) items to file storage and sets them as initial values.
  378. func saveISFValues() {
  379. let sensitivities = isfItems.map { item in
  380. let time = timeFormatter.string(from: Date(timeIntervalSince1970: isfTimeValues[item.timeIndex]))
  381. let offset = Int(isfTimeValues[item.timeIndex] / 60)
  382. let value = isfRateValues[item.rateIndex]
  383. return InsulinSensitivityEntry(sensitivity: value, offset: offset, start: time)
  384. }
  385. let profile = InsulinSensitivities(units: .mgdL, userPreferredUnits: .mgdL, sensitivities: sensitivities)
  386. fileStorage.save(profile, as: OpenAPS.Settings.insulinSensitivities)
  387. initialISFItems = isfItems
  388. }
  389. /// Saves the glucose target items to file storage and sets them as initial values.
  390. func saveTargets() {
  391. let targets = targetItems.map { item in
  392. let time = timeFormatter.string(from: Date(timeIntervalSince1970: targetTimeValues[item.timeIndex]))
  393. let offset = Int(targetTimeValues[item.timeIndex] / 60)
  394. let value = targetRateValues[item.lowIndex]
  395. return BGTargetEntry(low: value, high: value, start: time, offset: offset)
  396. }
  397. let profile = BGTargets(units: .mgdL, userPreferredUnits: .mgdL, targets: targets)
  398. fileStorage.save(profile, as: OpenAPS.Settings.bgTargets)
  399. initialTargetItems = targetItems
  400. }
  401. /// Persists all onboarding data by applying settings and saving therapy values.
  402. func saveOnboardingData() {
  403. applyDiagnostics()
  404. applyToSettings()
  405. applyToPreferences()
  406. applyToPumpSettings()
  407. saveTargets()
  408. saveBasalProfile()
  409. saveCarbRatios()
  410. saveISFValues()
  411. }
  412. /// Persists the current diagnostics sharing option to UserDefaults as a boolean.
  413. func applyDiagnostics() {
  414. let booleanValue: Bool = diagnosticsSharingOption == .enabled
  415. UserDefaults.standard.set(booleanValue, forKey: "DiagnosticsSharing")
  416. Crashlytics.crashlytics().setCrashlyticsCollectionEnabled(booleanValue)
  417. }
  418. /// Applies the selected glucose units to the app's settings.
  419. func applyToSettings() {
  420. var settingsCopy = settingsManager.settings
  421. settingsCopy.units = units
  422. settingsManager.settings = settingsCopy
  423. }
  424. /// Applies the selected delivery preferences to the app's settings.
  425. func applyToPreferences() {
  426. var preferencesCopy = settingsManager.preferences
  427. preferencesCopy.maxIOB = maxIOB
  428. preferencesCopy.maxCOB = maxCOB
  429. preferencesCopy.threshold_setting = minimumSafetyThreshold
  430. settingsManager.preferences = preferencesCopy
  431. }
  432. /// Saves pump delivery limits to persistent storage and broadcasts changes.
  433. func applyToPumpSettings() {
  434. let defaultDIA = settingsProvider.settings.dia.value
  435. let pumpSettings = PumpSettings(insulinActionCurve: defaultDIA, maxBolus: maxBolus, maxBasal: maxBasal)
  436. fileStorage.save(pumpSettings, as: OpenAPS.Settings.settings)
  437. }
  438. }
  439. }
  440. // MARK: - Protocol (optional) to unify type mapping
  441. protocol TherapyItemConvertible {
  442. var rateIndex: Int { get }
  443. var timeIndex: Int { get }
  444. }
  445. extension ISFEditor.Item: TherapyItemConvertible {}
  446. extension CarbRatioEditor.Item: TherapyItemConvertible {}
  447. extension BasalProfileEditor.Item: TherapyItemConvertible {}