OverrideSelectionHistory.swift 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166
  1. //
  2. // OverrideSelectionHistory.swift
  3. // LoopUI
  4. //
  5. // Created by Anna Quinlan on 8/1/20.
  6. // Copyright © 2020 LoopKit Authors. All rights reserved.
  7. //
  8. import SwiftUI
  9. import LoopKit
  10. import HealthKit
  11. public class OverrideHistoryViewModel: ObservableObject {
  12. var overrides: [TemporaryScheduleOverride]
  13. var glucoseUnit: HKUnit
  14. var didEditOverride: ((TemporaryScheduleOverride) -> Void)?
  15. var didDeleteOverride: ((TemporaryScheduleOverride) -> Void)?
  16. public init(
  17. overrides: [TemporaryScheduleOverride],
  18. glucoseUnit: HKUnit
  19. ) {
  20. self.overrides = overrides
  21. self.glucoseUnit = glucoseUnit
  22. }
  23. }
  24. public struct OverrideSelectionHistory: View {
  25. @ObservedObject var model: OverrideHistoryViewModel
  26. private var quantityFormatter: QuantityFormatter
  27. private var glucoseNumberFormatter: NumberFormatter
  28. private var durationFormatter: DateComponentsFormatter
  29. public init(model: OverrideHistoryViewModel) {
  30. self.model = model
  31. self.quantityFormatter = {
  32. let quantityFormatter = QuantityFormatter(for: model.glucoseUnit)
  33. return quantityFormatter
  34. }()
  35. self.glucoseNumberFormatter = quantityFormatter.numberFormatter
  36. self.durationFormatter = {
  37. let formatter = DateComponentsFormatter()
  38. formatter.allowedUnits = [.hour, .minute]
  39. formatter.unitsStyle = .short
  40. return formatter
  41. }()
  42. }
  43. // Style conditionally based on iOS so we get a grouped list style
  44. public var body: some View {
  45. bodyContents
  46. .insetGroupedListStyle()
  47. }
  48. private var bodyContents: some View {
  49. List {
  50. ForEach(model.overrides, id: \.self) { override in
  51. Group {
  52. // Don't show overrides in history that were never active
  53. if override.actualEnd != .deleted {
  54. Section {
  55. NavigationLink(destination: self.detailView(for: override)) {
  56. self.createCell(for: override)
  57. .padding(EdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0))
  58. }
  59. }
  60. }
  61. }
  62. }
  63. }
  64. .navigationBarTitle(Text(LocalizedString("Override History", comment: "Title for override history view")), displayMode: .large)
  65. }
  66. private func makeTargetRangeText(from targetRange: ClosedRange<HKQuantity>) -> String {
  67. guard
  68. let minTarget = glucoseNumberFormatter.string(from: targetRange.lowerBound.doubleValue(for: model.glucoseUnit)),
  69. let maxTarget = glucoseNumberFormatter.string(from: targetRange.upperBound.doubleValue(for: model.glucoseUnit))
  70. else {
  71. return ""
  72. }
  73. return String(format: LocalizedString("%1$@ – %2$@ %3$@", comment: "The format for a glucose target range. (1: min target)(2: max target)(3: glucose unit)"), minTarget, maxTarget, quantityFormatter.localizedUnitStringWithPlurality())
  74. }
  75. private func createCell(for override: TemporaryScheduleOverride) -> OverrideViewCell {
  76. let startTime = DateFormatter.localizedString(from: override.startDate, dateStyle: .none, timeStyle: .short)
  77. var targetRange: String = ""
  78. if let range = override.settings.targetRange {
  79. targetRange = makeTargetRangeText(from: range)
  80. }
  81. var duration: String {
  82. // Don't use the durationFormatter if the interval is infinite
  83. if !override.duration.isFinite && override.scheduledEndDate == override.actualEndDate {
  84. return "∞"
  85. }
  86. return durationFormatter.string(from: override.startDate, to: override.actualEndDate)!
  87. }
  88. let insulinNeeds = override.settings.insulinNeedsScaleFactor
  89. switch override.context {
  90. case .legacyWorkout:
  91. return OverrideViewCell(
  92. symbol: Text("🏃‍♂️"),
  93. name: Text("Workout", comment: "Title for workout override history cell"),
  94. targetRange: Text(targetRange),
  95. duration: Text(duration),
  96. subtitle: Text(startTime),
  97. insulinNeedsScaleFactor: insulinNeeds)
  98. case .preMeal:
  99. return OverrideViewCell(
  100. symbol: Text("🍽"),
  101. name: Text("Pre-Meal", comment: "Title for pre-meal override history cell"),
  102. targetRange: Text(targetRange),
  103. duration: Text(duration),
  104. subtitle: Text(startTime),
  105. insulinNeedsScaleFactor: insulinNeeds)
  106. case .preset(let preset):
  107. return OverrideViewCell(
  108. symbol: Text(preset.symbol),
  109. name: Text(preset.name),
  110. targetRange: Text(targetRange),
  111. duration: Text(duration),
  112. subtitle: Text(startTime),
  113. insulinNeedsScaleFactor: insulinNeeds)
  114. case .custom:
  115. return OverrideViewCell(
  116. symbol: Text("···"),
  117. name: Text("Custom", comment: "Title for custom override history cell"),
  118. targetRange: Text(targetRange),
  119. duration: Text(duration),
  120. subtitle: Text(startTime),
  121. insulinNeedsScaleFactor: insulinNeeds)
  122. }
  123. }
  124. private func title(for override: TemporaryScheduleOverride) -> String {
  125. switch override.context {
  126. case .legacyWorkout:
  127. return LocalizedString("🏃‍♂️ Workout", comment: "Workout override preset title")
  128. case .preMeal:
  129. return LocalizedString("🍽 Pre-Meal", comment: "Premeal override preset title")
  130. case .preset(let preset):
  131. let symbol = preset.symbol
  132. let name = preset.name
  133. let format = LocalizedString("%1$@ %2$@", comment: "The format for an override symbol and name (1: symbol)(2: name)")
  134. return String(format: format, symbol, name)
  135. case .custom:
  136. return LocalizedString("Custom Override", comment: "Custom override preset title")
  137. }
  138. }
  139. private func detailView(for override: TemporaryScheduleOverride) -> some View {
  140. let editorTitle = title(for: override)
  141. return HistoricalOverrideDetailView(
  142. override: override,
  143. glucoseUnit: model.glucoseUnit,
  144. delegate: nil
  145. ).navigationBarTitle(editorTitle)
  146. }
  147. }