|
|
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
|
|
|
}
|
|
|
}
|