MealSettingsRootView.swift 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284
  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: 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 displayPickerMaxCarbs: Bool = false
  14. @State private var displayPickerMaxFat: Bool = false
  15. @State private var displayPickerMaxProtein: Bool = false
  16. @Environment(\.colorScheme) var colorScheme
  17. @Environment(AppState.self) var appState
  18. private var conversionFormatter: NumberFormatter {
  19. let formatter = NumberFormatter()
  20. formatter.numberStyle = .decimal
  21. formatter.maximumFractionDigits = 1
  22. return formatter
  23. }
  24. private var intFormater: NumberFormatter {
  25. let formatter = NumberFormatter()
  26. formatter.allowsFloats = false
  27. return formatter
  28. }
  29. private var formatter: NumberFormatter {
  30. let formatter = NumberFormatter()
  31. formatter.numberStyle = .decimal
  32. return formatter
  33. }
  34. var body: some View {
  35. Form {
  36. Section(
  37. header: Text("Limits per Entry"),
  38. content: {
  39. VStack {
  40. VStack {
  41. HStack {
  42. Text("Max Carbs")
  43. Spacer()
  44. Group {
  45. Text(state.maxCarbs.description)
  46. .foregroundColor(!displayPickerMaxCarbs ? .primary : .accentColor)
  47. Text(" g").foregroundColor(.secondary)
  48. }
  49. }
  50. .onTapGesture {
  51. displayPickerMaxCarbs.toggle()
  52. }
  53. }.padding(.top)
  54. if displayPickerMaxCarbs {
  55. let setting = PickerSettingsProvider.shared.settings.maxCarbs
  56. Picker(selection: $state.maxCarbs, label: Text("")) {
  57. ForEach(
  58. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  59. id: \.self
  60. ) { value in
  61. Text("\(value.description)").tag(value)
  62. }
  63. }
  64. .pickerStyle(WheelPickerStyle())
  65. .frame(maxWidth: .infinity)
  66. }
  67. if state.useFPUconversion {
  68. VStack {
  69. HStack {
  70. Text("Max Fat")
  71. Spacer()
  72. Group {
  73. Text(state.maxFat.description)
  74. .foregroundColor(!displayPickerMaxFat ? .primary : .accentColor)
  75. Text(" g").foregroundColor(.secondary)
  76. }
  77. }
  78. .onTapGesture {
  79. displayPickerMaxFat.toggle()
  80. }
  81. }
  82. .padding(.top)
  83. if displayPickerMaxFat {
  84. let setting = PickerSettingsProvider.shared.settings.maxFat
  85. Picker(selection: $state.maxFat, label: Text("")) {
  86. ForEach(
  87. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  88. id: \.self
  89. ) { value in
  90. Text("\(value.description)").tag(value)
  91. }
  92. }
  93. .pickerStyle(WheelPickerStyle())
  94. .frame(maxWidth: .infinity)
  95. }
  96. VStack {
  97. HStack {
  98. Text("Max Protein")
  99. Spacer()
  100. Group {
  101. Text(state.maxProtein.description)
  102. .foregroundColor(!displayPickerMaxProtein ? .primary : .accentColor)
  103. Text(" g").foregroundColor(.secondary)
  104. }
  105. }
  106. .onTapGesture {
  107. displayPickerMaxProtein.toggle()
  108. }
  109. }
  110. .padding(.top)
  111. if displayPickerMaxProtein {
  112. let setting = PickerSettingsProvider.shared.settings.maxProtein
  113. Picker(selection: $state.maxProtein, label: Text("")) {
  114. ForEach(
  115. PickerSettingsProvider.shared.generatePickerValues(from: setting, units: state.units),
  116. id: \.self
  117. ) { value in
  118. Text("\(value.description)").tag(value)
  119. }
  120. }
  121. .pickerStyle(WheelPickerStyle())
  122. .frame(maxWidth: .infinity)
  123. }
  124. }
  125. HStack(alignment: .top) {
  126. Text(
  127. "Set limits for entering meals in treatment view."
  128. )
  129. .lineLimit(nil)
  130. .font(.footnote)
  131. .foregroundColor(.secondary)
  132. Spacer()
  133. Button(
  134. action: {
  135. hintLabel = "Limits per Entry"
  136. selectedVerboseHint = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr."
  137. shouldDisplayHint.toggle()
  138. },
  139. label: {
  140. HStack {
  141. Image(systemName: "questionmark.circle")
  142. }
  143. }
  144. ).buttonStyle(BorderlessButtonStyle())
  145. }.padding(.top)
  146. }.padding(.bottom)
  147. }
  148. ).listRowBackground(Color.chart)
  149. SettingInputSection(
  150. decimalValue: $decimalPlaceholder,
  151. booleanValue: $state.useFPUconversion,
  152. shouldDisplayHint: $shouldDisplayHint,
  153. selectedVerboseHint: Binding(
  154. get: { selectedVerboseHint },
  155. set: {
  156. selectedVerboseHint = $0
  157. hintLabel = "Display and Allow Fat and Protein Entries"
  158. }
  159. ),
  160. units: state.units,
  161. type: .boolean,
  162. label: "Display and Allow Fat and Protein Entries",
  163. miniHint: "Lorem ipsum dolor sit amet, consetetur sadipscing elitr.",
  164. verboseHint: "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.",
  165. headerText: "Fat and Protein"
  166. )
  167. if state.useFPUconversion {
  168. SettingInputSection(
  169. decimalValue: $state.delay,
  170. booleanValue: $booleanPlaceholder,
  171. shouldDisplayHint: $shouldDisplayHint,
  172. selectedVerboseHint: Binding(
  173. get: { selectedVerboseHint },
  174. set: {
  175. selectedVerboseHint = $0
  176. hintLabel = "Fat and Protein Delay"
  177. }
  178. ),
  179. units: state.units,
  180. type: .decimal("delay"),
  181. label: "Fat and Protein Delay",
  182. miniHint: "Delay is time from now until the first future carb entry.",
  183. verboseHint: "X-Axis Interval Step… bla bla bla"
  184. )
  185. SettingInputSection(
  186. decimalValue: $state.timeCap,
  187. booleanValue: $booleanPlaceholder,
  188. shouldDisplayHint: $shouldDisplayHint,
  189. selectedVerboseHint: Binding(
  190. get: { selectedVerboseHint },
  191. set: {
  192. selectedVerboseHint = $0
  193. hintLabel = "Maximum Duration (hours)"
  194. }
  195. ),
  196. units: state.units,
  197. type: .decimal("timeCap"),
  198. label: "Maximum Duration (hours)",
  199. miniHint: "Carb spread over a maximum number of hours (5-12).",
  200. verboseHint: "This spreads the carb equivilants over a maximum duration setting that can be configured from 5-12 hours."
  201. )
  202. SettingInputSection(
  203. decimalValue: $state.minuteInterval,
  204. booleanValue: $booleanPlaceholder,
  205. shouldDisplayHint: $shouldDisplayHint,
  206. selectedVerboseHint: Binding(
  207. get: { selectedVerboseHint },
  208. set: {
  209. selectedVerboseHint = $0
  210. hintLabel = "Spread Interval (minutes)"
  211. }
  212. ),
  213. units: state.units,
  214. type: .decimal("minuteInterval"),
  215. label: "Spread Interval (minutes)",
  216. miniHint: "Interval in minutes is how many minutes are between entries.",
  217. verboseHint: "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."
  218. )
  219. SettingInputSection(
  220. decimalValue: $state.individualAdjustmentFactor,
  221. booleanValue: $booleanPlaceholder,
  222. shouldDisplayHint: $shouldDisplayHint,
  223. selectedVerboseHint: Binding(
  224. get: { selectedVerboseHint },
  225. set: {
  226. selectedVerboseHint = $0
  227. hintLabel = "Fat and Protein Factor"
  228. }
  229. ),
  230. units: state.units,
  231. type: .decimal("individualAdjustmentFactor"),
  232. label: "Fat and Protein Factor",
  233. miniHint: "Influences how many carb equivalents are recorded for fat and protein.",
  234. verboseHint: "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."
  235. )
  236. }
  237. }
  238. .sheet(isPresented: $shouldDisplayHint) {
  239. SettingInputHintView(
  240. hintDetent: $hintDetent,
  241. shouldDisplayHint: $shouldDisplayHint,
  242. hintLabel: hintLabel ?? "",
  243. hintText: selectedVerboseHint ?? "",
  244. sheetTitle: "Help"
  245. )
  246. }
  247. .scrollContentBackground(.hidden).background(appState.trioBackgroundColor(for: colorScheme))
  248. .onAppear(perform: configureView)
  249. .navigationBarTitle("Meal Settings")
  250. .navigationBarTitleDisplayMode(.automatic)
  251. }
  252. }
  253. }