Przeglądaj źródła

Detection of inactive state

Jonas Björkert 2 lat temu
rodzic
commit
47e7493f78

+ 4 - 0
LoopFollow.xcodeproj/project.pbxproj

@@ -22,6 +22,7 @@
 		DD493AE72ACF23CF009A6922 /* DeviceStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD493AE62ACF23CF009A6922 /* DeviceStatus.swift */; };
 		DD493AE72ACF23CF009A6922 /* DeviceStatus.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD493AE62ACF23CF009A6922 /* DeviceStatus.swift */; };
 		DD493AE92ACF2445009A6922 /* BGData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD493AE82ACF2445009A6922 /* BGData.swift */; };
 		DD493AE92ACF2445009A6922 /* BGData.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD493AE82ACF2445009A6922 /* BGData.swift */; };
 		DD608A082C1F584900F91132 /* DeviceStatusLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD608A072C1F584900F91132 /* DeviceStatusLoop.swift */; };
 		DD608A082C1F584900F91132 /* DeviceStatusLoop.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD608A072C1F584900F91132 /* DeviceStatusLoop.swift */; };
+		DD608A0C2C27415C00F91132 /* BackgroundAlertManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD608A0B2C27415C00F91132 /* BackgroundAlertManager.swift */; };
 		DD6A935E2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6A935D2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift */; };
 		DD6A935E2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD6A935D2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift */; };
 		DD7E19842ACDA50C00DBD158 /* Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7E19832ACDA50C00DBD158 /* Overrides.swift */; };
 		DD7E19842ACDA50C00DBD158 /* Overrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7E19832ACDA50C00DBD158 /* Overrides.swift */; };
 		DD7E19862ACDA59700DBD158 /* BGCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7E19852ACDA59700DBD158 /* BGCheck.swift */; };
 		DD7E19862ACDA59700DBD158 /* BGCheck.swift in Sources */ = {isa = PBXBuildFile; fileRef = DD7E19852ACDA59700DBD158 /* BGCheck.swift */; };
@@ -214,6 +215,7 @@
 		DD493AE62ACF23CF009A6922 /* DeviceStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStatus.swift; sourceTree = "<group>"; };
 		DD493AE62ACF23CF009A6922 /* DeviceStatus.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStatus.swift; sourceTree = "<group>"; };
 		DD493AE82ACF2445009A6922 /* BGData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGData.swift; sourceTree = "<group>"; };
 		DD493AE82ACF2445009A6922 /* BGData.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGData.swift; sourceTree = "<group>"; };
 		DD608A072C1F584900F91132 /* DeviceStatusLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStatusLoop.swift; sourceTree = "<group>"; };
 		DD608A072C1F584900F91132 /* DeviceStatusLoop.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStatusLoop.swift; sourceTree = "<group>"; };
+		DD608A0B2C27415C00F91132 /* BackgroundAlertManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BackgroundAlertManager.swift; sourceTree = "<group>"; };
 		DD6A935D2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStatusOpenAPS.swift; sourceTree = "<group>"; };
 		DD6A935D2BFA6FA2003FFB8E /* DeviceStatusOpenAPS.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DeviceStatusOpenAPS.swift; sourceTree = "<group>"; };
 		DD7E19832ACDA50C00DBD158 /* Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Overrides.swift; sourceTree = "<group>"; };
 		DD7E19832ACDA50C00DBD158 /* Overrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Overrides.swift; sourceTree = "<group>"; };
 		DD7E19852ACDA59700DBD158 /* BGCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGCheck.swift; sourceTree = "<group>"; };
 		DD7E19852ACDA59700DBD158 /* BGCheck.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BGCheck.swift; sourceTree = "<group>"; };
@@ -501,6 +503,7 @@
 				FC1BDD2A24A22650001B652C /* Stats.swift */,
 				FC1BDD2A24A22650001B652C /* Stats.swift */,
 				FC1BDD2C24A23204001B652C /* StatsView.swift */,
 				FC1BDD2C24A23204001B652C /* StatsView.swift */,
 				FCA2DDE52501095000254A8C /* Timers.swift */,
 				FCA2DDE52501095000254A8C /* Timers.swift */,
+				DD608A0B2C27415C00F91132 /* BackgroundAlertManager.swift */,
 			);
 			);
 			path = Controllers;
 			path = Controllers;
 			sourceTree = "<group>";
 			sourceTree = "<group>";
@@ -1025,6 +1028,7 @@
 				FCC0FAC224922A22003E610E /* DictionaryKeyPath.swift in Sources */,
 				FCC0FAC224922A22003E610E /* DictionaryKeyPath.swift in Sources */,
 				DD493AD72ACF2139009A6922 /* SuspendPump.swift in Sources */,
 				DD493AD72ACF2139009A6922 /* SuspendPump.swift in Sources */,
 				FC9788182485969B00A7906C /* AppDelegate.swift in Sources */,
 				FC9788182485969B00A7906C /* AppDelegate.swift in Sources */,
+				DD608A0C2C27415C00F91132 /* BackgroundAlertManager.swift in Sources */,
 				DD493AD92ACF2171009A6922 /* Carbs.swift in Sources */,
 				DD493AD92ACF2171009A6922 /* Carbs.swift in Sources */,
 				DD493AE92ACF2445009A6922 /* BGData.swift in Sources */,
 				DD493AE92ACF2445009A6922 /* BGData.swift in Sources */,
 				FCC6886B24898FD800A0279D /* ObservationToken.swift in Sources */,
 				FCC6886B24898FD800A0279D /* ObservationToken.swift in Sources */,

+ 22 - 12
LoopFollow/Application/AppDelegate.swift

@@ -16,11 +16,10 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
 
 
     var window: UIWindow?
     var window: UIWindow?
     let notificationCenter = UNUserNotificationCenter.current()
     let notificationCenter = UNUserNotificationCenter.current()
-   
+
     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
     func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
         // Override point for customization after application launch.
         // Override point for customization after application launch.
         
         
-        
         let options: UNAuthorizationOptions = [.alert, .sound, .badge]
         let options: UNAuthorizationOptions = [.alert, .sound, .badge]
         notificationCenter.requestAuthorization(options: options) {
         notificationCenter.requestAuthorization(options: options) {
             (didAllow, error) in
             (didAllow, error) in
@@ -37,11 +36,15 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
             }
             }
         }
         }
         
         
+        let action = UNNotificationAction(identifier: "OPEN_APP_ACTION", title: "Open App", options: .foreground)
+        let category = UNNotificationCategory(identifier: "loopfollow.background.alert", actions: [action], intentIdentifiers: [], options: [])
+        UNUserNotificationCenter.current().setNotificationCategories([category])
+
         UNUserNotificationCenter.current().delegate = self
         UNUserNotificationCenter.current().delegate = self
-        
-         return true
-      }
-        
+
+        return true
+    }
+
     func applicationWillTerminate(_ application: UIApplication) {
     func applicationWillTerminate(_ application: UIApplication) {
         if UserDefaultsRepository.alertAppInactive.value {
         if UserDefaultsRepository.alertAppInactive.value {
             AlarmSound.setSoundFile(str: "Alarm_Buzzer")
             AlarmSound.setSoundFile(str: "Alarm_Buzzer")
@@ -53,7 +56,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
     
     
     func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
     func application(_ application: UIApplication, willFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
         
         
-
         // This application should be called in background every X Minutes
         // This application should be called in background every X Minutes
         UIApplication.shared.setMinimumBackgroundFetchInterval(
         UIApplication.shared.setMinimumBackgroundFetchInterval(
             TimeInterval(UserDefaultsRepository.backgroundRefreshFrequency.value * 60)
             TimeInterval(UserDefaultsRepository.backgroundRefreshFrequency.value * 60)
@@ -91,13 +93,13 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
          creates and returns a container, having loaded the store for the
          creates and returns a container, having loaded the store for the
          application to it. This property is optional since there are legitimate
          application to it. This property is optional since there are legitimate
          error conditions that could cause the creation of the store to fail.
          error conditions that could cause the creation of the store to fail.
-        */
+         */
         let container = NSPersistentCloudKitContainer(name: "LoopFollow")
         let container = NSPersistentCloudKitContainer(name: "LoopFollow")
         container.loadPersistentStores(completionHandler: { (storeDescription, error) in
         container.loadPersistentStores(completionHandler: { (storeDescription, error) in
             if let error = error as NSError? {
             if let error = error as NSError? {
                 // Replace this implementation with code to handle the error appropriately.
                 // Replace this implementation with code to handle the error appropriately.
                 // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
                 // fatalError() causes the application to generate a crash log and terminate. You should not use this function in a shipping application, although it may be useful during development.
-                 
+
                 /*
                 /*
                  Typical reasons for an error here include:
                  Typical reasons for an error here include:
                  * The parent directory does not exist, cannot be created, or disallows writing.
                  * The parent directory does not exist, cannot be created, or disallows writing.
@@ -128,13 +130,21 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
         }
         }
     }
     }
 
 
+    func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
+        if response.actionIdentifier == "OPEN_APP_ACTION" {
+            if let window = window {
+                window.rootViewController?.dismiss(animated: true, completion: nil)
+                window.rootViewController?.present(MainViewController(), animated: true, completion: nil)
+            }
+        }
+        completionHandler()
+    }
 }
 }
 
 
 extension AppDelegate: UNUserNotificationCenterDelegate {
 extension AppDelegate: UNUserNotificationCenterDelegate {
-
     func userNotificationCenter(_ center: UNUserNotificationCenter,
     func userNotificationCenter(_ center: UNUserNotificationCenter,
-           willPresent notification: UNNotification,
-           withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)
+                                willPresent notification: UNNotification,
+                                withCompletionHandler completionHandler: @escaping (UNNotificationPresentationOptions) -> Void)
     {
     {
         completionHandler(.alert)
         completionHandler(.alert)
     }
     }

+ 52 - 0
LoopFollow/Controllers/BackgroundAlertManager.swift

@@ -0,0 +1,52 @@
+//
+//  BackgroundAlertManager.swift
+//  LoopFollow
+//
+//  Created by Jonas Björkert on 2024-06-22.
+//  Copyright © 2024 Jon Fawcett. All rights reserved.
+//
+
+import Foundation
+import UserNotifications
+
+class BackgroundAlertManager {
+    static let shared = BackgroundAlertManager()
+    
+    private init() {}
+    
+    private var isAlertScheduled: Bool = false
+    
+    func startBackgroundAlert() {
+        isAlertScheduled = true
+        scheduleBackgroundAlert()
+    }
+    
+    func stopBackgroundAlert() {
+        isAlertScheduled = false
+        cancelBackgroundAlert()
+    }
+    
+    func scheduleBackgroundAlert() {
+        guard isAlertScheduled, UserDefaultsRepository.backgroundRefresh.value else { return }
+        
+        let content = UNMutableNotificationContent()
+        content.title = "LoopFollow Background Refresh"
+        content.body = "The app is not active, open the app to resume."
+        content.sound = .defaultCritical
+        content.categoryIdentifier = "loopfollow.background.alert"
+        
+        let trigger = UNTimeIntervalNotificationTrigger(timeInterval: 360, repeats: false)
+        
+        let request = UNNotificationRequest(identifier: "loopfollow.background.alert", content: content, trigger: trigger)
+        
+        UNUserNotificationCenter.current().add(request) { error in
+            if let error = error {
+                print("Error scheduling background alert: \(error)")
+            }
+        }
+    }
+    
+    private func cancelBackgroundAlert() {
+        UNUserNotificationCenter.current().removePendingNotificationRequests(withIdentifiers: ["loopfollow.background.alert"])
+    }
+}

+ 1 - 2
LoopFollow/Controllers/Timers.swift

@@ -143,7 +143,6 @@ extension MainViewController {
     }
     }
     
     
     @objc func bgTimerDidEnd(_ timer:Timer) {
     @objc func bgTimerDidEnd(_ timer:Timer) {
-        
         // reset timer to 1 minute if settings aren't entered
         // reset timer to 1 minute if settings aren't entered
         if UserDefaultsRepository.shareUserName.value == "" && UserDefaultsRepository.sharePassword.value == "" && UserDefaultsRepository.url.value == "" {
         if UserDefaultsRepository.shareUserName.value == "" && UserDefaultsRepository.sharePassword.value == "" && UserDefaultsRepository.url.value == "" {
             startBGTimer(time: 60)
             startBGTimer(time: 60)
@@ -155,7 +154,7 @@ extension MainViewController {
         } else {
         } else {
             webLoadNSBGData()
             webLoadNSBGData()
         }
         }
-        
+        BackgroundAlertManager.shared.scheduleBackgroundAlert()
     }
     }
     
     
     // Device Status Timer
     // Device Status Timer

+ 2 - 0
LoopFollow/ViewControllers/MainViewController.swift

@@ -432,6 +432,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         // Cancel the current timer and start a fresh background timer using the settings value only if background task is enabled
         // Cancel the current timer and start a fresh background timer using the settings value only if background task is enabled
         
         
         if UserDefaultsRepository.backgroundRefresh.value {
         if UserDefaultsRepository.backgroundRefresh.value {
+            BackgroundAlertManager.shared.startBackgroundAlert()
             backgroundTask.startBackgroundTask()
             backgroundTask.startBackgroundTask()
         }
         }
         
         
@@ -444,6 +445,7 @@ class MainViewController: UIViewController, UITableViewDataSource, ChartViewDele
         // Cancel the background tasks, start a fresh timer
         // Cancel the background tasks, start a fresh timer
         if UserDefaultsRepository.backgroundRefresh.value {
         if UserDefaultsRepository.backgroundRefresh.value {
             backgroundTask.stopBackgroundTask()
             backgroundTask.stopBackgroundTask()
+            BackgroundAlertManager.shared.stopBackgroundAlert()
         }
         }
         
         
         restartAllTimers()
         restartAllTimers()