AdjustmentsRootView+TempTargets.swift 7.3 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201
  1. import CoreData
  2. import SwiftUI
  3. extension Adjustments.RootView {
  4. @ViewBuilder func tempTargets() -> some View {
  5. if state.isTempTargetEnabled, state.activeTempTargetName.isNotEmpty {
  6. currentActiveAdjustment
  7. }
  8. if state.scheduledTempTargets.isNotEmpty {
  9. scheduledTempTargets
  10. }
  11. if state.tempTargetPresets.isNotEmpty {
  12. tempTargetPresets
  13. } else {
  14. defaultText
  15. }
  16. }
  17. private var scheduledTempTargets: some View {
  18. Section {
  19. ForEach(state.scheduledTempTargets) { tempTarget in
  20. tempTargetView(for: tempTarget)
  21. .swipeActions(edge: .trailing, allowsFullSwipe: true) {
  22. swipeActionsForTempTargets(for: tempTarget)
  23. }
  24. }
  25. .listRowBackground(Color.chart)
  26. } header: {
  27. Text("Scheduled Temp Targets")
  28. }
  29. }
  30. private var tempTargetPresets: some View {
  31. Section {
  32. ForEach(state.tempTargetPresets) { preset in
  33. tempTargetView(for: preset, showCheckmark: showTempTargetCheckmark) {
  34. enactTempTargetPreset(preset)
  35. }
  36. .swipeActions(edge: .trailing, allowsFullSwipe: true) {
  37. swipeActionsForTempTargets(for: preset)
  38. }
  39. }
  40. .onMove(perform: state.reorderTempTargets)
  41. .confirmationDialog(
  42. deleteConfirmationTitle,
  43. isPresented: $isConfirmDeletePresented,
  44. titleVisibility: .visible
  45. ) {
  46. deleteConfirmationButtons()
  47. } message: {
  48. deleteConfirmationMessage
  49. }
  50. .listRowBackground(Color.chart)
  51. } header: {
  52. Text("Temporary Target Presets")
  53. } footer: {
  54. HStack {
  55. Image(systemName: "hand.draw.fill").foregroundStyle(.primary)
  56. Text("Swipe left to edit or delete a temporary target preset. Hold, drag and drop to reorder a preset.")
  57. }
  58. }
  59. }
  60. private func enactTempTargetPreset(_ preset: TempTargetStored) {
  61. Task {
  62. let objectID = preset.objectID
  63. await state.enactTempTargetPreset(withID: objectID)
  64. selectedTempTargetPresetID = preset.id?.uuidString
  65. showTempTargetCheckmark = true
  66. DispatchQueue.main.asyncAfter(deadline: .now() + 3) {
  67. showTempTargetCheckmark = false
  68. }
  69. }
  70. }
  71. private func swipeActionsForTempTargets(for tempTarget: TempTargetStored) -> some View {
  72. Group {
  73. Button {
  74. Task {
  75. selectedTempTarget = tempTarget
  76. isConfirmDeletePresented = true
  77. }
  78. } label: {
  79. Label("Delete", systemImage: "trash")
  80. .tint(.red)
  81. }
  82. Button(action: {
  83. selectedTempTarget = tempTarget
  84. state.showTempTargetEditSheet = true
  85. }, label: {
  86. Label("Edit", systemImage: "pencil")
  87. .tint(.blue)
  88. })
  89. }
  90. }
  91. private var deleteConfirmationTitle: String {
  92. "Delete the Temp Target Preset \"\(selectedTempTarget?.name ?? "")\"?"
  93. }
  94. private func deleteConfirmationButtons() -> some View {
  95. Group {
  96. if let itemToDelete = selectedTempTarget {
  97. Button(
  98. state.currentActiveTempTarget == selectedTempTarget ? "Stop and Delete" : "Delete",
  99. role: .destructive
  100. ) {
  101. if state.currentActiveTempTarget == selectedTempTarget {
  102. Task {
  103. await state.disableAllActiveTempTargets(createTempTargetRunEntry: true)
  104. }
  105. }
  106. Task {
  107. await state.invokeTempTargetPresetDeletion(itemToDelete.objectID)
  108. }
  109. selectedTempTarget = nil
  110. }
  111. }
  112. Button("Cancel", role: .cancel) {
  113. selectedTempTarget = nil
  114. }
  115. }
  116. }
  117. private var deleteConfirmationMessage: Text? {
  118. if state.currentActiveTempTarget == selectedTempTarget {
  119. return Text("This Temp Target preset is currently running. Deleting will stop it.")
  120. }
  121. return nil
  122. }
  123. private func tempTargetView(
  124. for tempTarget: TempTargetStored,
  125. showCheckmark: Bool = false,
  126. onTap: (() -> Void)? = nil
  127. ) -> some View {
  128. let target = tempTarget.target ?? 100
  129. let tempTargetValue = Decimal(target as! Double.RawValue)
  130. let isSelected = tempTarget.id?.uuidString == selectedTempTargetPresetID
  131. let tempTargetHalfBasal = Decimal(
  132. tempTarget.halfBasalTarget as? Double
  133. .RawValue ?? Double(state.settingHalfBasalTarget)
  134. )
  135. let percentage = Int(
  136. state.computeAdjustedPercentage(usingHBT: tempTargetHalfBasal, usingTarget: tempTargetValue)
  137. )
  138. let remainingTime = tempTarget.date?.timeIntervalSinceNow ?? 0
  139. return ZStack(alignment: .trailing) {
  140. HStack {
  141. VStack(alignment: .leading) {
  142. HStack {
  143. Text(tempTarget.name ?? "")
  144. Spacer()
  145. if remainingTime > 0 {
  146. Text("Starts in \(formattedTimeRemaining(remainingTime))")
  147. .foregroundColor(colorScheme == .dark ? .orange : .accentColor)
  148. }
  149. }
  150. HStack(spacing: 2) {
  151. Text(formattedGlucose(glucose: target as Decimal))
  152. .foregroundColor(.secondary)
  153. .font(.caption)
  154. Text("for")
  155. .foregroundColor(.secondary)
  156. .font(.caption)
  157. Text("\(Formatter.integerFormatter.string(from: (tempTarget.duration ?? 0) as NSNumber)!)")
  158. .foregroundColor(.secondary)
  159. .font(.caption)
  160. Text("min")
  161. .foregroundColor(.secondary)
  162. .font(.caption)
  163. if state.isAdjustSensEnabled(usingTarget: tempTargetValue) {
  164. Text(", \(percentage)%")
  165. .foregroundColor(.secondary)
  166. .font(.caption)
  167. }
  168. Spacer()
  169. }
  170. .padding(.top, 2)
  171. }
  172. .contentShape(Rectangle())
  173. .onTapGesture {
  174. onTap?()
  175. }
  176. }
  177. if showCheckmark && isSelected {
  178. Image(systemName: "checkmark.circle.fill")
  179. .imageScale(.large)
  180. .fontWeight(.bold)
  181. .foregroundStyle(Color.green)
  182. } else if onTap != nil {
  183. Image(systemName: "line.3.horizontal")
  184. .imageScale(.medium)
  185. .foregroundStyle(.secondary)
  186. }
  187. }
  188. }
  189. }