OverrideView.swift 9.4 KB

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