ソースを参照

Add modal for selecting duration of overrides for Loop (#457)

* Add modal for selecting duration of overrides for Loop
* Add decimal to override targets for Loop if user setting is mmol
* Change text for set durations and update ui
* Change UI for Loop overrides duration
Daniel Mini Johansson 10 ヶ月 前
コミット
7ee73e1dd4
1 ファイル変更191 行追加24 行削除
  1. 191 24
      LoopFollow/Remote/LoopAPNS/OverridePresetsView.swift

+ 191 - 24
LoopFollow/Remote/LoopAPNS/OverridePresetsView.swift

@@ -67,8 +67,7 @@ struct OverridePresetsView: View {
                                     isActivating: viewModel.isActivating && viewModel.selectedPreset?.name == preset.name,
                                     onActivate: {
                                         viewModel.selectedPreset = preset
-                                        viewModel.alertType = .confirmActivation
-                                        viewModel.showAlert = true
+                                        viewModel.showOverrideModal = true
                                     }
                                 )
                             }
@@ -87,21 +86,24 @@ struct OverridePresetsView: View {
                     await viewModel.loadOverridePresets()
                 }
             }
-            .alert(isPresented: $viewModel.showAlert) {
-                switch viewModel.alertType {
-                case .confirmActivation:
-                    return Alert(
-                        title: Text("Activate Override"),
-                        message: Text("Do you want to activate the override '\(viewModel.selectedPreset?.name ?? "")'?"),
-                        primaryButton: .default(Text("Confirm"), action: {
-                            if let preset = viewModel.selectedPreset {
-                                Task {
-                                    await viewModel.activateOverride(preset: preset)
-                                }
+            .sheet(isPresented: $viewModel.showOverrideModal) {
+                if let preset = viewModel.selectedPreset {
+                    OverrideActivationModal(
+                        preset: preset,
+                        onActivate: { duration in
+                            viewModel.showOverrideModal = false
+                            Task {
+                                await viewModel.activateOverride(preset: preset, duration: duration)
                             }
-                        }),
-                        secondaryButton: .cancel()
+                        },
+                        onCancel: {
+                            viewModel.showOverrideModal = false
+                        }
                     )
+                }
+            }
+            .alert(isPresented: $viewModel.showAlert) {
+                switch viewModel.alertType {
                 case .confirmCancellation:
                     return Alert(
                         title: Text("Cancel Override"),
@@ -155,7 +157,7 @@ struct OverridePresetRow: View {
 
                     HStack(spacing: 8) {
                         if let targetRange = preset.targetRange {
-                            Text("Target: \(Int(targetRange.lowerBound))-\(Int(targetRange.upperBound))")
+                            Text("Target: \(Localizer.formatLocalDouble(targetRange.lowerBound))-\(Localizer.formatLocalDouble(targetRange.upperBound))")
                                 .font(.caption)
                                 .foregroundColor(.secondary)
                         }
@@ -191,6 +193,172 @@ struct OverridePresetRow: View {
     }
 }
 
+struct OverrideActivationModal: View {
+    let preset: OverridePreset
+    let onActivate: (TimeInterval?) -> Void
+    let onCancel: () -> Void
+
+    @State private var enableIndefinitely: Bool
+    @State private var durationHours: Double = 1.0
+
+    init(preset: OverridePreset, onActivate: @escaping (TimeInterval?) -> Void, onCancel: @escaping () -> Void) {
+        self.preset = preset
+        self.onActivate = onActivate
+        self.onCancel = onCancel
+
+        // Initialize state based on preset duration
+        if preset.duration == 0 {
+            // Indefinite override - allow user to choose
+            _enableIndefinitely = State(initialValue: true)
+        } else {
+            // Override with predefined duration - use preset duration
+            _enableIndefinitely = State(initialValue: false)
+            _durationHours = State(initialValue: preset.duration / 3600)
+        }
+    }
+
+    var body: some View {
+        NavigationView {
+            VStack(spacing: 20) {
+                // Preset Info
+                VStack(spacing: 12) {
+                    if let symbol = preset.symbol {
+                        Text(symbol)
+                            .font(.largeTitle)
+                    }
+
+                    Text(preset.name)
+                        .font(.title2)
+                        .fontWeight(.semibold)
+                        .multilineTextAlignment(.center)
+
+                    if let targetRange = preset.targetRange {
+                        Text("Target: \(Localizer.formatLocalDouble(targetRange.lowerBound))-\(Localizer.formatLocalDouble(targetRange.upperBound))")
+                            .font(.subheadline)
+                            .foregroundColor(.secondary)
+                    }
+
+                    if let insulinNeedsScaleFactor = preset.insulinNeedsScaleFactor {
+                        Text("Insulin: \(Int(insulinNeedsScaleFactor * 100))%")
+                            .font(.subheadline)
+                            .foregroundColor(.secondary)
+                    }
+
+                    // Only show duration for overrides with predefined duration
+                    if preset.duration != 0 {
+                        Text("Duration: \(preset.durationDescription)")
+                            .font(.subheadline)
+                            .foregroundColor(.secondary)
+                    }
+                }
+                .padding(.top)
+
+                Spacer()
+
+                // Duration Settings (only show for overrides without predefined duration)
+                if preset.duration == 0 {
+                    VStack(spacing: 16) {
+                        // Duration Input (only show when not indefinite)
+                        if !enableIndefinitely {
+                            VStack(spacing: 8) {
+                                HStack {
+                                    Text("Duration")
+                                        .font(.headline)
+                                    Spacer()
+                                    Text(formatDuration(durationHours))
+                                        .font(.headline)
+                                        .foregroundColor(.blue)
+                                }
+
+                                Slider(value: $durationHours, in: 0.25 ... 24.0, step: 0.25)
+                                    .accentColor(.blue)
+                                HStack {
+                                    Text("15m")
+                                        .font(.caption)
+                                        .foregroundColor(.secondary)
+                                        .frame(width: 80, alignment: .leading)
+                                    Spacer()
+                                    Text("24h")
+                                        .font(.caption)
+                                        .foregroundColor(.secondary)
+                                        .frame(width: 80, alignment: .trailing)
+                                }
+                            }
+                            .padding(.horizontal)
+                        }
+
+                        // Indefinitely Toggle
+                        HStack {
+                            Toggle("Enable indefinitely", isOn: $enableIndefinitely)
+                            Spacer()
+                        }
+                        .padding(.horizontal)
+                    }
+                }
+
+                // Action Buttons
+                VStack(spacing: 12) {
+                    Button(action: {
+                        let duration: TimeInterval?
+                        if preset.duration == 0 {
+                            // For indefinite overrides, use user selection
+                            duration = enableIndefinitely ? nil : (durationHours * 3600)
+                        } else {
+                            // For overrides with predefined duration, use preset duration
+                            duration = preset.duration
+                        }
+                        onActivate(duration)
+                    }) {
+                        Text("Activate Override")
+                            .font(.headline)
+                            .foregroundColor(.white)
+                            .frame(maxWidth: .infinity)
+                            .padding()
+                            .background(Color.blue)
+                            .cornerRadius(10)
+                    }
+
+                    Button(action: onCancel) {
+                        Text("Cancel")
+                            .font(.headline)
+                            .foregroundColor(.secondary)
+                            .frame(maxWidth: .infinity)
+                            .padding()
+                            .background(Color.gray.opacity(0.2))
+                            .cornerRadius(10)
+                    }
+                }
+                .padding(.horizontal)
+                .padding(.bottom)
+            }
+            .navigationBarTitle("Activate Override", displayMode: .inline)
+            .navigationBarTitleDisplayMode(.inline)
+            .toolbar {
+                ToolbarItem(placement: .navigationBarTrailing) {
+                    Button("Cancel") {
+                        onCancel()
+                    }
+                }
+            }
+        }
+    }
+
+    // Helper function to format duration in hours and minutes
+    private func formatDuration(_ hours: Double) -> String {
+        let totalMinutes = Int(hours * 60)
+        let hours = totalMinutes / 60
+        let minutes = totalMinutes % 60
+
+        if hours > 0 && minutes > 0 {
+            return "\(hours)h \(minutes)m"
+        } else if hours > 0 {
+            return "\(hours)h"
+        } else {
+            return "\(minutes)m"
+        }
+    }
+}
+
 class OverridePresetsViewModel: ObservableObject {
     @Published var overridePresets: [OverridePreset] = []
     @Published var isLoading = false
@@ -199,9 +367,9 @@ class OverridePresetsViewModel: ObservableObject {
     @Published var alertType: AlertType? = nil
     @Published var statusMessage: String? = nil
     @Published var selectedPreset: OverridePreset? = nil
+    @Published var showOverrideModal = false
 
     enum AlertType {
-        case confirmActivation
         case confirmCancellation
         case statusSuccess
         case statusFailure
@@ -213,7 +381,7 @@ class OverridePresetsViewModel: ObservableObject {
         }
 
         do {
-            let presets = try await fetchOverridePresetsFromNightscout()
+            let presets = try await fetchOverridePresetsFromStorage()
             await MainActor.run {
                 self.overridePresets = presets
                 self.isLoading = false
@@ -228,13 +396,13 @@ class OverridePresetsViewModel: ObservableObject {
         }
     }
 
-    func activateOverride(preset: OverridePreset) async {
+    func activateOverride(preset: OverridePreset, duration: TimeInterval?) async {
         await MainActor.run {
             isActivating = true
         }
 
         do {
-            try await sendOverrideNotification(preset: preset)
+            try await sendOverrideNotification(preset: preset, duration: duration)
             await MainActor.run {
                 self.isActivating = false
                 self.statusMessage = "\(preset.name) override activated successfully."
@@ -274,8 +442,7 @@ class OverridePresetsViewModel: ObservableObject {
         }
     }
 
-    private func fetchOverridePresetsFromNightscout() async throws -> [OverridePreset] {
-        // Use ProfileManager's already loaded overrides instead of fetching from Nightscout
+    private func fetchOverridePresetsFromStorage() async throws -> [OverridePreset] {
         let loopOverrides = ProfileManager.shared.loopOverrides
 
         return loopOverrides.map { override in
@@ -298,11 +465,11 @@ class OverridePresetsViewModel: ObservableObject {
         }
     }
 
-    private func sendOverrideNotification(preset: OverridePreset) async throws {
+    private func sendOverrideNotification(preset: OverridePreset, duration: TimeInterval?) async throws {
         let apnsService = LoopAPNSService()
         try await apnsService.sendOverrideNotification(
             presetName: preset.name,
-            duration: preset.duration
+            duration: duration
         )
     }