AlternativeBolusCalcRootView.swift 30 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766
  1. import Charts
  2. import CoreData
  3. import SwiftUI
  4. import Swinject
  5. extension Bolus {
  6. struct AlternativeBolusCalcRootView: BaseView {
  7. let resolver: Resolver
  8. let waitForSuggestion: Bool
  9. let fetch: Bool
  10. @StateObject var state: StateModel
  11. @State private var showInfo = false
  12. @State private var exceededMaxBolus = false
  13. @State private var keepForNextWiew: Bool = false
  14. @State private var calculatorDetent = PresentationDetent.medium
  15. private enum Config {
  16. static let dividerHeight: CGFloat = 2
  17. static let spacing: CGFloat = 3
  18. }
  19. @Environment(\.colorScheme) var colorScheme
  20. @FetchRequest(
  21. entity: Meals.entity(),
  22. sortDescriptors: [NSSortDescriptor(key: "createdAt", ascending: false)]
  23. ) var meal: FetchedResults<Meals>
  24. private var formatter: NumberFormatter {
  25. let formatter = NumberFormatter()
  26. formatter.numberStyle = .decimal
  27. formatter.maximumFractionDigits = 2
  28. return formatter
  29. }
  30. private var mealFormatter: NumberFormatter {
  31. let formatter = NumberFormatter()
  32. formatter.numberStyle = .decimal
  33. formatter.maximumFractionDigits = 1
  34. return formatter
  35. }
  36. private var gluoseFormatter: NumberFormatter {
  37. let formatter = NumberFormatter()
  38. formatter.numberStyle = .decimal
  39. if state.units == .mmolL {
  40. formatter.maximumFractionDigits = 1
  41. } else { formatter.maximumFractionDigits = 0 }
  42. return formatter
  43. }
  44. private var fractionDigits: Int {
  45. if state.units == .mmolL {
  46. return 1
  47. } else { return 0 }
  48. }
  49. var body: some View {
  50. Form {
  51. Section {
  52. if state.waitForSuggestion {
  53. Text("Please wait")
  54. } else {
  55. predictionChart
  56. }
  57. } header: { Text("Predictions") }
  58. Section {}
  59. if fetch {
  60. Section {
  61. mealEntries
  62. } header: { Text("Meal Summary") }
  63. }
  64. Section {
  65. HStack {
  66. Button(action: {
  67. showInfo.toggle()
  68. }, label: {
  69. Image(systemName: "info.circle")
  70. Text("Calculations")
  71. })
  72. .foregroundStyle(.blue)
  73. .font(.footnote)
  74. .buttonStyle(PlainButtonStyle())
  75. .frame(maxWidth: .infinity, alignment: .leading)
  76. if state.fattyMeals {
  77. Spacer()
  78. Toggle(isOn: $state.useFattyMealCorrectionFactor) {
  79. Text("Fatty Meal")
  80. }
  81. .toggleStyle(CheckboxToggleStyle())
  82. .font(.footnote)
  83. .onChange(of: state.useFattyMealCorrectionFactor) { _ in
  84. state.insulinCalculated = state.calculateInsulin()
  85. if state.useFattyMealCorrectionFactor {
  86. state.useSuperBolus = false
  87. }
  88. }
  89. }
  90. if state.sweetMeals {
  91. Spacer()
  92. Toggle(isOn: $state.useSuperBolus) {
  93. Text("Super Bolus")
  94. }
  95. .toggleStyle(CheckboxToggleStyleVariant())
  96. .font(.footnote)
  97. .onChange(of: state.useSuperBolus) { _ in
  98. state.insulinCalculated = state.calculateInsulin()
  99. if state.useSuperBolus {
  100. state.useFattyMealCorrectionFactor = false
  101. }
  102. }
  103. }
  104. }
  105. if state.waitForSuggestion {
  106. HStack {
  107. Text("Wait please").foregroundColor(.secondary)
  108. Spacer()
  109. ActivityIndicator(isAnimating: .constant(true), style: .medium) // fix iOS 15 bug
  110. }
  111. } else {
  112. HStack {
  113. Text("Recommended Bolus")
  114. Spacer()
  115. Text(
  116. formatter
  117. .string(from: Double(state.insulinCalculated) as NSNumber) ?? ""
  118. )
  119. Text(
  120. NSLocalizedString(" U", comment: "Unit in number of units delivered (keep the space character!)")
  121. ).foregroundColor(.secondary)
  122. }.contentShape(Rectangle())
  123. .onTapGesture { state.amount = state.insulinCalculated }
  124. }
  125. HStack {
  126. Text("Bolus")
  127. Spacer()
  128. DecimalTextField(
  129. "0",
  130. value: $state.amount,
  131. formatter: formatter,
  132. autofocus: false,
  133. cleanInput: true
  134. )
  135. Text(exceededMaxBolus ? "😵" : " U").foregroundColor(.secondary)
  136. }
  137. .onChange(of: state.amount) { newValue in
  138. if newValue > state.maxBolus {
  139. exceededMaxBolus = true
  140. } else {
  141. exceededMaxBolus = false
  142. }
  143. }
  144. } header: { Text("Bolus") }
  145. if state.amount > 0 {
  146. Section {
  147. Button {
  148. keepForNextWiew = true
  149. state.add()
  150. }
  151. label: { Text(exceededMaxBolus ? "Max Bolus exceeded!" : "Enact bolus") }
  152. .frame(maxWidth: .infinity, alignment: .center)
  153. .disabled(disabled)
  154. .listRowBackground(!disabled ? Color(.systemBlue) : Color(.systemGray4))
  155. .tint(.white)
  156. }
  157. }
  158. if state.amount <= 0 {
  159. Section {
  160. Button {
  161. keepForNextWiew = true
  162. state.showModal(for: nil)
  163. }
  164. label: { Text("Continue without bolus") }.frame(maxWidth: .infinity, alignment: .center)
  165. }
  166. }
  167. }.scrollContentBackground(.hidden).background(color)
  168. .blur(radius: showInfo ? 3 : 0)
  169. .navigationTitle("Enact Bolus")
  170. .navigationBarTitleDisplayMode(.inline)
  171. .navigationBarItems(
  172. leading: Button {
  173. carbsView()
  174. }
  175. label: {
  176. HStack {
  177. Image(systemName: "chevron.backward")
  178. Text("Meal")
  179. }
  180. },
  181. trailing: Button { state.hideModal() }
  182. label: { Text("Close") }
  183. )
  184. .onAppear {
  185. configureView {
  186. state.waitForSuggestionInitial = waitForSuggestion
  187. state.waitForSuggestion = waitForSuggestion
  188. state.insulinCalculated = state.calculateInsulin()
  189. }
  190. }
  191. .onDisappear {
  192. if fetch, hasFatOrProtein, !keepForNextWiew, state.useCalc {
  193. state.delete(deleteTwice: true, meal: meal)
  194. } else if fetch, !keepForNextWiew, state.useCalc {
  195. state.delete(deleteTwice: false, meal: meal)
  196. }
  197. }
  198. .sheet(isPresented: $showInfo) {
  199. calculationsDetailView
  200. .presentationDetents(
  201. [fetch ? .large : .fraction(0.9), .large],
  202. selection: $calculatorDetent
  203. )
  204. }
  205. }
  206. private var color: LinearGradient {
  207. colorScheme == .dark ? LinearGradient(
  208. gradient: Gradient(colors: [
  209. // RGB(10, 34, 55)
  210. Color(red: 0.03921568627, green: 0.1333333333, blue: 0.2156862745),
  211. // RGB(3, 15, 28)
  212. Color(red: 0.011, green: 0.058, blue: 0.109),
  213. // RGB(10, 34, 55)
  214. Color(red: 0.03921568627, green: 0.1333333333, blue: 0.2156862745)
  215. ]),
  216. startPoint: .top,
  217. endPoint: .bottom
  218. )
  219. :
  220. LinearGradient(gradient: Gradient(colors: [Color.gray.opacity(0.1)]), startPoint: .top, endPoint: .bottom)
  221. }
  222. var predictionChart: some View {
  223. ZStack {
  224. PredictionView(
  225. predictions: $state.predictions, units: $state.units, eventualBG: $state.evBG, target: $state.target,
  226. displayPredictions: $state.displayPredictions
  227. )
  228. }
  229. }
  230. var calcSettingsFirstRow: some View {
  231. GridRow {
  232. Group {
  233. Text("Carb Ratio:")
  234. .foregroundColor(.secondary)
  235. }.gridCellAnchor(.leading)
  236. Group {
  237. Text("ISF:")
  238. .foregroundColor(.secondary)
  239. }.gridCellAnchor(.leading)
  240. VStack {
  241. Text("Target:")
  242. .foregroundColor(.secondary)
  243. }.gridCellAnchor(.leading)
  244. }
  245. }
  246. var calcSettingsSecondRow: some View {
  247. GridRow {
  248. Text(state.carbRatio.formatted() + " " + NSLocalizedString("g/U", comment: " grams per Unit"))
  249. .gridCellAnchor(.leading)
  250. Text(
  251. state.isf.formatted() + " " + state.units
  252. .rawValue + NSLocalizedString("/U", comment: "/Insulin unit")
  253. ).gridCellAnchor(.leading)
  254. let target = state.units == .mmolL ? state.target.asMmolL : state.target
  255. Text(
  256. target
  257. .formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits))) +
  258. " " + state.units.rawValue
  259. ).gridCellAnchor(.leading)
  260. }
  261. }
  262. var calcGlucoseFirstRow: some View {
  263. GridRow(alignment: .center) {
  264. let currentBG = state.units == .mmolL ? state.currentBG.asMmolL : state.currentBG
  265. let target = state.units == .mmolL ? state.target.asMmolL : state.target
  266. Text("Glucose:").foregroundColor(.secondary)
  267. let firstRow = currentBG
  268. .formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits)))
  269. + " - " +
  270. target
  271. .formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits)))
  272. + " = " +
  273. state.targetDifference
  274. .formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits)))
  275. Text(firstRow).frame(minWidth: 0, alignment: .leading).foregroundColor(.secondary)
  276. .gridColumnAlignment(.leading)
  277. HStack {
  278. Text(
  279. self.insulinRounder(state.targetDifferenceInsulin).formatted()
  280. )
  281. Text("U").foregroundColor(.secondary)
  282. }.fontWeight(.bold)
  283. .gridColumnAlignment(.trailing)
  284. }
  285. }
  286. var calcGlucoseSecondRow: some View {
  287. GridRow(alignment: .center) {
  288. let currentBG = state.units == .mmolL ? state.currentBG.asMmolL : state.currentBG
  289. Text(
  290. currentBG
  291. .formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits))) +
  292. " " +
  293. state.units.rawValue
  294. )
  295. let secondRow = state.targetDifference
  296. .formatted(
  297. .number.grouping(.never).rounded()
  298. .precision(.fractionLength(fractionDigits))
  299. )
  300. + " / " +
  301. state.isf.formatted()
  302. + " ≈ " +
  303. self.insulinRounder(state.targetDifferenceInsulin).formatted()
  304. Text(secondRow).foregroundColor(.secondary).gridColumnAlignment(.leading)
  305. Color.clear.gridCellUnsizedAxes([.horizontal, .vertical])
  306. }
  307. }
  308. var calcGlucoseFormulaRow: some View {
  309. GridRow(alignment: .top) {
  310. Color.clear.gridCellUnsizedAxes([.horizontal, .vertical])
  311. Text("(Current - Target) / ISF").foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8))
  312. .gridColumnAlignment(.leading)
  313. .gridCellColumns(2)
  314. }
  315. .font(.caption)
  316. }
  317. var calcIOBRow: some View {
  318. GridRow(alignment: .center) {
  319. HStack {
  320. Text("IOB:").foregroundColor(.secondary)
  321. Text(
  322. self.insulinRounder(state.iob).formatted()
  323. )
  324. }
  325. Text("Subtract IOB").foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8)).font(.footnote)
  326. let iobFormatted = self.insulinRounder(state.iob).formatted()
  327. HStack {
  328. Text((state.iob != 0 ? "-" : "") + (state.iob >= 0 ? iobFormatted : "(" + iobFormatted + ")"))
  329. Text("U").foregroundColor(.secondary)
  330. }.fontWeight(.bold)
  331. .gridColumnAlignment(.trailing)
  332. }
  333. }
  334. var calcCOBRow: some View {
  335. GridRow(alignment: .center) {
  336. HStack {
  337. Text("COB:").foregroundColor(.secondary)
  338. Text(
  339. state.cob
  340. .formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits))) +
  341. NSLocalizedString(" g", comment: "grams")
  342. )
  343. }
  344. Text(
  345. state.cob
  346. .formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits)))
  347. + " / " +
  348. state.carbRatio.formatted()
  349. + " ≈ " +
  350. self.insulinRounder(state.wholeCobInsulin).formatted()
  351. )
  352. .foregroundColor(.secondary)
  353. .gridColumnAlignment(.leading)
  354. HStack {
  355. Text(
  356. self.insulinRounder(state.wholeCobInsulin).formatted()
  357. )
  358. Text("U").foregroundColor(.secondary)
  359. }.fontWeight(.bold)
  360. .gridColumnAlignment(.trailing)
  361. }
  362. }
  363. var calcCOBFormulaRow: some View {
  364. GridRow(alignment: .center) {
  365. Color.clear.gridCellUnsizedAxes([.horizontal, .vertical])
  366. Text("COB / Carb Ratio").foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8))
  367. .gridColumnAlignment(.leading)
  368. .gridCellColumns(2)
  369. }
  370. .font(.caption)
  371. }
  372. var calcDeltaRow: some View {
  373. GridRow(alignment: .center) {
  374. Text("Delta:").foregroundColor(.secondary)
  375. let deltaBG = state.units == .mmolL ? state.deltaBG.asMmolL : state.deltaBG
  376. Text(
  377. deltaBG
  378. .formatted(
  379. .number.grouping(.never).rounded()
  380. .precision(.fractionLength(fractionDigits))
  381. )
  382. + " / " +
  383. state.isf.formatted()
  384. + " ≈ " +
  385. self.insulinRounder(state.fifteenMinInsulin).formatted()
  386. )
  387. .foregroundColor(.secondary)
  388. .gridColumnAlignment(.leading)
  389. HStack {
  390. Text(
  391. self.insulinRounder(state.fifteenMinInsulin).formatted()
  392. )
  393. Text("U").foregroundColor(.secondary)
  394. }.fontWeight(.bold)
  395. .gridColumnAlignment(.trailing)
  396. }
  397. }
  398. var calcDeltaFormulaRow: some View {
  399. GridRow(alignment: .center) {
  400. let deltaBG = state.units == .mmolL ? state.deltaBG.asMmolL : state.deltaBG
  401. Text(
  402. deltaBG
  403. .formatted(
  404. .number.grouping(.never).rounded()
  405. .precision(.fractionLength(fractionDigits))
  406. ) + " " +
  407. state.units.rawValue
  408. )
  409. Text("15min Delta / ISF").font(.caption).foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8))
  410. .gridColumnAlignment(.leading)
  411. .gridCellColumns(2).padding(.top, 5)
  412. }
  413. }
  414. var calcFullBolusRow: some View {
  415. GridRow(alignment: .center) {
  416. Text("Full Bolus")
  417. .foregroundColor(.secondary)
  418. Color.clear.gridCellUnsizedAxes([.horizontal, .vertical])
  419. HStack {
  420. Text(self.insulinRounder(state.wholeCalc).formatted())
  421. .foregroundStyle(state.wholeCalc < 0 ? Color.loopRed : Color.primary)
  422. Text("U").foregroundColor(.secondary)
  423. }.gridColumnAlignment(.trailing)
  424. .fontWeight(.bold)
  425. }
  426. }
  427. var calcSuperBolusRow: some View {
  428. GridRow(alignment: .center) {
  429. Text("Super Bolus")
  430. .foregroundColor(.secondary)
  431. Text("Added to Result").foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8)).font(.footnote)
  432. HStack {
  433. Text("+" + self.insulinRounder(state.superBolusInsulin).formatted())
  434. .foregroundStyle(Color.loopRed)
  435. Text("U").foregroundColor(.secondary)
  436. }.gridColumnAlignment(.trailing)
  437. .fontWeight(.bold)
  438. }
  439. }
  440. var calcResultRow: some View {
  441. GridRow(alignment: .center) {
  442. Text("Result").fontWeight(.bold)
  443. HStack {
  444. Text(state.useSuperBolus ? "(" : "")
  445. .foregroundColor(.loopRed)
  446. + Text(state.fraction.formatted())
  447. + Text(" x ")
  448. .foregroundColor(.secondary)
  449. // if fatty meal is chosen
  450. + Text(state.useFattyMealCorrectionFactor ? state.fattyMealFactor.formatted() : "")
  451. .foregroundColor(.orange)
  452. + Text(state.useFattyMealCorrectionFactor ? " x " : "")
  453. .foregroundColor(.secondary)
  454. // endif fatty meal is chosen
  455. + Text(self.insulinRounder(state.wholeCalc).formatted())
  456. .foregroundColor(state.wholeCalc < 0 ? Color.loopRed : Color.primary)
  457. // if superbolus is chosen
  458. + Text(state.useSuperBolus ? ")" : "")
  459. .foregroundColor(.loopRed)
  460. + Text(state.useSuperBolus ? " + " : "")
  461. .foregroundColor(.secondary)
  462. + Text(state.useSuperBolus ? state.superBolusInsulin.formatted() : "")
  463. .foregroundColor(.loopRed)
  464. // endif superbolus is chosen
  465. + Text(" ≈ ")
  466. .foregroundColor(.secondary)
  467. }
  468. .gridColumnAlignment(.leading)
  469. HStack {
  470. Text(self.insulinRounder(state.insulinCalculated).formatted())
  471. .fontWeight(.bold)
  472. .foregroundColor(.blue)
  473. Text("U").foregroundColor(.secondary)
  474. }
  475. .gridColumnAlignment(.trailing)
  476. .fontWeight(.bold)
  477. }
  478. }
  479. var calcResultFormulaRow: some View {
  480. GridRow(alignment: .bottom) {
  481. if state.useFattyMealCorrectionFactor {
  482. Text("Factor x Fatty Meal Factor x Full Bolus")
  483. .foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8))
  484. .font(.caption)
  485. .gridCellAnchor(.center)
  486. .gridCellColumns(3)
  487. } else if state.useSuperBolus {
  488. Text("(Factor x Full Bolus) + Super Bolus")
  489. .foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8))
  490. .font(.caption)
  491. .gridCellAnchor(.center)
  492. .gridCellColumns(3)
  493. } else {
  494. Color.clear.gridCellUnsizedAxes([.horizontal, .vertical])
  495. Text("Factor x Full Bolus")
  496. .foregroundColor(.secondary.opacity(colorScheme == .dark ? 0.65 : 0.8))
  497. .font(.caption)
  498. .padding(.top, 5)
  499. .gridCellAnchor(.leading)
  500. .gridCellColumns(2)
  501. }
  502. }
  503. }
  504. var calculationsDetailView: some View {
  505. NavigationStack {
  506. ScrollView {
  507. Grid(alignment: .topLeading, horizontalSpacing: 3, verticalSpacing: 0) {
  508. GridRow {
  509. Text("Calculations").fontWeight(.bold).gridCellColumns(3).gridCellAnchor(.center).padding(.vertical)
  510. }
  511. calcSettingsFirstRow
  512. calcSettingsSecondRow
  513. DividerCustom()
  514. if fetch {
  515. // meal entries as grid rows
  516. GridRow {
  517. if let carbs = meal.first?.carbs, carbs > 0 {
  518. Text("Carbs").foregroundColor(.secondary)
  519. Color.clear.gridCellUnsizedAxes([.horizontal, .vertical])
  520. HStack {
  521. Text(carbs.formatted())
  522. Text("g").foregroundColor(.secondary)
  523. }.gridCellAnchor(.trailing)
  524. }
  525. }
  526. GridRow {
  527. if let fat = meal.first?.fat, fat > 0 {
  528. Text("Fat").foregroundColor(.secondary)
  529. Color.clear.gridCellUnsizedAxes([.horizontal, .vertical])
  530. HStack {
  531. Text(fat.formatted())
  532. Text("g").foregroundColor(.secondary)
  533. }.gridCellAnchor(.trailing)
  534. }
  535. }
  536. GridRow {
  537. if let protein = meal.first?.protein, protein > 0 {
  538. Text("Protein").foregroundColor(.secondary)
  539. Color.clear.gridCellUnsizedAxes([.horizontal, .vertical])
  540. HStack {
  541. Text(protein.formatted())
  542. Text("g").foregroundColor(.secondary)
  543. }.gridCellAnchor(.trailing)
  544. }
  545. }
  546. GridRow {
  547. if let note = meal.first?.note, note != "" {
  548. Text("Note").foregroundColor(.secondary)
  549. Text(note).foregroundColor(.secondary).gridCellColumns(2).gridCellAnchor(.trailing)
  550. }
  551. }
  552. DividerCustom()
  553. }
  554. GridRow {
  555. Text("Detailed Calculation Steps").gridCellColumns(3).gridCellAnchor(.center)
  556. .padding(.bottom, 10)
  557. }
  558. calcGlucoseFirstRow
  559. calcGlucoseSecondRow.padding(.bottom, 5)
  560. calcGlucoseFormulaRow
  561. DividerCustom()
  562. calcIOBRow
  563. DividerCustom()
  564. calcCOBRow.padding(.bottom, 5)
  565. calcCOBFormulaRow
  566. DividerCustom()
  567. calcDeltaRow
  568. calcDeltaFormulaRow
  569. DividerCustom()
  570. calcFullBolusRow
  571. if state.useSuperBolus {
  572. DividerCustom()
  573. calcSuperBolusRow
  574. }
  575. DividerDouble()
  576. calcResultRow
  577. calcResultFormulaRow
  578. }
  579. Spacer()
  580. Button { showInfo = false }
  581. label: { Text("Got it!").frame(maxWidth: .infinity, alignment: .center) }
  582. .buttonStyle(.bordered)
  583. .padding(.top)
  584. }
  585. .padding([.horizontal, .bottom])
  586. .font(.system(size: 15))
  587. }
  588. }
  589. private func insulinRounder(_ value: Decimal) -> Decimal {
  590. let toRound = NSDecimalNumber(decimal: value).doubleValue
  591. return Decimal(floor(100 * toRound) / 100)
  592. }
  593. private var disabled: Bool {
  594. state.amount <= 0 || state.amount > state.maxBolus
  595. }
  596. var changed: Bool {
  597. ((meal.first?.carbs ?? 0) > 0) || ((meal.first?.fat ?? 0) > 0) || ((meal.first?.protein ?? 0) > 0)
  598. }
  599. var hasFatOrProtein: Bool {
  600. ((meal.first?.fat ?? 0) > 0) || ((meal.first?.protein ?? 0) > 0)
  601. }
  602. func carbsView() {
  603. if fetch {
  604. keepForNextWiew = true
  605. state.backToCarbsView(complexEntry: true, meal, override: false)
  606. } else {
  607. state.backToCarbsView(complexEntry: false, meal, override: true)
  608. }
  609. }
  610. var mealEntries: some View {
  611. VStack {
  612. if let carbs = meal.first?.carbs, carbs > 0 {
  613. HStack {
  614. Text("Carbs").foregroundColor(.secondary)
  615. Spacer()
  616. Text(carbs.formatted())
  617. Text("g").foregroundColor(.secondary)
  618. }
  619. }
  620. if let fat = meal.first?.fat, fat > 0 {
  621. HStack {
  622. Text("Fat").foregroundColor(.secondary)
  623. Spacer()
  624. Text(fat.formatted())
  625. Text("g").foregroundColor(.secondary)
  626. }
  627. }
  628. if let protein = meal.first?.protein, protein > 0 {
  629. HStack {
  630. Text("Protein").foregroundColor(.secondary)
  631. Spacer()
  632. Text(protein.formatted())
  633. Text("g").foregroundColor(.secondary)
  634. }
  635. }
  636. if let note = meal.first?.note, note != "" {
  637. HStack {
  638. Text("Note").foregroundColor(.secondary)
  639. Spacer()
  640. Text(note).foregroundColor(.secondary)
  641. }
  642. }
  643. }
  644. }
  645. }
  646. struct DividerDouble: View {
  647. var body: some View {
  648. VStack(spacing: 2) {
  649. Rectangle()
  650. .frame(height: 1)
  651. .foregroundColor(.gray.opacity(0.65))
  652. Rectangle()
  653. .frame(height: 1)
  654. .foregroundColor(.gray.opacity(0.65))
  655. }
  656. .frame(height: 4)
  657. .padding(.vertical)
  658. }
  659. }
  660. struct DividerCustom: View {
  661. var body: some View {
  662. Rectangle()
  663. .frame(height: 1)
  664. .foregroundColor(.gray.opacity(0.65))
  665. .padding(.vertical)
  666. }
  667. }
  668. }