ContactImageStateModel.swift 7.3 KB

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