Parcourir la source

Working on snoozer

Jonas Björkert il y a 1 an
Parent
commit
e096c9a748

+ 4 - 0
LoopFollow.xcodeproj/project.pbxproj

@@ -116,6 +116,7 @@
 		DDC7E5452DBD8A1600EB1127 /* HighBgAlarmEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC7E53D2DBD8A1600EB1127 /* HighBgAlarmEditor.swift */; };
 		DDC7E5462DBD8A1600EB1127 /* LowBgAlarmEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC7E53E2DBD8A1600EB1127 /* LowBgAlarmEditor.swift */; };
 		DDC7E5472DBD8A1600EB1127 /* AlarmEditor.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC7E5402DBD8A1600EB1127 /* AlarmEditor.swift */; };
+		DDC7E5CF2DC77C2000EB1127 /* SnoozerViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDC7E5CE2DC77C2000EB1127 /* SnoozerViewModel.swift */; };
 		DDCF979424C0D380002C9752 /* UIViewExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979324C0D380002C9752 /* UIViewExtension.swift */; };
 		DDCF979624C1443C002C9752 /* GeneralSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979524C1443C002C9752 /* GeneralSettingsViewController.swift */; };
 		DDCF979824C1489C002C9752 /* GraphSettingsViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = DDCF979724C1489C002C9752 /* GraphSettingsViewController.swift */; };
@@ -426,6 +427,7 @@
 		DDC7E53D2DBD8A1600EB1127 /* HighBgAlarmEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = HighBgAlarmEditor.swift; sourceTree = "<group>"; };
 		DDC7E53E2DBD8A1600EB1127 /* LowBgAlarmEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LowBgAlarmEditor.swift; sourceTree = "<group>"; };
 		DDC7E5402DBD8A1600EB1127 /* AlarmEditor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AlarmEditor.swift; sourceTree = "<group>"; };
+		DDC7E5CE2DC77C2000EB1127 /* SnoozerViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SnoozerViewModel.swift; sourceTree = "<group>"; };
 		DDCF979324C0D380002C9752 /* UIViewExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIViewExtension.swift; sourceTree = "<group>"; };
 		DDCF979524C1443C002C9752 /* GeneralSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GeneralSettingsViewController.swift; sourceTree = "<group>"; };
 		DDCF979724C1489C002C9752 /* GraphSettingsViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphSettingsViewController.swift; sourceTree = "<group>"; };
@@ -882,6 +884,7 @@
 		DDC7E5142DBCE1B900EB1127 /* Snoozer */ = {
 			isa = PBXGroup;
 			children = (
+				DDC7E5CE2DC77C2000EB1127 /* SnoozerViewModel.swift */,
 				DDC7E5122DBCE1B900EB1127 /* SnoozerView.swift */,
 				DDC7E5132DBCE1B900EB1127 /* SnoozerViewController.swift */,
 			);
@@ -1754,6 +1757,7 @@
 				DDCF9A802D85FD0B004DF4DD /* Alarm.swift in Sources */,
 				DDD10F012C510C6B00D76A8E /* ObservableUserDefaults.swift in Sources */,
 				DD16AF0D2C98485400FB655A /* SecureStorageValue.swift in Sources */,
+				DDC7E5CF2DC77C2000EB1127 /* SnoozerViewModel.swift in Sources */,
 				DD2C2E4F2D3B8AF1006413A5 /* NightscoutSettingsView.swift in Sources */,
 				DD1A97162D4294B3000DDC11 /* AdvancedSettingsViewModel.swift in Sources */,
 				FCA2DDE62501095000254A8C /* Timers.swift in Sources */,

+ 13 - 10
LoopFollow/Alarm/AlarmManager.swift

@@ -78,16 +78,19 @@ class AlarmManager {
         }
     }
 
-    func snoozeCurrentAlarm(by minutes: Int) {
-        //guard var alarm = currentAlarm else { return }
-        //alarm.snoozedUntil = Date().addingTimeInterval(TimeInterval(minutes * 60))
+    //TODO: Handle default snooze for notofication snoze
+    //TODO: Check interval type handling
+    func performSnooze(_ minutes: Int? = nil) {
+        if let alarmID = Observable.shared.currentAlarm.value {
+            var alarms = Storage.shared.alarms.value
+            if let idx = alarms.firstIndex(where: { $0.id == alarmID }) {
+                alarms[idx].snoozedUntil = Date().addingTimeInterval(
+                    TimeInterval((minutes ?? 5) * 60)) // fix default value
+            }
+            Storage.shared.alarms.value = alarms
 
-        // write it back to your Storage
-        /*
-        Storage.shared.alarms.value = Storage.shared.alarms.value
-            .map { $0.id == alarm.id ? alarm : $0 }
-*/
-        // clear so you can’t snooze twice
-        //currentAlarm = nil
+            AlarmSound.stop()
+            Observable.shared.currentAlarm.value = nil
+        }
     }
 }

+ 5 - 0
LoopFollow/Application/AppDelegate.swift

@@ -134,6 +134,11 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
                 window.rootViewController?.present(MainViewController(), animated: true, completion: nil)
             }
         }
+
+        if response.actionIdentifier == "snooze" {
+            AlarmManager.shared.performSnooze()
+        }
+
         completionHandler()
     }
 }

+ 0 - 1
LoopFollow/Extensions/ShareClientExtension.swift

@@ -61,7 +61,6 @@ private var TrendTable: [String] = [
    "RATE OUT OF RANGE" // 9
 ]
 
-// TODO: probably better to make this an inherited class rather than an extension
 extension ShareClient {
 
     public func fetchData(_ entries: Int, callback: @escaping (ShareError?, [ShareGlucoseData]?) -> Void) {

+ 0 - 1
LoopFollow/Helpers/Mobileprovision.swift

@@ -99,7 +99,6 @@ extension MobileProvision {
             let provision = try decoder.decode(MobileProvision.self, from: plist)
             return provision
         } catch {
-            // TODO: log / handle error
             return nil
         }
     }

+ 54 - 67
LoopFollow/Snoozer/SnoozerView.swift

@@ -9,6 +9,8 @@
 import SwiftUI
 
 struct SnoozerView: View {
+    @StateObject private var vm = SnoozerViewModel()
+
     @ObservedObject var minAgoText = Observable.shared.minAgoText
     @ObservedObject var bgText     = Observable.shared.bgText
     @ObservedObject var bgTextColor = Observable.shared.bgTextColor
@@ -16,37 +18,26 @@ struct SnoozerView: View {
     @ObservedObject var deltaText   = Observable.shared.deltaText
     @ObservedObject var bgStale     = Observable.shared.bgStale
 
-    @Binding var snoozeMinutes: Int
-    var onSnooze: () -> Void
-
     var body: some View {
         GeometryReader { geo in
-            ZStack {
-                Color.black
-                    .edgesIgnoringSafeArea(.all)
-
-                Group {
-                    if geo.size.width > geo.size.height {
-                        // Landscape: two columns
-                        HStack(spacing: 0) {
-                            leftColumn
-                            rightColumn
-                        }
-                    } else {
-                        // Portrait: single column
-                        VStack(spacing: 0) {
-                            leftColumn
-                            rightColumn
-                        }
-                    }
-                }
-                .frame(width: geo.size.width, height: geo.size.height)
-            }
+            Color.black.ignoresSafeArea()
+                .overlay(contentColumn(size: geo.size))
+                .animation(.easeInOut, value: vm.activeAlarm != nil)
+        }
+    }
+
+
+    // MARK: – Layout helper
+    @ViewBuilder private func contentColumn(size: CGSize) -> some View {
+        if size.width > size.height {   // landscape
+            HStack(spacing: 0) { leftPanel ; rightPanel }
+        } else {                        // portrait
+            VStack(spacing: 0) { leftPanel ; rightPanel }
         }
     }
 
     // MARK: - Left Column (BG / Direction / Delta / Age)
-    private var leftColumn: some View {
+    private var leftPanel: some View  {
         VStack(spacing: 0) {
             Text(bgText.value)
                 .font(.system(size: 220, weight: .black))
@@ -81,62 +72,58 @@ struct SnoozerView: View {
         .padding(.horizontal, 16)
     }
 
-    // MARK: - Right Column (Clock/Alert + Snooze Controls)
-    private var rightColumn: some View {
+    // MARK: – Right (Clock + Snooze)
+    private var rightPanel: some View {
         VStack(spacing: 0) {
             Spacer()
 
-            VStack(spacing: 8) {
-                TimelineView(.periodic(from: .now, by: 1)) { context in
-                    Text(context.date, format:
-                        Date.FormatStyle(date: .omitted, time: .shortened)
-                    )
-                    .font(.system(size: 70))
-                    .minimumScaleFactor(0.5)
+            TimelineView(.periodic(from: .now, by: 1)) { ctx in
+                Text(ctx.date, style: .time)
+                    .font(.system(size: 70, weight: .regular))
                     .foregroundColor(.white)
-                    .frame(height: 78)
-                }
-
-                /*
-                if let alarm = alarmText.value, !alarm.isEmpty {
-                    Text(alarm)
-                        .font(.system(size: 40, weight: .semibold))
-                        .foregroundColor(.red)
-                        .minimumScaleFactor(0.5)
-                        .lineLimit(1)
-                        .frame(height: 48)
-                }
-                */
+            }
+            .frame(height: 80)
+
+            if let alarm = vm.activeAlarm {
+                Text(alarm.name)
+                    .font(.system(size: 40, weight: .semibold))
+                    .foregroundColor(.red)
+                    .minimumScaleFactor(0.5)
+                    .padding(.top, 4)
+
+                Spacer(minLength: 32)
+
+                snoozeControls(alarm: alarm)
             }
 
             Spacer()
+        }
+        .padding(.horizontal, 24)
+    }
 
-            // Snooze controls
-            HStack(spacing: 12) {
+    // MARK: – Snooze UI
+    @ViewBuilder private func snoozeControls(alarm: Alarm) -> some View {
+        VStack(spacing: 16) {
+            HStack {
                 Text("Snooze for")
-                    .font(.system(size: 20))
-                    .foregroundColor(.white)
-                Text("\(snoozeMinutes) min")
-                    .font(.system(size: 20, weight: .semibold))
-                    .foregroundColor(.white)
                 Spacer()
-                Stepper("", value: $snoozeMinutes, in: 1...60)
-                    .labelsHidden()
+                Text("\(vm.snoozeMins) \(vm.timeUnitLabel)")
             }
-            .padding(.horizontal, 32)
-            .frame(height: 44)
+            .font(.title3)
+            .foregroundColor(.white)
+
+            Stepper("", value: $vm.snoozeMins,
+                    in: 1...120,
+                    step: alarm.type.timeUnit == .minute ? 5 : 1)
+            .labelsHidden()
 
-            Button(action: onSnooze) {
+            Button(action: vm.snoozeTapped) {
                 Text("Snooze")
-                    .font(.system(size: 24, weight: .bold))
-                    .frame(maxWidth: .infinity, minHeight: 56)
+                    .bold()
+                    .frame(maxWidth: .infinity, minHeight: 50)
+                    .background(Color.white.opacity(0.15))
+                    .cornerRadius(10)
             }
-            .background(Color(white: 0.15))
-            .foregroundColor(.white)
-            .cornerRadius(8)
-            .padding(.horizontal, 32)
-            .padding(.bottom, 32)
         }
-        .padding(.top, 16)
     }
 }

+ 1 - 6
LoopFollow/Snoozer/SnoozerViewController.swift

@@ -20,12 +20,7 @@ class SnoozerViewController: UIViewController {
         super.viewDidLoad()
         view.backgroundColor = .black
 
-        let snoozerView = SnoozerView(snoozeMinutes: $snoozeMinutes,
-            onSnooze: {
-                // Trigger snooze logic here (e.g., update UserDefaultsRepository, stop alarm, etc.)
-                print("Snoozed for \(self.snoozeMinutes) minutes")
-            },
-        )
+        let snoozerView = SnoozerView()
 
         let hosting = UIHostingController(rootView: snoozerView)
         self.hostingController = hosting

+ 42 - 0
LoopFollow/Snoozer/SnoozerViewModel.swift

@@ -0,0 +1,42 @@
+//
+//  SnoozerViewModel.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2025-05-04.
+//  Copyright © 2025 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import Combine
+
+final class SnoozerViewModel: ObservableObject {
+    @Published var activeAlarm: Alarm?
+    @Published var snoozeMins: Int = 5
+    @Published var timeUnitLabel: String = "minutes"
+
+    private var bag = Set<AnyCancellable>()
+
+    init() {
+        Observable.shared.currentAlarm.$value
+            .compactMap { $0 }                      // drop nils
+            .map { id in
+                Storage.shared.alarms.value.first { $0.id == id }
+            }
+            .receive(on: DispatchQueue.main)
+            .sink { [weak self] alarm in
+                self?.activeAlarm = alarm           // may be nil
+                if let a = alarm {
+                    self?.snoozeMins = a.snoozeDuration
+                    self?.timeUnitLabel = a.type.timeUnit.label
+                }
+            }
+            .store(in: &bag)
+    }
+
+    func snoozeTapped() {
+        guard let alarm = activeAlarm else { return }
+        AlarmManager.shared.performSnooze(
+            snoozeMins * Int(alarm.type.timeUnit.seconds) / 60
+        )
+    }
+}

+ 0 - 6
LoopFollow/ViewControllers/MainViewController.swift

@@ -171,11 +171,6 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
             self.tabBarController?.overrideUserInterfaceStyle = .dark
         }
 
-        // Load the snoozer tab
-        //TODO:
-        //guard let snoozer = self.tabBarController!.viewControllers?[2] as? SnoozeViewController else { return }
-        //snoozer.loadViewIfNeeded()
-
         // Trigger foreground and background functions
         let notificationCenter = NotificationCenter.default
         notificationCenter.addObserver(self, selector: #selector(appMovedToBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
@@ -305,7 +300,6 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         UIApplication.shared.isIdleTimerDisabled = UserDefaultsRepository.screenlockSwitchState.value;
         
         // check the app state
-        // TODO: move to a function ?
         if let appState = self.appStateController {
             
             if appState.chartSettingsChanged {