AlternativeBolusCalcRootView.swift 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415
  1. import SwiftUI
  2. import Swinject
  3. extension Bolus {
  4. // alternative bolus calc
  5. struct AlternativeBolusCalcRootView: BaseView {
  6. let resolver: Resolver
  7. let waitForSuggestion: Bool
  8. @ObservedObject var state: StateModel
  9. @State private var showInfo = false
  10. @State var insulinCalculated: Decimal = 0
  11. @Environment(\.colorScheme) var colorScheme
  12. private var formatter: NumberFormatter {
  13. let formatter = NumberFormatter()
  14. formatter.numberStyle = .decimal
  15. formatter.maximumFractionDigits = 2
  16. return formatter
  17. }
  18. private var gluoseFormatter: NumberFormatter {
  19. let formatter = NumberFormatter()
  20. formatter.numberStyle = .decimal
  21. if state.units == .mmolL {
  22. formatter.maximumFractionDigits = 1
  23. } else { formatter.maximumFractionDigits = 0 }
  24. return formatter
  25. }
  26. private var fractionDigits: Int {
  27. if state.units == .mmolL {
  28. return 1
  29. } else { return 0 }
  30. }
  31. var body: some View {
  32. Form {
  33. Section {
  34. HStack {
  35. Text("Glucose")
  36. DecimalTextField(
  37. "0",
  38. value: Binding(
  39. get: {
  40. if state.units == .mmolL {
  41. return state.currentBG.asMmolL
  42. } else {
  43. return state.currentBG
  44. }
  45. },
  46. set: { newValue in
  47. if state.units == .mmolL {
  48. state.currentBG = newValue.asMmolL
  49. } else {
  50. state.currentBG = newValue
  51. }
  52. }
  53. ),
  54. formatter: gluoseFormatter,
  55. autofocus: false,
  56. cleanInput: true
  57. )
  58. .onChange(of: state.currentBG) { newValue in
  59. if newValue > 500 {
  60. state.currentBG = 500 // ensure that user can not input more than 500 mg/dL
  61. }
  62. insulinCalculated = state.calculateInsulin()
  63. }
  64. Text(state.units.rawValue)
  65. .foregroundColor(.secondary)
  66. }
  67. .contentShape(Rectangle())
  68. HStack {
  69. Button(action: {
  70. showInfo.toggle()
  71. insulinCalculated = state.calculateInsulin()
  72. }, label: {
  73. Image(systemName: "info.circle")
  74. Text("Calculations")
  75. })
  76. .foregroundStyle(.blue)
  77. .font(.footnote)
  78. .buttonStyle(PlainButtonStyle())
  79. .frame(maxWidth: .infinity, alignment: .leading)
  80. if state.fattyMeals {
  81. Spacer()
  82. Toggle(isOn: $state.useFattyMealCorrectionFactor) {
  83. Text("Fatty Meal")
  84. }
  85. .toggleStyle(CheckboxToggleStyle())
  86. .font(.footnote)
  87. .onChange(of: state.useFattyMealCorrectionFactor) { _ in
  88. insulinCalculated = state.calculateInsulin()
  89. }
  90. }
  91. }
  92. }
  93. header: { Text("Values") }
  94. Section {
  95. if state.waitForSuggestion {
  96. HStack {
  97. Text("Wait please").foregroundColor(.secondary)
  98. Spacer()
  99. ActivityIndicator(isAnimating: .constant(true), style: .medium) // fix iOS 15 bug
  100. }
  101. } else {
  102. HStack {
  103. Text("Recommended Bolus")
  104. Spacer()
  105. Text(
  106. formatter
  107. .string(from: Double(insulinCalculated) as NSNumber)!
  108. )
  109. let unit = NSLocalizedString(
  110. " U",
  111. comment: "Unit in number of units delivered (keep the space character!)"
  112. )
  113. Text(unit).foregroundColor(.secondary)
  114. }.contentShape(Rectangle())
  115. .onTapGesture {
  116. state.amount = insulinCalculated
  117. }
  118. if !state.waitForSuggestion {
  119. HStack {
  120. Text("Bolus")
  121. Spacer()
  122. DecimalTextField(
  123. "0",
  124. value: $state.amount,
  125. formatter: formatter,
  126. autofocus: false,
  127. cleanInput: true
  128. )
  129. Text(!(state.amount > state.maxBolus) ? "U" : "😵").foregroundColor(.secondary)
  130. }
  131. }
  132. }
  133. }
  134. header: { Text("Bolus") }
  135. Section {
  136. Button(action: {
  137. state.add()
  138. }) {
  139. Text(!(state.amount > state.maxBolus) ? "Enact bolus" : "Max Bolus exceeded!")
  140. .frame(maxWidth: .infinity, alignment: .center)
  141. }
  142. .disabled(
  143. state.amount <= 0 || state.amount > state.maxBolus
  144. )
  145. }
  146. .onAppear {
  147. configureView {
  148. state.waitForSuggestionInitial = waitForSuggestion
  149. state.waitForSuggestion = waitForSuggestion
  150. }
  151. }
  152. .navigationTitle("Enact Bolus")
  153. .navigationBarTitleDisplayMode(.inline)
  154. .navigationBarItems(leading: Button("Close", action: state.hideModal))
  155. }
  156. .blur(radius: showInfo ? 3 : 0)
  157. .popup(isPresented: showInfo) {
  158. bolusInfoAlternativeCalculator
  159. }
  160. }
  161. // calculation showed in popup
  162. var bolusInfoAlternativeCalculator: some View {
  163. let unit = NSLocalizedString(
  164. " U",
  165. comment: "Unit in number of units delivered (keep the space character!)"
  166. )
  167. return VStack {
  168. VStack {
  169. VStack {
  170. HStack {
  171. Text("Calculations")
  172. .font(.title3)
  173. .fontWeight(.semibold)
  174. Spacer()
  175. }
  176. .padding(.vertical, 10)
  177. HStack {
  178. Text("Carb Ratio")
  179. .foregroundColor(.secondary)
  180. Spacer()
  181. Text(state.carbRatio.formatted())
  182. Text(NSLocalizedString(" g/U", comment: " grams per Unit"))
  183. .foregroundColor(.secondary)
  184. }
  185. HStack {
  186. Text("ISF")
  187. .foregroundColor(.secondary)
  188. Spacer()
  189. let isf = state.isf
  190. Text(isf.formatted())
  191. Text(state.units.rawValue + NSLocalizedString("/U", comment: "/Insulin unit"))
  192. .foregroundColor(.secondary)
  193. }
  194. HStack {
  195. Text("Target Glucose")
  196. .foregroundColor(.secondary)
  197. Spacer()
  198. let target = state.units == .mmolL ? state.target.asMmolL : state.target
  199. Text(target.formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits))))
  200. Text(state.units.rawValue)
  201. .foregroundColor(.secondary)
  202. }
  203. HStack {
  204. Text("Basal")
  205. .foregroundColor(.secondary)
  206. Spacer()
  207. let basal = state.basal
  208. Text(basal.formatted())
  209. Text(NSLocalizedString(" U/h", comment: " Units per hour"))
  210. .foregroundColor(.secondary)
  211. }
  212. HStack {
  213. Text("Fraction")
  214. .foregroundColor(.secondary)
  215. Spacer()
  216. let fraction = state.fraction
  217. Text(fraction.formatted())
  218. }
  219. if state.useFattyMealCorrectionFactor {
  220. HStack {
  221. Text("Fatty Meal Factor")
  222. .foregroundColor(.orange)
  223. Spacer()
  224. let fraction = state.fattyMealFactor
  225. Text(fraction.formatted())
  226. .foregroundColor(.orange)
  227. }
  228. }
  229. }
  230. .padding()
  231. VStack {
  232. HStack {
  233. Text("Glucose")
  234. .foregroundColor(.secondary)
  235. Spacer()
  236. let glucose = state.units == .mmolL ? state.currentBG.asMmolL : state.currentBG
  237. Text(glucose.formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits))))
  238. Text(state.units.rawValue)
  239. .foregroundColor(.secondary)
  240. Spacer()
  241. Image(systemName: "arrow.right")
  242. Spacer()
  243. let targetDifferenceInsulin = state.targetDifferenceInsulin
  244. // rounding
  245. let targetDifferenceInsulinAsDouble = NSDecimalNumber(decimal: targetDifferenceInsulin).doubleValue
  246. let roundedTargetDifferenceInsulin = Decimal(round(100 * targetDifferenceInsulinAsDouble) / 100)
  247. Text(roundedTargetDifferenceInsulin.formatted())
  248. Text(unit)
  249. .foregroundColor(.secondary)
  250. }
  251. HStack {
  252. Text("IOB")
  253. .foregroundColor(.secondary)
  254. Spacer()
  255. let iob = state.iob
  256. // rounding
  257. let iobAsDouble = NSDecimalNumber(decimal: iob).doubleValue
  258. let roundedIob = Decimal(round(100 * iobAsDouble) / 100)
  259. Text(roundedIob.formatted())
  260. Text(unit)
  261. .foregroundColor(.secondary)
  262. Spacer()
  263. Image(systemName: "arrow.right")
  264. Spacer()
  265. let iobCalc = state.iobInsulinReduction
  266. // rounding
  267. let iobCalcAsDouble = NSDecimalNumber(decimal: iobCalc).doubleValue
  268. let roundedIobCalc = Decimal(round(100 * iobCalcAsDouble) / 100)
  269. Text(roundedIobCalc.formatted())
  270. Text(unit).foregroundColor(.secondary)
  271. }
  272. HStack {
  273. Text("Trend")
  274. .foregroundColor(.secondary)
  275. Spacer()
  276. let trend = state.units == .mmolL ? state.deltaBG.asMmolL : state.deltaBG
  277. Text(trend.formatted(.number.grouping(.never).rounded().precision(.fractionLength(fractionDigits))))
  278. Text(state.units.rawValue).foregroundColor(.secondary)
  279. Spacer()
  280. Image(systemName: "arrow.right")
  281. Spacer()
  282. let trendInsulin = state.fifteenMinInsulin
  283. // rounding
  284. let trendInsulinAsDouble = NSDecimalNumber(decimal: trendInsulin).doubleValue
  285. let roundedTrendInsulin = Decimal(round(100 * trendInsulinAsDouble) / 100)
  286. Text(roundedTrendInsulin.formatted())
  287. Text(unit)
  288. .foregroundColor(.secondary)
  289. }
  290. HStack {
  291. Text("COB")
  292. .foregroundColor(.secondary)
  293. Spacer()
  294. let cob = state.cob
  295. Text(cob.formatted())
  296. let unitGrams = NSLocalizedString(" g", comment: "grams")
  297. Text(unitGrams).foregroundColor(.secondary)
  298. Spacer()
  299. Image(systemName: "arrow.right")
  300. Spacer()
  301. let insulinCob = state.wholeCobInsulin
  302. // rounding
  303. let insulinCobAsDouble = NSDecimalNumber(decimal: insulinCob).doubleValue
  304. let roundedInsulinCob = Decimal(round(100 * insulinCobAsDouble) / 100)
  305. Text(roundedInsulinCob.formatted())
  306. Text(unit)
  307. .foregroundColor(.secondary)
  308. }
  309. }
  310. .padding()
  311. Divider()
  312. .fontWeight(.bold)
  313. HStack {
  314. Text("Full Bolus")
  315. .foregroundColor(.secondary)
  316. Spacer()
  317. let insulin = state.roundedWholeCalc
  318. Text(insulin.formatted()).foregroundStyle(state.roundedWholeCalc < 0 ? Color.loopRed : Color.primary)
  319. Text(unit)
  320. .foregroundColor(.secondary)
  321. }
  322. .padding()
  323. Divider()
  324. .fontWeight(.bold)
  325. HStack {
  326. Text("Result")
  327. .fontWeight(.bold)
  328. Spacer()
  329. let fraction = state.fraction
  330. Text(fraction.formatted())
  331. Text(" x ")
  332. .foregroundColor(.secondary)
  333. // if fatty meal is chosen
  334. if state.useFattyMealCorrectionFactor {
  335. let fattyMealFactor = state.fattyMealFactor
  336. Text(fattyMealFactor.formatted())
  337. .foregroundColor(.orange)
  338. Text(" x ")
  339. .foregroundColor(.secondary)
  340. }
  341. let insulin = state.roundedWholeCalc
  342. Text(insulin.formatted()).foregroundStyle(state.roundedWholeCalc < 0 ? Color.loopRed : Color.primary)
  343. Text(unit)
  344. .foregroundColor(.secondary)
  345. Text(" = ")
  346. .foregroundColor(.secondary)
  347. let result = state.insulinCalculated
  348. // rounding
  349. let resultAsDouble = NSDecimalNumber(decimal: result).doubleValue
  350. let roundedResult = Decimal(round(100 * resultAsDouble) / 100)
  351. Text(roundedResult.formatted())
  352. .fontWeight(.bold)
  353. .font(.system(size: 16))
  354. .foregroundColor(.blue)
  355. Text(unit)
  356. .foregroundColor(.secondary)
  357. }
  358. .padding()
  359. }
  360. .padding(.top, 10)
  361. .padding(.bottom, 15)
  362. // Hide button
  363. VStack {
  364. Button { showInfo = false }
  365. label: {
  366. Text("OK")
  367. }
  368. .frame(maxWidth: .infinity, alignment: .center)
  369. .font(.system(size: 16))
  370. .fontWeight(.semibold)
  371. .foregroundColor(.blue)
  372. }
  373. .padding(.bottom, 20)
  374. }
  375. .font(.footnote)
  376. .background(
  377. RoundedRectangle(cornerRadius: 10, style: .continuous)
  378. .fill(Color(colorScheme == .dark ? UIColor.systemGray4 : UIColor.systemGray4).opacity(0.9))
  379. )
  380. }
  381. }
  382. }