SettingInputSection.swift 11 KB

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