浏览代码

Working alert

Jonas Björkert 2 年之前
父节点
当前提交
c38b0f4db3

+ 74 - 63
LoopFollow/Remote/RemoteView.swift

@@ -14,19 +14,23 @@ struct RemoteView: View {
     @ObservedObject var device = ObservableUserDefaults.shared.device
     @ObservedObject var nsWriteAuth = ObservableUserDefaults.shared.nsWriteAuth
     @ObservedObject var tempTarget = Observable.shared.tempTarget
-    @ObservedObject var statusMessage = Observable.shared.statusMessage
 
     @State private var newHKTarget = HKQuantity(unit: .milligramsPerDeciliter, doubleValue: 0.0)
     @State private var duration = HKQuantity(unit: .minute(), doubleValue: 0.0)
-    @State private var showConfirmation: Bool = false
-    @State private var showCancelConfirmation: Bool = false
-    @State private var showCheckmark: Bool = false
+    @State private var showAlert: Bool = false
+    @State private var alertType: AlertType? = nil
     @State private var isLoading: Bool = false
+    @State private var statusMessage: String? = nil
 
-    var onRefreshStatus: () -> Void
     var onCancelExistingTarget: (@escaping (Bool) -> Void) -> Void
     var sendTempTarget: (HKQuantity, HKQuantity, @escaping (Bool) -> Void) -> Void
 
+    enum AlertType {
+        case confirmCommand
+        case status
+        case confirmCancellation
+    }
+
     var body: some View {
         NavigationView {
             VStack {
@@ -52,16 +56,18 @@ struct RemoteView: View {
                                     Text(Localizer.formatQuantity(tempTargetValue))
                                     Text(UserDefaultsRepository.getPreferredUnit().localizedShortUnitString).foregroundColor(.secondary)
                                 }
-                                Button { showCancelConfirmation = true }
-                            label: {
-                                HStack {
-                                    Text("Cancel Temp Target")
-                                    Spacer()
-                                    Image(systemName: "xmark.app")
-                                        .font(.title)
+                                Button {
+                                    alertType = .confirmCancellation
+                                    showAlert = true
+                                } label: {
+                                    HStack {
+                                        Text("Cancel Temp Target")
+                                        Spacer()
+                                        Image(systemName: "xmark.app")
+                                            .font(.title)
+                                    }
                                 }
-                            }
-                            .tint(.red)
+                                .tint(.red)
                             }
                         }
                         Section(header: Text("Temporary Target")) {
@@ -79,7 +85,8 @@ struct RemoteView: View {
                             }
                             HStack {
                                 Button {
-                                    showConfirmation = true
+                                    alertType = .confirmCommand
+                                    showAlert = true
                                 } label: {
                                     Text("Enact")
                                 }
@@ -89,21 +96,8 @@ struct RemoteView: View {
                                 .controlSize(.mini)
                             }
                         }
-                        .alert(isPresented: $showConfirmation) {
-                            Alert(
-                                title: Text("Confirm Command"),
-                                message: Text("New Target: \(Localizer.formatQuantity(newHKTarget)) \(UserDefaultsRepository.getPreferredUnit().localizedShortUnitString)\nDuration: \(Int(duration.doubleValue(for: HKUnit.minute()))) minutes"),
-                                primaryButton: .default(Text("Confirm"), action: {
-                                    enactTempTarget()
-                                }),
-                                secondaryButton: .cancel()
-                            )
-                        }
                     }
-                    .navigationBarItems(trailing: Button(action: onRefreshStatus) {
-                        Image(systemName: "arrow.clockwise")
-                    })
-                    .disabled(isLoading) // Disable the form when loading
+                    .disabled(isLoading)
 
                     if isLoading {
                         ProgressView("Please wait...")
@@ -113,24 +107,37 @@ struct RemoteView: View {
             }
             .navigationTitle("Remote")
             .navigationBarTitleDisplayMode(.inline)
-            .alert(isPresented: .constant(!statusMessage.value.isEmpty)) {
-                Alert(
-                    title: Text("Status"),
-                    message: Text(statusMessage.value),
-                    dismissButton: .default(Text("OK"), action: {
-                        statusMessage.value = ""
-                    })
-                )
-            }
-            .alert(isPresented: $showCancelConfirmation) {
-                Alert(
-                    title: Text("Confirm Cancellation"),
-                    message: Text("Are you sure you want to cancel the existing temp target?"),
-                    primaryButton: .default(Text("Confirm"), action: {
-                        cancelTempTarget()
-                    }),
-                    secondaryButton: .cancel()
-                )
+            .alert(isPresented: $showAlert) {
+                switch alertType {
+                case .confirmCommand:
+                    return Alert(
+                        title: Text("Confirm Command"),
+                        message: Text("New Target: \(Localizer.formatQuantity(newHKTarget)) \(UserDefaultsRepository.getPreferredUnit().localizedShortUnitString)\nDuration: \(Int(duration.doubleValue(for: HKUnit.minute()))) minutes"),
+                        primaryButton: .default(Text("Confirm"), action: {
+                            enactTempTarget()
+                        }),
+                        secondaryButton: .cancel()
+                    )
+                case .status:
+                    return Alert(
+                        title: Text("Status"),
+                        message: Text(statusMessage ?? ""),
+                        dismissButton: .default(Text("OK"), action: {
+                            showAlert = false
+                        })
+                    )
+                case .confirmCancellation:
+                    return Alert(
+                        title: Text("Confirm Cancellation"),
+                        message: Text("Are you sure you want to cancel the existing temp target?"),
+                        primaryButton: .default(Text("Confirm"), action: {
+                            cancelTempTarget()
+                        }),
+                        secondaryButton: .cancel()
+                    )
+                case .none:
+                    return Alert(title: Text("Unknown Alert"))
+                }
             }
         }
     }
@@ -144,15 +151,17 @@ struct RemoteView: View {
         isLoading = true
         print("Enacting Temp Target with target: \(newHKTarget) and duration: \(duration)")
         sendTempTarget(newHKTarget, duration) { success in
+            self.isLoading = false
+            if success {
+                print("Target successfully enacted.")
+                self.statusMessage = "Target successfully enacted."
+            } else {
+                print("Failed to enact target.")
+                self.statusMessage = "Failed to enact target."
+            }
             DispatchQueue.main.async {
-                self.isLoading = false
-                if success {
-                    print("Target successfully enacted.")
-                    self.statusMessage.value = "Target successfully enacted."
-                } else {
-                    print("Failed to enact target.")
-                    self.statusMessage.value = "Failed to enact target."
-                }
+                self.alertType = .status
+                self.showAlert = true
             }
         }
     }
@@ -161,15 +170,17 @@ struct RemoteView: View {
         isLoading = true
         print("Cancelling Temp Target...")
         onCancelExistingTarget() { success in
+            self.isLoading = false
+            if success {
+                print("Temp target successfully cancelled.")
+                self.statusMessage = "Temp target successfully cancelled."
+            } else {
+                print("Failed to cancel temp target.")
+                self.statusMessage = "Failed to cancel temp target."
+            }
             DispatchQueue.main.async {
-                self.isLoading = false
-                if success {
-                    print("Temp target successfully cancelled.")
-                    self.statusMessage.value = "Temp target successfully cancelled."
-                } else {
-                    print("Failed to cancel temp target.")
-                    self.statusMessage.value = "Failed to cancel temp target."
-                }
+                self.alertType = .status
+                self.showAlert = true
             }
         }
     }

+ 22 - 48
LoopFollow/Remote/RemoteViewController.swift

@@ -12,15 +12,10 @@ import SwiftUI
 import HealthKit
 
 class RemoteViewController: UIViewController {
-    private var statusMessage: ObservableValue<String> {
-        return Observable.shared.statusMessage
-    }
-
     override func viewDidLoad() {
         super.viewDidLoad()
 
         let remoteView = RemoteView(
-            onRefreshStatus: refreshStatus,
             onCancelExistingTarget: cancelExistingTarget,
             sendTempTarget: sendTempTarget
         )
@@ -38,17 +33,6 @@ class RemoteViewController: UIViewController {
         ])
 
         hostingController.didMove(toParent: self)
-
-        initialSetup()
-    }
-
-    private func initialSetup() {
-        // Perform initial setup checks here
-        // For example, load the Nightscout URL and token from user defaults or another source
-    }
-
-    private func refreshStatus() {
-        // Refresh the status to check current temp targets and other relevant info
     }
 
     private func cancelExistingTarget(completion: @escaping (Bool) -> Void) {
@@ -60,22 +44,16 @@ class RemoteViewController: UIViewController {
             "created_at": ISO8601DateFormatter().string(from: Date())
         ]
 
-        DispatchQueue.global(qos: .userInitiated).async {
-            print("Executing cancelExistingTarget on thread: \(Thread.current)")
-            NightscoutUtils.executePostRequest(eventType: .treatments, body: tempTargetBody) { (result: Result<[TreatmentCancelResponse], Error>) in
-                DispatchQueue.main.async {
-                    print("Handling cancelExistingTarget result on thread: \(Thread.current)")
-                    switch result {
-                    case .success(let response):
-                        print("Success: \(response)")
-                        self.statusMessage.set("Temp target successfully cancelled.")
-                        completion(true)
-                    case .failure(let error):
-                        print("Error: \(error)")
-                        self.statusMessage.set("Failed to cancel temp target: \(error.localizedDescription)")
-                        completion(false)
-                    }
-                }
+        print("Executing cancelExistingTarget on thread: \(Thread.current), QoS: \(qos_class_self())")
+        NightscoutUtils.executePostRequest(eventType: .treatments, body: tempTargetBody) { (result: Result<[TreatmentCancelResponse], Error>) in
+            print("Handling cancelExistingTarget result on thread: \(Thread.current), QoS: \(qos_class_self())")
+            switch result {
+            case .success(let response):
+                print("Success: \(response)")
+                completion(true)
+            case .failure(let error):
+                print("Error: \(error)")
+                completion(false)
             }
         }
     }
@@ -91,22 +69,18 @@ class RemoteViewController: UIViewController {
             "created_at": ISO8601DateFormatter().string(from: Date())
         ]
 
-        DispatchQueue.global(qos: .userInitiated).async {
-            print("Executing sendTempTarget on thread: \(Thread.current)")
-            NightscoutUtils.executePostRequest(eventType: .treatments, body: tempTargetBody) { (result: Result<[TreatmentResponse], Error>) in
-                DispatchQueue.main.async {
-                    print("Handling sendTempTarget result on thread: \(Thread.current)")
-                    switch result {
-                    case .success(let response):
-                        print("Success: \(response)")
-                        self.statusMessage.set("Temp target sent successfully.")
-                        completion(true)
-                    case .failure(let error):
-                        print("Error: \(error)")
-                        self.statusMessage.set("Failed to send temp target: \(error.localizedDescription)")
-                        completion(false)
-                    }
-                }
+        completion(true)
+
+        print("Executing sendTempTarget on thread: \(Thread.current), QoS: \(qos_class_self())")
+        NightscoutUtils.executePostRequest(eventType: .treatments, body: tempTargetBody) { (result: Result<[TreatmentResponse], Error>) in
+            print("Handling sendTempTarget result on thread: \(Thread.current), QoS: \(qos_class_self())")
+            switch result {
+            case .success(let response):
+                print("Success: \(response)")
+                completion(true)
+            case .failure(let error):
+                print("Error: \(error)")
+                completion(false)
             }
         }
     }

+ 0 - 1
LoopFollow/Storage/Observable.swift

@@ -13,7 +13,6 @@ class Observable {
     static let shared = Observable()
 
     var tempTarget = ObservableValue<HKQuantity?>(default: nil)
-    var statusMessage = ObservableValue<String>(default: "")
 
     private init() {}
 }

+ 1 - 1
LoopFollow/Storage/ObservableValue.swift

@@ -19,8 +19,8 @@ class ObservableValue<T>: ObservableObject {
     }
 
     func set(_ newValue: T) {
+        print("Setting new value: \(newValue)")
         DispatchQueue.main.async {
-            print("Setting new value: \(newValue)")  // Logging
             self.value = newValue
         }
     }

+ 89 - 36
LoopFollow/helpers/NightscoutUtils.swift

@@ -36,7 +36,7 @@ class NightscoutUtils {
                 return "/api/v1/entries.json"
             case .profile:
                 return "/api/v1/profile/current.json"
-            case .deviceStatus: 
+            case .deviceStatus:
                 return "/api/v1/devicestatus.json"
             }
         }
@@ -150,25 +150,25 @@ class NightscoutUtils {
     static func verifyURLAndToken(completion: @escaping (NightscoutError?, String?, Bool) -> Void) {
         let urlUser = ObservableUserDefaults.shared.url.value
         let token = UserDefaultsRepository.token.value
-
+        
         if urlUser.isEmpty {
             completion(.emptyAddress, nil, false)
             return
         }
-
+        
         guard let _ = URL(string: urlUser), urlUser.hasPrefix("http://") || urlUser.hasPrefix("https://") else {
             completion(.invalidURL, nil, false)
             return
         }
-
+        
         guard let request = createURLRequest(url: urlUser, token: token, path: "/api/v1/status.json") else {
             completion(.invalidURL, nil, false)
             return
         }
-
+        
         let task = URLSession.shared.dataTask(with: request) { data, response, error in
             var nsWriteAuth = false
-
+            
             if let httpResponse = response as? HTTPURLResponse {
                 switch httpResponse.statusCode {
                 case 200:
@@ -178,7 +178,7 @@ class NightscoutUtils {
                                let authorized = jsonResponse["authorized"] as? [String: Any],
                                let token = authorized["token"] as? String,
                                let permissionGroups = authorized["permissionGroups"] as? [[String]] {
-
+                                
                                 if permissionGroups.contains(where: { $0.contains("*") }) {
                                     nsWriteAuth = true
                                 } else if permissionGroups.contains(where: { $0.contains("api:treatments:create") }) {
@@ -213,7 +213,7 @@ class NightscoutUtils {
         }
         task.resume()
     }
-
+    
     static func parseDate(_ dateString: String) -> Date? {
         let dateFormatterWithMilliseconds = DateFormatter()
         dateFormatterWithMilliseconds.dateFormat = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"
@@ -233,64 +233,117 @@ class NightscoutUtils {
         
         return nil
     }
-
+    
+    static func retrieveJWTToken(completion: @escaping (NightscoutError?, String?) -> Void) {
+        let urlUser = ObservableUserDefaults.shared.url.value
+        let token = UserDefaultsRepository.token.value
+        
+        if urlUser.isEmpty {
+            completion(.emptyAddress, nil)
+            return
+        }
+        
+        guard let _ = URL(string: urlUser), urlUser.hasPrefix("http://") || urlUser.hasPrefix("https://") else {
+            completion(.invalidURL, nil)
+            return
+        }
+        
+        guard let request = createURLRequest(url: urlUser, token: token, path: "/api/v1/status.json") else {
+            completion(.invalidURL, nil)
+            return
+        }
+        
+        let task = URLSession.shared.dataTask(with: request) { data, response, error in
+            if let httpResponse = response as? HTTPURLResponse {
+                switch httpResponse.statusCode {
+                case 200:
+                    if let data = data {
+                        do {
+                            if let jsonResponse = try JSONSerialization.jsonObject(with: data, options: []) as? [String: Any],
+                               let authorized = jsonResponse["authorized"] as? [String: Any],
+                               let jwtToken = authorized["token"] as? String {
+                                completion(nil, jwtToken)
+                            } else {
+                                completion(.invalidToken, nil)
+                            }
+                        } catch {
+                            completion(.invalidToken, nil)
+                        }
+                    } else {
+                        completion(.invalidToken, nil)
+                    }
+                case 401:
+                    if token.isEmpty {
+                        completion(.tokenRequired, nil)
+                    } else {
+                        completion(.invalidToken, nil)
+                    }
+                default:
+                    completion(.unknown, nil)
+                }
+            } else {
+                if let _ = error {
+                    completion(.siteNotFound, nil)
+                } else {
+                    completion(.networkError, nil)
+                }
+            }
+            
+        }
+        task.resume()
+    }
+    
     static func executePostRequest<T: Decodable>(eventType: EventType, body: [String: Any], completion: @escaping (Result<T, Error>) -> Void) {
-        verifyURLAndToken { error, jwtToken, nsWriteAuth in
+        retrieveJWTToken { error, jwtToken in
             if let error = error {
-                DispatchQueue.main.async {
-                    completion(.failure(NSError(domain: "NightscoutUtils", code: -1, userInfo: [NSLocalizedDescriptionKey: "Token verification failed: \(error)"])))
-                }
+                completion(.failure(NSError(domain: "NightscoutUtils", code: -1, userInfo: [NSLocalizedDescriptionKey: "Token retrieval failed: \(error)"])))
                 return
             }
-
+            
             guard let jwtToken = jwtToken else {
-                DispatchQueue.main.async {
-                    completion(.failure(NSError(domain: "NightscoutUtils", code: -1, userInfo: [NSLocalizedDescriptionKey: "No JWT token received"])))
-                }
+                completion(.failure(NSError(domain: "NightscoutUtils", code: -1, userInfo: [NSLocalizedDescriptionKey: "No JWT token received"])))
                 return
             }
-
+            
             let baseURL = ObservableUserDefaults.shared.url.value
-
+            
             guard let url = URL(string: "\(baseURL)\(eventType.endpoint)") else {
-                DispatchQueue.main.async {
-                    completion(.failure(NSError(domain: "NightscoutUtils", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
-                }
+                completion(.failure(NSError(domain: "NightscoutUtils", code: -1, userInfo: [NSLocalizedDescriptionKey: "Invalid URL"])))
                 return
             }
-
+            
             var request = URLRequest(url: url)
             request.httpMethod = "POST"
             request.setValue("application/json", forHTTPHeaderField: "Content-Type")
             request.setValue("Bearer \(jwtToken)", forHTTPHeaderField: "Authorization")
-
+            
             do {
                 request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
             } catch {
-                DispatchQueue.main.async {
-                    completion(.failure(NSError(domain: "NightscoutUtils", code: -2, userInfo: [NSLocalizedDescriptionKey: "Invalid body parameters"])))
-                }
+                completion(.failure(NSError(domain: "NightscoutUtils", code: -2, userInfo: [NSLocalizedDescriptionKey: "Invalid body parameters"])))
                 return
             }
-
+            
             let task = URLSession.shared.dataTask(with: request) { data, response, error in
                 guard let data = data, error == nil else {
-                    DispatchQueue.main.async {
-                        completion(.failure(error!))
-                    }
+                    completion(.failure(error!))
                     return
                 }
                 print("Network response received on thread: \(Thread.current)")
                 let decoder = JSONDecoder()
                 do {
                     let decodedObject = try decoder.decode(T.self, from: data)
-                    DispatchQueue.main.async {
-                        completion(.success(decodedObject))
-                    }
-                } catch {
-                    DispatchQueue.main.async {
+                    completion(.success(decodedObject))
+                } catch DecodingError.typeMismatch(let type, let context) {
+                    print("Type mismatch error: \(type), \(context.debugDescription)")
+                    do {
+                        let fallbackDecodedObject = try JSONSerialization.jsonObject(with: data, options: [])
+                        print("Fallback JSON decoding success: \(fallbackDecodedObject)")
+                    } catch {
                         completion(.failure(error))
                     }
+                } catch {
+                    completion(.failure(error))
                 }
             }
             task.resume()