SettingInputSection.swift 10 KB

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