InsulinStatusView.swift 6.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178
  1. //
  2. // InsulinStatusView.swift
  3. // MockKitUI
  4. //
  5. // Created by Nathaniel Hamming on 2023-05-18.
  6. // Copyright © 2023 LoopKit Authors. All rights reserved.
  7. //
  8. import SwiftUI
  9. import HealthKit
  10. import LoopKit
  11. struct InsulinStatusView: View {
  12. @Environment(\.guidanceColors) var guidanceColors
  13. @Environment(\.insulinTintColor) var insulinTintColor
  14. @ObservedObject var viewModel: MockPumpManagerSettingsViewModel
  15. private let subViewSpacing: CGFloat = 14
  16. var body: some View {
  17. HStack(alignment: .top, spacing: 0) {
  18. deliveryStatus
  19. .fixedSize(horizontal: true, vertical: true)
  20. Spacer()
  21. Divider()
  22. .frame(height: dividerHeight)
  23. .offset(y:3)
  24. Spacer()
  25. reservoirStatus
  26. .fixedSize(horizontal: true, vertical: true)
  27. }
  28. }
  29. private var dividerHeight: CGFloat {
  30. guard inNoDelivery == false else {
  31. return 65 + subViewSpacing-10
  32. }
  33. return 65 + subViewSpacing
  34. }
  35. let basalRateFormatter = QuantityFormatter(for: .internationalUnitsPerHour)
  36. let reservoirVolumeFormatter = QuantityFormatter(for: .internationalUnit())
  37. private var inNoDelivery: Bool {
  38. !viewModel.isDeliverySuspended && viewModel.basalDeliveryRate == nil
  39. }
  40. private var deliveryStatusSpacing: CGFloat {
  41. return subViewSpacing
  42. }
  43. var deliveryStatus: some View {
  44. VStack(alignment: .leading, spacing: deliveryStatusSpacing) {
  45. Text(deliverySectionTitle)
  46. .foregroundColor(.secondary)
  47. .fixedSize(horizontal: false, vertical: true)
  48. if viewModel.isDeliverySuspended {
  49. insulinSuspended
  50. } else if let basalRate = viewModel.basalDeliveryRate {
  51. basalRateView(basalRate)
  52. } else {
  53. noDelivery
  54. }
  55. }
  56. }
  57. var insulinSuspended: some View {
  58. HStack(alignment: .center, spacing: 2) {
  59. Image(systemName: "pause.circle.fill")
  60. .font(.system(size: 34))
  61. .fixedSize()
  62. .foregroundColor(guidanceColors.warning)
  63. Text("Insulin\nSuspended")
  64. .font(.system(size: 14, weight: .heavy, design: .default))
  65. .lineSpacing(0.01)
  66. .fixedSize()
  67. }
  68. }
  69. private func basalRateView(_ basalRate: Double) -> some View {
  70. HStack(alignment: .center) {
  71. VStack(alignment: .leading) {
  72. HStack(alignment: .lastTextBaseline, spacing: 3) {
  73. let unit = HKUnit.internationalUnitsPerHour
  74. let quantity = HKQuantity(unit: unit, doubleValue: basalRate)
  75. if viewModel.presentDeliveryWarning == true {
  76. Image(systemName: "exclamationmark.circle.fill")
  77. .foregroundColor(guidanceColors.warning)
  78. .font(.system(size: 28))
  79. .fixedSize()
  80. }
  81. Text(basalRateFormatter.string(from: quantity, includeUnit: false) ?? "")
  82. .font(.system(size: 28))
  83. .fontWeight(.heavy)
  84. .fixedSize()
  85. Text(basalRateFormatter.localizedUnitStringWithPlurality(forQuantity: quantity))
  86. .foregroundColor(.secondary)
  87. }
  88. Group {
  89. if viewModel.isScheduledBasal {
  90. Text("Scheduled\(String.nonBreakingSpace)Basal")
  91. } else if viewModel.isTempBasal {
  92. Text("Temporary\(String.nonBreakingSpace)Basal")
  93. }
  94. }
  95. .font(.footnote)
  96. .foregroundColor(.accentColor)
  97. }
  98. }
  99. }
  100. var noDelivery: some View {
  101. HStack(alignment: .center, spacing: 2) {
  102. Image(systemName: "xmark.circle.fill")
  103. .font(.system(size: 34))
  104. .fixedSize()
  105. .foregroundColor(guidanceColors.critical)
  106. Text("No\nDelivery")
  107. .font(.system(size: 16, weight: .heavy, design: .default))
  108. .lineSpacing(0.01)
  109. .fixedSize()
  110. }
  111. }
  112. var deliverySectionTitle: String {
  113. LocalizedString("Insulin\(String.nonBreakingSpace)Delivery", comment: "Title of insulin delivery section")
  114. }
  115. private var reservoirStatusSpacing: CGFloat {
  116. subViewSpacing
  117. }
  118. var reservoirStatus: some View {
  119. VStack(alignment: .trailing) {
  120. VStack(alignment: .leading, spacing: reservoirStatusSpacing) {
  121. Text("Insulin\(String.nonBreakingSpace)Remaining")
  122. .foregroundColor(Color(UIColor.secondaryLabel))
  123. HStack {
  124. reservoirLevelStatus
  125. }
  126. }
  127. }
  128. }
  129. @ViewBuilder
  130. var reservoirLevelStatus: some View {
  131. VStack(alignment: .leading, spacing: 0) {
  132. HStack(alignment: .lastTextBaseline) {
  133. ZStack(alignment: .center) {
  134. Image(frameworkImage: "generic-reservoir")
  135. .resizable()
  136. .foregroundColor(.accentColor)
  137. .frame(width: 26, height: 34, alignment: .bottom)
  138. Image(frameworkImage: "generic-reservoir-mask")
  139. .resizable()
  140. .foregroundColor(.accentColor)
  141. .frame(width: 23, height: 34, alignment: .bottom)
  142. }
  143. HStack(alignment: .firstTextBaseline, spacing: 3) {
  144. Text("50+")
  145. .font(.system(size: 28))
  146. .fontWeight(.heavy)
  147. .fixedSize()
  148. Text(reservoirVolumeFormatter.localizedUnitStringWithPlurality())
  149. .foregroundColor(.secondary)
  150. }
  151. }
  152. Text("Estimated Reading")
  153. .font(.footnote)
  154. .foregroundColor(.accentColor)
  155. }
  156. .offset(y: -7) // the reservoir image should have tight spacing so move the view up
  157. .padding(.bottom, -7)
  158. }
  159. }