import Foundation import Contacts import UIKit import MessageUI import Combine class ContactManager: ObservableObject { static let shared = ContactManager() private init() {} // MARK: - 添加联系人到通讯录 func addContactToAddressBook(vcardContent: String, completion: @escaping (Bool, String?) -> Void) { // 检查通讯录权限 let status = CNContactStore.authorizationStatus(for: .contacts) switch status { case .authorized: performAddContact(vcardContent: vcardContent, completion: completion) case .denied, .restricted: completion(false, "contact_permission_denied".localized) case .notDetermined: requestContactPermission { [weak self] granted in if granted { self?.performAddContact(vcardContent: vcardContent, completion: completion) } else { completion(false, "contact_permission_denied".localized) } } case .limited: completion(false, "contact_permission_limited".localized) @unknown default: completion(false, "contact_permission_unknown".localized) } } private func performAddContact(vcardContent: String, completion: @escaping (Bool, String?) -> Void) { DispatchQueue.global(qos: .userInitiated).async { do { let contactStore = CNContactStore() let data = vcardContent.data(using: .utf8)! // 解析vCard数据 let contacts = try CNContactVCardSerialization.contacts(with: data) guard let contact = contacts.first else { DispatchQueue.main.async { completion(false, "contact_parse_failed".localized) } return } // 创建可变的联系人副本 let mutableContact = contact.mutableCopy() as! CNMutableContact // 创建保存请求 let saveRequest = CNSaveRequest() saveRequest.add(mutableContact, toContainerWithIdentifier: nil) // 保存联系人 try contactStore.execute(saveRequest) DispatchQueue.main.async { completion(true, "contact_added_successfully".localized) } } catch { DispatchQueue.main.async { completion(false, "contact_add_failed".localized) } } } } private func requestContactPermission(completion: @escaping (Bool) -> Void) { let contactStore = CNContactStore() contactStore.requestAccess(for: .contacts) { granted, error in DispatchQueue.main.async { completion(granted) } } } // MARK: - 打电话 func makePhoneCall(phoneNumber: String, completion: @escaping (Bool, String?) -> Void) { // 清理电话号码(移除空格、括号等) let cleanNumber = phoneNumber.replacingOccurrences(of: "[^0-9+]", with: "", options: .regularExpression) guard let url = URL(string: "tel://\(cleanNumber)") else { completion(false, "invalid_phone_number".localized) return } if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url) { success in DispatchQueue.main.async { if success { completion(true, "phone_call_initiated".localized) } else { completion(false, "phone_call_failed".localized) } } } } else { completion(false, "phone_call_not_supported".localized) } } // MARK: - 发短信 func sendSMS(phoneNumber: String, message: String = "", completion: @escaping (Bool, String?) -> Void) { // 清理电话号码 let cleanNumber = phoneNumber.replacingOccurrences(of: "[^0-9+]", with: "", options: .regularExpression) guard let url = URL(string: "sms://\(cleanNumber)?body=\(message.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed) ?? "")") else { completion(false, "invalid_phone_number".localized) return } if UIApplication.shared.canOpenURL(url) { UIApplication.shared.open(url) { success in DispatchQueue.main.async { if success { completion(true, "sms_app_opened".localized) } else { completion(false, "sms_app_failed".localized) } } } } else { completion(false, "sms_app_not_supported".localized) } } // MARK: - 解析联系人信息(已迁移到QRCodeParser) // 此方法已废弃,请使用 QRCodeParser.parseContactInfo(from:) @available(*, deprecated, message: "请使用 QRCodeParser.parseContactInfo(from:)") func parseContactInfo(from content: String) -> ContactInfo? { return QRCodeParser.parseContactInfo(from: content) } } // MARK: - 联系人信息结构体 struct ContactInfo: Codable { var name: String = "" var phoneNumber: String = "" var email: String = "" var organization: String = "" var title: String = "" var address: String = "" var displayName: String { if !name.isEmpty { return name } else if !organization.isEmpty { return organization } else { return "contact".localized } } var hasPhoneNumber: Bool { return !phoneNumber.isEmpty } var hasEmail: Bool { return !email.isEmpty } }