PumpView.swift 8.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230
  1. import CoreData
  2. import SwiftUI
  3. struct PumpView: View {
  4. let reservoir: Decimal?
  5. let name: String
  6. let expiresAtDate: Date?
  7. let timerDate: Date
  8. let pumpStatusBadge: UIImage?
  9. let pumpStatusBadgeColor: Color?
  10. let pumpStatusHighlightMessage: String?
  11. let battery: [OpenAPS_Battery]
  12. @Environment(\.colorScheme) var colorScheme
  13. private var batteryFormatter: NumberFormatter {
  14. let formatter = NumberFormatter()
  15. formatter.numberStyle = .percent
  16. return formatter
  17. }
  18. var body: some View {
  19. ZStack(alignment: .topLeading) {
  20. if let pumpStatusHighlightMessage = pumpStatusHighlightMessage { // display message instead pump info
  21. VStack(alignment: .center) {
  22. Text(pumpStatusHighlightMessage).font(.footnote).fontWeight(.bold)
  23. .multilineTextAlignment(.center).frame(maxWidth: /*@START_MENU_TOKEN@*/ .infinity/*@END_MENU_TOKEN@*/)
  24. }.frame(width: 100)
  25. } else {
  26. VStack(alignment: .leading, spacing: 20) {
  27. if reservoir == nil && battery.isEmpty {
  28. VStack(alignment: .center, spacing: 12) {
  29. HStack {
  30. Image(systemName: "keyboard.onehanded.left")
  31. .font(.body)
  32. .imageScale(.large)
  33. }
  34. HStack {
  35. Text("Add pump")
  36. .font(.caption)
  37. .bold()
  38. }
  39. }
  40. .frame(alignment: .top)
  41. }
  42. if let reservoir = reservoir {
  43. HStack {
  44. Image(systemName: "cross.vial.fill")
  45. .font(.callout)
  46. if reservoir == 0xDEAD_BEEF {
  47. Text("50+ " + NSLocalizedString("U", comment: "Insulin unit"))
  48. .font(.callout)
  49. .fontWeight(.bold)
  50. .fontDesign(.rounded)
  51. } else {
  52. Text(
  53. Formatter.integerFormatter
  54. .string(from: reservoir as NSNumber)! + NSLocalizedString(" U", comment: "Insulin unit")
  55. )
  56. .font(.callout)
  57. .fontWeight(.bold)
  58. .fontDesign(.rounded)
  59. }
  60. }
  61. .padding(.vertical, 5)
  62. .padding(.horizontal, 10)
  63. .foregroundStyle(reservoirColor)
  64. .overlay(
  65. Capsule()
  66. .stroke(reservoirColor.opacity(0.4), lineWidth: 2)
  67. )
  68. }
  69. if (battery.first?.display) != nil, let shouldBatteryDisplay = battery.first?.display, shouldBatteryDisplay {
  70. HStack {
  71. Image(systemName: "battery.100")
  72. .font(.callout)
  73. .foregroundStyle(batteryColor)
  74. Text("\(Int(battery.first?.percent ?? 100)) %")
  75. .font(.callout).fontWeight(.bold).fontDesign(.rounded)
  76. }
  77. }
  78. if let date = expiresAtDate {
  79. HStack {
  80. Image(systemName: "stopwatch.fill")
  81. .font(.callout)
  82. .foregroundStyle(timerColor)
  83. Text(remainingTimeString(time: date.timeIntervalSince(timerDate)))
  84. .font(!(date.timeIntervalSince(timerDate) > 0) ? .subheadline : .callout)
  85. .fontWeight(.bold)
  86. .fontDesign(.rounded)
  87. }
  88. // aligns the stopwatch icon exactly with the first pixel of the reservoir icon
  89. .padding(.leading, 12)
  90. }
  91. }
  92. }
  93. if let badgeImage = pumpStatusBadge, let badgeColor = pumpStatusBadgeColor {
  94. HStack {
  95. Spacer()
  96. Image(uiImage: badgeImage.withRenderingMode(.alwaysTemplate))
  97. .colorMultiply(badgeColor)
  98. }
  99. .padding(.trailing, -2)
  100. .padding(.top, -10)
  101. .zIndex(1)
  102. }
  103. }
  104. .frame(width: 100)
  105. }
  106. private func remainingTimeString(time: TimeInterval) -> String {
  107. guard time > 0 else {
  108. return NSLocalizedString("Replace pod", comment: "View/Header when pod expired")
  109. }
  110. var time = time
  111. let days = Int(time / 1.days.timeInterval)
  112. time -= days.days.timeInterval
  113. let hours = Int(time / 1.hours.timeInterval)
  114. time -= hours.hours.timeInterval
  115. let minutes = Int(time / 1.minutes.timeInterval)
  116. if days >= 1 {
  117. return "\(days)" + NSLocalizedString("d", comment: "abbreviation for days") + " \(hours)" +
  118. NSLocalizedString("h", comment: "abbreviation for hours")
  119. }
  120. if hours >= 1 {
  121. return "\(hours)" + NSLocalizedString("h", comment: "abbreviation for hours")
  122. }
  123. return "\(minutes)" + NSLocalizedString("m", comment: "abbreviation for minutes")
  124. }
  125. private var batteryColor: Color {
  126. guard let battery = battery.first else {
  127. return .gray
  128. }
  129. switch battery.percent {
  130. case ...10:
  131. return Color.loopRed
  132. case ...20:
  133. return Color.orange
  134. default:
  135. return Color.loopGreen
  136. }
  137. }
  138. private var reservoirColor: Color {
  139. guard let reservoir = reservoir else {
  140. return .gray
  141. }
  142. switch reservoir {
  143. case ...10:
  144. return Color.loopRed
  145. case ...30:
  146. return Color.orange
  147. default:
  148. return Color.insulin
  149. }
  150. }
  151. private var timerColor: Color {
  152. guard let expisesAt = expiresAtDate else {
  153. return .gray
  154. }
  155. let time = expisesAt.timeIntervalSince(timerDate)
  156. switch time {
  157. case ...8.hours.timeInterval:
  158. return Color.loopRed
  159. case ...1.days.timeInterval:
  160. return Color.orange
  161. default:
  162. return Color.loopGreen
  163. }
  164. }
  165. }
  166. // #Preview("message") {
  167. // PumpView(
  168. // reservoir: .constant(Decimal(10.0)),
  169. // battery: .constant(nil),
  170. // name: .constant("Pump test"),
  171. // expiresAtDate: .constant(Date().addingTimeInterval(24.hours)),
  172. // timerDate: .constant(Date()),
  173. // pumpStatusHighlightMessage: .constant("⚠️\n Insulin suspended")
  174. // )
  175. // }
  176. //
  177. // #Preview("pump reservoir") {
  178. // PumpView(
  179. // reservoir: .constant(Decimal(40.0)),
  180. // battery: .constant(Battery(percent: 50, voltage: 2.0, string: BatteryState.normal, display: true)),
  181. // name: .constant("Pump test"),
  182. // expiresAtDate: .constant(nil),
  183. // timerDate: .constant(Date().addingTimeInterval(-24.hours)),
  184. // pumpStatusHighlightMessage: .constant(nil)
  185. // )
  186. // }
  187. //
  188. // #Preview("pump expiration") {
  189. // PumpView(
  190. // reservoir: .constant(Decimal(10.0)),
  191. // battery: .constant(Battery(percent: 50, voltage: 2.0, string: BatteryState.normal, display: false)),
  192. // name: .constant("Pump test"),
  193. // expiresAtDate: .constant(Date().addingTimeInterval(2.hours)),
  194. // timerDate: .constant(Date().addingTimeInterval(2.hours)),
  195. // pumpStatusHighlightMessage: .constant(nil)
  196. // )
  197. // }
  198. //
  199. // #Preview("no pump") {
  200. // PumpView(
  201. // reservoir: .constant(nil),
  202. // name: .constant(nil),
  203. // expiresAtDate: .constant(""),
  204. // timerDate: .constant(nil),
  205. // timeZone: .constant(Date()),
  206. // pumpStatusHighlightMessage: .constant(nil)
  207. // )
  208. // }