UserInterfaceSettingsRootView.swift 20 KB

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