TrioApp.swift 8.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219
  1. import ActivityKit
  2. import BackgroundTasks
  3. import CoreData
  4. import Foundation
  5. import SwiftUI
  6. import Swinject
  7. @main struct TrioApp: 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
  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. TrioApp.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. TrioApp.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(ContactImageManager.self)!
  44. _ = resolver.resolve(HealthKitManager.self)!
  45. _ = resolver.resolve(WatchManager.self)!
  46. _ = resolver.resolve(GarminManager.self)!
  47. _ = resolver.resolve(ContactImageManager.self)!
  48. _ = resolver.resolve(BluetoothStateManager.self)!
  49. _ = resolver.resolve(PluginManager.self)!
  50. _ = resolver.resolve(AlertPermissionsChecker.self)!
  51. if #available(iOS 16.2, *) {
  52. _ = resolver.resolve(LiveActivityBridge.self)!
  53. }
  54. }
  55. init() {
  56. debug(
  57. .default,
  58. "Trio Started: v\(Bundle.main.releaseVersionNumber ?? "")(\(Bundle.main.buildVersionNumber ?? "")) [buildDate: \(String(describing: BuildDetails.default.buildDate()))] [buildExpires: \(String(describing: BuildDetails.default.calculateExpirationDate()))]"
  59. )
  60. // Setup up the Core Data Stack
  61. coreDataStack = CoreDataStack.shared
  62. do {
  63. // Explicitly initialize Core Data Stacak
  64. try coreDataStack.initializeStack()
  65. // Load services
  66. loadServices()
  67. // Fix bug in iOS 18 related to the translucent tab bar
  68. configureTabBarAppearance()
  69. // Clear the persistentHistory and the NSManagedObjects that are older than 90 days every time the app starts
  70. cleanupOldData()
  71. } catch {
  72. debug(
  73. .coreData,
  74. "Failed to initialize Core Data Stack: \(error.localizedDescription)"
  75. )
  76. // Handle initialization failure
  77. fatalError("Core Data Stack initialization failed: \(error.localizedDescription)")
  78. }
  79. }
  80. var body: some Scene {
  81. WindowGroup {
  82. Main.RootView(resolver: resolver)
  83. .preferredColorScheme(colorScheme(for: colorSchemePreference) ?? nil)
  84. .environment(\.managedObjectContext, coreDataStack.persistentContainer.viewContext)
  85. .environment(appState)
  86. .environmentObject(Icons())
  87. .onOpenURL(perform: handleURL)
  88. }
  89. .onChange(of: scenePhase) { _, newScenePhase in
  90. debug(.default, "APPLICATION PHASE: \(newScenePhase)")
  91. /// If the App goes to the background we should ensure that all the changes are saved from the viewContext to the Persistent Container
  92. if newScenePhase == .background {
  93. coreDataStack.save()
  94. }
  95. }
  96. .backgroundTask(.appRefresh("com.trio.cleanup")) {
  97. await scheduleDatabaseCleaning()
  98. await cleanupOldData()
  99. }
  100. }
  101. func configureTabBarAppearance() {
  102. let appearance = UITabBarAppearance()
  103. appearance.configureWithDefaultBackground()
  104. appearance.backgroundEffect = UIBlurEffect(style: .systemChromeMaterial)
  105. appearance.backgroundColor = UIColor.clear
  106. UITabBar.appearance().standardAppearance = appearance
  107. UITabBar.appearance().scrollEdgeAppearance = appearance
  108. }
  109. private func colorScheme(for colorScheme: ColorSchemeOption) -> ColorScheme? {
  110. switch colorScheme {
  111. case .systemDefault:
  112. return nil // Uses the system theme.
  113. case .light:
  114. return .light
  115. case .dark:
  116. return .dark
  117. }
  118. }
  119. func scheduleDatabaseCleaning() {
  120. let request = BGAppRefreshTaskRequest(identifier: "com.trio.cleanup")
  121. request.earliestBeginDate = .now.addingTimeInterval(7 * 24 * 60 * 60) // 7 days
  122. do {
  123. try BGTaskScheduler.shared.submit(request)
  124. debugPrint("Task scheduled successfully")
  125. } catch {
  126. debugPrint("Failed to schedule tasks")
  127. }
  128. }
  129. private func cleanupOldData() {
  130. Task {
  131. async let cleanupTokens: () = coreDataStack.cleanupPersistentHistoryTokens(before: Date.oneWeekAgo)
  132. async let purgeData: () = purgeOldNSManagedObjects()
  133. await cleanupTokens
  134. try await purgeData
  135. }
  136. }
  137. private func purgeOldNSManagedObjects() async throws {
  138. async let glucoseDeletion: () = coreDataStack.batchDeleteOlderThan(GlucoseStored.self, dateKey: "date", days: 90)
  139. async let pumpEventDeletion: () = coreDataStack.batchDeleteOlderThan(PumpEventStored.self, dateKey: "timestamp", days: 90)
  140. async let bolusDeletion: () = coreDataStack.batchDeleteOlderThan(
  141. parentType: PumpEventStored.self,
  142. childType: BolusStored.self,
  143. dateKey: "timestamp",
  144. days: 90,
  145. relationshipKey: "pumpEvent"
  146. )
  147. async let tempBasalDeletion: () = coreDataStack.batchDeleteOlderThan(
  148. parentType: PumpEventStored.self,
  149. childType: TempBasalStored.self,
  150. dateKey: "timestamp",
  151. days: 90,
  152. relationshipKey: "pumpEvent"
  153. )
  154. async let determinationDeletion: () = coreDataStack
  155. .batchDeleteOlderThan(OrefDetermination.self, dateKey: "deliverAt", days: 90)
  156. async let batteryDeletion: () = coreDataStack.batchDeleteOlderThan(OpenAPS_Battery.self, dateKey: "date", days: 90)
  157. async let carbEntryDeletion: () = coreDataStack.batchDeleteOlderThan(CarbEntryStored.self, dateKey: "date", days: 90)
  158. async let forecastDeletion: () = coreDataStack.batchDeleteOlderThan(Forecast.self, dateKey: "date", days: 2)
  159. async let forecastValueDeletion: () = coreDataStack.batchDeleteOlderThan(
  160. parentType: Forecast.self,
  161. childType: ForecastValue.self,
  162. dateKey: "date",
  163. days: 2,
  164. relationshipKey: "forecast"
  165. )
  166. async let overrideDeletion: () = coreDataStack
  167. .batchDeleteOlderThan(OverrideStored.self, dateKey: "date", days: 3, isPresetKey: "isPreset")
  168. async let overrideRunDeletion: () = coreDataStack
  169. .batchDeleteOlderThan(OverrideRunStored.self, dateKey: "startDate", days: 3)
  170. // Await each task to ensure they are all completed
  171. try await glucoseDeletion
  172. try await pumpEventDeletion
  173. try await bolusDeletion
  174. try await tempBasalDeletion
  175. try await determinationDeletion
  176. try await batteryDeletion
  177. try await carbEntryDeletion
  178. try await forecastDeletion
  179. try await forecastValueDeletion
  180. try await overrideDeletion
  181. try await overrideRunDeletion
  182. }
  183. private func handleURL(_ url: URL) {
  184. let components = URLComponents(url: url, resolvingAgainstBaseURL: false)
  185. switch components?.host {
  186. case "device-select-resp":
  187. resolver.resolve(NotificationCenter.self)!.post(name: .openFromGarminConnect, object: url)
  188. default: break
  189. }
  190. }
  191. }