73 lines
2.3 KiB
Swift
73 lines
2.3 KiB
Swift
import Foundation
|
|
|
|
/// State management for the App Clip card display and save flow.
|
|
@MainActor
|
|
@Observable
|
|
final class ClipCardStore {
|
|
|
|
enum State {
|
|
case loading
|
|
case loaded(SharedCardSnapshot)
|
|
case saved
|
|
case error(String)
|
|
}
|
|
|
|
private let cloudKit: ClipCloudKitService
|
|
private let contactSave: ContactSaveService
|
|
|
|
var state: State = .loading
|
|
|
|
/// The currently loaded card snapshot, if any.
|
|
var snapshot: SharedCardSnapshot? {
|
|
if case .loaded(let snap) = state { return snap }
|
|
return nil
|
|
}
|
|
|
|
init(
|
|
cloudKit: ClipCloudKitService = ClipCloudKitService(),
|
|
contactSave: ContactSaveService = ContactSaveService()
|
|
) {
|
|
self.cloudKit = cloudKit
|
|
self.contactSave = contactSave
|
|
}
|
|
|
|
/// Loads a shared card from CloudKit.
|
|
/// - Parameter recordName: The record name (UUID) to fetch.
|
|
func load(recordName: String) async {
|
|
state = .loading
|
|
do {
|
|
let snapshot = try await cloudKit.fetchSharedCard(recordName: recordName)
|
|
state = .loaded(snapshot)
|
|
} catch {
|
|
state = .error(userMessage(for: error, fallback: ClipError.fetchFailed))
|
|
}
|
|
}
|
|
|
|
/// Saves the currently loaded card to Contacts via CNContactStore.
|
|
/// Note: App Clips cannot use CNContactStore; use the share sheet flow instead.
|
|
func saveToContacts() async {
|
|
guard let snapshot else { return }
|
|
do {
|
|
try await contactSave.saveContact(vCardData: snapshot.vCardData)
|
|
try? await cloudKit.deleteSharedCard(recordName: snapshot.recordName)
|
|
state = .saved
|
|
} catch {
|
|
state = .error(userMessage(for: error, fallback: ClipError.contactSaveFailed))
|
|
}
|
|
}
|
|
|
|
/// Best-effort cleanup after user shares the vCard (e.g. via share sheet).
|
|
/// App Clips use the share sheet instead of CNContactStore.
|
|
func cleanupAfterShare() async {
|
|
guard let snapshot else { return }
|
|
try? await cloudKit.deleteSharedCard(recordName: snapshot.recordName)
|
|
}
|
|
|
|
private func userMessage(for error: Error, fallback: ClipError) -> String {
|
|
if let clipError = error as? ClipError {
|
|
return clipError.localizedDescription
|
|
}
|
|
return fallback.localizedDescription
|
|
}
|
|
}
|