@@ -67,8 +67,7 @@ struct OverridePresetsView: View {
6767 isActivating: viewModel. isActivating && viewModel. selectedPreset? . name == preset. name,
6868 onActivate: {
6969 viewModel. selectedPreset = preset
70- viewModel. alertType = . confirmActivation
71- viewModel. showAlert = true
70+ viewModel. showOverrideModal = true
7271 }
7372 )
7473 }
@@ -87,21 +86,24 @@ struct OverridePresetsView: View {
8786 await viewModel. loadOverridePresets ( )
8887 }
8988 }
90- . alert ( isPresented: $viewModel. showAlert) {
91- switch viewModel. alertType {
92- case . confirmActivation:
93- return Alert (
94- title: Text ( " Activate Override " ) ,
95- message: Text ( " Do you want to activate the override ' \( viewModel. selectedPreset? . name ?? " " ) '? " ) ,
96- primaryButton: . default( Text ( " Confirm " ) , action: {
97- if let preset = viewModel. selectedPreset {
98- Task {
99- await viewModel. activateOverride ( preset: preset)
100- }
89+ . sheet ( isPresented: $viewModel. showOverrideModal) {
90+ if let preset = viewModel. selectedPreset {
91+ OverrideActivationModal (
92+ preset: preset,
93+ onActivate: { duration in
94+ viewModel. showOverrideModal = false
95+ Task {
96+ await viewModel. activateOverride ( preset: preset, duration: duration)
10197 }
102- } ) ,
103- secondaryButton: . cancel( )
98+ } ,
99+ onCancel: {
100+ viewModel. showOverrideModal = false
101+ }
104102 )
103+ }
104+ }
105+ . alert ( isPresented: $viewModel. showAlert) {
106+ switch viewModel. alertType {
105107 case . confirmCancellation:
106108 return Alert (
107109 title: Text ( " Cancel Override " ) ,
@@ -155,7 +157,7 @@ struct OverridePresetRow: View {
155157
156158 HStack ( spacing: 8 ) {
157159 if let targetRange = preset. targetRange {
158- Text ( " Target: \( Int ( targetRange. lowerBound) ) - \( Int ( targetRange. upperBound) ) " )
160+ Text ( " Target: \( Localizer . formatLocalDouble ( targetRange. lowerBound) ) - \( Localizer . formatLocalDouble ( targetRange. upperBound) ) " )
159161 . font ( . caption)
160162 . foregroundColor ( . secondary)
161163 }
@@ -191,6 +193,172 @@ struct OverridePresetRow: View {
191193 }
192194}
193195
196+ struct OverrideActivationModal : View {
197+ let preset : OverridePreset
198+ let onActivate : ( TimeInterval ? ) -> Void
199+ let onCancel : ( ) -> Void
200+
201+ @State private var enableIndefinitely : Bool
202+ @State private var durationHours : Double = 1.0
203+
204+ init ( preset: OverridePreset , onActivate: @escaping ( TimeInterval ? ) -> Void , onCancel: @escaping ( ) -> Void ) {
205+ self . preset = preset
206+ self . onActivate = onActivate
207+ self . onCancel = onCancel
208+
209+ // Initialize state based on preset duration
210+ if preset. duration == 0 {
211+ // Indefinite override - allow user to choose
212+ _enableIndefinitely = State ( initialValue: true )
213+ } else {
214+ // Override with predefined duration - use preset duration
215+ _enableIndefinitely = State ( initialValue: false )
216+ _durationHours = State ( initialValue: preset. duration / 3600 )
217+ }
218+ }
219+
220+ var body : some View {
221+ NavigationView {
222+ VStack ( spacing: 20 ) {
223+ // Preset Info
224+ VStack ( spacing: 12 ) {
225+ if let symbol = preset. symbol {
226+ Text ( symbol)
227+ . font ( . largeTitle)
228+ }
229+
230+ Text ( preset. name)
231+ . font ( . title2)
232+ . fontWeight ( . semibold)
233+ . multilineTextAlignment ( . center)
234+
235+ if let targetRange = preset. targetRange {
236+ Text ( " Target: \( Localizer . formatLocalDouble ( targetRange. lowerBound) ) - \( Localizer . formatLocalDouble ( targetRange. upperBound) ) " )
237+ . font ( . subheadline)
238+ . foregroundColor ( . secondary)
239+ }
240+
241+ if let insulinNeedsScaleFactor = preset. insulinNeedsScaleFactor {
242+ Text ( " Insulin: \( Int ( insulinNeedsScaleFactor * 100 ) ) % " )
243+ . font ( . subheadline)
244+ . foregroundColor ( . secondary)
245+ }
246+
247+ // Only show duration for overrides with predefined duration
248+ if preset. duration != 0 {
249+ Text ( " Duration: \( preset. durationDescription) " )
250+ . font ( . subheadline)
251+ . foregroundColor ( . secondary)
252+ }
253+ }
254+ . padding ( . top)
255+
256+ Spacer ( )
257+
258+ // Duration Settings (only show for overrides without predefined duration)
259+ if preset. duration == 0 {
260+ VStack ( spacing: 16 ) {
261+ // Duration Input (only show when not indefinite)
262+ if !enableIndefinitely {
263+ VStack ( spacing: 8 ) {
264+ HStack {
265+ Text ( " Duration " )
266+ . font ( . headline)
267+ Spacer ( )
268+ Text ( formatDuration ( durationHours) )
269+ . font ( . headline)
270+ . foregroundColor ( . blue)
271+ }
272+
273+ Slider ( value: $durationHours, in: 0.25 ... 24.0 , step: 0.25 )
274+ . accentColor ( . blue)
275+ HStack {
276+ Text ( " 15m " )
277+ . font ( . caption)
278+ . foregroundColor ( . secondary)
279+ . frame ( width: 80 , alignment: . leading)
280+ Spacer ( )
281+ Text ( " 24h " )
282+ . font ( . caption)
283+ . foregroundColor ( . secondary)
284+ . frame ( width: 80 , alignment: . trailing)
285+ }
286+ }
287+ . padding ( . horizontal)
288+ }
289+
290+ // Indefinitely Toggle
291+ HStack {
292+ Toggle ( " Enable indefinitely " , isOn: $enableIndefinitely)
293+ Spacer ( )
294+ }
295+ . padding ( . horizontal)
296+ }
297+ }
298+
299+ // Action Buttons
300+ VStack ( spacing: 12 ) {
301+ Button ( action: {
302+ let duration : TimeInterval ?
303+ if preset. duration == 0 {
304+ // For indefinite overrides, use user selection
305+ duration = enableIndefinitely ? nil : ( durationHours * 3600 )
306+ } else {
307+ // For overrides with predefined duration, use preset duration
308+ duration = preset. duration
309+ }
310+ onActivate ( duration)
311+ } ) {
312+ Text ( " Activate Override " )
313+ . font ( . headline)
314+ . foregroundColor ( . white)
315+ . frame ( maxWidth: . infinity)
316+ . padding ( )
317+ . background ( Color . blue)
318+ . cornerRadius ( 10 )
319+ }
320+
321+ Button ( action: onCancel) {
322+ Text ( " Cancel " )
323+ . font ( . headline)
324+ . foregroundColor ( . secondary)
325+ . frame ( maxWidth: . infinity)
326+ . padding ( )
327+ . background ( Color . gray. opacity ( 0.2 ) )
328+ . cornerRadius ( 10 )
329+ }
330+ }
331+ . padding ( . horizontal)
332+ . padding ( . bottom)
333+ }
334+ . navigationBarTitle ( " Activate Override " , displayMode: . inline)
335+ . navigationBarTitleDisplayMode ( . inline)
336+ . toolbar {
337+ ToolbarItem ( placement: . navigationBarTrailing) {
338+ Button ( " Cancel " ) {
339+ onCancel ( )
340+ }
341+ }
342+ }
343+ }
344+ }
345+
346+ // Helper function to format duration in hours and minutes
347+ private func formatDuration( _ hours: Double ) -> String {
348+ let totalMinutes = Int ( hours * 60 )
349+ let hours = totalMinutes / 60
350+ let minutes = totalMinutes % 60
351+
352+ if hours > 0 && minutes > 0 {
353+ return " \( hours) h \( minutes) m "
354+ } else if hours > 0 {
355+ return " \( hours) h "
356+ } else {
357+ return " \( minutes) m "
358+ }
359+ }
360+ }
361+
194362class OverridePresetsViewModel : ObservableObject {
195363 @Published var overridePresets : [ OverridePreset ] = [ ]
196364 @Published var isLoading = false
@@ -199,9 +367,9 @@ class OverridePresetsViewModel: ObservableObject {
199367 @Published var alertType : AlertType ? = nil
200368 @Published var statusMessage : String ? = nil
201369 @Published var selectedPreset : OverridePreset ? = nil
370+ @Published var showOverrideModal = false
202371
203372 enum AlertType {
204- case confirmActivation
205373 case confirmCancellation
206374 case statusSuccess
207375 case statusFailure
@@ -213,7 +381,7 @@ class OverridePresetsViewModel: ObservableObject {
213381 }
214382
215383 do {
216- let presets = try await fetchOverridePresetsFromNightscout ( )
384+ let presets = try await fetchOverridePresetsFromStorage ( )
217385 await MainActor . run {
218386 self . overridePresets = presets
219387 self . isLoading = false
@@ -228,13 +396,13 @@ class OverridePresetsViewModel: ObservableObject {
228396 }
229397 }
230398
231- func activateOverride( preset: OverridePreset ) async {
399+ func activateOverride( preset: OverridePreset , duration : TimeInterval ? ) async {
232400 await MainActor . run {
233401 isActivating = true
234402 }
235403
236404 do {
237- try await sendOverrideNotification ( preset: preset)
405+ try await sendOverrideNotification ( preset: preset, duration : duration )
238406 await MainActor . run {
239407 self . isActivating = false
240408 self . statusMessage = " \( preset. name) override activated successfully. "
@@ -274,8 +442,7 @@ class OverridePresetsViewModel: ObservableObject {
274442 }
275443 }
276444
277- private func fetchOverridePresetsFromNightscout( ) async throws -> [ OverridePreset ] {
278- // Use ProfileManager's already loaded overrides instead of fetching from Nightscout
445+ private func fetchOverridePresetsFromStorage( ) async throws -> [ OverridePreset ] {
279446 let loopOverrides = ProfileManager . shared. loopOverrides
280447
281448 return loopOverrides. map { override in
@@ -298,11 +465,11 @@ class OverridePresetsViewModel: ObservableObject {
298465 }
299466 }
300467
301- private func sendOverrideNotification( preset: OverridePreset ) async throws {
468+ private func sendOverrideNotification( preset: OverridePreset , duration : TimeInterval ? ) async throws {
302469 let apnsService = LoopAPNSService ( )
303470 try await apnsService. sendOverrideNotification (
304471 presetName: preset. name,
305- duration: preset . duration
472+ duration: duration
306473 )
307474 }
308475
0 commit comments