import ConnectIQ import CoreData import SwiftUI extension ContactTrick { @Observable final class StateModel: BaseStateModel, ContactTrickManagerDelegate { @ObservationIgnored @Injected() var contactTrickStorage: ContactTrickStorage! @ObservationIgnored @Injected() var contactTrickManager: ContactTrickManager! var contactTrickEntries = [ContactTrickEntry]() var units: GlucoseUnits = .mmolL // Help Sheet var isHelpSheetPresented: Bool = false var helpSheetDetent = PresentationDetent.large // Current state for live preview var state = ContactTrickState() /// Subscribes to updates and initializes data fetching. override func subscribe() { units = settingsManager.settings.units contactTrickManager.delegate = self Task { /// Initial fetch to fill the ContactTrickEntry array await fetchContactTrickEntriesAndUpdateUI() // Initial state update is needed for preview await contactTrickManager.updateContactTrickState() } } func contactTrickManagerDidUpdateState(_ state: ContactTrickState) { Task { @MainActor in self.state = state } } /// Fetches all ContactTrickEntries and validates them against iOS Contacts. func fetchContactTrickEntriesAndUpdateUI() async { // 1. Get all entries from Core Data let cdEntries = await contactTrickStorage.fetchContactTrickEntries() // 2. Validate entries against iOS Contacts let validatedEntries = await validateEntries(cdEntries) // 3. Update UI with validated entries await MainActor.run { self.contactTrickEntries = validatedEntries } } /// Validates entries against iOS Contacts and removes invalid ones private func validateEntries(_ entries: [ContactTrickEntry]) async -> [ContactTrickEntry] { var validated: [ContactTrickEntry] = [] for entry in entries { if let contactId = entry.contactId { // Check if contact still exists in iOS Contacts let exists = await contactTrickManager.validateContactExists(withIdentifier: contactId) if exists { validated.append(entry) } else { // Contact was deleted in iOS, remove from Core Data if let objectID = entry.managedObjectID { await contactTrickStorage.deleteContactTrickEntry(objectID) debugPrint("Removed orphaned contact entry: \(entry.name)") } } } } return validated } /// Creates a new contact in Apple Contacts and saves it to Core Data. /// - Parameters: /// - entry: The ContactTrickEntry to be saved. /// - name: The name of the contact. func createAndSaveContactTrick(entry: ContactTrickEntry, name: String) async { // 1. Check for contact access permissions. let hasAccess = await contactTrickManager.requestAccess() guard hasAccess else { debugPrint("\(DebuggingIdentifiers.failed) No access to contacts.") return } // 2. Create the contact and retrieve its `identifier`. guard let contactId = await contactTrickManager.createContact(name: name) else { debugPrint("\(DebuggingIdentifiers.failed) Failed to create contact.") return } // 3. Update the entry with the `contactId`. var updatedEntry = entry updatedEntry.contactId = contactId updatedEntry.name = name // 4. Save the contact to Core Data. await addContactTrickEntry(updatedEntry) // 5. Update ContactTrickState and set the image for the newly created contact await contactTrickManager.updateContactTrickState() await contactTrickManager.setImageForContact(contactId: contactId) } /// Adds a ContactTrickEntry to Core Data. /// - Parameter entry: The ContactTrickEntry to be saved. func addContactTrickEntry(_ entry: ContactTrickEntry) async { await contactTrickStorage.storeContactTrickEntry(entry) await fetchContactTrickEntriesAndUpdateUI() } /// Deletes a contact from Apple Contacts and Core Data. /// - Parameter entry: The ContactTrickEntry representing the contact to be deleted. func deleteContact(entry: ContactTrickEntry) async { guard let contactId = entry.contactId else { debugPrint("\(DebuggingIdentifiers.failed) Contact does not have a valid ID.") return } // 1. Attempt to delete the contact from Apple Contacts. let contactDeleted = await contactTrickManager.deleteContact(withIdentifier: contactId) if contactDeleted { debugPrint("\(DebuggingIdentifiers.succeeded) Contact successfully deleted from Apple Contacts: \(contactId)") } else { debugPrint("\(DebuggingIdentifiers.failed) Failed to delete contact from Apple Contacts. Check if it exists.") } // 2. Delete the entry from Core Data. if let objectID = entry.managedObjectID { await deleteContactTrick(objectID: objectID) } } /// Deletes a Core Data entry. /// - Parameter objectID: The Managed Object ID of the entry to be deleted. func deleteContactTrick(objectID: NSManagedObjectID) async { await contactTrickStorage.deleteContactTrickEntry(objectID) await fetchContactTrickEntriesAndUpdateUI() } /// Updates a contact in Apple Contacts and Core Data. /// - Parameters: /// - entry: The ContactTrickEntry to be updated. func updateContact(with entry: ContactTrickEntry) async { guard let contactId = entry.contactId else { debugPrint("\(DebuggingIdentifiers.failed) Contact does not have a valid ID.") return } // 1. Update the entry in Core Data. await updateContactTrick(entry) // 2. Update the contact in Apple Contacts. /// Update name let contactUpdated = await contactTrickManager .updateContact(withIdentifier: contactId, newName: entry.name) // TODO: - Probably not needed anymore guard contactUpdated else { debugPrint("\(DebuggingIdentifiers.failed) Failed to update contact.") return } /// Update state and image await contactTrickManager.updateContactTrickState() await contactTrickManager.setImageForContact(contactId: contactId) } /// Updates a Core Data entry. /// - Parameter entry: The updated ContactTrickEntry. func updateContactTrick(_ entry: ContactTrickEntry) async { await contactTrickStorage.updateContactTrickEntry(entry) await fetchContactTrickEntriesAndUpdateUI() } } }