LiveActivityBridge.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330
  1. import ActivityKit
  2. import Combine
  3. import CoreData
  4. import Foundation
  5. import Swinject
  6. import UIKit
  7. @available(iOS 16.2, *) private struct ActiveActivity {
  8. let activity: Activity<LiveActivityAttributes>
  9. let startDate: Date
  10. func needsRecreation() -> Bool {
  11. switch activity.activityState {
  12. case .dismissed,
  13. .ended,
  14. .stale:
  15. return true
  16. case .active:
  17. break
  18. @unknown default:
  19. return true
  20. }
  21. return -startDate.timeIntervalSinceNow > TimeInterval(60 * 60)
  22. }
  23. }
  24. @available(iOS 16.2, *)
  25. final class LiveActivityBridge: Injectable, ObservableObject, SettingsObserver {
  26. @Injected() private var settingsManager: SettingsManager!
  27. @Injected() private var broadcaster: Broadcaster!
  28. @Injected() private var storage: FileStorage!
  29. @Injected() private var glucoseStorage: GlucoseStorage!
  30. private let activityAuthorizationInfo = ActivityAuthorizationInfo()
  31. @Published private(set) var systemEnabled: Bool
  32. private var settings: FreeAPSSettings {
  33. settingsManager.settings
  34. }
  35. var determination: DeterminationData?
  36. private var currentActivity: ActiveActivity?
  37. private var latestGlucose: GlucoseData?
  38. var glucoseFromPersistence: [GlucoseData]?
  39. var override: OverrideData?
  40. var widgetItems: [LiveActivityAttributes.LiveActivityItem]?
  41. let context = CoreDataStack.shared.newTaskContext()
  42. private var coreDataPublisher: AnyPublisher<Set<NSManagedObject>, Never>?
  43. private var subscriptions = Set<AnyCancellable>()
  44. init(resolver: Resolver) {
  45. coreDataPublisher =
  46. changedObjectsOnManagedObjectContextDidSavePublisher()
  47. .receive(on: DispatchQueue.global(qos: .background))
  48. .share()
  49. .eraseToAnyPublisher()
  50. systemEnabled = activityAuthorizationInfo.areActivitiesEnabled
  51. injectServices(resolver)
  52. setupNotifications()
  53. registerSubscribers()
  54. registerHandler()
  55. monitorForLiveActivityAuthorizationChanges()
  56. setupGlucoseArray()
  57. broadcaster.register(SettingsObserver.self, observer: self)
  58. }
  59. private func setupNotifications() {
  60. let notificationCenter = Foundation.NotificationCenter.default
  61. notificationCenter
  62. .addObserver(forName: UIApplication.didEnterBackgroundNotification, object: nil, queue: nil) { [weak self] _ in
  63. Task { @MainActor in
  64. self?.forceActivityUpdate()
  65. }
  66. }
  67. notificationCenter
  68. .addObserver(forName: UIApplication.didBecomeActiveNotification, object: nil, queue: nil) { [weak self] _ in
  69. Task { @MainActor in
  70. self?.forceActivityUpdate()
  71. }
  72. }
  73. notificationCenter.addObserver(
  74. self,
  75. selector: #selector(handleLiveActivityOrderChange),
  76. name: .liveActivityOrderDidChange,
  77. object: nil
  78. )
  79. }
  80. func settingsDidChange(_: FreeAPSSettings) {
  81. Task {
  82. await updateContentState(determination)
  83. }
  84. }
  85. private func registerHandler() {
  86. coreDataPublisher?.filterByEntityName("OverrideStored").sink { [weak self] _ in
  87. guard let self = self else { return }
  88. self.overridesDidUpdate()
  89. }.store(in: &subscriptions)
  90. coreDataPublisher?.filterByEntityName("OrefDetermination").sink { [weak self] _ in
  91. guard let self = self else { return }
  92. self.cobOrIobDidUpdate()
  93. }.store(in: &subscriptions)
  94. }
  95. private func registerSubscribers() {
  96. glucoseStorage.updatePublisher
  97. .receive(on: DispatchQueue.global(qos: .background))
  98. .sink { [weak self] _ in
  99. guard let self = self else { return }
  100. self.setupGlucoseArray()
  101. }
  102. .store(in: &subscriptions)
  103. }
  104. private func cobOrIobDidUpdate() {
  105. Task { @MainActor in
  106. self.determination = await fetchAndMapDetermination()
  107. if let determination = determination {
  108. await self.updateContentState(determination)
  109. }
  110. }
  111. }
  112. private func overridesDidUpdate() {
  113. Task { @MainActor in
  114. self.override = await fetchAndMapOverride()
  115. if let determination = determination {
  116. await self.updateContentState(determination)
  117. }
  118. }
  119. }
  120. @objc private func handleLiveActivityOrderChange() {
  121. Task {
  122. self.widgetItems = UserDefaults.standard.loadLiveActivityOrderFromUserDefaults() ?? LiveActivityAttributes
  123. .LiveActivityItem.defaultItems
  124. await self.updateLiveActivityOrder()
  125. }
  126. }
  127. @MainActor private func updateContentState<T>(_ update: T) async {
  128. guard let latestGlucose = latestGlucose else {
  129. return
  130. }
  131. var content: LiveActivityAttributes.ContentState?
  132. widgetItems = UserDefaults.standard.loadLiveActivityOrderFromUserDefaults() ?? LiveActivityAttributes
  133. .LiveActivityItem.defaultItems
  134. if let determination = update as? DeterminationData {
  135. content = LiveActivityAttributes.ContentState(
  136. new: latestGlucose,
  137. prev: latestGlucose,
  138. units: settings.units,
  139. chart: glucoseFromPersistence ?? [],
  140. settings: settings,
  141. determination: determination,
  142. override: override,
  143. widgetItems: widgetItems
  144. )
  145. } else if let override = update as? OverrideData {
  146. content = LiveActivityAttributes.ContentState(
  147. new: latestGlucose,
  148. prev: latestGlucose,
  149. units: settings.units,
  150. chart: glucoseFromPersistence ?? [],
  151. settings: settings,
  152. determination: determination,
  153. override: override,
  154. widgetItems: widgetItems
  155. )
  156. }
  157. if let content = content {
  158. await pushUpdate(content)
  159. }
  160. }
  161. @MainActor private func updateLiveActivityOrder() async {
  162. Task {
  163. await updateContentState(determination)
  164. }
  165. }
  166. private func setupGlucoseArray() {
  167. Task { @MainActor in
  168. self.glucoseFromPersistence = await fetchAndMapGlucose()
  169. glucoseDidUpdate(glucoseFromPersistence ?? [])
  170. }
  171. }
  172. private func monitorForLiveActivityAuthorizationChanges() {
  173. Task {
  174. for await activityState in activityAuthorizationInfo.activityEnablementUpdates {
  175. if activityState != systemEnabled {
  176. await MainActor.run {
  177. systemEnabled = activityState
  178. }
  179. }
  180. }
  181. }
  182. }
  183. @MainActor private func forceActivityUpdate() {
  184. if settings.useLiveActivity {
  185. if currentActivity?.needsRecreation() ?? true {
  186. glucoseDidUpdate(glucoseFromPersistence ?? [])
  187. }
  188. } else {
  189. Task {
  190. await self.endActivity()
  191. }
  192. }
  193. }
  194. @MainActor private func pushUpdate(_ state: LiveActivityAttributes.ContentState) async {
  195. for unknownActivity in Activity<LiveActivityAttributes>.activities
  196. .filter({ self.currentActivity?.activity.id != $0.id })
  197. {
  198. await unknownActivity.end(nil, dismissalPolicy: .immediate)
  199. }
  200. if let currentActivity = currentActivity {
  201. if currentActivity.needsRecreation(), UIApplication.shared.applicationState == .active {
  202. await endActivity()
  203. await pushUpdate(state)
  204. } else {
  205. let content = ActivityContent(
  206. state: state,
  207. staleDate: min(state.date ?? Date.now, Date.now).addingTimeInterval(360) // 6 minutes in seconds
  208. )
  209. await currentActivity.activity.update(content)
  210. }
  211. } else {
  212. do {
  213. let expired = ActivityContent(
  214. state: LiveActivityAttributes
  215. .ContentState(
  216. bg: "--",
  217. direction: nil,
  218. change: "--",
  219. date: Date.now,
  220. highGlucose: settings.high,
  221. lowGlucose: settings.low,
  222. target: determination?.target ?? 100 as Decimal,
  223. glucoseColorScheme: settings.glucoseColorScheme.rawValue,
  224. detailedViewState: nil,
  225. isInitialState: true
  226. ),
  227. staleDate: Date.now.addingTimeInterval(60)
  228. )
  229. let activity = try Activity.request(
  230. attributes: LiveActivityAttributes(startDate: Date.now),
  231. content: expired,
  232. pushType: nil
  233. )
  234. currentActivity = ActiveActivity(activity: activity, startDate: Date.now)
  235. await pushUpdate(state)
  236. } catch {
  237. debug(
  238. .default,
  239. "\(#file): Error creating new activity: \(error)"
  240. )
  241. }
  242. }
  243. }
  244. private func endActivity() async {
  245. if let currentActivity {
  246. await currentActivity.activity.end(nil, dismissalPolicy: .immediate)
  247. self.currentActivity = nil
  248. }
  249. for unknownActivity in Activity<LiveActivityAttributes>.activities {
  250. await unknownActivity.end(nil, dismissalPolicy: .immediate)
  251. }
  252. }
  253. }
  254. @available(iOS 16.2, *)
  255. extension LiveActivityBridge {
  256. @MainActor func glucoseDidUpdate(_ glucose: [GlucoseData]) {
  257. guard settings.useLiveActivity else {
  258. if currentActivity != nil {
  259. Task {
  260. await self.endActivity()
  261. }
  262. }
  263. return
  264. }
  265. if glucose.count > 1 {
  266. latestGlucose = glucose.dropFirst().first
  267. }
  268. defer {
  269. self.latestGlucose = glucose.first
  270. }
  271. guard let bg = glucose.first else {
  272. return
  273. }
  274. if let determination = determination {
  275. let content = LiveActivityAttributes.ContentState(
  276. new: bg,
  277. prev: latestGlucose,
  278. units: settings.units,
  279. chart: glucose,
  280. settings: settings,
  281. determination: determination,
  282. override: override,
  283. widgetItems: widgetItems
  284. )
  285. if let content = content {
  286. Task {
  287. await self.pushUpdate(content)
  288. }
  289. }
  290. }
  291. }
  292. }