UserInterfaceSettingsRootView.swift 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407
  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. header: Text("Home View Settings"),
  89. content: {
  90. VStack {
  91. Toggle("Show X-Axis Grid Lines", isOn: $state.xGridLines)
  92. Toggle("Show Y-Axis Grid Lines", isOn: $state.yGridLines)
  93. HStack(alignment: .top) {
  94. Text(
  95. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  96. )
  97. .font(.footnote)
  98. .foregroundColor(.secondary)
  99. .lineLimit(nil)
  100. Spacer()
  101. Button(
  102. action: {
  103. hintLabel = "Show Main Chart X- and Y-Axis Grid Lines"
  104. selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  105. shouldDisplayHint.toggle()
  106. },
  107. label: {
  108. HStack {
  109. Image(systemName: "questionmark.circle")
  110. }
  111. }
  112. ).buttonStyle(BorderlessButtonStyle())
  113. }.padding(.top)
  114. }.padding(.vertical)
  115. }
  116. ).listRowBackground(Color.chart)
  117. SettingInputSection(
  118. decimalValue: $decimalPlaceholder,
  119. booleanValue: $state.rulerMarks,
  120. shouldDisplayHint: $shouldDisplayHint,
  121. selectedVerboseHint: Binding(
  122. get: { selectedVerboseHint },
  123. set: {
  124. selectedVerboseHint = $0
  125. hintLabel = "Show Low and High Thresholds"
  126. }
  127. ),
  128. units: state.units,
  129. type: .boolean,
  130. label: "Show Low and High Thresholds",
  131. miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
  132. verboseHint: "Display Low and High Thresholds… bla bla bla"
  133. )
  134. if state.rulerMarks {
  135. Section {
  136. VStack {
  137. VStack {
  138. HStack {
  139. Text("Low Threshold")
  140. Spacer()
  141. Group {
  142. Text(state.units == .mgdL ? state.low.description : state.low.asMmolL.description)
  143. .foregroundColor(!displayPickerLowThreshold ? .primary : .accentColor)
  144. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  145. }
  146. }
  147. .onTapGesture {
  148. displayPickerLowThreshold.toggle()
  149. }
  150. }
  151. .padding(.top)
  152. if displayPickerLowThreshold {
  153. let setting = PickerSettingsProvider.shared.settings.low
  154. Picker(selection: $state.low, label: Text("")) {
  155. ForEach(
  156. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  157. id: \.self
  158. ) { value in
  159. let displayValue = state.units == .mgdL ? value : value.asMmolL
  160. Text("\(displayValue.description)").tag(value)
  161. }
  162. }
  163. .pickerStyle(WheelPickerStyle())
  164. .frame(maxWidth: .infinity)
  165. }
  166. VStack {
  167. HStack {
  168. Text("High Threshold")
  169. Spacer()
  170. Group {
  171. Text(state.units == .mgdL ? state.high.description : state.high.asMmolL.description)
  172. .foregroundColor(!displayPickerHighThreshold ? .primary : .accentColor)
  173. Text(state.units == .mgdL ? " mg/dL" : " mmol/L").foregroundColor(.secondary)
  174. }
  175. }
  176. .onTapGesture {
  177. displayPickerHighThreshold.toggle()
  178. }
  179. }
  180. .padding(.top)
  181. if displayPickerHighThreshold {
  182. let setting = PickerSettingsProvider.shared.settings.high
  183. Picker(selection: $state.high, label: Text("")) {
  184. ForEach(
  185. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  186. id: \.self
  187. ) { value in
  188. let displayValue = state.units == .mgdL ? value : value.asMmolL
  189. Text("\(displayValue.description)").tag(value)
  190. }
  191. }
  192. .pickerStyle(WheelPickerStyle())
  193. .frame(maxWidth: .infinity)
  194. }
  195. HStack(alignment: .top) {
  196. Text(
  197. "Sets thresholds for low and high glucose in home view main chart and statistics view."
  198. )
  199. .lineLimit(nil)
  200. .font(.footnote)
  201. .foregroundColor(.secondary)
  202. Spacer()
  203. Button(
  204. action: {
  205. hintLabel = "Low and High Thresholds"
  206. selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  207. shouldDisplayHint.toggle()
  208. },
  209. label: {
  210. HStack {
  211. Image(systemName: "questionmark.circle")
  212. }
  213. }
  214. ).buttonStyle(BorderlessButtonStyle())
  215. }.padding(.top)
  216. }.padding(.bottom)
  217. }.listRowBackground(Color.chart)
  218. }
  219. Section {
  220. VStack {
  221. Picker(
  222. selection: $state.forecastDisplayType,
  223. label: Text("Forecast Display Type")
  224. ) {
  225. ForEach(ForecastDisplayType.allCases) { selection in
  226. Text(selection.displayName).tag(selection)
  227. }
  228. }.padding(.top)
  229. HStack(alignment: .top) {
  230. Text(
  231. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  232. )
  233. .font(.footnote)
  234. .foregroundColor(.secondary)
  235. .lineLimit(nil)
  236. Spacer()
  237. Button(
  238. action: {
  239. hintLabel = "Forecast Display Type"
  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. SettingInputSection(
  253. decimalValue: $state.hours,
  254. booleanValue: $booleanPlaceholder,
  255. shouldDisplayHint: $shouldDisplayHint,
  256. selectedVerboseHint: Binding(
  257. get: { selectedVerboseHint },
  258. set: {
  259. selectedVerboseHint = $0
  260. hintLabel = "X-Axis Interval Step"
  261. }
  262. ),
  263. units: state.units,
  264. type: .decimal("hours"),
  265. label: "X-Axis Interval Step",
  266. miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
  267. verboseHint: "X-Axis Interval Step… bla bla bla"
  268. )
  269. Section {
  270. VStack {
  271. Picker(
  272. selection: $state.totalInsulinDisplayType,
  273. label: Text("Total Insulin Display Type")
  274. ) {
  275. ForEach(TotalInsulinDisplayType.allCases) { selection in
  276. Text(selection.displayName).tag(selection)
  277. }
  278. }.padding(.top)
  279. HStack(alignment: .top) {
  280. Text(
  281. "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  282. )
  283. .font(.footnote)
  284. .foregroundColor(.secondary)
  285. .lineLimit(nil)
  286. Spacer()
  287. Button(
  288. action: {
  289. hintLabel = "Total Insulin Display Type"
  290. selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  291. shouldDisplayHint.toggle()
  292. },
  293. label: {
  294. HStack {
  295. Image(systemName: "questionmark.circle")
  296. }
  297. }
  298. ).buttonStyle(BorderlessButtonStyle())
  299. }.padding(.top)
  300. }.padding(.bottom)
  301. }.listRowBackground(Color.chart)
  302. // TODO: this needs to be a picker: mmol/L or %
  303. SettingInputSection(
  304. decimalValue: $decimalPlaceholder,
  305. booleanValue: $state.overrideHbA1cUnit,
  306. shouldDisplayHint: $shouldDisplayHint,
  307. selectedVerboseHint: Binding(
  308. get: { selectedVerboseHint },
  309. set: {
  310. selectedVerboseHint = $0
  311. hintLabel = "Override HbA1c Unit"
  312. }
  313. ),
  314. units: state.units,
  315. type: .boolean,
  316. label: "Override HbA1c Unit",
  317. miniHint: "Display HbA1c in mmol/L or %. Default is percent.",
  318. verboseHint: "Override HbA1c Unit… bla bla bla",
  319. headerText: "Trio Statistics"
  320. )
  321. // TODO: this needs to be a picker: choose bar chart or progress bar
  322. SettingInputSection(
  323. decimalValue: $decimalPlaceholder,
  324. booleanValue: $state.oneDimensionalGraph,
  325. shouldDisplayHint: $shouldDisplayHint,
  326. selectedVerboseHint: Binding(
  327. get: { selectedVerboseHint },
  328. set: {
  329. selectedVerboseHint = $0
  330. hintLabel = "Standing / Laying TIR Chart"
  331. }
  332. ),
  333. units: state.units,
  334. type: .boolean,
  335. label: "Standing / Laying TIR Chart",
  336. miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
  337. verboseHint: "Standing / Laying TIR Chart… bla bla bla"
  338. )
  339. SettingInputSection(
  340. decimalValue: $state.carbsRequiredThreshold,
  341. booleanValue: $state.showCarbsRequiredBadge,
  342. shouldDisplayHint: $shouldDisplayHint,
  343. selectedVerboseHint: Binding(
  344. get: { selectedVerboseHint },
  345. set: {
  346. selectedVerboseHint = $0
  347. hintLabel = "Show Carbs Required Badge"
  348. }
  349. ),
  350. units: state.units,
  351. type: .conditionalDecimal("carbsRequiredThreshold"),
  352. label: "Show Carbs Required Badge",
  353. conditionalLabel: "Carbs Required Threshold",
  354. miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
  355. verboseHint: "Show Carbs Required Badge… bla bla bla",
  356. headerText: "Carbs Required Badge"
  357. )
  358. }
  359. .sheet(isPresented: $shouldDisplayHint) {
  360. SettingInputHintView(
  361. hintDetent: $hintDetent,
  362. shouldDisplayHint: $shouldDisplayHint,
  363. hintLabel: hintLabel ?? "",
  364. hintText: selectedVerboseHint ?? "",
  365. sheetTitle: "Help"
  366. )
  367. }
  368. .scrollContentBackground(.hidden).background(color)
  369. .onAppear(perform: configureView)
  370. .navigationBarTitle("User Interface")
  371. .navigationBarTitleDisplayMode(.automatic)
  372. }
  373. }
  374. }