Model.swift 7.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import Foundation
  2. import SwiftUI
  3. /// Represents the different steps in the onboarding process.
  4. enum OnboardingStep: Int, CaseIterable, Identifiable {
  5. case welcome
  6. case glucoseTarget
  7. case basalProfile
  8. case carbRatio
  9. case insulinSensitivity
  10. case completed
  11. var id: Int { rawValue }
  12. /// The title to display for this onboarding step.
  13. var title: String {
  14. switch self {
  15. case .welcome:
  16. return "Welcome to Trio"
  17. case .glucoseTarget:
  18. return "Glucose Target"
  19. case .basalProfile:
  20. return "Basal Profile"
  21. case .carbRatio:
  22. return "Carbohydrate Ratio"
  23. case .insulinSensitivity:
  24. return "Insulin Sensitivity"
  25. case .completed:
  26. return "All Set!"
  27. }
  28. }
  29. /// A detailed description of what this onboarding step is about.
  30. var description: String {
  31. switch self {
  32. case .welcome:
  33. return "Trio is a powerful app that helps you manage your diabetes. Let's get started by setting up a few important parameters that will help Trio work effectively for you."
  34. case .glucoseTarget:
  35. return "Your glucose target is the blood glucose level you aim to maintain. Trio will use this to calculate insulin doses and provide recommendations."
  36. case .basalProfile:
  37. return "Your basal profile represents the amount of background insulin you need throughout the day. This helps Trio calculate your insulin needs."
  38. case .carbRatio:
  39. return "Your carb ratio tells how many grams of carbohydrates one unit of insulin will cover. This is essential for accurate meal bolus calculations."
  40. case .insulinSensitivity:
  41. return "Your insulin sensitivity factor (ISF) indicates how much one unit of insulin will lower your blood glucose. This helps calculate correction boluses."
  42. case .completed:
  43. return "Great job! You've completed the initial setup of Trio. You can always adjust these settings later in the app."
  44. }
  45. }
  46. /// The system icon name associated with this step.
  47. var iconName: String {
  48. switch self {
  49. case .welcome:
  50. return "hand.wave.fill"
  51. case .glucoseTarget:
  52. return "target"
  53. case .basalProfile:
  54. return "chart.xyaxis.line"
  55. case .carbRatio:
  56. return "fork.knife"
  57. case .insulinSensitivity:
  58. return "drop.fill"
  59. case .completed:
  60. return "checkmark.circle.fill"
  61. }
  62. }
  63. /// Returns the next step in the onboarding process, or nil if this is the last step.
  64. var next: OnboardingStep? {
  65. let allCases = OnboardingStep.allCases
  66. let currentIndex = allCases.firstIndex(of: self) ?? 0
  67. let nextIndex = currentIndex + 1
  68. return nextIndex < allCases.count ? allCases[nextIndex] : nil
  69. }
  70. /// Returns the previous step in the onboarding process, or nil if this is the first step.
  71. var previous: OnboardingStep? {
  72. let allCases = OnboardingStep.allCases
  73. let currentIndex = allCases.firstIndex(of: self) ?? 0
  74. let previousIndex = currentIndex - 1
  75. return previousIndex >= 0 ? allCases[previousIndex] : nil
  76. }
  77. /// The accent color to use for this step.
  78. var accentColor: Color {
  79. switch self {
  80. case .welcome:
  81. return Color.blue
  82. case .glucoseTarget:
  83. return Color.green
  84. case .basalProfile:
  85. return Color.purple
  86. case .carbRatio:
  87. return Color.orange
  88. case .insulinSensitivity:
  89. return Color.red
  90. case .completed:
  91. return Color.blue
  92. }
  93. }
  94. }
  95. /// Model that holds the data collected during onboarding.
  96. @Observable class OnboardingData: Injectable {
  97. @ObservationIgnored @Injected() var settingsManager: SettingsManager!
  98. @ObservationIgnored @Injected() var storage: FileStorage!
  99. // Carb Ratio related
  100. var items: [CarbRatioEditor.Item] = []
  101. var initialItems: [CarbRatioEditor.Item] = []
  102. let timeValues = stride(from: 0.0, to: 1.days.timeInterval, by: 30.minutes.timeInterval).map { $0 }
  103. let rateValues = stride(from: 30.0, to: 501.0, by: 1.0).map { ($0.decimal ?? .zero) / 10 }
  104. // Glucose Target
  105. var targetLow: Decimal = 70
  106. var targetHigh: Decimal = 180
  107. // Basal Profile
  108. var basalRates: [BasalRateEntry] = [BasalRateEntry(startTime: 0, rate: 1.0)]
  109. // Carb Ratio
  110. var carbRatio: Decimal = 10
  111. // Insulin Sensitivity Factor
  112. var isf: Decimal = 40
  113. // Blood Glucose Units
  114. var units: GlucoseUnits = .mgdL
  115. struct BasalRateEntry: Identifiable {
  116. var id = UUID()
  117. var startTime: Int // Minutes from midnight
  118. var rate: Decimal
  119. var timeFormatted: String {
  120. let hours = startTime / 60
  121. let minutes = startTime % 60
  122. return String(format: "%02d:%02d", hours, minutes)
  123. }
  124. }
  125. /// Applies the onboarding data to the app's settings.
  126. func applyToSettings() {
  127. // Make a copy of the current settings that we can mutate
  128. var settingsCopy = settingsManager.settings
  129. // Apply glucose target - We'll use lowGlucose and highGlucose properties
  130. settingsCopy.lowGlucose = targetLow
  131. settingsCopy.highGlucose = targetHigh
  132. // Apply glucose units
  133. settingsCopy.units = units
  134. // Apply basal profile
  135. // Apply carb ratio
  136. saveCarbRatios()
  137. // Apply ISF values
  138. // Instead of using updateSettings which doesn't exist,
  139. // we'll directly set the settings property which will trigger the didSet observer
  140. settingsManager.settings = settingsCopy
  141. }
  142. }
  143. // MARK: - Setup Carb Ratios
  144. extension OnboardingData {
  145. var hasChanges: Bool {
  146. if initialItems.count != items.count {
  147. return true
  148. }
  149. for (initialItem, currentItem) in zip(initialItems, items) {
  150. if initialItem.rateIndex != currentItem.rateIndex || initialItem.timeIndex != currentItem.timeIndex {
  151. return true
  152. }
  153. }
  154. return false
  155. }
  156. func addCarbRatio() {
  157. var time = 0
  158. var rate = 0
  159. if let last = items.last {
  160. time = last.timeIndex + 1
  161. rate = last.rateIndex
  162. }
  163. let newItem = CarbRatioEditor.Item(rateIndex: rate, timeIndex: time)
  164. items.append(newItem)
  165. }
  166. func saveCarbRatios() {
  167. guard hasChanges else { return }
  168. let schedule = items.enumerated().map { _, item -> CarbRatioEntry in
  169. let fotmatter = DateFormatter()
  170. fotmatter.timeZone = TimeZone(secondsFromGMT: 0)
  171. fotmatter.dateFormat = "HH:mm:ss"
  172. let date = Date(timeIntervalSince1970: self.timeValues[item.timeIndex])
  173. let minutes = Int(date.timeIntervalSince1970 / 60)
  174. let rate = self.rateValues[item.rateIndex]
  175. return CarbRatioEntry(start: fotmatter.string(from: date), offset: minutes, ratio: rate)
  176. }
  177. let profile = CarbRatios(units: .grams, schedule: schedule)
  178. saveProfile(profile)
  179. initialItems = items.map { CarbRatioEditor.Item(rateIndex: $0.rateIndex, timeIndex: $0.timeIndex) }
  180. }
  181. // func validate() {
  182. // DispatchQueue.main.async {
  183. // let uniq = Array(Set(self.items))
  184. // let sorted = uniq.sorted { $0.timeIndex < $1.timeIndex }
  185. // sorted.first?.timeIndex = 0
  186. // if self.items != sorted {
  187. // self.items = sorted
  188. // }
  189. // }
  190. // }
  191. func saveProfile(_ profile: CarbRatios) {
  192. storage.save(profile, as: OpenAPS.Settings.carbRatios)
  193. }
  194. }