Просмотр исходного кода

Merge pull request #524 from dnzxy/fix-unlock-error

Improve Authentication Failure Feedback in Treatments View
Sam King 1 год назад
Родитель
Сommit
ca248c2559

+ 1 - 1
Trio/Sources/APS/APSManager.swift

@@ -1270,7 +1270,7 @@ extension BaseAPSManager: PumpManagerStatusObserver {
                 guard self.privateContext.hasChanges else { return }
                 try self.privateContext.save()
             } catch {
-                print("Failed to fetch or save battery: \(error.localizedDescription)")
+                debug(.apsManager, "Failed to fetch or save battery: \(error)")
             }
         }
         // TODO: - remove this after ensuring that NS still gets the same infos from Core Data

+ 1 - 1
Trio/Sources/APS/DeviceDataManager.swift

@@ -176,7 +176,7 @@ final class BaseDeviceDataManager: DeviceDataManager, Injectable {
                             try self.privateContext.save()
 
                         } catch {
-                            print("Failed to delete OpenAPS_Battery entries: \(error.localizedDescription)")
+                            debug(.deviceManager, "Failed to delete OpenAPS_Battery entries: \(error)")
                         }
                     }
                 }

+ 1 - 1
Trio/Sources/APS/OpenAPS/Script.swift

@@ -10,7 +10,7 @@ struct Script {
             do {
                 body = try String(contentsOf: url)
             } catch {
-                print("Error loading script: \(error.localizedDescription)")
+                debug(.openAPS, "Error loading script: \(error)")
                 body = "Error loading script"
             }
         } else {

+ 1 - 1
Trio/Sources/APS/Storage/GlucoseStorage.swift

@@ -287,7 +287,7 @@ final class BaseGlucoseStorage: GlucoseStorage, Injectable {
                 let results = try self.context.fetch(fr)
                 date = results.first?.date
             } catch let error as NSError {
-                print("Fetch error: \(DebuggingIdentifiers.failed) \(error.localizedDescription), \(error.userInfo)")
+                debug(.storage, "Fetch error: \(DebuggingIdentifiers.failed) \(error), \(error.userInfo)")
             }
         }
 

+ 47 - 0
Trio/Sources/Localizations/Main/Localizable.xcstrings

@@ -34783,6 +34783,12 @@
         }
       }
     },
+    "An unknown authentication error occurred. Please try again." : {
+
+    },
+    "An unknown biometric authentication error occurred. Please try again." : {
+
+    },
     "Animated Background" : {
       "extractionState" : "manual",
       "localizations" : {
@@ -37824,6 +37830,27 @@
         }
       }
     },
+    "Authentication context is invalid. Please try again." : {
+
+    },
+    "Authentication failed. Please try again." : {
+
+    },
+    "Authentication requires a device passcode. Please set one in iOS Settings > Face ID & Passcode." : {
+
+    },
+    "Authentication UI cannot be displayed. Try restarting the app." : {
+
+    },
+    "Authentication was canceled by the app." : {
+
+    },
+    "Authentication was canceled by the system. Try again." : {
+
+    },
+    "Authentication was canceled by you." : {
+
+    },
     "Authorize Trio to send notifications and use Bluetooth. You must allow both for Trio to work properly." : {
       "localizations" : {
         "bg" : {
@@ -43525,6 +43552,15 @@
         }
       }
     },
+    "Biometric accessory is missing or not connected. Please reconnect it and try again." : {
+
+    },
+    "Biometric authentication is locked due to multiple failed attempts. Please unlock your device using your passcode." : {
+
+    },
+    "Biometric authentication is not available on this device." : {
+
+    },
     "Blood glucose (BG) readings" : {
       "extractionState" : "stale",
       "localizations" : {
@@ -72213,6 +72249,7 @@
       }
     },
     "Determination Failed" : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -87923,6 +87960,9 @@
         }
       }
     },
+    "Error while processing Treatment" : {
+
+    },
     "Error! Bolus cancellation failed with error: %@" : {
       "comment" : "Error message for canceling a bolus",
       "localizations" : {
@@ -91783,6 +91823,7 @@
       }
     },
     "Failed to update COB/IOB: %@" : {
+      "extractionState" : "stale",
       "localizations" : {
         "bg" : {
           "stringUnit" : {
@@ -138271,6 +138312,9 @@
         }
       }
     },
+    "No biometric identities are enrolled. Please set up Face ID or Touch ID." : {
+
+    },
     "No Bolus Data" : {
       "localizations" : {
         "bg" : {
@@ -227693,6 +227737,9 @@
         }
       }
     },
+    "You tapped the fallback option, but no fallback method is configured." : {
+
+    },
     "You will also be guided through re-configuring your algorithm settings, respecting Trio's new guardrails." : {
       "localizations" : {
         "bg" : {

+ 91 - 8
Trio/Sources/Modules/Treatments/TreatmentsStateModel.swift

@@ -1,6 +1,7 @@
 import Combine
 import CoreData
 import Foundation
+import LocalAuthentication
 import LoopKit
 import Observation
 import SwiftUI
@@ -457,6 +458,91 @@ extension Treatments {
             }
         }
 
+        /// Returns a user-facing localized error message for a given authentication error.
+        ///
+        /// This function inspects the provided `Error` to determine whether it is an `LAError`,
+        /// and maps its error code to a human-readable, localized string describing the reason
+        /// for the failure. If the error is not an `LAError`, a generic fallback message is returned.
+        ///
+        /// - Parameter error: The `Error` returned from an authentication attempt (e.g., via `LAContext.evaluatePolicy`).
+        /// - Returns: A localized `String` describing the cause of the authentication failure.
+        private func parseAuthenticationError(from error: Error) -> String {
+            guard let laError = error as? LAError else {
+                return String(
+                    localized: "An unknown authentication error occurred. Please try again."
+                )
+            }
+
+            switch laError.code {
+            case .authenticationFailed:
+                return String(
+                    localized: "Authentication failed. Please try again."
+                )
+
+            case .userCancel:
+                return String(
+                    localized: "Authentication was canceled by you."
+                )
+
+            case .userFallback:
+                return String(
+                    localized: "You tapped the fallback option, but no fallback method is configured."
+                )
+
+            case .systemCancel:
+                return String(
+                    localized: "Authentication was canceled by the system. Try again."
+                )
+
+            case .appCancel:
+                return String(
+                    localized: "Authentication was canceled by the app."
+                )
+
+            case .invalidContext:
+                return String(
+                    localized: "Authentication context is invalid. Please try again."
+                )
+
+            case .notInteractive:
+                return String(
+                    localized: "Authentication UI cannot be displayed. Try restarting the app."
+                )
+
+            case .passcodeNotSet:
+                return String(
+                    localized: "Authentication requires a device passcode. Please set one in iOS Settings > Face ID & Passcode."
+                )
+
+            case .biometryNotAvailable:
+                return String(
+                    localized: "Biometric authentication is not available on this device."
+                )
+
+            case .biometryNotEnrolled:
+                return String(
+                    localized: "No biometric identities are enrolled. Please set up Face ID or Touch ID."
+                )
+
+            case .biometryLockout,
+                 .touchIDLockout:
+                return String(
+                    localized: "Biometric authentication is locked due to multiple failed attempts. Please unlock your device using your passcode."
+                )
+
+            case .biometryDisconnected,
+                 .biometryNotPaired:
+                return String(
+                    localized: "Biometric accessory is missing or not connected. Please reconnect it and try again."
+                )
+
+            default:
+                return String(
+                    localized: "An unknown biometric authentication error occurred. Please try again."
+                )
+            }
+        }
+
         func addPumpInsulin() async {
             guard amount > 0 else {
                 showModal(for: nil)
@@ -473,15 +559,14 @@ extension Treatments {
                         self.isAwaitingDeterminationResult = true
                     }
                     await apsManager.enactBolus(amount: maxAmount, isSMB: false, callback: nil)
-                } else {
-                    print("authentication failed")
                 }
             } catch {
-                print("authentication error for pump bolus: \(error.localizedDescription)")
+                debug(.bolusState, "Authentication error for pump bolus: \(error)")
+
                 await MainActor.run {
                     self.isAwaitingDeterminationResult = false
                     self.showDeterminationFailureAlert = true
-                    self.determinationFailureMessage = error.localizedDescription
+                    self.determinationFailureMessage = parseAuthenticationError(from: error)
                 }
             }
         }
@@ -509,15 +594,13 @@ extension Treatments {
                     await pumpHistoryStorage.storeExternalInsulinEvent(amount: amount, timestamp: date)
                     // perform determine basal sync
                     try await apsManager.determineBasalSync()
-                } else {
-                    print("authentication failed")
                 }
             } catch {
-                print("authentication error for external insulin: \(error.localizedDescription)")
+                debug(.bolusState, "authentication error for external insulin: \(error)")
                 await MainActor.run {
                     self.isAwaitingDeterminationResult = false
                     self.showDeterminationFailureAlert = true
-                    self.determinationFailureMessage = error.localizedDescription
+                    self.determinationFailureMessage = parseAuthenticationError(from: error)
                 }
             }
         }

+ 2 - 2
Trio/Sources/Modules/Treatments/View/TreatmentsRootView.swift

@@ -400,12 +400,12 @@ extension Treatments {
             }) {
                 MealPresetView(state: state)
             }
-            .alert("Determination Failed", isPresented: $state.showDeterminationFailureAlert) {
+            .alert("Error while processing Treatment", isPresented: $state.showDeterminationFailureAlert) {
                 Button("OK", role: .cancel) {
                     state.hideModal()
                 }
             } message: {
-                Text("Failed to update COB/IOB: \(state.determinationFailureMessage)")
+                Text("\(state.determinationFailureMessage)")
             }
         }
 

+ 1 - 5
Trio/Sources/Services/UnlockManager/UnlockManager.swift

@@ -5,10 +5,6 @@ protocol UnlockManager {
     func unlock() async throws -> Bool
 }
 
-struct UnlockError: Error {
-    let error: Error?
-}
-
 final class BaseUnlockManager: UnlockManager {
     @MainActor func unlock() async throws -> Bool {
         let context = LAContext()
@@ -18,7 +14,7 @@ final class BaseUnlockManager: UnlockManager {
             _ = try await context.evaluatePolicy(.deviceOwnerAuthentication, localizedReason: reason)
             return true
         } catch {
-            throw UnlockError(error: error)
+            throw error
         }
     }
 }