mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-12-23 06:35:51 +00:00
Temp
This commit is contained in:
909
submodules/TelegramUI/Sources/DeviceContactDataManager.swift
Normal file
909
submodules/TelegramUI/Sources/DeviceContactDataManager.swift
Normal file
@@ -0,0 +1,909 @@
|
||||
import Foundation
|
||||
import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import SyncCore
|
||||
import Contacts
|
||||
import AddressBook
|
||||
import TelegramUIPreferences
|
||||
import DeviceAccess
|
||||
import AccountContext
|
||||
import PhoneNumberFormat
|
||||
|
||||
private protocol DeviceContactDataContext {
|
||||
func personNameDisplayOrder() -> PresentationPersonNameOrder
|
||||
func getExtendedContactData(stableId: DeviceContactStableId) -> DeviceContactExtendedData?
|
||||
func appendContactData(_ contactData: DeviceContactExtendedData, to stableId: DeviceContactStableId) -> DeviceContactExtendedData?
|
||||
func appendPhoneNumber(_ phoneNumber: DeviceContactPhoneNumberData, to stableId: DeviceContactStableId) -> DeviceContactExtendedData?
|
||||
func createContactWithData(_ contactData: DeviceContactExtendedData) -> (DeviceContactStableId, DeviceContactExtendedData)?
|
||||
func deleteContactWithAppSpecificReference(peerId: PeerId)
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
|
||||
private final class DeviceContactDataModernContext: DeviceContactDataContext {
|
||||
let store = CNContactStore()
|
||||
var updateHandle: NSObjectProtocol?
|
||||
var currentContacts: [DeviceContactStableId: DeviceContactBasicData] = [:]
|
||||
var currentAppSpecificReferences: [PeerId: DeviceContactBasicDataWithReference] = [:]
|
||||
|
||||
init(queue: Queue, updated: @escaping ([DeviceContactStableId: DeviceContactBasicData]) -> Void, appSpecificReferencesUpdated: @escaping ([PeerId: DeviceContactBasicDataWithReference]) -> Void) {
|
||||
let (contacts, references) = self.retrieveContacts()
|
||||
self.currentContacts = contacts
|
||||
self.currentAppSpecificReferences = references
|
||||
updated(self.currentContacts)
|
||||
appSpecificReferencesUpdated(self.currentAppSpecificReferences)
|
||||
let handle = NotificationCenter.default.addObserver(forName: NSNotification.Name.CNContactStoreDidChange, object: nil, queue: nil, using: { [weak self] _ in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let (contacts, references) = strongSelf.retrieveContacts()
|
||||
if strongSelf.currentContacts != contacts {
|
||||
strongSelf.currentContacts = contacts
|
||||
updated(strongSelf.currentContacts)
|
||||
}
|
||||
if strongSelf.currentAppSpecificReferences != references {
|
||||
strongSelf.currentAppSpecificReferences = references
|
||||
appSpecificReferencesUpdated(strongSelf.currentAppSpecificReferences)
|
||||
}
|
||||
}
|
||||
})
|
||||
self.updateHandle = handle
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let updateHandle = updateHandle {
|
||||
NotificationCenter.default.removeObserver(updateHandle)
|
||||
}
|
||||
}
|
||||
|
||||
private func retrieveContacts() -> ([DeviceContactStableId: DeviceContactBasicData], [PeerId: DeviceContactBasicDataWithReference]) {
|
||||
let keysToFetch: [CNKeyDescriptor] = [CNContactFormatter.descriptorForRequiredKeys(for: .fullName), CNContactPhoneNumbersKey as CNKeyDescriptor, CNContactUrlAddressesKey as CNKeyDescriptor]
|
||||
|
||||
let request = CNContactFetchRequest(keysToFetch: keysToFetch)
|
||||
request.unifyResults = true
|
||||
|
||||
var result: [DeviceContactStableId: DeviceContactBasicData] = [:]
|
||||
var references: [PeerId: DeviceContactBasicDataWithReference] = [:]
|
||||
let _ = try? self.store.enumerateContacts(with: request, usingBlock: { contact, _ in
|
||||
let stableIdAndContact = DeviceContactDataModernContext.parseContact(contact)
|
||||
result[stableIdAndContact.0] = stableIdAndContact.1
|
||||
for address in contact.urlAddresses {
|
||||
if address.label == "Telegram", let peerId = parseAppSpecificContactReference(address.value as String) {
|
||||
references[peerId] = DeviceContactBasicDataWithReference(stableId: stableIdAndContact.0, basicData: stableIdAndContact.1)
|
||||
}
|
||||
}
|
||||
})
|
||||
return (result, references)
|
||||
}
|
||||
|
||||
private static func parseContact(_ contact: CNContact) -> (DeviceContactStableId, DeviceContactBasicData) {
|
||||
var phoneNumbers: [DeviceContactPhoneNumberData] = []
|
||||
for number in contact.phoneNumbers {
|
||||
phoneNumbers.append(DeviceContactPhoneNumberData(label: number.label ?? "", value: number.value.stringValue))
|
||||
}
|
||||
return (contact.identifier, DeviceContactBasicData(firstName: contact.givenName, lastName: contact.familyName, phoneNumbers: phoneNumbers))
|
||||
}
|
||||
|
||||
func personNameDisplayOrder() -> PresentationPersonNameOrder {
|
||||
switch CNContactFormatter.nameOrder(for: CNContact()) {
|
||||
case .givenNameFirst:
|
||||
return .firstLast
|
||||
default:
|
||||
return .lastFirst
|
||||
}
|
||||
}
|
||||
|
||||
func getExtendedContactData(stableId: DeviceContactStableId) -> DeviceContactExtendedData? {
|
||||
let keysToFetch: [CNKeyDescriptor] = [
|
||||
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
|
||||
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
||||
CNContactEmailAddressesKey as CNKeyDescriptor,
|
||||
CNContactBirthdayKey as CNKeyDescriptor,
|
||||
CNContactSocialProfilesKey as CNKeyDescriptor,
|
||||
CNContactInstantMessageAddressesKey as CNKeyDescriptor,
|
||||
CNContactPostalAddressesKey as CNKeyDescriptor,
|
||||
CNContactUrlAddressesKey as CNKeyDescriptor,
|
||||
CNContactOrganizationNameKey as CNKeyDescriptor,
|
||||
CNContactJobTitleKey as CNKeyDescriptor,
|
||||
CNContactDepartmentNameKey as CNKeyDescriptor
|
||||
]
|
||||
|
||||
guard let contact = try? self.store.unifiedContact(withIdentifier: stableId, keysToFetch: keysToFetch) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
return DeviceContactExtendedData(contact: contact)
|
||||
}
|
||||
|
||||
func appendContactData(_ contactData: DeviceContactExtendedData, to stableId: DeviceContactStableId) -> DeviceContactExtendedData? {
|
||||
let keysToFetch: [CNKeyDescriptor] = [
|
||||
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
|
||||
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
||||
CNContactEmailAddressesKey as CNKeyDescriptor,
|
||||
CNContactBirthdayKey as CNKeyDescriptor,
|
||||
CNContactSocialProfilesKey as CNKeyDescriptor,
|
||||
CNContactInstantMessageAddressesKey as CNKeyDescriptor,
|
||||
CNContactPostalAddressesKey as CNKeyDescriptor,
|
||||
CNContactUrlAddressesKey as CNKeyDescriptor,
|
||||
CNContactOrganizationNameKey as CNKeyDescriptor,
|
||||
CNContactJobTitleKey as CNKeyDescriptor,
|
||||
CNContactDepartmentNameKey as CNKeyDescriptor
|
||||
]
|
||||
|
||||
guard let current = try? self.store.unifiedContact(withIdentifier: stableId, keysToFetch: keysToFetch) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let contact = contactData.asMutableCNContact()
|
||||
|
||||
let mutableContact = current.mutableCopy() as! CNMutableContact
|
||||
mutableContact.givenName = contact.givenName
|
||||
mutableContact.familyName = contact.familyName
|
||||
|
||||
var phoneNumbers = mutableContact.phoneNumbers
|
||||
for phoneNumber in contact.phoneNumbers.reversed() {
|
||||
var found = false
|
||||
inner: for n in phoneNumbers {
|
||||
if n.value.stringValue == phoneNumber.value.stringValue {
|
||||
found = true
|
||||
break inner
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
phoneNumbers.insert(phoneNumber, at: 0)
|
||||
}
|
||||
}
|
||||
mutableContact.phoneNumbers = phoneNumbers
|
||||
|
||||
var urlAddresses = mutableContact.urlAddresses
|
||||
for urlAddress in contact.urlAddresses.reversed() {
|
||||
var found = false
|
||||
inner: for n in urlAddresses {
|
||||
if n.value.isEqual(urlAddress.value) {
|
||||
found = true
|
||||
break inner
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
urlAddresses.insert(urlAddress, at: 0)
|
||||
}
|
||||
}
|
||||
mutableContact.urlAddresses = urlAddresses
|
||||
|
||||
var emailAddresses = mutableContact.emailAddresses
|
||||
for emailAddress in contact.emailAddresses.reversed() {
|
||||
var found = false
|
||||
inner: for n in emailAddresses {
|
||||
if n.value.isEqual(emailAddress.value) {
|
||||
found = true
|
||||
break inner
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
emailAddresses.insert(emailAddress, at: 0)
|
||||
}
|
||||
}
|
||||
mutableContact.emailAddresses = emailAddresses
|
||||
|
||||
var postalAddresses = mutableContact.postalAddresses
|
||||
for postalAddress in contact.postalAddresses.reversed() {
|
||||
var found = false
|
||||
inner: for n in postalAddresses {
|
||||
if n.value.isEqual(postalAddress.value) {
|
||||
found = true
|
||||
break inner
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
postalAddresses.insert(postalAddress, at: 0)
|
||||
}
|
||||
}
|
||||
mutableContact.postalAddresses = postalAddresses
|
||||
|
||||
if contact.birthday != nil {
|
||||
mutableContact.birthday = contact.birthday
|
||||
}
|
||||
|
||||
var socialProfiles = mutableContact.socialProfiles
|
||||
for socialProfile in contact.socialProfiles.reversed() {
|
||||
var found = false
|
||||
inner: for n in socialProfiles {
|
||||
if n.value.username.lowercased() == socialProfile.value.username.lowercased() && n.value.service.lowercased() == socialProfile.value.service.lowercased() {
|
||||
found = true
|
||||
break inner
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
socialProfiles.insert(socialProfile, at: 0)
|
||||
}
|
||||
}
|
||||
mutableContact.socialProfiles = socialProfiles
|
||||
|
||||
var instantMessageAddresses = mutableContact.instantMessageAddresses
|
||||
for instantMessageAddress in contact.instantMessageAddresses.reversed() {
|
||||
var found = false
|
||||
inner: for n in instantMessageAddresses {
|
||||
if n.value.isEqual(instantMessageAddress.value) {
|
||||
found = true
|
||||
break inner
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
instantMessageAddresses.insert(instantMessageAddress, at: 0)
|
||||
}
|
||||
}
|
||||
mutableContact.instantMessageAddresses = instantMessageAddresses
|
||||
|
||||
let saveRequest = CNSaveRequest()
|
||||
saveRequest.update(mutableContact)
|
||||
let _ = try? self.store.execute(saveRequest)
|
||||
|
||||
return DeviceContactExtendedData(contact: mutableContact)
|
||||
}
|
||||
|
||||
func appendPhoneNumber(_ phoneNumber: DeviceContactPhoneNumberData, to stableId: DeviceContactStableId) -> DeviceContactExtendedData? {
|
||||
let keysToFetch: [CNKeyDescriptor] = [
|
||||
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
|
||||
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
||||
CNContactEmailAddressesKey as CNKeyDescriptor,
|
||||
CNContactBirthdayKey as CNKeyDescriptor,
|
||||
CNContactSocialProfilesKey as CNKeyDescriptor,
|
||||
CNContactInstantMessageAddressesKey as CNKeyDescriptor,
|
||||
CNContactPostalAddressesKey as CNKeyDescriptor,
|
||||
CNContactUrlAddressesKey as CNKeyDescriptor,
|
||||
CNContactOrganizationNameKey as CNKeyDescriptor,
|
||||
CNContactJobTitleKey as CNKeyDescriptor,
|
||||
CNContactDepartmentNameKey as CNKeyDescriptor
|
||||
]
|
||||
|
||||
guard let current = try? self.store.unifiedContact(withIdentifier: stableId, keysToFetch: keysToFetch) else {
|
||||
return nil
|
||||
}
|
||||
|
||||
let mutableContact = current.mutableCopy() as! CNMutableContact
|
||||
|
||||
var phoneNumbers = mutableContact.phoneNumbers
|
||||
let appendPhoneNumbers: [CNLabeledValue<CNPhoneNumber>] = [CNLabeledValue<CNPhoneNumber>(label: phoneNumber.label, value: CNPhoneNumber(stringValue: phoneNumber.value))]
|
||||
for appendPhoneNumber in appendPhoneNumbers {
|
||||
var found = false
|
||||
inner: for n in phoneNumbers {
|
||||
if n.value.stringValue == appendPhoneNumber.value.stringValue {
|
||||
found = true
|
||||
break inner
|
||||
}
|
||||
}
|
||||
if !found {
|
||||
phoneNumbers.insert(appendPhoneNumber, at: 0)
|
||||
}
|
||||
}
|
||||
mutableContact.phoneNumbers = phoneNumbers
|
||||
|
||||
let saveRequest = CNSaveRequest()
|
||||
saveRequest.update(mutableContact)
|
||||
let _ = try? self.store.execute(saveRequest)
|
||||
|
||||
return DeviceContactExtendedData(contact: mutableContact)
|
||||
}
|
||||
|
||||
func createContactWithData(_ contactData: DeviceContactExtendedData) -> (DeviceContactStableId, DeviceContactExtendedData)? {
|
||||
let saveRequest = CNSaveRequest()
|
||||
let mutableContact = contactData.asMutableCNContact()
|
||||
saveRequest.add(mutableContact, toContainerWithIdentifier: nil)
|
||||
let _ = try? self.store.execute(saveRequest)
|
||||
|
||||
return (mutableContact.identifier, contactData)
|
||||
}
|
||||
|
||||
func deleteContactWithAppSpecificReference(peerId: PeerId) {
|
||||
guard let reference = self.currentAppSpecificReferences[peerId] else {
|
||||
return
|
||||
}
|
||||
guard let current = try? self.store.unifiedContact(withIdentifier: reference.stableId, keysToFetch: []) else {
|
||||
return
|
||||
}
|
||||
|
||||
let saveRequest = CNSaveRequest()
|
||||
saveRequest.delete(current.mutableCopy() as! CNMutableContact)
|
||||
let _ = try? self.store.execute(saveRequest)
|
||||
}
|
||||
}
|
||||
|
||||
private func withAddressBook(_ f: (ABAddressBook) -> Void) {
|
||||
let addressBookRef = ABAddressBookCreateWithOptions(nil, nil)
|
||||
|
||||
if let addressBook = addressBookRef?.takeRetainedValue() {
|
||||
f(addressBook)
|
||||
}
|
||||
}
|
||||
|
||||
private final class DeviceContactDataLegacyContext: DeviceContactDataContext {
|
||||
var currentContacts: [DeviceContactStableId: DeviceContactBasicData] = [:]
|
||||
|
||||
init(queue: Queue, updated: @escaping ([DeviceContactStableId: DeviceContactBasicData]) -> Void) {
|
||||
self.currentContacts = self.retrieveContacts()
|
||||
updated(self.currentContacts)
|
||||
/*let handle = NotificationCenter.default.addObserver(forName: NSNotification.Name.CNContactStoreDidChange, object: nil, queue: nil, using: { [weak self] _ in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
let contacts = strongSelf.retrieveContacts()
|
||||
if strongSelf.currentContacts != contacts {
|
||||
strongSelf.currentContacts = contacts
|
||||
updated(strongSelf.currentContacts)
|
||||
}
|
||||
}
|
||||
})*/
|
||||
//self.updateHandle = handle
|
||||
}
|
||||
|
||||
deinit {
|
||||
/*if let updateHandle = updateHandle {
|
||||
NotificationCenter.default.removeObserver(updateHandle)
|
||||
}*/
|
||||
}
|
||||
|
||||
private func retrieveContacts() -> [DeviceContactStableId: DeviceContactBasicData] {
|
||||
var result: [DeviceContactStableId: DeviceContactBasicData] = [:]
|
||||
withAddressBook { addressBook in
|
||||
guard let peopleRef = ABAddressBookCopyArrayOfAllPeople(addressBook)?.takeRetainedValue() else {
|
||||
return
|
||||
}
|
||||
|
||||
for recordRef in peopleRef as NSArray {
|
||||
let record = recordRef as ABRecord
|
||||
let (stableId, basicData) = DeviceContactDataLegacyContext.parseContact(record)
|
||||
result[stableId] = basicData
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private func getContactById(stableId: String) -> ABRecord? {
|
||||
let recordId: ABRecordID
|
||||
if stableId.hasPrefix("ab-"), let idValue = Int(String(stableId[stableId.index(stableId.startIndex, offsetBy: 3)])) {
|
||||
recordId = Int32(clamping: idValue)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
|
||||
var result: ABRecord?
|
||||
withAddressBook { addressBook in
|
||||
result = ABAddressBookGetPersonWithRecordID(addressBook, recordId)?.takeUnretainedValue()
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
private static func parseContact(_ contact: ABRecord) -> (DeviceContactStableId, DeviceContactBasicData) {
|
||||
let stableId = "ab-\(ABRecordGetRecordID(contact))"
|
||||
var firstName = ""
|
||||
var lastName = ""
|
||||
if let value = ABRecordCopyValue(contact, kABPersonFirstNameProperty)?.takeRetainedValue() {
|
||||
firstName = value as! CFString as String
|
||||
}
|
||||
if let value = ABRecordCopyValue(contact, kABPersonLastNameProperty)?.takeRetainedValue() {
|
||||
lastName = value as! CFString as String
|
||||
}
|
||||
|
||||
var phoneNumbers: [DeviceContactPhoneNumberData] = []
|
||||
if let value = ABRecordCopyValue(contact, kABPersonPhoneProperty)?.takeRetainedValue() {
|
||||
let phones = value as ABMultiValue
|
||||
let count = ABMultiValueGetCount(phones)
|
||||
for i in 0 ..< count {
|
||||
if let phoneRef = ABMultiValueCopyValueAtIndex(phones, i)?.takeRetainedValue() {
|
||||
let phone = phoneRef as! CFString as String
|
||||
var label = ""
|
||||
if let labelRef = ABMultiValueCopyLabelAtIndex(phones, i)?.takeRetainedValue() {
|
||||
label = labelRef as String
|
||||
}
|
||||
phoneNumbers.append(DeviceContactPhoneNumberData(label: label, value: phone))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return (stableId, DeviceContactBasicData(firstName: firstName, lastName: lastName, phoneNumbers: phoneNumbers))
|
||||
}
|
||||
|
||||
func personNameDisplayOrder() -> PresentationPersonNameOrder {
|
||||
if ABPersonGetCompositeNameFormat() == kABPersonCompositeNameFormatFirstNameFirst {
|
||||
return .firstLast
|
||||
} else {
|
||||
return .lastFirst
|
||||
}
|
||||
}
|
||||
|
||||
func getExtendedContactData(stableId: DeviceContactStableId) -> DeviceContactExtendedData? {
|
||||
if let contact = self.getContactById(stableId: stableId) {
|
||||
let basicData = DeviceContactDataLegacyContext.parseContact(contact).1
|
||||
return DeviceContactExtendedData(basicData: basicData, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: "")
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func appendContactData(_ contactData: DeviceContactExtendedData, to stableId: DeviceContactStableId) -> DeviceContactExtendedData? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func appendPhoneNumber(_ phoneNumber: DeviceContactPhoneNumberData, to stableId: DeviceContactStableId) -> DeviceContactExtendedData? {
|
||||
return nil
|
||||
}
|
||||
|
||||
func createContactWithData(_ contactData: DeviceContactExtendedData) -> (DeviceContactStableId, DeviceContactExtendedData)? {
|
||||
var result: (DeviceContactStableId, DeviceContactExtendedData)?
|
||||
withAddressBook { addressBook in
|
||||
let contact = ABPersonCreate()?.takeRetainedValue()
|
||||
ABRecordSetValue(contact, kABPersonFirstNameProperty, contactData.basicData.firstName as CFString, nil)
|
||||
ABRecordSetValue(contact, kABPersonLastNameProperty, contactData.basicData.lastName as CFString, nil)
|
||||
|
||||
let phones = ABMultiValueCreateMutable(ABPropertyType(kABMultiStringPropertyType))?.takeRetainedValue()
|
||||
for phone in contactData.basicData.phoneNumbers {
|
||||
ABMultiValueAddValueAndLabel(phones, phone.value as CFString, phone.label as CFString, nil)
|
||||
}
|
||||
ABRecordSetValue(contact, kABPersonPhoneProperty, phones, nil)
|
||||
|
||||
if ABAddressBookAddRecord(addressBook, contact, nil) {
|
||||
ABAddressBookSave(addressBook, nil)
|
||||
|
||||
let stableId = "ab-\(ABRecordGetRecordID(contact))"
|
||||
if let contact = self.getContactById(stableId: stableId) {
|
||||
let parsedContact = DeviceContactDataLegacyContext.parseContact(contact).1
|
||||
result = (stableId, DeviceContactExtendedData(basicData: parsedContact, middleName: "", prefix: "", suffix: "", organization: "", jobTitle: "", department: "", emailAddresses: [], urls: [], addresses: [], birthdayDate: nil, socialProfiles: [], instantMessagingProfiles: [], note: ""))
|
||||
}
|
||||
}
|
||||
}
|
||||
return result
|
||||
}
|
||||
|
||||
func deleteContactWithAppSpecificReference(peerId: PeerId) {
|
||||
}
|
||||
}
|
||||
|
||||
private final class ExtendedContactDataContext {
|
||||
var value: DeviceContactExtendedData?
|
||||
let subscribers = Bag<(DeviceContactExtendedData) -> Void>()
|
||||
}
|
||||
|
||||
private final class BasicDataForNormalizedNumberContext {
|
||||
var value: [(DeviceContactStableId, DeviceContactBasicData)]
|
||||
let subscribers = Bag<([(DeviceContactStableId, DeviceContactBasicData)]) -> Void>()
|
||||
|
||||
init(value: [(DeviceContactStableId, DeviceContactBasicData)]) {
|
||||
self.value = value
|
||||
}
|
||||
}
|
||||
|
||||
private final class DeviceContactDataManagerPrivateImpl {
|
||||
private let queue: Queue
|
||||
|
||||
private var accessInitialized = false
|
||||
|
||||
private var dataContext: DeviceContactDataContext?
|
||||
let personNameDisplayOrder = ValuePromise<PresentationPersonNameOrder>()
|
||||
private var extendedContexts: [DeviceContactStableId: ExtendedContactDataContext] = [:]
|
||||
|
||||
private var stableIdToBasicContactData: [DeviceContactStableId: DeviceContactBasicData] = [:]
|
||||
private var normalizedPhoneNumberToStableId: [DeviceContactNormalizedPhoneNumber: [DeviceContactStableId]] = [:]
|
||||
private var appSpecificReferences: [PeerId: DeviceContactBasicDataWithReference] = [:]
|
||||
private var stableIdToAppSpecificReference: [DeviceContactStableId: PeerId] = [:]
|
||||
|
||||
private var importableContacts: [DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData] = [:]
|
||||
|
||||
private var accessDisposable: Disposable?
|
||||
private let dataDisposable = MetaDisposable()
|
||||
|
||||
private let basicDataSubscribers = Bag<([DeviceContactStableId: DeviceContactBasicData]) -> Void>()
|
||||
private var basicDataForNormalizedNumberContexts: [DeviceContactNormalizedPhoneNumber: BasicDataForNormalizedNumberContext] = [:]
|
||||
private let importableContactsSubscribers = Bag<([DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData]) -> Void>()
|
||||
private let appSpecificReferencesSubscribers = Bag<([PeerId: DeviceContactBasicDataWithReference]) -> Void>()
|
||||
|
||||
init(queue: Queue) {
|
||||
self.queue = queue
|
||||
self.accessDisposable = (DeviceAccess.authorizationStatus(subject: .contacts)
|
||||
|> delay(2.0, queue: .mainQueue())
|
||||
|> deliverOn(self.queue)).start(next: { [weak self] authorizationStatus in
|
||||
guard let strongSelf = self, authorizationStatus != .notDetermined else {
|
||||
return
|
||||
}
|
||||
strongSelf.accessInitialized = true
|
||||
if authorizationStatus == .allowed {
|
||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
let dataContext = DeviceContactDataModernContext(queue: strongSelf.queue, updated: { stableIdToBasicContactData in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateAll(stableIdToBasicContactData)
|
||||
}, appSpecificReferencesUpdated: { appSpecificReferences in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateAppSpecificReferences(appSpecificReferences: appSpecificReferences)
|
||||
})
|
||||
strongSelf.dataContext = dataContext
|
||||
strongSelf.personNameDisplayOrder.set(dataContext.personNameDisplayOrder())
|
||||
} else {
|
||||
let dataContext = DeviceContactDataLegacyContext(queue: strongSelf.queue, updated: { stableIdToBasicContactData in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.updateAll(stableIdToBasicContactData)
|
||||
})
|
||||
strongSelf.dataContext = dataContext
|
||||
strongSelf.personNameDisplayOrder.set(dataContext.personNameDisplayOrder())
|
||||
}
|
||||
} else {
|
||||
strongSelf.updateAll([:])
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
deinit {
|
||||
self.accessDisposable?.dispose()
|
||||
}
|
||||
|
||||
private func updateAll(_ stableIdToBasicContactData: [DeviceContactStableId: DeviceContactBasicData]) {
|
||||
self.stableIdToBasicContactData = stableIdToBasicContactData
|
||||
var normalizedPhoneNumberToStableId: [DeviceContactNormalizedPhoneNumber: [DeviceContactStableId]] = [:]
|
||||
for (stableId, basicData) in self.stableIdToBasicContactData {
|
||||
for phoneNumber in basicData.phoneNumbers {
|
||||
let normalizedPhoneNumber = DeviceContactNormalizedPhoneNumber(rawValue: formatPhoneNumber(phoneNumber.value))
|
||||
if normalizedPhoneNumberToStableId[normalizedPhoneNumber] == nil {
|
||||
normalizedPhoneNumberToStableId[normalizedPhoneNumber] = []
|
||||
}
|
||||
normalizedPhoneNumberToStableId[normalizedPhoneNumber]!.append(stableId)
|
||||
}
|
||||
}
|
||||
self.normalizedPhoneNumberToStableId = normalizedPhoneNumberToStableId
|
||||
for f in self.basicDataSubscribers.copyItems() {
|
||||
f(self.stableIdToBasicContactData)
|
||||
}
|
||||
|
||||
for (normalizedNumber, context) in self.basicDataForNormalizedNumberContexts {
|
||||
var value: [(DeviceContactStableId, DeviceContactBasicData)] = []
|
||||
if let ids = self.normalizedPhoneNumberToStableId[normalizedNumber] {
|
||||
for id in ids {
|
||||
if let basicData = self.stableIdToBasicContactData[id] {
|
||||
value.append((id, basicData))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var updated = false
|
||||
if value.count != context.value.count {
|
||||
updated = true
|
||||
} else {
|
||||
for i in 0 ..< value.count {
|
||||
if value[i].0 != context.value[i].0 || value[i].1 != context.value[i].1 {
|
||||
updated = true
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if updated {
|
||||
context.value = value
|
||||
for f in context.subscribers.copyItems() {
|
||||
f(value)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
var importableContactData: [String: (DeviceContactStableId, ImportableDeviceContactData)] = [:]
|
||||
for (stableId, basicData) in self.stableIdToBasicContactData {
|
||||
for phoneNumber in basicData.phoneNumbers {
|
||||
var replace = false
|
||||
if let current = importableContactData[phoneNumber.value] {
|
||||
if stableId < current.0 {
|
||||
replace = true
|
||||
}
|
||||
} else {
|
||||
replace = true
|
||||
}
|
||||
if replace {
|
||||
importableContactData[phoneNumber.value] = (stableId, ImportableDeviceContactData(firstName: basicData.firstName, lastName: basicData.lastName))
|
||||
}
|
||||
}
|
||||
}
|
||||
var importabledContacts: [DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData] = [:]
|
||||
for (number, data) in importableContactData {
|
||||
importabledContacts[DeviceContactNormalizedPhoneNumber(rawValue: number)] = data.1
|
||||
}
|
||||
self.importableContacts = importabledContacts
|
||||
for f in self.importableContactsSubscribers.copyItems() {
|
||||
f(importableContacts)
|
||||
}
|
||||
}
|
||||
|
||||
private func updateAppSpecificReferences(appSpecificReferences: [PeerId: DeviceContactBasicDataWithReference]) {
|
||||
self.appSpecificReferences = appSpecificReferences
|
||||
var stableIdToAppSpecificReference: [DeviceContactStableId: PeerId] = [:]
|
||||
for (peerId, value) in appSpecificReferences {
|
||||
stableIdToAppSpecificReference[value.stableId] = peerId
|
||||
}
|
||||
self.stableIdToAppSpecificReference = stableIdToAppSpecificReference
|
||||
for f in self.appSpecificReferencesSubscribers.copyItems() {
|
||||
f(appSpecificReferences)
|
||||
}
|
||||
}
|
||||
|
||||
func basicData(updated: @escaping ([DeviceContactStableId: DeviceContactBasicData]) -> Void) -> Disposable {
|
||||
let queue = self.queue
|
||||
|
||||
let index = self.basicDataSubscribers.add({ data in
|
||||
updated(data)
|
||||
})
|
||||
|
||||
updated(self.stableIdToBasicContactData)
|
||||
|
||||
return ActionDisposable { [weak self] in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.basicDataSubscribers.remove(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func basicDataForNormalizedPhoneNumber(_ normalizedNumber: DeviceContactNormalizedPhoneNumber, updated: @escaping ([(DeviceContactStableId, DeviceContactBasicData)]) -> Void) -> Disposable {
|
||||
let queue = self.queue
|
||||
let context: BasicDataForNormalizedNumberContext
|
||||
if let current = self.basicDataForNormalizedNumberContexts[normalizedNumber] {
|
||||
context = current
|
||||
} else {
|
||||
var value: [(DeviceContactStableId, DeviceContactBasicData)] = []
|
||||
if let ids = self.normalizedPhoneNumberToStableId[normalizedNumber] {
|
||||
for id in ids {
|
||||
if let basicData = self.stableIdToBasicContactData[id] {
|
||||
value.append((id, basicData))
|
||||
}
|
||||
}
|
||||
}
|
||||
context = BasicDataForNormalizedNumberContext(value: value)
|
||||
self.basicDataForNormalizedNumberContexts[normalizedNumber] = context
|
||||
}
|
||||
updated(context.value)
|
||||
let index = context.subscribers.add({ value in
|
||||
updated(value)
|
||||
})
|
||||
return ActionDisposable { [weak self, weak context] in
|
||||
queue.async {
|
||||
if let strongSelf = self, let foundContext = strongSelf.basicDataForNormalizedNumberContexts[normalizedNumber], foundContext === context {
|
||||
foundContext.subscribers.remove(index)
|
||||
if foundContext.subscribers.isEmpty {
|
||||
strongSelf.basicDataForNormalizedNumberContexts.removeValue(forKey: normalizedNumber)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func extendedData(stableId: String, updated: @escaping (DeviceContactExtendedData?) -> Void) -> Disposable {
|
||||
let current = self.dataContext?.getExtendedContactData(stableId: stableId)
|
||||
updated(current)
|
||||
|
||||
return ActionDisposable {
|
||||
}
|
||||
}
|
||||
|
||||
func importable(updated: @escaping ([DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData]) -> Void) -> Disposable {
|
||||
let queue = self.queue
|
||||
|
||||
let index = self.importableContactsSubscribers.add({ data in
|
||||
updated(data)
|
||||
})
|
||||
if self.accessInitialized {
|
||||
updated(self.importableContacts)
|
||||
}
|
||||
|
||||
return ActionDisposable { [weak self] in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.importableContactsSubscribers.remove(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func appSpecificReferences(updated: @escaping ([PeerId: DeviceContactBasicDataWithReference]) -> Void) -> Disposable {
|
||||
let queue = self.queue
|
||||
|
||||
let index = self.appSpecificReferencesSubscribers.add({ data in
|
||||
updated(data)
|
||||
})
|
||||
if self.accessInitialized {
|
||||
updated(self.appSpecificReferences)
|
||||
}
|
||||
|
||||
return ActionDisposable { [weak self] in
|
||||
queue.async {
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
strongSelf.appSpecificReferencesSubscribers.remove(index)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func search(query: String, updated: @escaping ([DeviceContactStableId: (DeviceContactBasicData, PeerId?)]) -> Void) -> Disposable {
|
||||
let normalizedQuery = query.lowercased()
|
||||
var result: [DeviceContactStableId: (DeviceContactBasicData, PeerId?)] = [:]
|
||||
for (stableId, basicData) in self.stableIdToBasicContactData {
|
||||
if basicData.firstName.lowercased().hasPrefix(normalizedQuery) || basicData.lastName.lowercased().hasPrefix(normalizedQuery) {
|
||||
result[stableId] = (basicData, self.stableIdToAppSpecificReference[stableId])
|
||||
}
|
||||
}
|
||||
updated(result)
|
||||
return EmptyDisposable
|
||||
}
|
||||
|
||||
func appendContactData(_ contactData: DeviceContactExtendedData, to stableId: DeviceContactStableId, completion: @escaping (DeviceContactExtendedData?) -> Void) {
|
||||
let result = self.dataContext?.appendContactData(contactData, to: stableId)
|
||||
completion(result)
|
||||
}
|
||||
|
||||
func appendPhoneNumber(_ phoneNumber: DeviceContactPhoneNumberData, to stableId: DeviceContactStableId, completion: @escaping (DeviceContactExtendedData?) -> Void) {
|
||||
let result = self.dataContext?.appendPhoneNumber(phoneNumber, to: stableId)
|
||||
completion(result)
|
||||
}
|
||||
|
||||
func createContactWithData(_ contactData: DeviceContactExtendedData, completion: @escaping ((DeviceContactStableId, DeviceContactExtendedData)?) -> Void) {
|
||||
let result = self.dataContext?.createContactWithData(contactData)
|
||||
completion(result)
|
||||
}
|
||||
|
||||
func deleteContactWithAppSpecificReference(peerId: PeerId, completion: @escaping () -> Void) {
|
||||
self.dataContext?.deleteContactWithAppSpecificReference(peerId: peerId)
|
||||
completion()
|
||||
}
|
||||
}
|
||||
|
||||
public final class DeviceContactDataManagerImpl: DeviceContactDataManager {
|
||||
private let queue = Queue()
|
||||
private let impl: QueueLocalObject<DeviceContactDataManagerPrivateImpl>
|
||||
|
||||
init() {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return DeviceContactDataManagerPrivateImpl(queue: queue)
|
||||
})
|
||||
}
|
||||
|
||||
public func personNameDisplayOrder() -> Signal<PresentationPersonNameOrder, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with({ impl in
|
||||
disposable.set(impl.personNameDisplayOrder.get().start(next: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
})
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func basicData() -> Signal<[DeviceContactStableId: DeviceContactBasicData], NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with({ impl in
|
||||
disposable.set(impl.basicData(updated: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
})
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func basicDataForNormalizedPhoneNumber(_ normalizedNumber: DeviceContactNormalizedPhoneNumber) -> Signal<[(DeviceContactStableId, DeviceContactBasicData)], NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with({ impl in
|
||||
disposable.set(impl.basicDataForNormalizedPhoneNumber(normalizedNumber, updated: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
})
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func extendedData(stableId: DeviceContactStableId) -> Signal<DeviceContactExtendedData?, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with({ impl in
|
||||
disposable.set(impl.extendedData(stableId: stableId, updated: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
})
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func importable() -> Signal<[DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData], NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with({ impl in
|
||||
disposable.set(impl.importable(updated: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
})
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func appSpecificReferences() -> Signal<[PeerId: DeviceContactBasicDataWithReference], NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with({ impl in
|
||||
disposable.set(impl.appSpecificReferences(updated: { value in
|
||||
subscriber.putNext(value)
|
||||
}))
|
||||
})
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func search(query: String) -> Signal<[DeviceContactStableId: (DeviceContactBasicData, PeerId?)], NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with({ impl in
|
||||
disposable.set(impl.search(query: query, updated: { value in
|
||||
subscriber.putNext(value)
|
||||
subscriber.putCompletion()
|
||||
}))
|
||||
})
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func appendContactData(_ contactData: DeviceContactExtendedData, to stableId: DeviceContactStableId) -> Signal<DeviceContactExtendedData?, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with({ impl in
|
||||
impl.appendContactData(contactData, to: stableId, completion: { next in
|
||||
subscriber.putNext(next)
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
})
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func appendPhoneNumber(_ phoneNumber: DeviceContactPhoneNumberData, to stableId: DeviceContactStableId) -> Signal<DeviceContactExtendedData?, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with({ impl in
|
||||
impl.appendPhoneNumber(phoneNumber, to: stableId, completion: { next in
|
||||
subscriber.putNext(next)
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
})
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func createContactWithData(_ contactData: DeviceContactExtendedData) -> Signal<(DeviceContactStableId, DeviceContactExtendedData)?, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with({ impl in
|
||||
impl.createContactWithData(contactData, completion: { next in
|
||||
subscriber.putNext(next)
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
})
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
|
||||
public func deleteContactWithAppSpecificReference(peerId: PeerId) -> Signal<Never, NoError> {
|
||||
return Signal { subscriber in
|
||||
let disposable = MetaDisposable()
|
||||
self.impl.with({ impl in
|
||||
impl.deleteContactWithAppSpecificReference(peerId: peerId, completion: {
|
||||
subscriber.putCompletion()
|
||||
})
|
||||
})
|
||||
return disposable
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user