SettingInputSection.swift 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279
  1. import SwiftUI
  2. struct SettingInputSection: View {
  3. enum SettingInputSectionType: Equatable {
  4. case decimal(String)
  5. case boolean
  6. case conditionalDecimal(String)
  7. static func == (lhs: SettingInputSectionType, rhs: SettingInputSectionType) -> Bool {
  8. switch (lhs, rhs) {
  9. case (.boolean, .boolean):
  10. return true
  11. case let (.decimal(lhsValue), .decimal(rhsValue)):
  12. return lhsValue == rhsValue
  13. case let (.conditionalDecimal(lhsValue), .conditionalDecimal(rhsValue)):
  14. return lhsValue == rhsValue
  15. default:
  16. return false
  17. }
  18. }
  19. }
  20. @Binding var decimalValue: Decimal
  21. @Binding var booleanValue: Bool
  22. @Binding var shouldDisplayHint: Bool
  23. @Binding var selectedVerboseHint: String?
  24. var units: GlucoseUnits
  25. var type: SettingInputSectionType
  26. var label: String
  27. var conditionalLabel: String?
  28. var miniHint: String
  29. var verboseHint: String
  30. var headerText: String?
  31. var footerText: String?
  32. // Access the shared PickerSettingsProvider instance
  33. @ObservedObject private var pickerSettingsProvider = PickerSettingsProvider.shared
  34. @State private var displayPicker: Bool = false
  35. @State private var displayConditionalPicker: Bool = false
  36. var body: some View {
  37. Section(
  38. content: {
  39. VStack {
  40. switch type {
  41. case let .decimal(key):
  42. if let setting = getPickerSetting(for: key) {
  43. VStack {
  44. HStack {
  45. Text(label)
  46. Spacer()
  47. Group {
  48. Text(
  49. units == .mmolL ? decimalValue.asMmolL.description : decimalValue
  50. .description
  51. )
  52. .foregroundColor(!displayPicker ? .primary : .accentColor)
  53. if setting.type == PickerSetting.PickerSettingType.glucose {
  54. Text(units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  55. } else if setting.type == PickerSetting.PickerSettingType.insulinUnit {
  56. Text(" U").foregroundColor(.secondary)
  57. } else if setting.type == PickerSetting.PickerSettingType.gramms {
  58. Text(" g").foregroundColor(.secondary)
  59. }
  60. }.onTapGesture {
  61. displayPicker.toggle()
  62. }
  63. }.padding(.top)
  64. if displayPicker {
  65. Picker(selection: $decimalValue, label: Text("")) {
  66. ForEach(pickerSettingsProvider.generatePickerValues(from: setting), id: \.self) { value in
  67. if setting.type == PickerSetting.PickerSettingType.glucose {
  68. let displayValue = units == .mgdL ? value : value.asMmolL
  69. Text("\(displayValue.description)").tag(value)
  70. } else {
  71. Text("\(value.description)").tag(value)
  72. }
  73. }
  74. }
  75. .pickerStyle(WheelPickerStyle())
  76. .frame(maxWidth: .infinity)
  77. }
  78. }
  79. }
  80. case .boolean:
  81. HStack {
  82. Toggle(isOn: $booleanValue) {
  83. Text(label)
  84. }
  85. }.padding(.top)
  86. case let .conditionalDecimal(key):
  87. HStack {
  88. Toggle(isOn: $booleanValue) {
  89. Text(label)
  90. }
  91. }.padding(.vertical)
  92. if $booleanValue.wrappedValue {
  93. if let setting = getPickerSetting(for: key) {
  94. VStack {
  95. HStack {
  96. Text(conditionalLabel ?? label)
  97. Spacer()
  98. Group {
  99. Text(
  100. units == .mmolL ? decimalValue.asMmolL
  101. .description : decimalValue.description
  102. )
  103. .foregroundColor(!displayPicker ? .primary : .accentColor)
  104. if setting.type == PickerSetting.PickerSettingType.glucose {
  105. Text(units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  106. } else if setting.type == PickerSetting.PickerSettingType.insulinUnit {
  107. Text(" U").foregroundColor(.secondary)
  108. } else if setting.type == PickerSetting.PickerSettingType.gramms {
  109. Text(" g").foregroundColor(.secondary)
  110. }
  111. }.onTapGesture {
  112. displayConditionalPicker.toggle()
  113. }
  114. }.padding(.top)
  115. if displayConditionalPicker {
  116. Picker(selection: $decimalValue, label: Text("")) {
  117. ForEach(
  118. pickerSettingsProvider.generatePickerValues(from: setting),
  119. id: \.self
  120. ) { value in
  121. if setting.type == PickerSetting.PickerSettingType.glucose {
  122. let displayValue = units == .mgdL ? value : value.asMmolL
  123. Text("\(displayValue.description)").tag(value)
  124. } else {
  125. Text("\(value.description)").tag(value)
  126. }
  127. }
  128. }
  129. .pickerStyle(WheelPickerStyle())
  130. .frame(maxWidth: .infinity)
  131. }
  132. }
  133. }
  134. }
  135. }
  136. HStack(alignment: .top) {
  137. Text(miniHint)
  138. .font(.footnote)
  139. .foregroundColor(.secondary)
  140. .lineLimit(nil)
  141. Spacer()
  142. Button(
  143. action: {
  144. shouldDisplayHint.toggle()
  145. selectedVerboseHint = shouldDisplayHint ? verboseHint : nil
  146. },
  147. label: {
  148. HStack {
  149. Image(systemName: "questionmark.circle")
  150. }
  151. }
  152. ).buttonStyle(BorderlessButtonStyle())
  153. }.padding(.vertical)
  154. }
  155. },
  156. header: {
  157. if let headerText = headerText {
  158. Text(headerText)
  159. }
  160. },
  161. footer: {
  162. if let footerText = footerText {
  163. Text(footerText)
  164. }
  165. }
  166. ).listRowBackground(Color.chart)
  167. }
  168. // Helper function to retrieve PickerSetting based on key
  169. private func getPickerSetting(for key: String) -> PickerSetting? {
  170. switch key {
  171. case "lowGlucose":
  172. return pickerSettingsProvider.settings.lowGlucose
  173. case "highGlucose":
  174. return pickerSettingsProvider.settings.highGlucose
  175. case "carbsRequiredThreshold":
  176. return pickerSettingsProvider.settings.carbsRequiredThreshold
  177. case "individualAdjustmentFactor":
  178. return pickerSettingsProvider.settings.individualAdjustmentFactor
  179. case "delay":
  180. return pickerSettingsProvider.settings.delay
  181. case "timeCap":
  182. return pickerSettingsProvider.settings.timeCap
  183. case "minuteInterval":
  184. return pickerSettingsProvider.settings.minuteInterval
  185. case "high":
  186. return pickerSettingsProvider.settings.high
  187. case "low":
  188. return pickerSettingsProvider.settings.low
  189. case "hours":
  190. return pickerSettingsProvider.settings.hours
  191. case "maxCarbs":
  192. return pickerSettingsProvider.settings.maxCarbs
  193. case "maxFat":
  194. return pickerSettingsProvider.settings.maxFat
  195. case "maxProtein":
  196. return pickerSettingsProvider.settings.maxProtein
  197. case "overrideFactor":
  198. return pickerSettingsProvider.settings.overrideFactor
  199. case "fattyMealFactor":
  200. return pickerSettingsProvider.settings.fattyMealFactor
  201. case "sweetMealFactor":
  202. return pickerSettingsProvider.settings.sweetMealFactor
  203. case "maxIOB":
  204. return pickerSettingsProvider.settings.maxIOB
  205. case "maxDailySafetyMultiplier":
  206. return pickerSettingsProvider.settings.maxDailySafetyMultiplier
  207. case "currentBasalSafetyMultiplier":
  208. return pickerSettingsProvider.settings.currentBasalSafetyMultiplier
  209. case "autosensMax":
  210. return pickerSettingsProvider.settings.autosensMax
  211. case "autosensMin":
  212. return pickerSettingsProvider.settings.autosensMin
  213. case "smbDeliveryRatio":
  214. return pickerSettingsProvider.settings.smbDeliveryRatio
  215. case "halfBasalExerciseTarget":
  216. return pickerSettingsProvider.settings.halfBasalExerciseTarget
  217. case "maxCOB":
  218. return pickerSettingsProvider.settings.maxCOB
  219. case "min5mCarbimpact":
  220. return pickerSettingsProvider.settings.min5mCarbimpact
  221. case "autotuneISFAdjustmentFraction":
  222. return pickerSettingsProvider.settings.autotuneISFAdjustmentFraction
  223. case "remainingCarbsFraction":
  224. return pickerSettingsProvider.settings.remainingCarbsFraction
  225. case "remainingCarbsCap":
  226. return pickerSettingsProvider.settings.remainingCarbsCap
  227. case "maxSMBBasalMinutes":
  228. return pickerSettingsProvider.settings.maxSMBBasalMinutes
  229. case "maxUAMSMBBasalMinutes":
  230. return pickerSettingsProvider.settings.maxUAMSMBBasalMinutes
  231. case "smbInterval":
  232. return pickerSettingsProvider.settings.smbInterval
  233. case "bolusIncrement":
  234. return pickerSettingsProvider.settings.bolusIncrement
  235. case "insulinPeakTime":
  236. return pickerSettingsProvider.settings.insulinPeakTime
  237. case "carbsReqThreshold":
  238. return pickerSettingsProvider.settings.carbsReqThreshold
  239. case "noisyCGMTargetMultiplier":
  240. return pickerSettingsProvider.settings.noisyCGMTargetMultiplier
  241. case "maxDeltaBGthreshold":
  242. return pickerSettingsProvider.settings.maxDeltaBGthreshold
  243. case "adjustmentFactor":
  244. return pickerSettingsProvider.settings.adjustmentFactor
  245. case "adjustmentFactorSigmoid":
  246. return pickerSettingsProvider.settings.adjustmentFactorSigmoid
  247. case "weightPercentage":
  248. return pickerSettingsProvider.settings.weightPercentage
  249. case "enableSMB_high_bg_target":
  250. return pickerSettingsProvider.settings.enableSMB_high_bg_target
  251. case "threshold_setting":
  252. return pickerSettingsProvider.settings.threshold_setting
  253. case "updateInterval":
  254. return pickerSettingsProvider.settings.updateInterval
  255. default:
  256. return nil
  257. }
  258. }
  259. }