ContactTrickStateModel.swift 7.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177
  1. import ConnectIQ
  2. import CoreData
  3. import SwiftUI
  4. extension ContactTrick {
  5. @Observable final class StateModel: BaseStateModel<Provider> {
  6. @ObservationIgnored @Injected() var contactTrickStorage: ContactTrickStorage!
  7. @ObservationIgnored @Injected() var contactTrickManager: ContactTrickManager!
  8. var contactTrickEntries = [ContactTrickEntry]()
  9. var units: GlucoseUnits = .mmolL
  10. var previewState: ContactTrickState {
  11. ContactTrickState(
  12. glucose: self.units == .mmolL ? "6,8" : "127",
  13. trend: "↗︎",
  14. delta: units == .mmolL ? "+0,3" : "+7",
  15. lastLoopDate: .now,
  16. iob: 6.1,
  17. iobText: "6,1",
  18. cob: 27.0,
  19. cobText: "27",
  20. eventualBG: units == .mmolL ? "8,9" : "163",
  21. maxIOB: 12.0,
  22. maxCOB: 120.0
  23. )
  24. }
  25. /// Subscribes to updates and initializes data fetching.
  26. override func subscribe() {
  27. units = settingsManager.settings.units
  28. Task {
  29. /// Initial fetch to fill the ContactTrickEntry array
  30. await fetchContactTrickEntriesAndUpdateUI()
  31. }
  32. }
  33. /// Fetches all ContactTrickEntries and validates them against iOS Contacts.
  34. func fetchContactTrickEntriesAndUpdateUI() async {
  35. // 1. Get all entries from Core Data
  36. let cdEntries = await contactTrickStorage.fetchContactTrickEntries()
  37. // 2. Validate entries against iOS Contacts
  38. let validatedEntries = await validateEntries(cdEntries)
  39. // 3. Update UI with validated entries
  40. await MainActor.run {
  41. self.contactTrickEntries = validatedEntries
  42. }
  43. }
  44. /// Validates entries against iOS Contacts and removes invalid ones
  45. private func validateEntries(_ entries: [ContactTrickEntry]) async -> [ContactTrickEntry] {
  46. var validated: [ContactTrickEntry] = []
  47. for entry in entries {
  48. if let contactId = entry.contactId {
  49. // Check if contact still exists in iOS Contacts
  50. let exists = await contactTrickManager.validateContactExists(withIdentifier: contactId)
  51. if exists {
  52. validated.append(entry)
  53. } else {
  54. // Contact was deleted in iOS, remove from Core Data
  55. if let objectID = entry.managedObjectID {
  56. await contactTrickStorage.deleteContactTrickEntry(objectID)
  57. debugPrint("Removed orphaned contact entry: \(entry.name)")
  58. }
  59. }
  60. }
  61. }
  62. return validated
  63. }
  64. /// Creates a new contact in Apple Contacts and saves it to Core Data.
  65. /// - Parameters:
  66. /// - entry: The ContactTrickEntry to be saved.
  67. /// - name: The name of the contact.
  68. func createAndSaveContactTrick(entry: ContactTrickEntry, name: String) async {
  69. // 1. Check for contact access permissions.
  70. let hasAccess = await contactTrickManager.requestAccess()
  71. guard hasAccess else {
  72. debugPrint("\(DebuggingIdentifiers.failed) No access to contacts.")
  73. return
  74. }
  75. // 2. Create the contact and retrieve its `identifier`.
  76. guard let contactId = await contactTrickManager.createContact(name: name) else {
  77. debugPrint("\(DebuggingIdentifiers.failed) Failed to create contact.")
  78. return
  79. }
  80. // 3. Update the entry with the `contactId`.
  81. var updatedEntry = entry
  82. updatedEntry.contactId = contactId
  83. // 4. Save the contact to Core Data.
  84. await addContactTrickEntry(updatedEntry)
  85. // 5. Update ContactTrickState and set the image for the newly created contact
  86. await contactTrickManager.updateContactTrickState()
  87. await contactTrickManager.setImageForContact(contactId: contactId)
  88. }
  89. /// Adds a ContactTrickEntry to Core Data.
  90. /// - Parameter entry: The ContactTrickEntry to be saved.
  91. func addContactTrickEntry(_ entry: ContactTrickEntry) async {
  92. await contactTrickStorage.storeContactTrickEntry(entry)
  93. await fetchContactTrickEntriesAndUpdateUI()
  94. }
  95. /// Deletes a contact from Apple Contacts and Core Data.
  96. /// - Parameter entry: The ContactTrickEntry representing the contact to be deleted.
  97. func deleteContact(entry: ContactTrickEntry) async {
  98. guard let contactId = entry.contactId else {
  99. debugPrint("\(DebuggingIdentifiers.failed) Contact does not have a valid ID.")
  100. return
  101. }
  102. // 1. Attempt to delete the contact from Apple Contacts.
  103. let contactDeleted = await contactTrickManager.deleteContact(withIdentifier: contactId)
  104. if contactDeleted {
  105. debugPrint("\(DebuggingIdentifiers.succeeded) Contact successfully deleted from Apple Contacts: \(contactId)")
  106. } else {
  107. debugPrint("\(DebuggingIdentifiers.failed) Failed to delete contact from Apple Contacts. Check if it exists.")
  108. }
  109. // 2. Delete the entry from Core Data.
  110. if let objectID = entry.managedObjectID {
  111. await deleteContactTrick(objectID: objectID)
  112. }
  113. }
  114. /// Deletes a Core Data entry.
  115. /// - Parameter objectID: The Managed Object ID of the entry to be deleted.
  116. func deleteContactTrick(objectID: NSManagedObjectID) async {
  117. await contactTrickStorage.deleteContactTrickEntry(objectID)
  118. await fetchContactTrickEntriesAndUpdateUI()
  119. }
  120. /// Updates a contact in Apple Contacts and Core Data.
  121. /// - Parameters:
  122. /// - entry: The ContactTrickEntry to be updated.
  123. func updateContact(with entry: ContactTrickEntry) async {
  124. guard let contactId = entry.contactId else {
  125. debugPrint("\(DebuggingIdentifiers.failed) Contact does not have a valid ID.")
  126. return
  127. }
  128. // 1. Update the entry in Core Data.
  129. await updateContactTrick(entry)
  130. // 2. Update the contact in Apple Contacts.
  131. /// Update name
  132. let contactUpdated = await contactTrickManager
  133. .updateContact(withIdentifier: contactId, newName: entry.name) // TODO: - Probably not needed anymore
  134. guard contactUpdated else {
  135. debugPrint("\(DebuggingIdentifiers.failed) Failed to update contact.")
  136. return
  137. }
  138. /// Update state and image
  139. await contactTrickManager.updateContactTrickState()
  140. await contactTrickManager.setImageForContact(contactId: contactId)
  141. }
  142. /// Updates a Core Data entry.
  143. /// - Parameter entry: The updated ContactTrickEntry.
  144. func updateContactTrick(_ entry: ContactTrickEntry) async {
  145. await contactTrickStorage.updateContactTrickEntry(entry)
  146. await fetchContactTrickEntriesAndUpdateUI()
  147. }
  148. }
  149. }