LiveActivityBridge.swift 11 KB

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