MealSettingsRootView.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308
  1. import SwiftUI
  2. import Swinject
  3. extension MealSettings {
  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: AnyView?
  10. @State var hintLabel: String?
  11. @State private var decimalPlaceholder: Decimal = 0.0
  12. @State private var booleanPlaceholder: Bool = false
  13. @State private var displayPickerMaxCarbs: Bool = false
  14. @State private var displayPickerMaxFat: Bool = false
  15. @State private var displayPickerMaxProtein: Bool = false
  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 conversionFormatter: NumberFormatter {
  34. let formatter = NumberFormatter()
  35. formatter.numberStyle = .decimal
  36. formatter.maximumFractionDigits = 1
  37. return formatter
  38. }
  39. private var intFormater: NumberFormatter {
  40. let formatter = NumberFormatter()
  41. formatter.allowsFloats = false
  42. return formatter
  43. }
  44. private var formatter: NumberFormatter {
  45. let formatter = NumberFormatter()
  46. formatter.numberStyle = .decimal
  47. return formatter
  48. }
  49. var body: some View {
  50. Form {
  51. Section(
  52. header: Text("Limits per Entry"),
  53. content: {
  54. VStack {
  55. VStack {
  56. HStack {
  57. Text("Max Carbs")
  58. Spacer()
  59. Group {
  60. Text(state.maxCarbs.description)
  61. .foregroundColor(!displayPickerMaxCarbs ? .primary : .accentColor)
  62. Text(" g").foregroundColor(.secondary)
  63. }
  64. }
  65. .onTapGesture {
  66. displayPickerMaxCarbs.toggle()
  67. }
  68. }.padding(.top)
  69. if displayPickerMaxCarbs {
  70. let setting = PickerSettingsProvider.shared.settings.maxCarbs
  71. Picker(selection: $state.maxCarbs, label: Text("")) {
  72. ForEach(
  73. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  74. id: \.self
  75. ) { value in
  76. Text("\(value.description)").tag(value)
  77. }
  78. }
  79. .pickerStyle(WheelPickerStyle())
  80. .frame(maxWidth: .infinity)
  81. }
  82. if state.useFPUconversion {
  83. VStack {
  84. HStack {
  85. Text("Max Fat")
  86. Spacer()
  87. Group {
  88. Text(state.maxFat.description)
  89. .foregroundColor(!displayPickerMaxFat ? .primary : .accentColor)
  90. Text(" g").foregroundColor(.secondary)
  91. }
  92. }
  93. .onTapGesture {
  94. displayPickerMaxFat.toggle()
  95. }
  96. }
  97. .padding(.top)
  98. if displayPickerMaxFat {
  99. let setting = PickerSettingsProvider.shared.settings.maxFat
  100. Picker(selection: $state.maxFat, label: Text("")) {
  101. ForEach(
  102. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  103. id: \.self
  104. ) { value in
  105. Text("\(value.description)").tag(value)
  106. }
  107. }
  108. .pickerStyle(WheelPickerStyle())
  109. .frame(maxWidth: .infinity)
  110. }
  111. VStack {
  112. HStack {
  113. Text("Max Protein")
  114. Spacer()
  115. Group {
  116. Text(state.maxProtein.description)
  117. .foregroundColor(!displayPickerMaxProtein ? .primary : .accentColor)
  118. Text(" g").foregroundColor(.secondary)
  119. }
  120. }
  121. .onTapGesture {
  122. displayPickerMaxProtein.toggle()
  123. }
  124. }
  125. .padding(.top)
  126. if displayPickerMaxProtein {
  127. let setting = PickerSettingsProvider.shared.settings.maxProtein
  128. Picker(selection: $state.maxProtein, label: Text("")) {
  129. ForEach(
  130. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  131. id: \.self
  132. ) { value in
  133. Text("\(value.description)").tag(value)
  134. }
  135. }
  136. .pickerStyle(WheelPickerStyle())
  137. .frame(maxWidth: .infinity)
  138. }
  139. }
  140. HStack(alignment: .top) {
  141. Text(
  142. "Set limits for entering meals in treatment view."
  143. )
  144. .lineLimit(nil)
  145. .font(.footnote)
  146. .foregroundColor(.secondary)
  147. Spacer()
  148. Button(
  149. action: {
  150. hintLabel = "Limits per Entry"
  151. selectedVerboseHint =
  152. AnyView(Text("Lorem ipsum dolor sit amet, consetetur sadipscing elitr."))
  153. shouldDisplayHint.toggle()
  154. },
  155. label: {
  156. HStack {
  157. Image(systemName: "questionmark.circle")
  158. }
  159. }
  160. ).buttonStyle(BorderlessButtonStyle())
  161. }.padding(.top)
  162. }.padding(.bottom)
  163. }
  164. ).listRowBackground(Color.chart)
  165. SettingInputSection(
  166. decimalValue: $decimalPlaceholder,
  167. booleanValue: $state.useFPUconversion,
  168. shouldDisplayHint: $shouldDisplayHint,
  169. selectedVerboseHint: Binding(
  170. get: { selectedVerboseHint },
  171. set: {
  172. selectedVerboseHint = $0.map { AnyView($0) }
  173. hintLabel = "Display and Allow Fat and Protein Entries"
  174. }
  175. ),
  176. units: state.units,
  177. type: .boolean,
  178. label: "Display and Allow Fat and Protein Entries",
  179. miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
  180. verboseHint: Text(
  181. "Allows fat and protein to be converted into future carb equivalents using the Warsaw formula of kilocalories divided by 10.\n\nDefaults: Spread Duration: 8 h, Spread Interval: 30 min, FPU Factor: 0.5, Delay 60 min."
  182. ),
  183. headerText: "Fat and Protein"
  184. )
  185. if state.useFPUconversion {
  186. SettingInputSection(
  187. decimalValue: $state.delay,
  188. booleanValue: $booleanPlaceholder,
  189. shouldDisplayHint: $shouldDisplayHint,
  190. selectedVerboseHint: Binding(
  191. get: { selectedVerboseHint },
  192. set: {
  193. selectedVerboseHint = $0.map { AnyView($0) }
  194. hintLabel = "Fat and Protein Delay"
  195. }
  196. ),
  197. units: state.units,
  198. type: .decimal("delay"),
  199. label: "Fat and Protein Delay",
  200. miniHint: "Delay is time from now until the first future carb entry.",
  201. verboseHint: Text("X-Axis Interval Step… bla bla bla")
  202. )
  203. SettingInputSection(
  204. decimalValue: $state.timeCap,
  205. booleanValue: $booleanPlaceholder,
  206. shouldDisplayHint: $shouldDisplayHint,
  207. selectedVerboseHint: Binding(
  208. get: { selectedVerboseHint },
  209. set: {
  210. selectedVerboseHint = $0.map { AnyView($0) }
  211. hintLabel = "Maximum Duration (hours)"
  212. }
  213. ),
  214. units: state.units,
  215. type: .decimal("timeCap"),
  216. label: "Maximum Duration (hours)",
  217. miniHint: "Carb spread over a maximum number of hours (5-12).",
  218. verboseHint: Text(
  219. "This spreads the carb equivilants over a maximum duration setting that can be configured from 5-12 hours."
  220. )
  221. )
  222. SettingInputSection(
  223. decimalValue: $state.minuteInterval,
  224. booleanValue: $booleanPlaceholder,
  225. shouldDisplayHint: $shouldDisplayHint,
  226. selectedVerboseHint: Binding(
  227. get: { selectedVerboseHint },
  228. set: {
  229. selectedVerboseHint = $0.map { AnyView($0) }
  230. hintLabel = "Spread Interval (minutes)"
  231. }
  232. ),
  233. units: state.units,
  234. type: .decimal("minuteInterval"),
  235. label: "Spread Interval (minutes)",
  236. miniHint: "Interval in minutes is how many minutes are between entries.",
  237. verboseHint: Text(
  238. "Interval in minutes is how many minutes are between entries. The shorter the interval, the smoother the result. 10, 15, 20, 30, or 60 are reasonable choices."
  239. )
  240. )
  241. SettingInputSection(
  242. decimalValue: $state.individualAdjustmentFactor,
  243. booleanValue: $booleanPlaceholder,
  244. shouldDisplayHint: $shouldDisplayHint,
  245. selectedVerboseHint: Binding(
  246. get: { selectedVerboseHint },
  247. set: {
  248. selectedVerboseHint = $0.map { AnyView($0) }
  249. hintLabel = "Fat and Protein Factor"
  250. }
  251. ),
  252. units: state.units,
  253. type: .decimal("individualAdjustmentFactor"),
  254. label: "Fat and Protein Factor",
  255. miniHint: "Influences how many carb equivalents are recorded for fat and protein.",
  256. verboseHint: Text(
  257. "The Fat and Protein Factor influences how much effect the fat and protein has on the entries. 1.0 is full effect (original Warsaw Method) and 0.5 is half effect. Note that you may find that your normal carb ratio needs to increase to a larger number if you begin adding fat and protein entries. For this reason, it is best to start with a factor of about 0.5 to ease into it."
  258. )
  259. )
  260. }
  261. }
  262. .sheet(isPresented: $shouldDisplayHint) {
  263. SettingInputHintView(
  264. hintDetent: $hintDetent,
  265. shouldDisplayHint: $shouldDisplayHint,
  266. hintLabel: hintLabel ?? "",
  267. hintText: selectedVerboseHint ?? AnyView(EmptyView()),
  268. sheetTitle: "Help"
  269. )
  270. }
  271. .scrollContentBackground(.hidden).background(color)
  272. .onAppear(perform: configureView)
  273. .navigationBarTitle("Meal Settings")
  274. .navigationBarTitleDisplayMode(.automatic)
  275. }
  276. }
  277. }