You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

169 lines
5.9 KiB

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