OverrideSelectionHistory.swift 6.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167
  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()
  33. quantityFormatter.setPreferredNumberFormatter(for: model.glucoseUnit)
  34. return quantityFormatter
  35. }()
  36. self.glucoseNumberFormatter = quantityFormatter.numberFormatter
  37. self.durationFormatter = {
  38. let formatter = DateComponentsFormatter()
  39. formatter.allowedUnits = [.hour, .minute]
  40. formatter.unitsStyle = .short
  41. return formatter
  42. }()
  43. }
  44. // Style conditionally based on iOS so we get a grouped list style
  45. public var body: some View {
  46. bodyContents
  47. .insetGroupedListStyle()
  48. }
  49. private var bodyContents: some View {
  50. List {
  51. ForEach(model.overrides, id: \.self) { override in
  52. Group {
  53. // Don't show overrides in history that were never active
  54. if override.actualEnd != .deleted {
  55. Section {
  56. NavigationLink(destination: self.detailView(for: override)) {
  57. self.createCell(for: override)
  58. .padding(EdgeInsets(top: 4, leading: 0, bottom: 4, trailing: 0))
  59. }
  60. }
  61. }
  62. }
  63. }
  64. }
  65. .navigationBarTitle(Text(LocalizedString("Override History", comment: "Title for override history view")), displayMode: .large)
  66. }
  67. private func makeTargetRangeText(from targetRange: ClosedRange<HKQuantity>) -> String {
  68. guard
  69. let minTarget = glucoseNumberFormatter.string(from: targetRange.lowerBound.doubleValue(for: model.glucoseUnit)),
  70. let maxTarget = glucoseNumberFormatter.string(from: targetRange.upperBound.doubleValue(for: model.glucoseUnit))
  71. else {
  72. return ""
  73. }
  74. 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.string(from: model.glucoseUnit))
  75. }
  76. private func createCell(for override: TemporaryScheduleOverride) -> OverrideViewCell {
  77. let startTime = DateFormatter.localizedString(from: override.startDate, dateStyle: .none, timeStyle: .short)
  78. var targetRange: String = ""
  79. if let range = override.settings.targetRange {
  80. targetRange = makeTargetRangeText(from: range)
  81. }
  82. var duration: String {
  83. // Don't use the durationFormatter if the interval is infinite
  84. if !override.duration.isFinite && override.scheduledEndDate == override.actualEndDate {
  85. return "∞"
  86. }
  87. return durationFormatter.string(from: override.startDate, to: override.actualEndDate)!
  88. }
  89. let insulinNeeds = override.settings.insulinNeedsScaleFactor
  90. switch override.context {
  91. case .legacyWorkout:
  92. return OverrideViewCell(
  93. symbol: Text("🏃‍♂️"),
  94. name: Text("Workout", comment: "Title for workout override history cell"),
  95. targetRange: Text(targetRange),
  96. duration: Text(duration),
  97. subtitle: Text(startTime),
  98. insulinNeedsScaleFactor: insulinNeeds)
  99. case .preMeal:
  100. return OverrideViewCell(
  101. symbol: Text("🍽"),
  102. name: Text("Pre-Meal", comment: "Title for pre-meal override history cell"),
  103. targetRange: Text(targetRange),
  104. duration: Text(duration),
  105. subtitle: Text(startTime),
  106. insulinNeedsScaleFactor: insulinNeeds)
  107. case .preset(let preset):
  108. return OverrideViewCell(
  109. symbol: Text(preset.symbol),
  110. name: Text(preset.name),
  111. targetRange: Text(targetRange),
  112. duration: Text(duration),
  113. subtitle: Text(startTime),
  114. insulinNeedsScaleFactor: insulinNeeds)
  115. case .custom:
  116. return OverrideViewCell(
  117. symbol: Text("···"),
  118. name: Text("Custom", comment: "Title for custom override history cell"),
  119. targetRange: Text(targetRange),
  120. duration: Text(duration),
  121. subtitle: Text(startTime),
  122. insulinNeedsScaleFactor: insulinNeeds)
  123. }
  124. }
  125. private func title(for override: TemporaryScheduleOverride) -> String {
  126. switch override.context {
  127. case .legacyWorkout:
  128. return LocalizedString("🏃‍♂️ Workout", comment: "Workout override preset title")
  129. case .preMeal:
  130. return LocalizedString("🍽 Pre-Meal", comment: "Premeal override preset title")
  131. case .preset(let preset):
  132. let symbol = preset.symbol
  133. let name = preset.name
  134. let format = LocalizedString("%1$@ %2$@", comment: "The format for an override symbol and name (1: symbol)(2: name)")
  135. return String(format: format, symbol, name)
  136. case .custom:
  137. return LocalizedString("Custom Override", comment: "Custom override preset title")
  138. }
  139. }
  140. private func detailView(for override: TemporaryScheduleOverride) -> some View {
  141. let editorTitle = title(for: override)
  142. return HistoricalOverrideDetailView(
  143. override: override,
  144. glucoseUnit: model.glucoseUnit,
  145. delegate: nil
  146. ).navigationBarTitle(editorTitle)
  147. }
  148. }