SettingInputSection.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335
  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. if setting.type == PickerSetting.PickerSettingType.glucose {
  49. Text(
  50. units == .mmolL ? decimalValue.asMmolL.description : decimalValue
  51. .description
  52. )
  53. .foregroundColor(!displayPicker ? .primary : .accentColor)
  54. Text(units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  55. } else if setting.type == PickerSetting.PickerSettingType.factor {
  56. Text("\(decimalValue * 100)")
  57. .foregroundColor(!displayPicker ? .primary : .accentColor)
  58. Text(" %").foregroundColor(.secondary)
  59. } else if setting.type == PickerSetting.PickerSettingType.insulinUnit {
  60. Text("\(decimalValue)")
  61. .foregroundColor(!displayPicker ? .primary : .accentColor)
  62. Text(NSLocalizedString(" U", comment: "Insulin unit")).foregroundColor(.secondary)
  63. } else if setting.type == PickerSetting.PickerSettingType.gramms {
  64. Text("\(decimalValue)")
  65. .foregroundColor(!displayPicker ? .primary : .accentColor)
  66. Text(NSLocalizedString(" g", comment: "gram of carbs")).foregroundColor(.secondary)
  67. } else if setting.type == PickerSetting.PickerSettingType.minute {
  68. Text("\(decimalValue)")
  69. .foregroundColor(!displayPicker ? .primary : .accentColor)
  70. Text(" min").foregroundColor(.secondary)
  71. } else if setting.type == PickerSetting.PickerSettingType.hour {
  72. Text("\(decimalValue)")
  73. .foregroundColor(!displayPicker ? .primary : .accentColor)
  74. Text(" hr").foregroundColor(.secondary)
  75. }
  76. }.onTapGesture {
  77. displayPicker.toggle()
  78. }
  79. }.padding(.top)
  80. if displayPicker {
  81. Picker(selection: $decimalValue, label: Text("")) {
  82. ForEach(
  83. pickerSettingsProvider.generatePickerValues(from: setting),
  84. id: \.self
  85. ) { value in
  86. if setting.type == PickerSetting.PickerSettingType.glucose {
  87. let displayValue = units == .mgdL ? value : value.asMmolL
  88. Text("\(displayValue.description)").tag(value)
  89. } else if setting.type == PickerSetting.PickerSettingType.factor {
  90. let factorValue = value * 100
  91. Text("\(factorValue.description)").tag(value)
  92. } else {
  93. Text("\(value.description)").tag(value)
  94. }
  95. }
  96. }
  97. .pickerStyle(WheelPickerStyle())
  98. .frame(maxWidth: .infinity)
  99. }
  100. }
  101. }
  102. case .boolean:
  103. HStack {
  104. Toggle(isOn: $booleanValue) {
  105. Text(label)
  106. }
  107. }.padding(.top)
  108. case let .conditionalDecimal(key):
  109. HStack {
  110. Toggle(isOn: $booleanValue) {
  111. Text(label)
  112. }
  113. }.padding(.vertical)
  114. if $booleanValue.wrappedValue {
  115. if let setting = getPickerSetting(for: key) {
  116. VStack {
  117. HStack {
  118. Text(conditionalLabel ?? label)
  119. Spacer()
  120. Group {
  121. if setting.type == PickerSetting.PickerSettingType.glucose {
  122. Text(
  123. units == .mmolL ? decimalValue.asMmolL.description : decimalValue
  124. .description
  125. )
  126. .foregroundColor(!displayPicker ? .primary : .accentColor)
  127. Text(units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  128. } else if setting.type == PickerSetting.PickerSettingType.factor {
  129. Text("\(decimalValue * 100)")
  130. .foregroundColor(!displayPicker ? .primary : .accentColor)
  131. Text(" %").foregroundColor(.secondary)
  132. } else if setting.type == PickerSetting.PickerSettingType.insulinUnit {
  133. Text("\(decimalValue)")
  134. .foregroundColor(!displayPicker ? .primary : .accentColor)
  135. Text(NSLocalizedString(" U", comment: "Insulin unit")).foregroundColor(.secondary)
  136. } else if setting.type == PickerSetting.PickerSettingType.gramms {
  137. Text("\(decimalValue)")
  138. .foregroundColor(!displayPicker ? .primary : .accentColor)
  139. Text(NSLocalizedString(" g", comment: "gram of carbs"))
  140. .foregroundColor(.secondary)
  141. } else if setting.type == PickerSetting.PickerSettingType.minute {
  142. Text("\(decimalValue)")
  143. .foregroundColor(!displayPicker ? .primary : .accentColor)
  144. Text(" min").foregroundColor(.secondary)
  145. } else if setting.type == PickerSetting.PickerSettingType.hour {
  146. Text("\(decimalValue)")
  147. .foregroundColor(!displayPicker ? .primary : .accentColor)
  148. Text(" hr").foregroundColor(.secondary)
  149. }
  150. }.onTapGesture {
  151. displayConditionalPicker.toggle()
  152. }
  153. }.padding(.top)
  154. if displayConditionalPicker {
  155. Picker(selection: $decimalValue, label: Text("")) {
  156. ForEach(
  157. pickerSettingsProvider.generatePickerValues(from: setting),
  158. id: \.self
  159. ) { value in
  160. if setting.type == PickerSetting.PickerSettingType.glucose {
  161. let displayValue = units == .mgdL ? value : value.asMmolL
  162. Text("\(displayValue.description) \(units.rawValue)").tag(value)
  163. } else if setting.type == PickerSetting.PickerSettingType.factor {
  164. let factorValue = value * 100
  165. Text("\(factorValue.description) %").tag(value)
  166. } else {
  167. Text("\(value.description)").tag(value)
  168. }
  169. }
  170. }
  171. .pickerStyle(WheelPickerStyle())
  172. .frame(maxWidth: .infinity)
  173. }
  174. }
  175. }
  176. }
  177. }
  178. HStack(alignment: .top) {
  179. Text(miniHint)
  180. .font(.footnote)
  181. .foregroundColor(.secondary)
  182. .lineLimit(nil)
  183. Spacer()
  184. Button(
  185. action: {
  186. shouldDisplayHint.toggle()
  187. selectedVerboseHint = shouldDisplayHint ? verboseHint : nil
  188. },
  189. label: {
  190. HStack {
  191. Image(systemName: "questionmark.circle")
  192. }
  193. }
  194. ).buttonStyle(BorderlessButtonStyle())
  195. }.padding(.vertical)
  196. }
  197. },
  198. header: {
  199. if let headerText = headerText {
  200. Text(headerText)
  201. }
  202. },
  203. footer: {
  204. if let footerText = footerText {
  205. Text(footerText)
  206. }
  207. }
  208. ).listRowBackground(Color.chart)
  209. }
  210. // Helper function to retrieve PickerSetting based on key
  211. private func getPickerSetting(for key: String) -> PickerSetting? {
  212. switch key {
  213. case "lowGlucose":
  214. return pickerSettingsProvider.settings.lowGlucose
  215. case "highGlucose":
  216. return pickerSettingsProvider.settings.highGlucose
  217. case "carbsRequiredThreshold":
  218. return pickerSettingsProvider.settings.carbsRequiredThreshold
  219. case "individualAdjustmentFactor":
  220. return pickerSettingsProvider.settings.individualAdjustmentFactor
  221. case "delay":
  222. return pickerSettingsProvider.settings.delay
  223. case "timeCap":
  224. return pickerSettingsProvider.settings.timeCap
  225. case "minuteInterval":
  226. return pickerSettingsProvider.settings.minuteInterval
  227. case "high":
  228. return pickerSettingsProvider.settings.high
  229. case "low":
  230. return pickerSettingsProvider.settings.low
  231. case "hours":
  232. return pickerSettingsProvider.settings.hours
  233. case "maxCarbs":
  234. return pickerSettingsProvider.settings.maxCarbs
  235. case "maxFat":
  236. return pickerSettingsProvider.settings.maxFat
  237. case "maxProtein":
  238. return pickerSettingsProvider.settings.maxProtein
  239. case "overrideFactor":
  240. return pickerSettingsProvider.settings.overrideFactor
  241. case "fattyMealFactor":
  242. return pickerSettingsProvider.settings.fattyMealFactor
  243. case "sweetMealFactor":
  244. return pickerSettingsProvider.settings.sweetMealFactor
  245. case "maxIOB":
  246. return pickerSettingsProvider.settings.maxIOB
  247. case "maxDailySafetyMultiplier":
  248. return pickerSettingsProvider.settings.maxDailySafetyMultiplier
  249. case "currentBasalSafetyMultiplier":
  250. return pickerSettingsProvider.settings.currentBasalSafetyMultiplier
  251. case "autosensMax":
  252. return pickerSettingsProvider.settings.autosensMax
  253. case "autosensMin":
  254. return pickerSettingsProvider.settings.autosensMin
  255. case "smbDeliveryRatio":
  256. return pickerSettingsProvider.settings.smbDeliveryRatio
  257. case "halfBasalExerciseTarget":
  258. return pickerSettingsProvider.settings.halfBasalExerciseTarget
  259. case "maxCOB":
  260. return pickerSettingsProvider.settings.maxCOB
  261. case "min5mCarbimpact":
  262. return pickerSettingsProvider.settings.min5mCarbimpact
  263. case "autotuneISFAdjustmentFraction":
  264. return pickerSettingsProvider.settings.autotuneISFAdjustmentFraction
  265. case "remainingCarbsFraction":
  266. return pickerSettingsProvider.settings.remainingCarbsFraction
  267. case "remainingCarbsCap":
  268. return pickerSettingsProvider.settings.remainingCarbsCap
  269. case "maxSMBBasalMinutes":
  270. return pickerSettingsProvider.settings.maxSMBBasalMinutes
  271. case "maxUAMSMBBasalMinutes":
  272. return pickerSettingsProvider.settings.maxUAMSMBBasalMinutes
  273. case "smbInterval":
  274. return pickerSettingsProvider.settings.smbInterval
  275. case "bolusIncrement":
  276. return pickerSettingsProvider.settings.bolusIncrement
  277. case "insulinPeakTime":
  278. return pickerSettingsProvider.settings.insulinPeakTime
  279. case "carbsReqThreshold":
  280. return pickerSettingsProvider.settings.carbsReqThreshold
  281. case "noisyCGMTargetMultiplier":
  282. return pickerSettingsProvider.settings.noisyCGMTargetMultiplier
  283. case "maxDeltaBGthreshold":
  284. return pickerSettingsProvider.settings.maxDeltaBGthreshold
  285. case "adjustmentFactor":
  286. return pickerSettingsProvider.settings.adjustmentFactor
  287. case "adjustmentFactorSigmoid":
  288. return pickerSettingsProvider.settings.adjustmentFactorSigmoid
  289. case "weightPercentage":
  290. return pickerSettingsProvider.settings.weightPercentage
  291. case "enableSMB_high_bg_target":
  292. return pickerSettingsProvider.settings.enableSMB_high_bg_target
  293. case "threshold_setting":
  294. return pickerSettingsProvider.settings.threshold_setting
  295. case "updateInterval":
  296. return pickerSettingsProvider.settings.updateInterval
  297. case "dia":
  298. return pickerSettingsProvider.settings.dia
  299. case "maxBolus":
  300. return pickerSettingsProvider.settings.maxBolus
  301. case "maxBasal":
  302. return pickerSettingsProvider.settings.maxBasal
  303. default:
  304. return nil
  305. }
  306. }
  307. }