OverrideView.swift 9.9 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217
  1. // LoopFollow
  2. // OverrideView.swift
  3. import HealthKit
  4. import SwiftUI
  5. struct OverrideView: View {
  6. @Environment(\.presentationMode) private var presentationMode
  7. private let pushNotificationManager = PushNotificationManager()
  8. @ObservedObject var device = Storage.shared.device
  9. @ObservedObject var overrideNote = Observable.shared.override
  10. @ObservedObject var overrideEndAt = Observable.shared.overrideEndAt
  11. @State private var showAlert: Bool = false
  12. @State private var alertType: AlertType? = nil
  13. @State private var alertMessage: String? = nil
  14. @State private var isLoading: Bool = false
  15. @State private var statusMessage: String? = nil
  16. @State private var selectedOverride: ProfileManager.TrioOverride? = nil
  17. @State private var showConfirmation: Bool = false
  18. @FocusState private var noteFieldIsFocused: Bool
  19. private var profileManager = ProfileManager.shared
  20. enum AlertType {
  21. case confirmActivation
  22. case confirmCancellation
  23. case statusSuccess
  24. case statusFailure
  25. case validation
  26. }
  27. var body: some View {
  28. NavigationView {
  29. VStack {
  30. if device.value != "Trio" {
  31. ErrorMessageView(
  32. message: String(localized: "Remote commands are currently only available for Trio.")
  33. )
  34. } else {
  35. Form {
  36. if let activeNote = overrideNote.value {
  37. Section(header: Text("Active Override")) {
  38. HStack {
  39. Text("Override")
  40. Spacer()
  41. Text(activeNote)
  42. .foregroundColor(.secondary)
  43. }
  44. if let endAt = overrideEndAt.value {
  45. HStack {
  46. Text("Time Remaining")
  47. Spacer()
  48. RemainingTimeText(endAt: endAt)
  49. .foregroundColor(.secondary)
  50. }
  51. }
  52. Button {
  53. alertType = .confirmCancellation
  54. showAlert = true
  55. } label: {
  56. HStack {
  57. Text("Cancel Override")
  58. Spacer()
  59. Image(systemName: "xmark.app")
  60. .font(.title)
  61. }
  62. }
  63. .tint(.red)
  64. }
  65. }
  66. Section(header: Text("Available Overrides")) {
  67. if profileManager.trioOverrides.isEmpty {
  68. Text("No overrides available.")
  69. .foregroundColor(.secondary)
  70. } else {
  71. ForEach(profileManager.trioOverrides, id: \.name) { override in
  72. Button(action: {
  73. selectedOverride = override
  74. alertType = .confirmActivation
  75. showAlert = true
  76. }) {
  77. HStack {
  78. VStack(alignment: .leading) {
  79. Text(override.name)
  80. .font(.headline)
  81. if let duration = override.duration {
  82. Text("Duration: \(Int(duration)) minutes")
  83. .font(.subheadline)
  84. .foregroundColor(.secondary)
  85. }
  86. if let percentage = override.percentage {
  87. Text("Percentage: \(Int(percentage))%")
  88. .font(.subheadline)
  89. .foregroundColor(.secondary)
  90. }
  91. if let target = override.target {
  92. Text("Target: \(Localizer.formatQuantity(target)) \(Localizer.getPreferredUnit().localizedShortUnitString)")
  93. .font(.subheadline)
  94. .foregroundColor(.secondary)
  95. }
  96. }
  97. Spacer()
  98. Image(systemName: "arrow.right.circle")
  99. .foregroundColor(.blue)
  100. }
  101. }
  102. }
  103. }
  104. }
  105. }
  106. if isLoading {
  107. ProgressView("Please wait...")
  108. .padding()
  109. }
  110. }
  111. }
  112. .navigationTitle("Overrides")
  113. .navigationBarTitleDisplayMode(.inline)
  114. .alert(isPresented: $showAlert) {
  115. switch alertType {
  116. case .confirmActivation:
  117. return Alert(
  118. title: Text("Activate Override"),
  119. message: Text("Do you want to activate the override '\(selectedOverride?.name ?? "")'?"),
  120. primaryButton: .default(Text("Confirm"), action: {
  121. if let override = selectedOverride {
  122. activateOverride(override)
  123. }
  124. }),
  125. secondaryButton: .cancel()
  126. )
  127. case .confirmCancellation:
  128. return Alert(
  129. title: Text("Cancel Override"),
  130. message: Text("Are you sure you want to cancel the active override?"),
  131. primaryButton: .default(Text("Confirm"), action: {
  132. cancelOverride()
  133. }),
  134. secondaryButton: .cancel()
  135. )
  136. case .statusSuccess:
  137. return Alert(
  138. title: Text("Success"),
  139. message: Text(statusMessage ?? ""),
  140. dismissButton: .default(Text("OK"), action: {
  141. presentationMode.wrappedValue.dismiss()
  142. })
  143. )
  144. case .statusFailure:
  145. return Alert(
  146. title: Text("Error"),
  147. message: Text(statusMessage ?? String(localized: "An error occurred.")),
  148. dismissButton: .default(Text("OK"))
  149. )
  150. case .validation:
  151. return Alert(
  152. title: Text("Validation Error"),
  153. message: Text(alertMessage ?? String(localized: "Invalid input.")),
  154. dismissButton: .default(Text("OK"))
  155. )
  156. case .none:
  157. return Alert(title: Text("Unknown Alert"))
  158. }
  159. }
  160. }
  161. }
  162. // MARK: - Functions
  163. private func activateOverride(_ override: ProfileManager.TrioOverride) {
  164. isLoading = true
  165. pushNotificationManager.sendOverridePushNotification(override: override) { success, errorMessage in
  166. DispatchQueue.main.async {
  167. self.isLoading = false
  168. if success {
  169. self.statusMessage = String(localized: "Override command sent successfully.")
  170. self.alertType = .statusSuccess
  171. LogManager.shared.log(category: .apns, message: "sendOverridePushNotification succeeded for override: \(override.name)")
  172. } else {
  173. self.statusMessage = errorMessage ?? String(localized: "Failed to send override command.")
  174. self.alertType = .statusFailure
  175. LogManager.shared.log(category: .apns, message: "sendOverridePushNotification failed for override: \(override.name). Error: \(errorMessage ?? "unknown error")")
  176. }
  177. self.showAlert = true
  178. }
  179. }
  180. }
  181. private func cancelOverride() {
  182. isLoading = true
  183. pushNotificationManager.sendCancelOverridePushNotification { success, errorMessage in
  184. DispatchQueue.main.async {
  185. self.isLoading = false
  186. if success {
  187. self.statusMessage = String(localized: "Cancel override command sent successfully.")
  188. self.alertType = .statusSuccess
  189. LogManager.shared.log(category: .apns, message: "sendCancelOverridePushNotification succeeded")
  190. } else {
  191. self.statusMessage = errorMessage ?? String(localized: "Failed to send cancel override command.")
  192. self.alertType = .statusFailure
  193. LogManager.shared.log(category: .apns, message: "sendCancelOverridePushNotification failed. Error: \(errorMessage ?? "unknown error")")
  194. }
  195. self.showAlert = true
  196. }
  197. }
  198. }
  199. }