LiveActivityBridge.swift 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327
  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. if let determination = update as? DeterminationData {
  133. content = LiveActivityAttributes.ContentState(
  134. new: latestGlucose,
  135. prev: latestGlucose,
  136. units: settings.units,
  137. chart: glucoseFromPersistence ?? [],
  138. settings: settings,
  139. determination: determination,
  140. override: override,
  141. widgetItems: widgetItems
  142. )
  143. } else if let override = update as? OverrideData {
  144. content = LiveActivityAttributes.ContentState(
  145. new: latestGlucose,
  146. prev: latestGlucose,
  147. units: settings.units,
  148. chart: glucoseFromPersistence ?? [],
  149. settings: settings,
  150. determination: determination,
  151. override: override,
  152. widgetItems: widgetItems
  153. )
  154. }
  155. if let content = content {
  156. await pushUpdate(content)
  157. }
  158. }
  159. @MainActor private func updateLiveActivityOrder() async {
  160. Task {
  161. await updateContentState(determination)
  162. }
  163. }
  164. private func setupGlucoseArray() {
  165. Task { @MainActor in
  166. self.glucoseFromPersistence = await fetchAndMapGlucose()
  167. glucoseDidUpdate(glucoseFromPersistence ?? [])
  168. }
  169. }
  170. private func monitorForLiveActivityAuthorizationChanges() {
  171. Task {
  172. for await activityState in activityAuthorizationInfo.activityEnablementUpdates {
  173. if activityState != systemEnabled {
  174. await MainActor.run {
  175. systemEnabled = activityState
  176. }
  177. }
  178. }
  179. }
  180. }
  181. @MainActor private func forceActivityUpdate() {
  182. if settings.useLiveActivity {
  183. if currentActivity?.needsRecreation() ?? true {
  184. glucoseDidUpdate(glucoseFromPersistence ?? [])
  185. }
  186. } else {
  187. Task {
  188. await self.endActivity()
  189. }
  190. }
  191. }
  192. @MainActor private func pushUpdate(_ state: LiveActivityAttributes.ContentState) async {
  193. for unknownActivity in Activity<LiveActivityAttributes>.activities
  194. .filter({ self.currentActivity?.activity.id != $0.id })
  195. {
  196. await unknownActivity.end(nil, dismissalPolicy: .immediate)
  197. }
  198. if let currentActivity = currentActivity {
  199. if currentActivity.needsRecreation(), UIApplication.shared.applicationState == .active {
  200. await endActivity()
  201. await pushUpdate(state)
  202. } else {
  203. let content = ActivityContent(
  204. state: state,
  205. staleDate: min(state.date ?? Date.now, Date.now).addingTimeInterval(360) // 6 minutes in seconds
  206. )
  207. await currentActivity.activity.update(content)
  208. }
  209. } else {
  210. do {
  211. let expired = ActivityContent(
  212. state: LiveActivityAttributes
  213. .ContentState(
  214. bg: "--",
  215. direction: nil,
  216. change: "--",
  217. date: Date.now,
  218. highGlucose: settings.high,
  219. lowGlucose: settings.low,
  220. target: determination?.target ?? 100 as Decimal,
  221. glucoseColorScheme: settings.glucoseColorScheme.rawValue,
  222. detailedViewState: nil,
  223. isInitialState: true
  224. ),
  225. staleDate: Date.now.addingTimeInterval(60)
  226. )
  227. let activity = try Activity.request(
  228. attributes: LiveActivityAttributes(startDate: Date.now),
  229. content: expired,
  230. pushType: nil
  231. )
  232. currentActivity = ActiveActivity(activity: activity, startDate: Date.now)
  233. await pushUpdate(state)
  234. } catch {
  235. debug(
  236. .default,
  237. "\(#file): Error creating new activity: \(error)"
  238. )
  239. }
  240. }
  241. }
  242. private func endActivity() async {
  243. if let currentActivity {
  244. await currentActivity.activity.end(nil, dismissalPolicy: .immediate)
  245. self.currentActivity = nil
  246. }
  247. for unknownActivity in Activity<LiveActivityAttributes>.activities {
  248. await unknownActivity.end(nil, dismissalPolicy: .immediate)
  249. }
  250. }
  251. }
  252. @available(iOS 16.2, *)
  253. extension LiveActivityBridge {
  254. @MainActor func glucoseDidUpdate(_ glucose: [GlucoseData]) {
  255. guard settings.useLiveActivity else {
  256. if currentActivity != nil {
  257. Task {
  258. await self.endActivity()
  259. }
  260. }
  261. return
  262. }
  263. if glucose.count > 1 {
  264. latestGlucose = glucose.dropFirst().first
  265. }
  266. defer {
  267. self.latestGlucose = glucose.first
  268. }
  269. guard let bg = glucose.first else {
  270. return
  271. }
  272. if let determination = determination {
  273. let content = LiveActivityAttributes.ContentState(
  274. new: bg,
  275. prev: latestGlucose,
  276. units: settings.units,
  277. chart: glucose,
  278. settings: settings,
  279. determination: determination,
  280. override: override,
  281. widgetItems: widgetItems
  282. )
  283. if let content = content {
  284. Task {
  285. await self.pushUpdate(content)
  286. }
  287. }
  288. }
  289. }
  290. }