ContactTrickStateModel.swift 7.4 KB

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