From ce46f630ac7aa89346656bd8c363105a7dbe16b8 Mon Sep 17 00:00:00 2001 From: Pete Schwamb Date: Mon, 8 Apr 2024 17:46:10 -0500 Subject: [PATCH] Cleanup around diagnostic commands --- OmniBLE.xcodeproj/project.pbxproj | 14 +- OmniBLE/PumpManager/OmniBLEPumpManager.swift | 239 +++++++++--------- .../ViewModels/OmniBLESettingsViewModel.swift | 57 +---- OmniBLE/PumpManagerUI/Views/FirstAppear.swift | 30 --- .../Views/OmniBLESettingsView.swift | 9 +- .../Views/PlayTestBeepsView.swift | 34 +-- ...nostics.swift => PodDiagnosticsView.swift} | 43 ++-- .../Views/PumpManagerDetailsView.swift | 27 +- .../PumpManagerUI/Views/ReadPodInfoView.swift | 37 ++- .../Views/ReadPodStatusView.swift | 36 ++- 10 files changed, 224 insertions(+), 302 deletions(-) delete mode 100644 OmniBLE/PumpManagerUI/Views/FirstAppear.swift rename OmniBLE/PumpManagerUI/Views/{PodDiagnostics.swift => PodDiagnosticsView.swift} (72%) diff --git a/OmniBLE.xcodeproj/project.pbxproj b/OmniBLE.xcodeproj/project.pbxproj index fe6df88e..a2357c38 100644 --- a/OmniBLE.xcodeproj/project.pbxproj +++ b/OmniBLE.xcodeproj/project.pbxproj @@ -67,9 +67,9 @@ 10389A3C26FF7841002115E9 /* Message.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10389A1D26FF7841002115E9 /* Message.swift */; }; 10389A3F26FF7841002115E9 /* CRC16.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10389A2026FF7841002115E9 /* CRC16.swift */; }; 10389A4126FF7841002115E9 /* MessageTransport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 10389A2226FF7841002115E9 /* MessageTransport.swift */; }; + 196A6F232AFFFD1700E3C089 /* SilencePodPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196A6F222AFFFD1200E3C089 /* SilencePodPreference.swift */; }; 2742C7052AD875B100E67833 /* SlideButton in Frameworks */ = {isa = PBXBuildFile; productRef = 2742C7042AD875B100E67833 /* SlideButton */; }; 4B67E2D5289B4EDB002D92AF /* Localizable.strings in Resources */ = {isa = PBXBuildFile; fileRef = 4B67E2D3289B4EDB002D92AF /* Localizable.strings */; }; - 196A6F232AFFFD1700E3C089 /* SilencePodPreference.swift in Sources */ = {isa = PBXBuildFile; fileRef = 196A6F222AFFFD1200E3C089 /* SilencePodPreference.swift */; }; 84752E9326ED0FFE009FD801 /* OmniBLE.h in Headers */ = {isa = PBXBuildFile; fileRef = 84752E8526ED0FFE009FD801 /* OmniBLE.h */; settings = {ATTRIBUTES = (Public, ); }; }; 84752ED626ED13F5009FD801 /* Id.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84752EBF26ED13F5009FD801 /* Id.swift */; }; 84752ED726ED13F5009FD801 /* X25519KeyGenerator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 84752EC126ED13F5009FD801 /* X25519KeyGenerator.swift */; }; @@ -170,12 +170,11 @@ D802CD1027DD99AB0072E3A1 /* CRC16Tests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D802CD0F27DD99AB0072E3A1 /* CRC16Tests.swift */; }; D802CD1227DD9AE10072E3A1 /* BasalScheduleTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = D802CD1127DD9AE10072E3A1 /* BasalScheduleTests.swift */; }; D845A1372AF89F5500EA0853 /* ActivityView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845A1362AF89F5500EA0853 /* ActivityView.swift */; }; - D845A1392AF89F6300EA0853 /* FirstAppear.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845A1382AF89F6300EA0853 /* FirstAppear.swift */; }; D845A13B2AF89F7100EA0853 /* PlayTestBeepsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845A13A2AF89F7100EA0853 /* PlayTestBeepsView.swift */; }; D845A13F2AF89F8400EA0853 /* ReadPodStatusView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845A13C2AF89F8400EA0853 /* ReadPodStatusView.swift */; }; D845A1412AF89F8400EA0853 /* PumpManagerDetailsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845A13E2AF89F8400EA0853 /* PumpManagerDetailsView.swift */; }; D845A1432AF89F9200EA0853 /* SilencePodSelectionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D845A1422AF89F9200EA0853 /* SilencePodSelectionView.swift */; }; - D85AEABC2B12D76F00081044 /* PodDiagnostics.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85AEABB2B12D76F00081044 /* PodDiagnostics.swift */; }; + D85AEABC2B12D76F00081044 /* PodDiagnosticsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85AEABB2B12D76F00081044 /* PodDiagnosticsView.swift */; }; D85AEAC42B13083F00081044 /* ReadPodInfoView.swift in Sources */ = {isa = PBXBuildFile; fileRef = D85AEAC32B13083F00081044 /* ReadPodInfoView.swift */; }; D8896C6227890E6B00E09A96 /* DetailedStatus+OmniBLE.swift in Sources */ = {isa = PBXBuildFile; fileRef = D8896C6127890E6B00E09A96 /* DetailedStatus+OmniBLE.swift */; }; D895BF5B275DE64000D51FC7 /* StringLengthPrefixEncoding.swift in Sources */ = {isa = PBXBuildFile; fileRef = D895BF5A275DE64000D51FC7 /* StringLengthPrefixEncoding.swift */; }; @@ -455,12 +454,11 @@ D802CD0F27DD99AB0072E3A1 /* CRC16Tests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CRC16Tests.swift; sourceTree = ""; }; D802CD1127DD9AE10072E3A1 /* BasalScheduleTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = BasalScheduleTests.swift; sourceTree = ""; }; D845A1362AF89F5500EA0853 /* ActivityView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ActivityView.swift; sourceTree = ""; }; - D845A1382AF89F6300EA0853 /* FirstAppear.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = FirstAppear.swift; sourceTree = ""; }; D845A13A2AF89F7100EA0853 /* PlayTestBeepsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PlayTestBeepsView.swift; sourceTree = ""; }; D845A13C2AF89F8400EA0853 /* ReadPodStatusView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadPodStatusView.swift; sourceTree = ""; }; D845A13E2AF89F8400EA0853 /* PumpManagerDetailsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PumpManagerDetailsView.swift; sourceTree = ""; }; D845A1422AF89F9200EA0853 /* SilencePodSelectionView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SilencePodSelectionView.swift; sourceTree = ""; }; - D85AEABB2B12D76F00081044 /* PodDiagnostics.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodDiagnostics.swift; sourceTree = ""; }; + D85AEABB2B12D76F00081044 /* PodDiagnosticsView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PodDiagnosticsView.swift; sourceTree = ""; }; D85AEAC32B13083F00081044 /* ReadPodInfoView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReadPodInfoView.swift; sourceTree = ""; }; D8896C6127890E6B00E09A96 /* DetailedStatus+OmniBLE.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "DetailedStatus+OmniBLE.swift"; sourceTree = ""; }; D895BF5A275DE64000D51FC7 /* StringLengthPrefixEncoding.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StringLengthPrefixEncoding.swift; sourceTree = ""; }; @@ -775,7 +773,6 @@ C1F67EB227975E710017487F /* DesignElements */, C1F67E7B27975B830017487F /* ExpirationReminderPickerView.swift */, C1F67E7827975B830017487F /* ExpirationReminderSetupView.swift */, - D845A1382AF89F6300EA0853 /* FirstAppear.swift */, C1F67E7D27975B830017487F /* HUDAssets.xcassets */, C1F67E8527975B830017487F /* InsertCannulaView.swift */, C187C190278FCEC9006E3557 /* InsulinTypeConfirmation.swift */, @@ -789,7 +786,7 @@ C1F67E7F27975B830017487F /* PairPodView.swift */, D845A13A2AF89F7100EA0853 /* PlayTestBeepsView.swift */, C1F67E8A27975B830017487F /* PodDetailsView.swift */, - D85AEABB2B12D76F00081044 /* PodDiagnostics.swift */, + D85AEABB2B12D76F00081044 /* PodDiagnosticsView.swift */, C1F67E8827975B830017487F /* PodSetupView.swift */, D845A13E2AF89F8400EA0853 /* PumpManagerDetailsView.swift */, D85AEAC32B13083F00081044 /* ReadPodInfoView.swift */, @@ -1149,7 +1146,7 @@ 10389A3A26FF7841002115E9 /* SetInsulinScheduleCommand.swift in Sources */, D845A13B2AF89F7100EA0853 /* PlayTestBeepsView.swift in Sources */, 10389A3826FF7841002115E9 /* DetailedStatus.swift in Sources */, - D85AEABC2B12D76F00081044 /* PodDiagnostics.swift in Sources */, + D85AEABC2B12D76F00081044 /* PodDiagnosticsView.swift in Sources */, C1F67E9927975B830017487F /* NotificationSettingsView.swift in Sources */, 10389A2B26FF7841002115E9 /* PlaceholderMessageBlock.swift in Sources */, 10389A3026FF7841002115E9 /* StatusResponse.swift in Sources */, @@ -1221,7 +1218,6 @@ C1F67E9227975B830017487F /* BasalStateView.swift in Sources */, 1024E32B27446DB000DE01F2 /* MessagePacket.swift in Sources */, 10289E7B2739F886000339E6 /* EapMessage.swift in Sources */, - D845A1392AF89F6300EA0853 /* FirstAppear.swift in Sources */, C1C001C127A2349D00533D35 /* OmniBLE.swift in Sources */, 10389A3326FF7841002115E9 /* CancelDeliveryCommand.swift in Sources */, C1DBD513282FF79D009FCF74 /* ManualTempBasalEntryView.swift in Sources */, diff --git a/OmniBLE/PumpManager/OmniBLEPumpManager.swift b/OmniBLE/PumpManager/OmniBLEPumpManager.swift index b45b14eb..a86b0e9f 100644 --- a/OmniBLE/PumpManager/OmniBLEPumpManager.swift +++ b/OmniBLE/PumpManager/OmniBLEPumpManager.swift @@ -1082,30 +1082,31 @@ extension OmniBLEPumpManager { } } - public func getDetailedStatus(completion: ((_ result: PumpManagerResult) -> Void)? = nil) { + public func getDetailedStatus() async throws -> DetailedStatus { // use hasSetupPod here instead of hasActivePod as DetailedStatus can be read with a faulted Pod guard self.hasSetupPod else { - completion?(.failure(PumpManagerError.configuration(OmniBLEPumpManagerError.noPodPaired))) - return + throw PumpManagerError.configuration(OmniBLEPumpManagerError.noPodPaired) } - podComms.runSession(withName: "Get detailed status") { (result) in - do { - switch result { - case .success(let session): - let beepBlock = self.beepMessageBlock(beepType: .bipBip) - let detailedStatus = try session.getDetailedStatus(beepBlock: beepBlock) - session.dosesForStorage({ (doses) -> Bool in - self.store(doses: doses, in: session) - }) - completion?(.success(detailedStatus)) - case .failure(let error): - throw error + return try await withCheckedThrowingContinuation { continuation in + podComms.runSession(withName: "Get detailed status") { (result) in + do { + switch result { + case .success(let session): + let beepBlock = self.beepMessageBlock(beepType: .bipBip) + let detailedStatus = try session.getDetailedStatus(beepBlock: beepBlock) + session.dosesForStorage({ (doses) -> Bool in + self.store(doses: doses, in: session) + }) + continuation.resume(returning: detailedStatus) + case .failure(let error): + continuation.resume(throwing: error) + } + } catch let error { + self.log.error("Failed to fetch detailed status: %{public}@", String(describing: error)) + continuation.resume(throwing: PumpManagerError.communication(error as? LocalizedError)) } - } catch let error { - completion?(.failure(.communication(error as? LocalizedError))) - self.log.error("Failed to fetch detailed status: %{public}@", String(describing: error)) } } } @@ -1276,163 +1277,165 @@ extension OmniBLEPumpManager { #endif } - public func playTestBeeps(completion: @escaping (Error?) -> Void) { + public func playTestBeeps() async throws { guard self.hasActivePod else { - completion(OmniBLEPumpManagerError.noPodPaired) - return + throw OmniBLEPumpManagerError.noPodPaired } guard state.podState?.unfinalizedBolus?.scheduledCertainty == .uncertain || state.podState?.unfinalizedBolus?.isFinished() != false else { self.log.info("Skipping Play Test Beeps due to bolus still in progress.") - completion(PodCommsError.unfinalizedBolus) - return + throw PodCommsError.unfinalizedBolus } - self.podComms.runSession(withName: "Play Test Beeps") { (result) in - switch result { - case .success(let session): - // preserve the pod's completion beep state which gets reset playing beeps - let enabled: Bool = self.silencePod ? false : self.beepPreference.shouldBeepForManualCommand - let result = session.beepConfig( - beepType: .bipBeepBipBeepBipBeepBipBeep, - tempBasalCompletionBeep: enabled && self.hasUnfinalizedManualTempBasal, - bolusCompletionBeep: enabled && self.hasUnfinalizedManualBolus - ) - + try await withCheckedThrowingContinuation { continuation in + self.podComms.runSession(withName: "Play Test Beeps") { (result) in switch result { - case .success: - completion(nil) + case .success(let session): + // preserve the pod's completion beep state which gets reset playing beeps + let enabled: Bool = self.silencePod ? false : self.beepPreference.shouldBeepForManualCommand + let result = session.beepConfig( + beepType: .bipBeepBipBeepBipBeepBipBeep, + tempBasalCompletionBeep: enabled && self.hasUnfinalizedManualTempBasal, + bolusCompletionBeep: enabled && self.hasUnfinalizedManualBolus + ) + + switch result { + case .success: + continuation.resume() + case .failure(let error): + continuation.resume(throwing: error) + } case .failure(let error): - completion(error) + continuation.resume(throwing: error) } - case .failure(let error): - completion(error) } } } - public func readPulseLog(completion: @escaping (Result) -> Void) { + public func readPulseLog() async throws -> String { // use hasSetupPod to be able to read pulse log from a faulted Pod guard self.hasSetupPod else { - completion(.failure(OmniBLEPumpManagerError.noPodPaired)) - return + throw OmniBLEPumpManagerError.noPodPaired } + guard state.podState?.isFaulted == true || state.podState?.unfinalizedBolus?.scheduledCertainty == .uncertain || state.podState?.unfinalizedBolus?.isFinished() != false else { self.log.info("Skipping Read Pulse Log due to bolus still in progress.") - completion(.failure(PodCommsError.unfinalizedBolus)) - return + throw PodCommsError.unfinalizedBolus } - self.podComms.runSession(withName: "Read Pulse Log") { (result) in - switch result { - case .success(let session): - do { - // read the most recent 50 entries from the pulse log - let beepBlock = self.beepMessageBlock(beepType: .bipBeeeeep) - let podInfoResponse = try session.readPodInfo(podInfoResponseSubType: .pulseLogRecent, beepBlock: beepBlock) - guard let podInfoPulseLogRecent = podInfoResponse.podInfo as? PodInfoPulseLogRecent else { - self.log.error("Unable to decode PulseLogRecent: %s", String(describing: podInfoResponse)) - completion(.failure(PodCommsError.unexpectedResponse(response: .podInfoResponse))) - return + return try await withCheckedThrowingContinuation { continuation in + self.podComms.runSession(withName: "Read Pulse Log") { (result) in + switch result { + case .success(let session): + do { + // read the most recent 50 entries from the pulse log + let beepBlock = self.beepMessageBlock(beepType: .bipBeeeeep) + let podInfoResponse = try session.readPodInfo(podInfoResponseSubType: .pulseLogRecent, beepBlock: beepBlock) + guard let podInfoPulseLogRecent = podInfoResponse.podInfo as? PodInfoPulseLogRecent else { + self.log.error("Unable to decode PulseLogRecent: %s", String(describing: podInfoResponse)) + throw PodCommsError.unexpectedResponse(response: .podInfoResponse) + } + let lastPulseNumber = Int(podInfoPulseLogRecent.indexLastEntry) + let str = pulseLogString(pulseLogEntries: podInfoPulseLogRecent.pulseLog, lastPulseNumber: lastPulseNumber) + continuation.resume(returning: str) + } catch { + continuation.resume(throwing: error) } - let lastPulseNumber = Int(podInfoPulseLogRecent.indexLastEntry) - let str = pulseLogString(pulseLogEntries: podInfoPulseLogRecent.pulseLog, lastPulseNumber: lastPulseNumber) - completion(.success(str)) - } catch let error { - completion(.failure(error)) + case .failure(let error): + continuation.resume(throwing: error) } - case .failure(let error): - completion(.failure(error)) } } } - public func readPulseLogPlus(completion: @escaping (Result) -> Void) { + public func readPulseLogPlus() async throws -> String { // use hasSetupPod here instead of hasActivePod as PodInfo can be read with a faulted Pod guard self.hasSetupPod else { - completion(.failure(OmniBLEPumpManagerError.noPodPaired)) - return + throw OmniBLEPumpManagerError.noPodPaired } guard state.podState?.isFaulted == true || state.podState?.unfinalizedBolus?.scheduledCertainty == .uncertain || state.podState?.unfinalizedBolus?.isFinished() != false else { self.log.info("Skipping Read Pulse Log Plus due to bolus still in progress.") - completion(.failure(PodCommsError.unfinalizedBolus)) - return + throw PodCommsError.unfinalizedBolus } - podComms.runSession(withName: "Read Pulse Log Plus") { (result) in - do { - switch result { - case .success(let session): - let beepBlock = self.beepMessageBlock(beepType: .bipBeeeeep) - let podInfoResponse = try session.readPodInfo(podInfoResponseSubType: .pulseLogPlus, beepBlock: beepBlock) - guard let podInfoPulseLogPlus = podInfoResponse.podInfo as? PodInfoPulseLogPlus else { - self.log.error("Unable to decode Pulse Log Plus: %s", String(describing: podInfoResponse)) - throw PodCommsError.unexpectedResponse(response: .podInfoResponse) + return try await withCheckedThrowingContinuation { continuation in + podComms.runSession(withName: "Read Pulse Log Plus") { (result) in + do { + switch result { + case .success(let session): + let beepBlock = self.beepMessageBlock(beepType: .bipBeeeeep) + let podInfoResponse = try session.readPodInfo(podInfoResponseSubType: .pulseLogPlus, beepBlock: beepBlock) + guard let podInfoPulseLogPlus = podInfoResponse.podInfo as? PodInfoPulseLogPlus else { + self.log.error("Unable to decode Pulse Log Plus: %s", String(describing: podInfoResponse)) + throw PodCommsError.unexpectedResponse(response: .podInfoResponse) + } + let str = pulseLogPlusString(podInfoPulseLogPlus: podInfoPulseLogPlus) + continuation.resume(returning: str) + case .failure(let error): + continuation.resume(throwing: error) } - let str = pulseLogPlusString(podInfoPulseLogPlus: podInfoPulseLogPlus) - completion(.success(str)) - case .failure(let error): - throw error + } catch { + continuation.resume(throwing: error) } - } catch let error { - completion(.failure(error)) } } } - public func readActivationTime(completion: @escaping (Result) -> Void) { + public func readActivationTime() async throws -> String { // use hasSetupPod here instead of hasActivePod as PodInfo can be read with a faulted Pod guard self.hasSetupPod else { - completion(.failure(OmniBLEPumpManagerError.noPodPaired)) - return + throw OmniBLEPumpManagerError.noPodPaired } - podComms.runSession(withName: "Read Activation Time") { (result) in - do { - switch result { - case .success(let session): - let beepBlock = self.beepMessageBlock(beepType: .beepBeep) - let podInfoResponse = try session.readPodInfo(podInfoResponseSubType: .activationTime, beepBlock: beepBlock) - guard let podInfoActivationTime = podInfoResponse.podInfo as? PodInfoActivationTime else { - self.log.error("Unable to decode Activation Time: %s", String(describing: podInfoResponse)) - throw PodCommsError.unexpectedResponse(response: .podInfoResponse) + return try await withCheckedThrowingContinuation { continuation in + podComms.runSession(withName: "Read Activation Time") { (result) in + do { + switch result { + case .success(let session): + let beepBlock = self.beepMessageBlock(beepType: .beepBeep) + let podInfoResponse = try session.readPodInfo(podInfoResponseSubType: .activationTime, beepBlock: beepBlock) + guard let podInfoActivationTime = podInfoResponse.podInfo as? PodInfoActivationTime else { + self.log.error("Unable to decode Activation Time: %s", String(describing: podInfoResponse)) + throw PodCommsError.unexpectedResponse(response: .podInfoResponse) + } + let str = activationTimeString(podInfoActivationTime: podInfoActivationTime) + continuation.resume(returning: str) + case .failure(let error): + continuation.resume(throwing: error) } - let str = activationTimeString(podInfoActivationTime: podInfoActivationTime) - completion(.success(str)) - case .failure(let error): - throw error + } catch { + continuation.resume(throwing: error) } - } catch let error { - completion(.failure(error)) } } } - public func readTriggeredAlerts(completion: @escaping (Result) -> Void) { + public func readTriggeredAlerts() async throws -> String { // use hasSetupPod here instead of hasActivePod as PodInfo can be read with a faulted Pod guard self.hasSetupPod else { - completion(.failure(OmniBLEPumpManagerError.noPodPaired)) - return + throw OmniBLEPumpManagerError.noPodPaired } - podComms.runSession(withName: "Read Triggered Alerts") { (result) in - do { - switch result { - case .success(let session): - let beepBlock = self.beepMessageBlock(beepType: .beepBeep) - let podInfoResponse = try session.readPodInfo(podInfoResponseSubType: .triggeredAlerts, beepBlock: beepBlock) - guard let podInfoTriggeredAlerts = podInfoResponse.podInfo as? PodInfoTriggeredAlerts else { - self.log.error("Unable to decode Read Triggered Alerts: %s", String(describing: podInfoResponse)) - throw PodCommsError.unexpectedResponse(response: .podInfoResponse) + return try await withCheckedThrowingContinuation { continuation in + podComms.runSession(withName: "Read Triggered Alerts") { (result) in + do { + switch result { + case .success(let session): + let beepBlock = self.beepMessageBlock(beepType: .beepBeep) + let podInfoResponse = try session.readPodInfo(podInfoResponseSubType: .triggeredAlerts, beepBlock: beepBlock) + guard let podInfoTriggeredAlerts = podInfoResponse.podInfo as? PodInfoTriggeredAlerts else { + self.log.error("Unable to decode Read Triggered Alerts: %s", String(describing: podInfoResponse)) + throw PodCommsError.unexpectedResponse(response: .podInfoResponse) + } + let str = triggeredAlertsString(podInfoTriggeredAlerts: podInfoTriggeredAlerts) + continuation.resume(returning: str) + case .failure(let error): + continuation.resume(throwing: error) } - let str = triggeredAlertsString(podInfoTriggeredAlerts: podInfoTriggeredAlerts) - completion(.success(str)) - case .failure(let error): - throw error + } catch let error { + continuation.resume(throwing: error) } - } catch let error { - completion(.failure(error)) } } } diff --git a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift index a7993dc6..14f937a8 100644 --- a/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift +++ b/OmniBLE/PumpManagerUI/ViewModels/OmniBLESettingsViewModel.swift @@ -324,52 +324,8 @@ class OmniBLESettingsViewModel: ObservableObject { } } - func readPodStatus(_ completion: @escaping (_ result: PumpManagerResult) -> Void) { - pumpManager.getDetailedStatus() { (result) in - DispatchQueue.main.async { - completion(result) - } - } - } - - func readPulseLog(_ completion: @escaping (_ result: Result) -> Void) { - pumpManager.readPulseLog() { (result) in - DispatchQueue.main.async { - completion(result) - } - } - } - - func readPulseLogPlus(_ completion: @escaping (_ result: Result) -> Void) { - pumpManager.readPulseLogPlus() { (result) in - DispatchQueue.main.async { - completion(result) - } - } - } - - func readActivationTime(_ completion: @escaping (_ result: Result) -> Void) { - pumpManager.readActivationTime() { (result) in - DispatchQueue.main.async { - completion(result) - } - } - } - - func readTriggeredAlerts(_ completion: @escaping (_ result: Result) -> Void) { - pumpManager.readTriggeredAlerts() { (result) in - DispatchQueue.main.async { - completion(result) - } - } - } - - func playTestBeeps(_ completion: @escaping (Error?) -> Void) { - pumpManager.playTestBeeps(completion: completion) - } - - func pumpManagerDetails(_ completion: @escaping (_ result: String) -> Void) { - completion(pumpManager.debugDescription) + func playTestBeeps() async throws { + try await pumpManager.playTestBeeps() } func setConfirmationBeeps(_ preference: BeepPreference, _ completion: @escaping (_ error: LocalizedError?) -> Void) { @@ -413,6 +369,10 @@ class OmniBLESettingsViewModel: ObservableObject { return podCommState == .noPod } + var diagnosticCommands: DiagnosticCommands { + return pumpManager + } + var podError: String? { switch podCommState { case .fault(let status): @@ -641,3 +601,8 @@ extension OmniBLEPumpManager { } +extension OmniBLEPumpManager: DiagnosticCommands { + func pumpManagerDetails() -> String { + return debugDescription + } +} diff --git a/OmniBLE/PumpManagerUI/Views/FirstAppear.swift b/OmniBLE/PumpManagerUI/Views/FirstAppear.swift deleted file mode 100644 index 2c042a75..00000000 --- a/OmniBLE/PumpManagerUI/Views/FirstAppear.swift +++ /dev/null @@ -1,30 +0,0 @@ -// -// FirstAppear.swift -// OmniBLE -// -// Created by Joe Moran on 9/24/23. -// Copyright © 2023 LoopKit Authors. All rights reserved. -// - -import SwiftUI - -extension View { - func onFirstAppear(_ action: @escaping () -> ()) -> some View { - modifier(FirstAppear(action: action)) - } -} - -private struct FirstAppear: ViewModifier { - let action: () -> () - - // State used to insure action is invoked here only once - @State private var hasAppeared = false - - func body(content: Content) -> some View { - content.onAppear { - guard !hasAppeared else { return } - hasAppeared = true - action() - } - } -} diff --git a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift index 812118fb..399b91eb 100644 --- a/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift +++ b/OmniBLE/PumpManagerUI/Views/OmniBLESettingsView.swift @@ -264,7 +264,10 @@ struct OmniBLESettingsView: View { VStack(alignment: .trailing) { Button(action: { sendingTestBeepsCommand = true - viewModel.playTestBeeps { _ in + Task { @MainActor in + do { + try await viewModel.playTestBeeps() + } sendingTestBeepsCommand = false } }) { @@ -479,7 +482,9 @@ struct OmniBLESettingsView: View { Section() { NavigationLink(destination: PodDiagnosticsView( title: LocalizedString("Pod Diagnostics", comment: "Title for the pod diagnostic view"), - viewModel: viewModel)) + diagnosticCommands: viewModel.diagnosticCommands, + podOk: viewModel.podOk, + noPod: viewModel.noPod)) { FrameworkLocalText("Pod Diagnostics", comment: "Text for pod diagnostics row") .foregroundColor(Color.primary) diff --git a/OmniBLE/PumpManagerUI/Views/PlayTestBeepsView.swift b/OmniBLE/PumpManagerUI/Views/PlayTestBeepsView.swift index 16b844f5..6f4fcaec 100644 --- a/OmniBLE/PumpManagerUI/Views/PlayTestBeepsView.swift +++ b/OmniBLE/PumpManagerUI/Views/PlayTestBeepsView.swift @@ -14,15 +14,15 @@ struct PlayTestBeepsView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.presentationMode) var presentationMode: Binding - var toRun: ((_ completion: @escaping (_ result: Error?) -> Void) -> Void)? + var playTestBeeps: () async throws -> Void private let title = LocalizedString("Play Test Beeps", comment: "navigation title for play test beeps") private let actionString = LocalizedString("Playing Test Beeps...", comment: "button title when executing play test beeps") private let failedString: String = LocalizedString("Failed to play test beeps.", comment: "Alert title for error when playing test beeps") + private let successMessage = LocalizedString("Play test beeps command sent successfully.\n\nIf you did not hear any beeps from your Pod, the piezo speaker in your Pod may be broken or disabled.", comment: "Success message for play test beeps") @State private var alertIsPresented: Bool = false @State private var displayString: String = "" - @State private var successMessage = LocalizedString("Play test beeps command sent successfully.\n\nIf you did not hear any beeps from your Pod, the piezo speaker in your Pod may be broken or disabled.", comment: "Success message for play test beeps") @State private var error: Error? = nil @State private var executing: Bool = false @State private var showActivityView = false @@ -36,7 +36,7 @@ struct PlayTestBeepsView: View { } VStack { Button(action: { - asyncAction() + Task { await playTestBeepsAndHandleError() } }) { Text(buttonText) .actionButtonStyle(.primary) @@ -51,25 +51,19 @@ struct PlayTestBeepsView: View { .navigationTitle(title) .navigationBarTitleDisplayMode(.inline) .alert(isPresented: $alertIsPresented, content: { alert(error: error) }) - .onFirstAppear { - asyncAction() + .task { + await playTestBeepsAndHandleError() } } - private func asyncAction () { - DispatchQueue.global(qos: .utility).async { - executing = true + private func playTestBeepsAndHandleError() async { + do { + try await playTestBeeps() + self.displayString = successMessage + } catch { self.displayString = "" - toRun?() { (error) in - executing = false - if let error = error { - self.displayString = "" - self.error = error - self.alertIsPresented = true - } else { - self.displayString = successMessage - } - } + self.error = error + self.alertIsPresented = true } } @@ -92,8 +86,8 @@ struct PlayTestBeepsView: View { struct PlayTestBeepsView_Previews: PreviewProvider { static var previews: some View { NavigationView { - PlayTestBeepsView() { completion in - completion(nil) + PlayTestBeepsView { + print("Beep!") } } } diff --git a/OmniBLE/PumpManagerUI/Views/PodDiagnostics.swift b/OmniBLE/PumpManagerUI/Views/PodDiagnosticsView.swift similarity index 72% rename from OmniBLE/PumpManagerUI/Views/PodDiagnostics.swift rename to OmniBLE/PumpManagerUI/Views/PodDiagnosticsView.swift index 7bb036cf..b058efe7 100644 --- a/OmniBLE/PumpManagerUI/Views/PodDiagnostics.swift +++ b/OmniBLE/PumpManagerUI/Views/PodDiagnosticsView.swift @@ -12,72 +12,85 @@ import LoopKitUI import HealthKit +protocol DiagnosticCommands { + func playTestBeeps() async throws + func readPulseLog() async throws -> String + func readPulseLogPlus() async throws -> String + func readActivationTime() async throws -> String + func readTriggeredAlerts() async throws -> String + func getDetailedStatus() async throws -> DetailedStatus + func pumpManagerDetails() -> String +} + struct PodDiagnosticsView: View { var title: String - @ObservedObject var viewModel: OmniBLESettingsViewModel + var diagnosticCommands: DiagnosticCommands + var podOk: Bool + var noPod: Bool var body: some View { List { - NavigationLink(destination: ReadPodStatusView(toRun: viewModel.readPodStatus)) { + NavigationLink(destination: ReadPodStatusView(getDetailedStatus: diagnosticCommands.getDetailedStatus)) { FrameworkLocalText("Read Pod Status", comment: "Text for read pod status navigation link") .foregroundColor(Color.primary) } - .disabled(self.viewModel.noPod) + .disabled(noPod) - NavigationLink(destination: PlayTestBeepsView(toRun: viewModel.playTestBeeps)) { + NavigationLink(destination: PlayTestBeepsView(playTestBeeps: { + try await diagnosticCommands.playTestBeeps() + })) { FrameworkLocalText("Play Test Beeps", comment: "Text for play test beeps navigation link") .foregroundColor(Color.primary) } - .disabled(!self.viewModel.podOk) + .disabled(!podOk) NavigationLink(destination: ReadPodInfoView( title: LocalizedString("Read Pulse Log", comment: "Text for read pulse log title"), actionString: LocalizedString("Reading Pulse Log...", comment: "Text for read pulse log action"), failedString: LocalizedString("Failed to read pulse log.", comment: "Alert title for error when reading pulse log"), - toRun: viewModel.readPulseLog)) + action: { try await diagnosticCommands.readPulseLog() })) { FrameworkLocalText("Read Pulse Log", comment: "Text for read pulse log navigation link") .foregroundColor(Color.primary) } - .disabled(self.viewModel.noPod) + .disabled(noPod) NavigationLink(destination: ReadPodInfoView( title: LocalizedString("Read Pulse Log Plus", comment: "Text for read pulse log plus title"), actionString: LocalizedString("Reading Pulse Log Plus...", comment: "Text for read pulse log plus action"), failedString: LocalizedString("Failed to read pulse log plus.", comment: "Alert title for error when reading pulse log plus"), - toRun: viewModel.readPulseLogPlus)) + action: { try await diagnosticCommands.readPulseLogPlus() })) { FrameworkLocalText("Read Pulse Log Plus", comment: "Text for read pulse log plus navigation link") .foregroundColor(Color.primary) } - .disabled(self.viewModel.noPod) + .disabled(noPod) NavigationLink(destination: ReadPodInfoView( title: LocalizedString("Read Activation Time", comment: "Text for read activation time title"), actionString: LocalizedString("Reading Activation Time...", comment: "Text for read activation time action"), failedString: LocalizedString("Failed to read activation time.", comment: "Alert title for error when reading activation time"), - toRun: self.viewModel.readActivationTime)) + action: { try await diagnosticCommands.readActivationTime() })) { FrameworkLocalText("Read Activation Time", comment: "Text for read activation time navigation link") .foregroundColor(Color.primary) } - .disabled(self.viewModel.noPod) + .disabled(noPod) NavigationLink(destination: ReadPodInfoView( title: LocalizedString("Read Triggered Alerts", comment: "Text for read triggered alerts title"), actionString: LocalizedString("Reading Triggered Alerts...", comment: "Text for read triggered alerts action"), failedString: LocalizedString("Failed to read triggered alerts.", comment: "Alert title for error when reading triggered alerts"), - toRun: self.viewModel.readTriggeredAlerts)) + action: { try await diagnosticCommands.readTriggeredAlerts() })) { FrameworkLocalText("Read Triggered Alerts", comment: "Text for read triggered alerts navigation link") .foregroundColor(Color.primary) } - .disabled(self.viewModel.noPod) + .disabled(noPod) - NavigationLink(destination: PumpManagerDetailsView( - toRun: self.viewModel.pumpManagerDetails)) + NavigationLink(destination: PumpManagerDetailsView() { diagnosticCommands.pumpManagerDetails() }) { FrameworkLocalText("Pump Manager Details", comment: "Text for pump manager details navigation link") .foregroundColor(Color.primary) diff --git a/OmniBLE/PumpManagerUI/Views/PumpManagerDetailsView.swift b/OmniBLE/PumpManagerUI/Views/PumpManagerDetailsView.swift index af9b6587..30af4277 100644 --- a/OmniBLE/PumpManagerUI/Views/PumpManagerDetailsView.swift +++ b/OmniBLE/PumpManagerUI/Views/PumpManagerDetailsView.swift @@ -14,7 +14,7 @@ struct PumpManagerDetailsView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.presentationMode) var presentationMode: Binding - var toRun: ((_ completion: @escaping (_ result: String) -> Void) -> Void)? + var getPumpManagerDetails: () -> String private let title = LocalizedString("Pump Manager Details", comment: "navigation title for pump manager details") private let actionString = LocalizedString("Retrieving Pump Manager Details...", comment: "button title when retrieving pump manager details") @@ -25,10 +25,6 @@ struct PumpManagerDetailsView: View { @State private var executing: Bool = false @State private var showActivityView: Bool = false - init(toRun: @escaping (_ completion: @escaping (_ result: String) -> Void) -> Void) { - self.toRun = toRun - } - var body: some View { VStack { List { @@ -53,7 +49,7 @@ struct PumpManagerDetailsView: View { } VStack { Button(action: { - asyncAction() + self.displayString = getPumpManagerDetails() }) { Text(buttonText) .actionButtonStyle(.primary) @@ -67,19 +63,8 @@ struct PumpManagerDetailsView: View { .insetGroupedListStyle() .navigationTitle(title) .navigationBarTitleDisplayMode(.inline) - .onFirstAppear { - asyncAction() - } - } - - private func asyncAction () { - DispatchQueue.global(qos: .utility).async { - executing = true - self.displayString = "" - toRun?() { (result) in - self.displayString = result - executing = false - } + .task { + self.displayString = getPumpManagerDetails() } } @@ -96,9 +81,7 @@ struct PumpManagerDetailsView_Previews: PreviewProvider { static var previews: some View { let examplePumpManagerDetails: String = "## OmniBLEPumpManager\nprovideHeartbeat: false\nconnected: true\n\npodComms: ## PodComms\n* myId: 171637F8\n* podId: 171637FB\ndelegate: true\n\nstatusObservers.count: 2\nstatus: ## PumpManagerStatus\n* timeZone: GMT-0700 (fixed)\n* device: <, name:Omnipod-Dash, manufacturer:Insulet, model:Dash, hardware:4, firmware:4.10.0 1.4.0, software:1.0, localIdentifier:171637FB>\n* pumpBatteryChargeRemaining: nil\n* basalDeliveryState: Optional(LoopKit.PumpManagerStatus.BasalDeliveryState.tempBasal(LoopKit.DoseEntry(type: LoopKit.DoseType.tempBasal, startDate: 2023-10-08 00:21:42 +0000, endDate: 2023-10-08 00:51:42 +0000, value: 1.55, unit: LoopKit.DoseUnit.unitsPerHour, deliveredUnits: nil, description: nil, insulinType: Optional(LoopKit.InsulinType.humalog), automatic: Optional(true), manuallyEntered: false, syncIdentifier: nil, isMutable: true, wasProgrammedByPumpUI: false, scheduledBasalRate: nil)))\n* bolusState: noBolus\n* insulinType: Optional(LoopKit.InsulinType.humalog)\n* deliveryIsUncertain: false\n\npodStateObservers.count: 1\nstate: ## OmniBLEPumpManagerState\n* isOnboarded: true\n* timeZone: GMT-0700 (fixed)\n* basalSchedule: BasalSchedule(entries: [OmniBLE.BasalScheduleEntry(rate: 1.0, startTime: 0.0)])\n* maximumTempBasalRate: 5.0\n* unstoredDoses: []\n* suspendEngageState: stable\n* bolusEngageState: stable\n* tempBasalEngageState: stable\n* lastPumpDataReportDate: Optional(2023-09-28 14:03:50 +0000)\n* isPumpDataStale: false\n* silencePod: true\n* confirmationBeeps: extended\n* controllerId: 171637F8\n* podId: 171637FB\n* insulinType: Optional(LoopKit.InsulinType.humalog)\n* scheduledExpirationReminderOffset: Optional(22h0m)\n* defaultExpirationReminderOffset: 24h0m\n* lowReservoirReminderValue: 50.0\n* podAttachmentConfirmed: true\n* activeAlerts: []\n* alertsWithPendingAcknowledgment: []\n* acknowledgedTimeOffsetAlert: false\n* initialConfigurationCompleted: true\n* podState: ### PodState\n* address: 171637FB\n* bleIdentifier: 20672963-16E5-D8F8-9C06-1233FEAA61EB\n* activatedAt: Optional(2023-09-25 06:04:36 +0000)\n* expiresAt: Optional(2023-09-28 06:02:46 +0000)\n* timeActive: 79h59m\n* timeActiveUpdated: Optional(2023-09-28 14:03:50 +0000)\n* setupUnitsDelivered: Optional(2.8)\n* firmwareVersion: 4.10.0\n* bleFirmwareVersion: 1.4.0\n* lotNo: 139865265\n* lotSeq: 2770428\n* suspendState: suspended(2023-09-28 14:02:47 +0000)\n* unacknowledgedCommand: nil\n* unfinalizedBolus: nil\n* unfinalizedTempBasal: nil\n* unfinalizedSuspend: Optional(Suspend: 9/28/23, 7:02:47 AM Certain)\n* unfinalizedResume: Optional(Resume: 9/24/23, 11:06:33 PM Certain)\n* finalizedDoses: []\n* activeAlertsSlots: No alerts\n* messageTransportState: ##\nMessageTransportState\neapSeq: 1059\nmsgSeq: 7\nnonceSeq: 6\nmessageNumber: 14\n* setupProgress: completed\n* primeFinishTime: Optional(2023-10-05 05:46:46 +0000)\n* configuredAlerts: [OmniBLE.AlertSlot.slot7Expired: Pod expired, OmniBLE.AlertSlot.slot2ShutdownImminent: Shutdown imminent, OmniBLE.AlertSlot.slot3ExpirationReminder: Expiration reminder, OmniBLE.AlertSlot.slot4LowReservoir: Low reservoir]\n* insulinType: humalog\n* PdmRef: nil\n* Fault: nil\n\nPreviousPodState: nil" NavigationView { - PumpManagerDetailsView() { completion in - completion(examplePumpManagerDetails) - } + PumpManagerDetailsView() { examplePumpManagerDetails } } } } diff --git a/OmniBLE/PumpManagerUI/Views/ReadPodInfoView.swift b/OmniBLE/PumpManagerUI/Views/ReadPodInfoView.swift index 22e54931..fe985aea 100644 --- a/OmniBLE/PumpManagerUI/Views/ReadPodInfoView.swift +++ b/OmniBLE/PumpManagerUI/Views/ReadPodInfoView.swift @@ -18,7 +18,7 @@ struct ReadPodInfoView: View { var actionString: String // e.g., "Reading Pulse Log..." var failedString: String // e.g., "Failed to read pulse log." - var toRun: ((_ completion: @escaping (_ result: Result) -> Void) -> Void)? + var action: () async throws -> String @State private var alertIsPresented: Bool = false @State private var displayString: String = "" @@ -50,7 +50,9 @@ struct ReadPodInfoView: View { } VStack { Button(action: { - asyncAction() + Task { @MainActor in + await attemptAction() + } }) { Text(buttonText) .actionButtonStyle(.primary) @@ -65,27 +67,22 @@ struct ReadPodInfoView: View { .navigationTitle(title) .navigationBarTitleDisplayMode(.inline) .alert(isPresented: $alertIsPresented, content: { alert(error: error) }) - .onFirstAppear { - asyncAction() + .task { + await attemptAction() } } - private func asyncAction () { - DispatchQueue.global(qos: .utility).async { - executing = true + private func attemptAction() async { + executing = true + self.displayString = "" + do { + self.displayString = try await action() + } catch { self.displayString = "" - toRun?() { (result) in - executing = false - switch result { - case .success(let resultString): - self.displayString = resultString - case .failure(let error): - self.displayString = "" - self.error = error - self.alertIsPresented = true - } - } + self.error = error + self.alertIsPresented = true } + executing = false } private var buttonText: String { @@ -111,7 +108,7 @@ struct ReadPodInfoView_Previews: PreviewProvider { title: "Read Pulse Log", actionString: "Reading Pulse Log...", failedString: "Failed to read pulse log" - ) { completion in + ) { let podInfoPulseLogRecent = try! PodInfoPulseLogRecent(encodedData: Data([0x50, 0x03, 0x17, 0x39, 0x72, 0x58, 0x01, 0x3c, 0x72, 0x43, 0x01, 0x41, 0x72, 0x5a, 0x01, 0x44, 0x71, 0x47, 0x01, 0x49, 0x51, 0x59, 0x01, 0x4c, 0x51, 0x44, 0x01, 0x51, 0x73, 0x59, 0x01, 0x54, 0x50, 0x43, 0x01, @@ -127,7 +124,7 @@ struct ReadPodInfoView_Previews: PreviewProvider { 0x21, 0x72, 0x52, 0x00, 0x24, 0x72, 0x40, 0x00, 0x29, 0x71, 0x53, 0x00, 0x2c, 0x50, 0x42, 0x00, 0x31, 0x51, 0x55, 0x00, 0x34, 0x50, 0x42, 0x00 ])) let lastPulseNumber = Int(podInfoPulseLogRecent.indexLastEntry) - completion(.success(pulseLogString(pulseLogEntries: podInfoPulseLogRecent.pulseLog, lastPulseNumber: lastPulseNumber))) + return pulseLogString(pulseLogEntries: podInfoPulseLogRecent.pulseLog, lastPulseNumber: lastPulseNumber) } } } diff --git a/OmniBLE/PumpManagerUI/Views/ReadPodStatusView.swift b/OmniBLE/PumpManagerUI/Views/ReadPodStatusView.swift index 3a76a904..f9af2f03 100644 --- a/OmniBLE/PumpManagerUI/Views/ReadPodStatusView.swift +++ b/OmniBLE/PumpManagerUI/Views/ReadPodStatusView.swift @@ -14,7 +14,7 @@ struct ReadPodStatusView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass @Environment(\.presentationMode) var presentationMode: Binding - var toRun: ((_ completion: @escaping (_ result: PumpManagerResult) -> Void) -> Void)? + var getDetailedStatus: () async throws -> DetailedStatus private let title = LocalizedString("Read Pod Status", comment: "navigation title for read pod status") private let actionString = LocalizedString("Reading Pod Status...", comment: "button title when executing read pod status") @@ -22,7 +22,7 @@ struct ReadPodStatusView: View { @State private var alertIsPresented: Bool = false @State private var displayString: String = "" - @State private var error: LocalizedError? = nil + @State private var error: Error? = nil @State private var executing: Bool = false @State private var showActivityView: Bool = false @@ -46,7 +46,9 @@ struct ReadPodStatusView: View { } VStack { Button(action: { - asyncAction() + Task { @MainActor in + await fetchDetailedStatus() + } }) { Text(buttonText) .actionButtonStyle(.primary) @@ -61,26 +63,22 @@ struct ReadPodStatusView: View { .navigationTitle(title) .navigationBarTitleDisplayMode(.inline) .alert(isPresented: $alertIsPresented, content: { alert(error: error) }) - .onFirstAppear { - asyncAction() + .task { + await fetchDetailedStatus() } } - private func asyncAction () { - DispatchQueue.global(qos: .utility).async { + private func fetchDetailedStatus() async { + do { executing = true self.displayString = "" - toRun?() { (result) in - executing = false - switch result { - case .success(let detailedStatus): - self.displayString = podStatusString(status: detailedStatus) - case .failure(let error): - self.error = error - self.alertIsPresented = true - } - } + let detailedStatus = try await getDetailedStatus() + self.displayString = podStatusString(status: detailedStatus) + } catch { + self.error = error + self.alertIsPresented = true } + executing = false } private var buttonText: String { @@ -160,9 +158,7 @@ struct ReadPodStatusView_Previews: PreviewProvider { static var previews: some View { NavigationView { let detailedStatus = try! DetailedStatus(encodedData: Data([0x02, 0x0d, 0x00, 0x00, 0x00, 0x0e, 0x00, 0xc3, 0x6a, 0x02, 0x07, 0x03, 0xff, 0x02, 0x09, 0x20, 0x00, 0x28, 0x00, 0x08, 0x00, 0x82])) - ReadPodStatusView() { completion in - completion(.success(detailedStatus)) - } + ReadPodStatusView() { detailedStatus } } } }