UserInterfaceSettingsRootView.swift 20 KB

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