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

Quick bolus: apply code review fixes

- SlideToConfirmView: guard maxDrag > 0 in onEnded; fix opacity formula
  (1 - progress instead of 1 - progress * 1.5); remove .animation()
  modifier that caused drag lag; remove post-action dragOffset reset
  animation (view is dismissed anyway)
- QuickBolusInfoView: remove dead @State detent binding
- QuickBolusConfigStateModel: add units property so the settings view
  reflects the user's chosen glucose unit
- QuickBolusConfigRootView: pass state.units instead of hardcoded .mgdL
- QuickBolusView: sort fix (prefix before sort), isEnacting guard to
  prevent double-fire, dismiss only on auth success, auth-failure alert
- HomeStateModel: enactQuickBolus returns Bool for auth result
- Localizable.xcstrings: add strings for auth-failure alert
Magnus Reintz 1 неделя назад
Родитель
Сommit
feadf999f6

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

@@ -79023,6 +79023,9 @@
         }
       }
     },
+    "Could not authenticate" : {
+      "comment" : "Alert title when biometric auth fails for quick bolus"
+    },
     "Could not calculate eventual glucose. Sensitivity: %@, Deviation: %@" : {
       "localizations" : {
         "en" : {
@@ -118406,6 +118409,9 @@
         }
       }
     },
+    "Face ID or Touch ID did not succeed. The bolus was not enacted." : {
+      "comment" : "Alert body when biometric auth fails for quick bolus"
+    },
     "Factory Calibration Parameters" : {
       "extractionState" : "manual",
       "localizations" : {

+ 5 - 2
Trio/Sources/Modules/Home/HomeStateModel.swift

@@ -611,8 +611,8 @@ extension Home {
             }
         }
 
-        func enactQuickBolus(amount: Decimal) async {
-            guard amount > 0 else { return }
+        func enactQuickBolus(amount: Decimal) async -> Bool {
+            guard amount > 0 else { return false }
             let delivery = min(
                 Double(truncating: amount as NSDecimalNumber),
                 pumpInitialSettings.maxBolusUnits
@@ -621,9 +621,12 @@ extension Home {
                 let authenticated = try await unlockmanager.unlock()
                 if authenticated {
                     await apsManager.enactBolus(amount: delivery, isSMB: false, callback: nil)
+                    return true
                 }
+                return false
             } catch {
                 debug(.bolusState, "Quick bolus authentication error: \(error)")
+                return false
             }
         }
 

+ 1 - 3
Trio/Sources/Modules/Home/View/QuickBolus/QuickBolusInfoView.swift

@@ -3,8 +3,6 @@ import SwiftUI
 struct QuickBolusInfoView: View {
     @Binding var isPresented: Bool
 
-    @State private var detent = PresentationDetent.medium
-
     var body: some View {
         NavigationStack {
             ScrollView {
@@ -31,6 +29,6 @@ struct QuickBolusInfoView: View {
             .padding([.horizontal, .bottom])
             .padding(.top, 4)
         }
-        .presentationDetents([.medium], selection: $detent)
+        .presentationDetents([.medium])
     }
 }

+ 29 - 6
Trio/Sources/Modules/Home/View/QuickBolus/QuickBolusView.swift

@@ -2,11 +2,13 @@ import SwiftUI
 
 struct QuickBolusView: View {
     let suggestions: [Decimal]
-    let onEnact: (Decimal) async -> Void
+    let onEnact: (Decimal) async -> Bool
     @Binding var isPresented: Bool
 
     @State private var selectedAmount: Decimal?
     @State private var showInfo = false
+    @State private var isEnacting = false
+    @State private var showAuthFailedAlert = false
 
     var body: some View {
         NavigationStack {
@@ -27,11 +29,21 @@ struct QuickBolusView: View {
             .safeAreaInset(edge: .bottom, spacing: 0) {
                 SlideToConfirmView(
                     label: String(localized: "Slide to Enact Bolus", comment: "Slide to confirm label for quick bolus"),
-                    isEnabled: selectedAmount != nil
+                    isEnabled: selectedAmount != nil && !isEnacting
                 ) {
-                    guard let amount = selectedAmount else { return }
-                    isPresented = false
-                    Task { await onEnact(amount) }
+                    guard let amount = selectedAmount, !isEnacting else { return }
+                    isEnacting = true
+                    Task {
+                        let success = await onEnact(amount)
+                        await MainActor.run {
+                            if success {
+                                isPresented = false
+                            } else {
+                                isEnacting = false
+                                showAuthFailedAlert = true
+                            }
+                        }
+                    }
                 }
                 .padding(.horizontal)
                 .padding(.vertical, 12)
@@ -50,12 +62,23 @@ struct QuickBolusView: View {
             .sheet(isPresented: $showInfo) {
                 QuickBolusInfoView(isPresented: $showInfo)
             }
+            .alert(
+                String(localized: "Could not authenticate", comment: "Alert title when biometric auth fails for quick bolus"),
+                isPresented: $showAuthFailedAlert
+            ) {
+                Button(String(localized: "OK"), role: .cancel) {}
+            } message: {
+                Text(String(
+                    localized: "Face ID or Touch ID did not succeed. The bolus was not enacted.",
+                    comment: "Alert body when biometric auth fails for quick bolus"
+                ))
+            }
         }
         .presentationDetents([.height(320)])
     }
 
     private var displayedSuggestions: [Decimal] {
-        Array(suggestions.sorted().prefix(3))
+        Array(suggestions.prefix(3).sorted())
     }
 
     private var pillRow: some View {

+ 2 - 3
Trio/Sources/Modules/Home/View/QuickBolus/SlideToConfirmView.swift

@@ -22,7 +22,7 @@ struct SlideToConfirmView: View {
 
                 Text(label)
                     .font(.headline)
-                    .foregroundStyle(isEnabled ? .white.opacity(1 - progress * 1.5) : .secondary)
+                    .foregroundStyle(isEnabled ? .white.opacity(1 - progress) : .secondary)
                     .frame(maxWidth: .infinity)
 
                 RoundedRectangle(cornerRadius: thumbSize / 2)
@@ -40,16 +40,15 @@ struct SlideToConfirmView: View {
                                 dragOffset = min(max(0, value.translation.width), maxDrag)
                             }
                             .onEnded { _ in
+                                guard maxDrag > 0 else { return }
                                 if dragOffset >= maxDrag * completionThreshold {
                                     UIImpactFeedbackGenerator(style: .heavy).impactOccurred()
                                     action()
-                                    withAnimation(.spring(duration: 0.4)) { dragOffset = 0 }
                                 } else {
                                     withAnimation(.spring()) { dragOffset = 0 }
                                 }
                             } : nil
                     )
-                    .animation(.interactiveSpring(), value: dragOffset)
             }
             .frame(height: trackHeight)
         }

+ 5 - 1
Trio/Sources/Modules/QuickBolusConfig/QuickBolusConfigStateModel.swift

@@ -3,13 +3,17 @@ import SwiftUI
 extension QuickBolusConfig {
     final class StateModel: BaseStateModel<Provider> {
         @Published var enableQuickBolus: Bool = false
+        @Published var units: GlucoseUnits = .mgdL
 
         override func subscribe() {
             subscribeSetting(\.enableQuickBolus, on: $enableQuickBolus) { enableQuickBolus = $0 }
+            units = settingsManager.settings.units
         }
     }
 }
 
 extension QuickBolusConfig.StateModel: SettingsObserver {
-    func settingsDidChange(_: TrioSettings) {}
+    func settingsDidChange(_ settings: TrioSettings) {
+        units = settings.units
+    }
 }

+ 1 - 1
Trio/Sources/Modules/QuickBolusConfig/View/QuickBolusConfigRootView.swift

@@ -29,7 +29,7 @@ extension QuickBolusConfig {
                             hintLabel = String(localized: "Enable Quick Bolus")
                         }
                     ),
-                    units: .mgdL,
+                    units: state.units,
                     type: .boolean,
                     label: String(localized: "Enable Quick Bolus"),
                     miniHint: String(localized: "Long-press the + button on the home screen to enact a quick bolus."),