mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-06-16 05:55:20 +00:00
751 lines
32 KiB
Swift
751 lines
32 KiB
Swift
import Foundation
|
|
import SwiftSignalKit
|
|
import Postbox
|
|
import TelegramCore
|
|
import Contacts
|
|
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 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 {
|
|
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 {
|
|
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
|
|
var currentLocalIdentifiers: [String] = []
|
|
if let current = importableContactData[phoneNumber.value] {
|
|
if stableId < current.0 {
|
|
replace = true
|
|
currentLocalIdentifiers = current.1.localIdentifiers
|
|
}
|
|
} else {
|
|
replace = true
|
|
}
|
|
if replace {
|
|
if !currentLocalIdentifiers.contains(stableId) {
|
|
currentLocalIdentifiers.append(stableId)
|
|
}
|
|
importableContactData[phoneNumber.value] = (stableId, ImportableDeviceContactData(firstName: basicData.firstName, lastName: basicData.lastName, localIdentifiers: currentLocalIdentifiers))
|
|
}
|
|
}
|
|
}
|
|
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
|
|
}
|
|
}
|
|
}
|