FreeAPSApp.swift 7.7 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200
  1. import ActivityKit
  2. import BackgroundTasks
  3. import CoreData
  4. import Foundation
  5. import SwiftUI
  6. import Swinject
  7. @main struct FreeAPSApp: App {
  8. @Environment(\.scenePhase) var scenePhase
  9. @UIApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
  10. // Read the color scheme preference from UserDefaults; defaults to system default setting
  11. @AppStorage("colorSchemePreference") private var colorSchemePreference: ColorSchemeOption = .systemDefault
  12. let coreDataStack = CoreDataStack.shared
  13. @State private var appState = AppState()
  14. // Dependencies Assembler
  15. // contain all dependencies Assemblies
  16. // TODO: Remove static key after update "Use Dependencies" logic
  17. private static var assembler = Assembler([
  18. StorageAssembly(),
  19. ServiceAssembly(),
  20. APSAssembly(),
  21. NetworkAssembly(),
  22. UIAssembly(),
  23. SecurityAssembly()
  24. ], parent: nil, defaultObjectScope: .container)
  25. var resolver: Resolver {
  26. FreeAPSApp.assembler.resolver
  27. }
  28. // Temp static var
  29. // Use to backward compatibility with old Dependencies logic on Logger
  30. // TODO: Remove var after update "Use Dependencies" logic in Logger
  31. static var resolver: Resolver {
  32. FreeAPSApp.assembler.resolver
  33. }
  34. private func loadServices() {
  35. resolver.resolve(AppearanceManager.self)!.setupGlobalAppearance()
  36. _ = resolver.resolve(DeviceDataManager.self)!
  37. _ = resolver.resolve(APSManager.self)!
  38. _ = resolver.resolve(FetchGlucoseManager.self)!
  39. _ = resolver.resolve(FetchTreatmentsManager.self)!
  40. _ = resolver.resolve(CalendarManager.self)!
  41. _ = resolver.resolve(UserNotificationsManager.self)!
  42. _ = resolver.resolve(WatchManager.self)!
  43. _ = resolver.resolve(HealthKitManager.self)!
  44. _ = resolver.resolve(BluetoothStateManager.self)!
  45. _ = resolver.resolve(PluginManager.self)!
  46. _ = resolver.resolve(AlertPermissionsChecker.self)!
  47. if #available(iOS 16.2, *) {
  48. _ = resolver.resolve(LiveActivityBridge.self)!
  49. }
  50. }
  51. init() {
  52. debug(
  53. .default,
  54. "Trio Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(String(describing: BuildDetails.default.buildDate()))] [buildExpires: \(String(describing: BuildDetails.default.calculateExpirationDate()))]"
  55. )
  56. // Load services
  57. loadServices()
  58. // Fix bug in iOS 18 related to the translucent tab bar
  59. configureTabBarAppearance()
  60. // Clear the persistentHistory and the NSManagedObjects that are older than 90 days every time the app starts
  61. cleanupOldData()
  62. }
  63. var body: some Scene {
  64. WindowGroup {
  65. Main.RootView(resolver: resolver)
  66. .preferredColorScheme(colorScheme(for: colorSchemePreference) ?? nil)
  67. .environment(\.managedObjectContext, coreDataStack.persistentContainer.viewContext)
  68. .environment(appState)
  69. .environmentObject(Icons())
  70. .onOpenURL(perform: handleURL)
  71. }
  72. .onChange(of: scenePhase) { _, newScenePhase in
  73. debug(.default, "APPLICATION PHASE: \(newScenePhase)")
  74. /// If the App goes to the background we should ensure that all the changes are saved from the viewContext to the Persistent Container
  75. if newScenePhase == .background {
  76. coreDataStack.save()
  77. }
  78. }
  79. .backgroundTask(.appRefresh("com.openiaps.cleanup")) {
  80. await scheduleDatabaseCleaning()
  81. await cleanupOldData()
  82. }
  83. }
  84. func configureTabBarAppearance() {
  85. let appearance = UITabBarAppearance()
  86. appearance.configureWithDefaultBackground()
  87. appearance.backgroundEffect = UIBlurEffect(style: .systemChromeMaterial)
  88. appearance.backgroundColor = UIColor.clear
  89. UITabBar.appearance().standardAppearance = appearance
  90. UITabBar.appearance().scrollEdgeAppearance = appearance
  91. }
  92. private func colorScheme(for colorScheme: ColorSchemeOption) -> ColorScheme? {
  93. switch colorScheme {
  94. case .systemDefault:
  95. return nil // Uses the system theme.
  96. case .light:
  97. return .light
  98. case .dark:
  99. return .dark
  100. }
  101. }
  102. func scheduleDatabaseCleaning() {
  103. let request = BGAppRefreshTaskRequest(identifier: "com.openiaps.cleanup")
  104. request.earliestBeginDate = .now.addingTimeInterval(7 * 24 * 60 * 60) // 7 days
  105. do {
  106. try BGTaskScheduler.shared.submit(request)
  107. debugPrint("Task scheduled successfully")
  108. } catch {
  109. debugPrint("Failed to schedule tasks")
  110. }
  111. }
  112. private func cleanupOldData() {
  113. Task {
  114. async let cleanupTokens: () = coreDataStack.cleanupPersistentHistoryTokens(before: Date.oneWeekAgo)
  115. async let purgeData: () = purgeOldNSManagedObjects()
  116. await cleanupTokens
  117. try await purgeData
  118. }
  119. }
  120. private func purgeOldNSManagedObjects() async throws {
  121. async let glucoseDeletion: () = coreDataStack.batchDeleteOlderThan(GlucoseStored.self, dateKey: "date", days: 90)
  122. async let pumpEventDeletion: () = coreDataStack.batchDeleteOlderThan(PumpEventStored.self, dateKey: "timestamp", days: 90)
  123. async let bolusDeletion: () = coreDataStack.batchDeleteOlderThan(
  124. parentType: PumpEventStored.self,
  125. childType: BolusStored.self,
  126. dateKey: "timestamp",
  127. days: 90,
  128. relationshipKey: "pumpEvent"
  129. )
  130. async let tempBasalDeletion: () = coreDataStack.batchDeleteOlderThan(
  131. parentType: PumpEventStored.self,
  132. childType: TempBasalStored.self,
  133. dateKey: "timestamp",
  134. days: 90,
  135. relationshipKey: "pumpEvent"
  136. )
  137. async let determinationDeletion: () = coreDataStack
  138. .batchDeleteOlderThan(OrefDetermination.self, dateKey: "deliverAt", days: 90)
  139. async let batteryDeletion: () = coreDataStack.batchDeleteOlderThan(OpenAPS_Battery.self, dateKey: "date", days: 90)
  140. async let carbEntryDeletion: () = coreDataStack.batchDeleteOlderThan(CarbEntryStored.self, dateKey: "date", days: 90)
  141. async let forecastDeletion: () = coreDataStack.batchDeleteOlderThan(Forecast.self, dateKey: "date", days: 2)
  142. async let forecastValueDeletion: () = coreDataStack.batchDeleteOlderThan(
  143. parentType: Forecast.self,
  144. childType: ForecastValue.self,
  145. dateKey: "date",
  146. days: 2,
  147. relationshipKey: "forecast"
  148. )
  149. async let overrideDeletion: () = coreDataStack
  150. .batchDeleteOlderThan(OverrideStored.self, dateKey: "date", days: 3, isPresetKey: "isPreset")
  151. async let overrideRunDeletion: () = coreDataStack
  152. .batchDeleteOlderThan(OverrideRunStored.self, dateKey: "startDate", days: 3)
  153. // Await each task to ensure they are all completed
  154. try await glucoseDeletion
  155. try await pumpEventDeletion
  156. try await bolusDeletion
  157. try await tempBasalDeletion
  158. try await determinationDeletion
  159. try await batteryDeletion
  160. try await carbEntryDeletion
  161. try await forecastDeletion
  162. try await forecastValueDeletion
  163. try await overrideDeletion
  164. try await overrideRunDeletion
  165. }
  166. private func handleURL(_ url: URL) {
  167. let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
  168. switch components?.host {
  169. case "device-select-resp":
  170. resolver.resolve(NotificationCenter.self)!.post(name: .openFromGarminConnect, object: url)
  171. default: break
  172. }
  173. }
  174. }