UserInterfaceSettingsRootView.swift 17 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367
  1. import SwiftUI
  2. import Swinject
  3. extension UserInterfaceSettings {
  4. struct RootView: BaseView {
  5. let resolver: Resolver
  6. @StateObject var state = StateModel()
  7. @State private var shouldDisplayHint: Bool = false
  8. @State var hintDetent = PresentationDetent.large
  9. @State var selectedVerboseHint: String?
  10. @State var hintLabel: String?
  11. @State private var decimalPlaceholder: Decimal = 0.0
  12. @State private var booleanPlaceholder: Bool = false
  13. @State private var displayPickerLowThreshold: Bool = false
  14. @State private var displayPickerHighThreshold: Bool = false
  15. @Environment(\.colorScheme) var colorScheme
  16. var color: LinearGradient {
  17. colorScheme == .dark ? LinearGradient(
  18. gradient: Gradient(colors: [
  19. Color.bgDarkBlue,
  20. Color.bgDarkerDarkBlue
  21. ]),
  22. startPoint: .top,
  23. endPoint: .bottom
  24. )
  25. :
  26. LinearGradient(
  27. gradient: Gradient(colors: [Color.gray.opacity(0.1)]),
  28. startPoint: .top,
  29. endPoint: .bottom
  30. )
  31. }
  32. private var glucoseFormatter: NumberFormatter {
  33. let formatter = NumberFormatter()
  34. formatter.numberStyle = .decimal
  35. formatter.maximumFractionDigits = 0
  36. if state.units == .mmolL {
  37. formatter.maximumFractionDigits = 1
  38. }
  39. formatter.roundingMode = .halfUp
  40. return formatter
  41. }
  42. private var carbsFormatter: NumberFormatter {
  43. let formatter = NumberFormatter()
  44. formatter.numberStyle = .decimal
  45. formatter.maximumFractionDigits = 0
  46. return formatter
  47. }
  48. var body: some View {
  49. Form {
  50. Section(
  51. header: Text("Home View Settings"),
  52. content: {
  53. VStack {
  54. Toggle("Show X-Axis Grid Lines", isOn: $state.xGridLines)
  55. Toggle("Show Y-Axis Grid Lines", isOn: $state.yGridLines)
  56. HStack(alignment: .top) {
  57. Text(
  58. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  59. )
  60. .font(.footnote)
  61. .foregroundColor(.secondary)
  62. .lineLimit(nil)
  63. Spacer()
  64. Button(
  65. action: {
  66. hintLabel = "Show Main Chart X- and Y-Axis Grid Lines"
  67. selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  68. shouldDisplayHint.toggle()
  69. },
  70. label: {
  71. HStack {
  72. Image(systemName: "questionmark.circle")
  73. }
  74. }
  75. ).buttonStyle(BorderlessButtonStyle())
  76. }.padding(.top)
  77. }.padding(.vertical)
  78. }
  79. ).listRowBackground(Color.chart)
  80. SettingInputSection(
  81. decimalValue: $decimalPlaceholder,
  82. booleanValue: $state.rulerMarks,
  83. shouldDisplayHint: $shouldDisplayHint,
  84. selectedVerboseHint: Binding(
  85. get: { selectedVerboseHint },
  86. set: {
  87. selectedVerboseHint = $0
  88. hintLabel = "Show Low and High Thresholds"
  89. }
  90. ),
  91. units: state.units,
  92. type: .boolean,
  93. label: "Show Low and High Thresholds",
  94. miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
  95. verboseHint: "Display Low and High Thresholds… bla bla bla"
  96. )
  97. if state.rulerMarks {
  98. Section {
  99. VStack {
  100. VStack {
  101. HStack {
  102. Text("Low Threshold")
  103. Spacer()
  104. Group {
  105. Text(state.units == .mgdL ? state.low.description : state.low.asMmolL.description)
  106. .foregroundColor(!displayPickerLowThreshold ? .primary : .accentColor)
  107. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  108. }
  109. }
  110. .onTapGesture {
  111. displayPickerLowThreshold.toggle()
  112. }
  113. }
  114. .padding(.top)
  115. if displayPickerLowThreshold {
  116. let setting = PickerSettingsProvider.shared.settings.low
  117. Picker(selection: $state.low, label: Text("")) {
  118. ForEach(
  119. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  120. id: \.self
  121. ) { value in
  122. let displayValue = state.units == .mgdL ? value : value.asMmolL
  123. Text("\(displayValue.description)").tag(value)
  124. }
  125. }
  126. .pickerStyle(WheelPickerStyle())
  127. .frame(maxWidth: .infinity)
  128. }
  129. VStack {
  130. HStack {
  131. Text("High Threshold")
  132. Spacer()
  133. Group {
  134. Text(state.units == .mgdL ? state.high.description : state.high.asMmolL.description)
  135. .foregroundColor(!displayPickerHighThreshold ? .primary : .accentColor)
  136. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  137. }
  138. }
  139. .onTapGesture {
  140. displayPickerHighThreshold.toggle()
  141. }
  142. }
  143. .padding(.top)
  144. if displayPickerHighThreshold {
  145. let setting = PickerSettingsProvider.shared.settings.high
  146. Picker(selection: $state.high, label: Text("")) {
  147. ForEach(
  148. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  149. id: \.self
  150. ) { value in
  151. let displayValue = state.units == .mgdL ? value : value.asMmolL
  152. Text("\(displayValue.description)").tag(value)
  153. }
  154. }
  155. .pickerStyle(WheelPickerStyle())
  156. .frame(maxWidth: .infinity)
  157. }
  158. HStack(alignment: .top) {
  159. Text(
  160. "Sets thresholds for low and high glucose in home view main chart and statistics view."
  161. )
  162. .lineLimit(nil)
  163. .font(.footnote)
  164. .foregroundColor(.secondary)
  165. Spacer()
  166. Button(
  167. action: {
  168. hintLabel = "Low and High Thresholds"
  169. selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  170. shouldDisplayHint.toggle()
  171. },
  172. label: {
  173. HStack {
  174. Image(systemName: "questionmark.circle")
  175. }
  176. }
  177. ).buttonStyle(BorderlessButtonStyle())
  178. }.padding(.top)
  179. }.padding(.bottom)
  180. }.listRowBackground(Color.chart)
  181. }
  182. Section {
  183. VStack {
  184. Picker(
  185. selection: $state.forecastDisplayType,
  186. label: Text("Forecast Display Type")
  187. ) {
  188. ForEach(ForecastDisplayType.allCases) { selection in
  189. Text(selection.displayName).tag(selection)
  190. }
  191. }.padding(.top)
  192. HStack(alignment: .top) {
  193. Text(
  194. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  195. )
  196. .font(.footnote)
  197. .foregroundColor(.secondary)
  198. .lineLimit(nil)
  199. Spacer()
  200. Button(
  201. action: {
  202. hintLabel = "Forecast Display Type"
  203. selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  204. shouldDisplayHint.toggle()
  205. },
  206. label: {
  207. HStack {
  208. Image(systemName: "questionmark.circle")
  209. }
  210. }
  211. ).buttonStyle(BorderlessButtonStyle())
  212. }.padding(.top)
  213. }.padding(.bottom)
  214. }.listRowBackground(Color.chart)
  215. SettingInputSection(
  216. decimalValue: $state.hours,
  217. booleanValue: $booleanPlaceholder,
  218. shouldDisplayHint: $shouldDisplayHint,
  219. selectedVerboseHint: Binding(
  220. get: { selectedVerboseHint },
  221. set: {
  222. selectedVerboseHint = $0
  223. hintLabel = "X-Axis Interval Step"
  224. }
  225. ),
  226. units: state.units,
  227. type: .decimal("hours"),
  228. label: "X-Axis Interval Step",
  229. miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
  230. verboseHint: "X-Axis Interval Step… bla bla bla"
  231. )
  232. Section {
  233. VStack {
  234. Picker(
  235. selection: $state.totalInsulinDisplayType,
  236. label: Text("Total Insulin Display Type")
  237. ) {
  238. ForEach(TotalInsulinDisplayType.allCases) { selection in
  239. Text(selection.displayName).tag(selection)
  240. }
  241. }.padding(.top)
  242. HStack(alignment: .top) {
  243. Text(
  244. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  245. )
  246. .font(.footnote)
  247. .foregroundColor(.secondary)
  248. .lineLimit(nil)
  249. Spacer()
  250. Button(
  251. action: {
  252. hintLabel = "Total Insulin Display Type"
  253. selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  254. shouldDisplayHint.toggle()
  255. },
  256. label: {
  257. HStack {
  258. Image(systemName: "questionmark.circle")
  259. }
  260. }
  261. ).buttonStyle(BorderlessButtonStyle())
  262. }.padding(.top)
  263. }.padding(.bottom)
  264. }.listRowBackground(Color.chart)
  265. // TODO: this needs to be a picker: mmol/L or %
  266. SettingInputSection(
  267. decimalValue: $decimalPlaceholder,
  268. booleanValue: $state.overrideHbA1cUnit,
  269. shouldDisplayHint: $shouldDisplayHint,
  270. selectedVerboseHint: Binding(
  271. get: { selectedVerboseHint },
  272. set: {
  273. selectedVerboseHint = $0
  274. hintLabel = "Override HbA1c Unit"
  275. }
  276. ),
  277. units: state.units,
  278. type: .boolean,
  279. label: "Override HbA1c Unit",
  280. miniHint: "Display HbA1c in mmol/L or %. Default is percent.",
  281. verboseHint: "Override HbA1c Unit… bla bla bla",
  282. headerText: "Trio Statistics"
  283. )
  284. // TODO: this needs to be a picker: choose bar chart or progress bar
  285. SettingInputSection(
  286. decimalValue: $decimalPlaceholder,
  287. booleanValue: $state.oneDimensionalGraph,
  288. shouldDisplayHint: $shouldDisplayHint,
  289. selectedVerboseHint: Binding(
  290. get: { selectedVerboseHint },
  291. set: {
  292. selectedVerboseHint = $0
  293. hintLabel = "Standing / Laying TIR Chart"
  294. }
  295. ),
  296. units: state.units,
  297. type: .boolean,
  298. label: "Standing / Laying TIR Chart",
  299. miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
  300. verboseHint: "Standing / Laying TIR Chart… bla bla bla"
  301. )
  302. SettingInputSection(
  303. decimalValue: $state.carbsRequiredThreshold,
  304. booleanValue: $state.showCarbsRequiredBadge,
  305. shouldDisplayHint: $shouldDisplayHint,
  306. selectedVerboseHint: Binding(
  307. get: { selectedVerboseHint },
  308. set: {
  309. selectedVerboseHint = $0
  310. hintLabel = "Show Carbs Required Badge"
  311. }
  312. ),
  313. units: state.units,
  314. type: .conditionalDecimal("carbsRequiredThreshold"),
  315. label: "Show Carbs Required Badge",
  316. conditionalLabel: "Carbs Required Threshold",
  317. miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
  318. verboseHint: "Show Carbs Required Badge… bla bla bla",
  319. headerText: "Carbs Required Badge"
  320. )
  321. }
  322. .sheet(isPresented: $shouldDisplayHint) {
  323. SettingInputHintView(
  324. hintDetent: $hintDetent,
  325. shouldDisplayHint: $shouldDisplayHint,
  326. hintLabel: hintLabel ?? "",
  327. hintText: selectedVerboseHint ?? "",
  328. sheetTitle: "Help"
  329. )
  330. }
  331. .scrollContentBackground(.hidden).background(color)
  332. .onAppear(perform: configureView)
  333. .navigationBarTitle("User Interface")
  334. .navigationBarTitleDisplayMode(.automatic)
  335. }
  336. }
  337. }