SettingInputSection.swift 10.0 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255
  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 "maxFat":
  101. return pickerSettingsProvider.settings.maxFat
  102. case "maxProtein":
  103. return pickerSettingsProvider.settings.maxProtein
  104. case "overrideFactor":
  105. return pickerSettingsProvider.settings.overrideFactor
  106. case "fattyMealFactor":
  107. return pickerSettingsProvider.settings.fattyMealFactor
  108. case "sweetMealFactor":
  109. return pickerSettingsProvider.settings.sweetMealFactor
  110. case "maxIOB":
  111. return pickerSettingsProvider.settings.maxIOB
  112. case "maxDailySafetyMultiplier":
  113. return pickerSettingsProvider.settings.maxDailySafetyMultiplier
  114. case "currentBasalSafetyMultiplier":
  115. return pickerSettingsProvider.settings.currentBasalSafetyMultiplier
  116. case "autosensMax":
  117. return pickerSettingsProvider.settings.autosensMax
  118. case "autosensMin":
  119. return pickerSettingsProvider.settings.autosensMin
  120. case "smbDeliveryRatio":
  121. return pickerSettingsProvider.settings.smbDeliveryRatio
  122. case "halfBasalExerciseTarget":
  123. return pickerSettingsProvider.settings.halfBasalExerciseTarget
  124. case "maxCOB":
  125. return pickerSettingsProvider.settings.maxCOB
  126. case "min5mCarbimpact":
  127. return pickerSettingsProvider.settings.min5mCarbimpact
  128. case "remainingCarbsFraction":
  129. return pickerSettingsProvider.settings.remainingCarbsFraction
  130. case "remainingCarbsCap":
  131. return pickerSettingsProvider.settings.remainingCarbsCap
  132. case "maxSMBBasalMinutes":
  133. return pickerSettingsProvider.settings.maxSMBBasalMinutes
  134. case "maxUAMSMBBasalMinutes":
  135. return pickerSettingsProvider.settings.maxUAMSMBBasalMinutes
  136. case "smbInterval":
  137. return pickerSettingsProvider.settings.smbInterval
  138. case "bolusIncrement":
  139. return pickerSettingsProvider.settings.bolusIncrement
  140. case "insulinPeakTime":
  141. return pickerSettingsProvider.settings.insulinPeakTime
  142. case "carbsReqThreshold":
  143. return pickerSettingsProvider.settings.carbsReqThreshold
  144. case "noisyCGMTargetMultiplier":
  145. return pickerSettingsProvider.settings.noisyCGMTargetMultiplier
  146. case "maxDeltaBGthreshold":
  147. return pickerSettingsProvider.settings.maxDeltaBGthreshold
  148. case "adjustmentFactor":
  149. return pickerSettingsProvider.settings.adjustmentFactor
  150. case "adjustmentFactorSigmoid":
  151. return pickerSettingsProvider.settings.adjustmentFactorSigmoid
  152. case "weightPercentage":
  153. return pickerSettingsProvider.settings.weightPercentage
  154. case "enableSMB_high_bg_target":
  155. return pickerSettingsProvider.settings.enableSMB_high_bg_target
  156. case "threshold_setting":
  157. return pickerSettingsProvider.settings.threshold_setting
  158. case "updateInterval":
  159. return pickerSettingsProvider.settings.updateInterval
  160. case "dia":
  161. return pickerSettingsProvider.settings.dia
  162. case "maxBolus":
  163. return pickerSettingsProvider.settings.maxBolus
  164. case "maxBasal":
  165. return pickerSettingsProvider.settings.maxBasal
  166. default:
  167. return nil
  168. }
  169. }
  170. private func pickerView(
  171. label: String,
  172. displayPicker: Binding<Bool>,
  173. setting: PickerSetting,
  174. decimalValue: Binding<Decimal>
  175. ) -> some View {
  176. VStack {
  177. HStack {
  178. Text(label)
  179. Spacer()
  180. displayText(for: setting, decimalValue: decimalValue.wrappedValue)
  181. .foregroundColor(!displayPicker.wrappedValue ? .primary : .accentColor)
  182. .onTapGesture {
  183. displayPicker.wrappedValue.toggle()
  184. }
  185. }.padding(.top)
  186. if displayPicker.wrappedValue {
  187. Picker(selection: decimalValue, label: Text(label)) {
  188. ForEach(pickerSettingsProvider.generatePickerValues(from: setting, units: self.units), id: \.self) { value in
  189. displayText(for: setting, decimalValue: value).tag(value)
  190. }
  191. }
  192. .pickerStyle(WheelPickerStyle())
  193. .frame(maxWidth: .infinity)
  194. }
  195. }
  196. }
  197. private func displayText(for setting: PickerSetting, decimalValue: Decimal) -> Text {
  198. switch setting.type {
  199. case .glucose:
  200. let displayValue = units == .mmolL ? decimalValue.asMmolL : decimalValue
  201. return Text("\(displayValue.description) \(units.rawValue)")
  202. case .factor:
  203. return Text("\(decimalValue * 100) %")
  204. case .insulinUnit:
  205. return Text("\(decimalValue) U")
  206. case .gram:
  207. return Text("\(decimalValue) g")
  208. case .minute:
  209. return Text("\(decimalValue) min")
  210. case .hour:
  211. return Text("\(decimalValue) hr")
  212. }
  213. }
  214. private func toggleView(label: String, isOn: Binding<Bool>) -> some View {
  215. HStack {
  216. Toggle(isOn: isOn) {
  217. Text(label)
  218. }
  219. }.padding(.top)
  220. }
  221. private func hintSection(miniHint: String, shouldDisplayHint: Binding<Bool>, verboseHint: VerboseHint) -> some View {
  222. HStack(alignment: .center) {
  223. Text(miniHint)
  224. .font(.footnote)
  225. .foregroundColor(.secondary)
  226. .lineLimit(nil)
  227. Spacer()
  228. Button(action: {
  229. shouldDisplayHint.wrappedValue.toggle()
  230. selectedVerboseHint = shouldDisplayHint.wrappedValue ? verboseHint : nil
  231. }) {
  232. HStack {
  233. Image(systemName: "questionmark.circle")
  234. }
  235. }
  236. .buttonStyle(BorderlessButtonStyle())
  237. }.padding(.vertical)
  238. }
  239. }