mirror of
https://github.com/Swiftgram/Telegram-iOS.git
synced 2025-08-29 00:50:40 +00:00
New address book implementation
(cherry picked from commit a16a3c393628a3e44a14b0c7196b24205f40c1e4)
This commit is contained in:
parent
681ab8248e
commit
735b497a23
@ -25,6 +25,8 @@ swift_library(
|
||||
"//submodules/TextFormat:TextFormat",
|
||||
"//submodules/TelegramUI/Components/AnimationCache:AnimationCache",
|
||||
"//submodules/TelegramUI/Components/MultiAnimationRenderer:MultiAnimationRenderer",
|
||||
"//submodules/TelegramCore/FlatBuffers",
|
||||
"//submodules/TelegramCore/FlatSerialization",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
|
@ -1,6 +1,8 @@
|
||||
import Foundation
|
||||
import Contacts
|
||||
import TelegramCore
|
||||
import FlatBuffers
|
||||
import FlatSerialization
|
||||
|
||||
public final class DeviceContactPhoneNumberData: Equatable {
|
||||
public let label: String
|
||||
@ -11,6 +13,22 @@ public final class DeviceContactPhoneNumberData: Equatable {
|
||||
self.value = value
|
||||
}
|
||||
|
||||
init(flatBuffersObject: TelegramCore_DeviceContactPhoneNumberData) {
|
||||
self.label = flatBuffersObject.label
|
||||
self.value = flatBuffersObject.value
|
||||
}
|
||||
|
||||
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
|
||||
let labelOffset = builder.create(string: self.label)
|
||||
let valueOffset = builder.create(string: self.value)
|
||||
|
||||
return TelegramCore_DeviceContactPhoneNumberData.createDeviceContactPhoneNumberData(
|
||||
&builder,
|
||||
labelOffset: labelOffset,
|
||||
valueOffset: valueOffset
|
||||
)
|
||||
}
|
||||
|
||||
public static func == (lhs: DeviceContactPhoneNumberData, rhs: DeviceContactPhoneNumberData) -> Bool {
|
||||
if lhs.label != rhs.label {
|
||||
return false
|
||||
@ -216,6 +234,38 @@ public final class DeviceContactBasicData: Equatable {
|
||||
self.phoneNumbers = phoneNumbers
|
||||
}
|
||||
|
||||
public init(flatBuffersObject: TelegramCore_StoredDeviceContactData) {
|
||||
self.firstName = flatBuffersObject.firstName
|
||||
self.lastName = flatBuffersObject.lastName
|
||||
|
||||
if flatBuffersObject.phoneNumbersCount == 1 {
|
||||
self.phoneNumbers = [
|
||||
DeviceContactPhoneNumberData(flatBuffersObject: flatBuffersObject.phoneNumbers(at: 0)!)
|
||||
]
|
||||
} else {
|
||||
var phoneNumbers: [DeviceContactPhoneNumberData] = []
|
||||
for i in 0 ..< flatBuffersObject.phoneNumbersCount {
|
||||
phoneNumbers.append(DeviceContactPhoneNumberData(flatBuffersObject: flatBuffersObject.phoneNumbers(at: i)!))
|
||||
}
|
||||
self.phoneNumbers = phoneNumbers
|
||||
}
|
||||
}
|
||||
|
||||
public func encodeToFlatBuffers(builder: inout FlatBufferBuilder) -> Offset {
|
||||
let phoneNumberOffsets = self.phoneNumbers.map { $0.encodeToFlatBuffers(builder: &builder) }
|
||||
let phoneNumberOffset = builder.createVector(ofOffsets: phoneNumberOffsets, len: phoneNumberOffsets.count)
|
||||
|
||||
let firstNameOffset = builder.create(string: self.firstName)
|
||||
let lastNameOffset = builder.create(string: self.lastName)
|
||||
|
||||
return TelegramCore_StoredDeviceContactData.createStoredDeviceContactData(
|
||||
&builder,
|
||||
firstNameOffset: firstNameOffset,
|
||||
lastNameOffset: lastNameOffset,
|
||||
phoneNumbersVectorOffset: phoneNumberOffset
|
||||
)
|
||||
}
|
||||
|
||||
public static func ==(lhs: DeviceContactBasicData, rhs: DeviceContactBasicData) -> Bool {
|
||||
if lhs.firstName != rhs.firstName {
|
||||
return false
|
||||
@ -230,6 +280,86 @@ public final class DeviceContactBasicData: Equatable {
|
||||
}
|
||||
}
|
||||
|
||||
public final class DeviceContactDataState: Codable {
|
||||
private enum CodingKeys: String, CodingKey {
|
||||
case contactsData
|
||||
case contactsKeys
|
||||
case telegramReferencesKeys
|
||||
case telegramReferencesValues
|
||||
case stateToken
|
||||
}
|
||||
|
||||
public let contacts: [String: DeviceContactBasicData]
|
||||
public let telegramReferences: [EnginePeer.Id: String]
|
||||
public let stateToken: Data?
|
||||
|
||||
public init(contacts: [String: DeviceContactBasicData], telegramReferences: [EnginePeer.Id: String], stateToken: Data?) {
|
||||
self.contacts = contacts
|
||||
self.telegramReferences = telegramReferences
|
||||
self.stateToken = stateToken
|
||||
}
|
||||
|
||||
public init(from decoder: any Decoder) throws {
|
||||
let container = try decoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
let contactsData = try container.decode([Data].self, forKey: .contactsData).map { data in
|
||||
var byteBuffer = ByteBuffer(data: data)
|
||||
let deserializedValue = FlatBuffers_getRoot(byteBuffer: &byteBuffer) as TelegramCore_StoredDeviceContactData
|
||||
let parsedValue = DeviceContactBasicData(flatBuffersObject: deserializedValue)
|
||||
return parsedValue
|
||||
}
|
||||
let contactsKeys = try container.decode([String].self, forKey: .contactsKeys)
|
||||
|
||||
var contacts: [String: DeviceContactBasicData] = [:]
|
||||
for i in 0 ..< min(contactsData.count, contactsKeys.count) {
|
||||
contacts[contactsKeys[i]] = contactsData[i]
|
||||
}
|
||||
self.contacts = contacts
|
||||
|
||||
let telegramReferencesKeys = try container.decode([Int64].self, forKey: .telegramReferencesKeys).map { value in
|
||||
return EnginePeer.Id(value)
|
||||
}
|
||||
let telegramReferencesValues = try container.decode([String].self, forKey: .telegramReferencesValues)
|
||||
|
||||
var telegramReferences: [EnginePeer.Id: String] = [:]
|
||||
for i in 0 ..< min(telegramReferencesValues.count, telegramReferencesKeys.count) {
|
||||
telegramReferences[telegramReferencesKeys[i]] = telegramReferencesValues[i]
|
||||
}
|
||||
self.telegramReferences = telegramReferences
|
||||
|
||||
self.stateToken = try container.decodeIfPresent(Data.self, forKey: .stateToken)
|
||||
}
|
||||
|
||||
public func encode(to encoder: any Encoder) throws {
|
||||
var container = encoder.container(keyedBy: CodingKeys.self)
|
||||
|
||||
var contactsData: [Data] = []
|
||||
var contactsKeys: [String] = []
|
||||
for (key, contact) in self.contacts {
|
||||
var builder = FlatBufferBuilder(initialSize: 1024)
|
||||
let offset = contact.encodeToFlatBuffers(builder: &builder)
|
||||
builder.finish(offset: offset)
|
||||
contactsData.append(builder.data)
|
||||
contactsKeys.append(key)
|
||||
}
|
||||
|
||||
var telegramReferencesKeys: [Int64] = []
|
||||
var telegramReferencesValues: [String] = []
|
||||
for (key, value) in self.telegramReferences {
|
||||
telegramReferencesKeys.append(key.toInt64())
|
||||
telegramReferencesValues.append(value)
|
||||
}
|
||||
|
||||
try container.encode(contactsKeys, forKey: .contactsKeys)
|
||||
try container.encode(contactsData, forKey: .contactsData)
|
||||
|
||||
try container.encode(telegramReferencesKeys, forKey: .telegramReferencesKeys)
|
||||
try container.encode(telegramReferencesValues, forKey: .telegramReferencesValues)
|
||||
|
||||
try container.encodeIfPresent(stateToken, forKey: .stateToken)
|
||||
}
|
||||
}
|
||||
|
||||
public final class DeviceContactBasicDataWithReference: Equatable {
|
||||
public let stableId: DeviceContactStableId
|
||||
public let basicData: DeviceContactBasicData
|
||||
@ -327,17 +457,12 @@ public final class DeviceContactExtendedData: Equatable {
|
||||
|
||||
public extension DeviceContactExtendedData {
|
||||
convenience init?(vcard: Data) {
|
||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
guard let contact = (try? CNContactVCardSerialization.contacts(with: vcard))?.first else {
|
||||
return nil
|
||||
}
|
||||
self.init(contact: contact)
|
||||
} else {
|
||||
guard let contact = (try? CNContactVCardSerialization.contacts(with: vcard))?.first else {
|
||||
return nil
|
||||
}
|
||||
self.init(contact: contact)
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
|
||||
func asMutableCNContact() -> CNMutableContact {
|
||||
let contact = CNMutableContact()
|
||||
contact.givenName = self.basicData.firstName
|
||||
@ -385,7 +510,6 @@ public extension DeviceContactExtendedData {
|
||||
return nil
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
|
||||
convenience init(contact: CNContact) {
|
||||
var phoneNumbers: [DeviceContactPhoneNumberData] = []
|
||||
for number in contact.phoneNumbers {
|
||||
|
@ -5,6 +5,8 @@ import SwiftSignalKit
|
||||
|
||||
public typealias DeviceContactStableId = String
|
||||
|
||||
public var sharedDisableDeviceContactDataDiffing: Bool = false
|
||||
|
||||
public protocol DeviceContactDataManager: AnyObject {
|
||||
func personNameDisplayOrder() -> Signal<PresentationPersonNameOrder, NoError>
|
||||
func basicData() -> Signal<[DeviceContactStableId: DeviceContactBasicData], NoError>
|
||||
|
22
submodules/ContactsHelper/BUILD
Normal file
22
submodules/ContactsHelper/BUILD
Normal file
@ -0,0 +1,22 @@
|
||||
|
||||
objc_library(
|
||||
name = "ContactsHelper",
|
||||
enable_modules = True,
|
||||
module_name = "ContactsHelper",
|
||||
srcs = glob([
|
||||
"Sources/**/*.m",
|
||||
"Sources/**/*.h",
|
||||
], allow_empty = True),
|
||||
hdrs = glob([
|
||||
"PublicHeaders/**/*.h",
|
||||
]),
|
||||
includes = [
|
||||
"PublicHeaders",
|
||||
],
|
||||
sdk_frameworks = [
|
||||
"Foundation",
|
||||
],
|
||||
visibility = [
|
||||
"//visibility:public",
|
||||
],
|
||||
)
|
30
submodules/ContactsHelper/PublicHeaders/ContactsHelper/ContactsHelper.h
Executable file
30
submodules/ContactsHelper/PublicHeaders/ContactsHelper/ContactsHelper.h
Executable file
@ -0,0 +1,30 @@
|
||||
#ifndef ContactsHelper_h
|
||||
#define ContactsHelper_h
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
#import <Contacts/Contacts.h>
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
@interface ContactsEnumerateChangeResult : NSObject
|
||||
|
||||
@property (nonatomic, strong) NSData * _Nullable stateToken;
|
||||
|
||||
- (instancetype)initWithStateToken:(NSData *)stateToken;
|
||||
|
||||
@end
|
||||
|
||||
@interface ContactsEnumerateResult : NSObject
|
||||
|
||||
@property (nonatomic, strong) NSData * _Nullable stateToken;
|
||||
|
||||
- (instancetype)initWithStateToken:(NSData *)stateToken;
|
||||
|
||||
@end
|
||||
|
||||
ContactsEnumerateChangeResult * _Nullable ContactsEnumerateChangeRequest(CNContactStore *store, CNChangeHistoryFetchRequest *fetchRequest, id<CNChangeHistoryEventVisitor> visitor);
|
||||
ContactsEnumerateResult * _Nullable ContactsEnumerateRequest(CNContactStore *store, CNContactFetchRequest *fetchRequest, void (^onContact)(CNContact *));
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
|
||||
#endif /* ContactsHelper_h */
|
46
submodules/ContactsHelper/Sources/ContactsHelper.m
Executable file
46
submodules/ContactsHelper/Sources/ContactsHelper.m
Executable file
@ -0,0 +1,46 @@
|
||||
#import <ContactsHelper/ContactsHelper.h>
|
||||
|
||||
@implementation ContactsEnumerateChangeResult
|
||||
|
||||
- (instancetype)initWithStateToken:(NSData *)stateToken {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_stateToken = stateToken;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation ContactsEnumerateResult
|
||||
|
||||
- (instancetype)initWithStateToken:(NSData *)stateToken {
|
||||
self = [super init];
|
||||
if (self != nil) {
|
||||
_stateToken = stateToken;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
ContactsEnumerateChangeResult * _Nullable ContactsEnumerateChangeRequest(CNContactStore *store, CNChangeHistoryFetchRequest *fetchRequest, id<CNChangeHistoryEventVisitor> visitor) {
|
||||
NSError *error = nil;
|
||||
CNFetchResult<NSEnumerator<CNChangeHistoryEvent *> *> *fetchResult = [store enumeratorForChangeHistoryFetchRequest:fetchRequest error:&error];
|
||||
|
||||
for (CNChangeHistoryEvent *event in fetchResult.value) {
|
||||
[event acceptEventVisitor:visitor];
|
||||
}
|
||||
|
||||
return [[ContactsEnumerateChangeResult alloc] initWithStateToken:fetchResult.currentHistoryToken];
|
||||
}
|
||||
|
||||
ContactsEnumerateResult * _Nullable ContactsEnumerateRequest(CNContactStore *store, CNContactFetchRequest *fetchRequest, void (^onContact)(CNContact *)) {
|
||||
NSError *error = nil;
|
||||
CNFetchResult<NSEnumerator<CNContact *> *> *fetchResult = [store enumeratorForContactFetchRequest:fetchRequest error:&error];
|
||||
for (CNContact *contact in fetchResult.value) {
|
||||
onContact(contact);
|
||||
}
|
||||
|
||||
return [[ContactsEnumerateResult alloc] initWithStateToken:fetchResult.currentHistoryToken];
|
||||
}
|
@ -7,7 +7,6 @@ import SwiftSignalKit
|
||||
import Photos
|
||||
import CoreLocation
|
||||
import Contacts
|
||||
import AddressBook
|
||||
import UserNotifications
|
||||
import CoreTelephony
|
||||
import TelegramPresentationData
|
||||
@ -155,29 +154,17 @@ public final class DeviceAccess {
|
||||
}
|
||||
case .contacts:
|
||||
let status = Signal<AccessType, NoError> { subscriber in
|
||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
switch CNContactStore.authorizationStatus(for: .contacts) {
|
||||
case .notDetermined:
|
||||
subscriber.putNext(.notDetermined)
|
||||
case .authorized:
|
||||
subscriber.putNext(.allowed)
|
||||
case .limited:
|
||||
subscriber.putNext(.limited)
|
||||
default:
|
||||
subscriber.putNext(.denied)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
} else {
|
||||
switch ABAddressBookGetAuthorizationStatus() {
|
||||
case .notDetermined:
|
||||
subscriber.putNext(.notDetermined)
|
||||
case .authorized:
|
||||
subscriber.putNext(.allowed)
|
||||
default:
|
||||
subscriber.putNext(.denied)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
switch CNContactStore.authorizationStatus(for: .contacts) {
|
||||
case .notDetermined:
|
||||
subscriber.putNext(.notDetermined)
|
||||
case .authorized:
|
||||
subscriber.putNext(.allowed)
|
||||
case .limited:
|
||||
subscriber.putNext(.limited)
|
||||
default:
|
||||
subscriber.putNext(.denied)
|
||||
}
|
||||
subscriber.putCompletion()
|
||||
return EmptyDisposable
|
||||
}
|
||||
return status
|
||||
@ -531,47 +518,22 @@ public final class DeviceAccess {
|
||||
if let value = value {
|
||||
completion(value)
|
||||
} else {
|
||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
switch CNContactStore.authorizationStatus(for: .contacts) {
|
||||
case .notDetermined:
|
||||
let store = CNContactStore()
|
||||
store.requestAccess(for: .contacts, completionHandler: { authorized, _ in
|
||||
self.contactsPromise.set(.single(authorized))
|
||||
completion(authorized)
|
||||
})
|
||||
case .authorized:
|
||||
self.contactsPromise.set(.single(true))
|
||||
completion(true)
|
||||
case .limited:
|
||||
self.contactsPromise.set(.single(true))
|
||||
completion(true)
|
||||
default:
|
||||
self.contactsPromise.set(.single(false))
|
||||
completion(false)
|
||||
}
|
||||
} else {
|
||||
switch ABAddressBookGetAuthorizationStatus() {
|
||||
case .notDetermined:
|
||||
var error: Unmanaged<CFError>?
|
||||
let addressBook = ABAddressBookCreateWithOptions(nil, &error)
|
||||
if let addressBook = addressBook?.takeUnretainedValue() {
|
||||
ABAddressBookRequestAccessWithCompletion(addressBook, { authorized, _ in
|
||||
Queue.mainQueue().async {
|
||||
self.contactsPromise.set(.single(authorized))
|
||||
completion(authorized)
|
||||
}
|
||||
})
|
||||
} else {
|
||||
self.contactsPromise.set(.single(false))
|
||||
completion(false)
|
||||
}
|
||||
case .authorized:
|
||||
self.contactsPromise.set(.single(true))
|
||||
completion(true)
|
||||
default:
|
||||
self.contactsPromise.set(.single(false))
|
||||
completion(false)
|
||||
}
|
||||
switch CNContactStore.authorizationStatus(for: .contacts) {
|
||||
case .notDetermined:
|
||||
let store = CNContactStore()
|
||||
store.requestAccess(for: .contacts, completionHandler: { authorized, _ in
|
||||
self.contactsPromise.set(.single(authorized))
|
||||
completion(authorized)
|
||||
})
|
||||
case .authorized:
|
||||
self.contactsPromise.set(.single(true))
|
||||
completion(true)
|
||||
case .limited:
|
||||
self.contactsPromise.set(.single(true))
|
||||
completion(true)
|
||||
default:
|
||||
self.contactsPromise.set(.single(false))
|
||||
completion(false)
|
||||
}
|
||||
}
|
||||
})
|
||||
|
@ -0,0 +1,14 @@
|
||||
namespace TelegramCore;
|
||||
|
||||
table DeviceContactPhoneNumberData {
|
||||
label:string (id: 0, required);
|
||||
value:string (id: 1, required);
|
||||
}
|
||||
|
||||
table StoredDeviceContactData {
|
||||
firstName:string (id: 0, required);
|
||||
lastName:string (id: 1, required);
|
||||
phoneNumbers:[DeviceContactPhoneNumberData] (id: 2);
|
||||
}
|
||||
|
||||
root_type StoredDeviceContactData;
|
@ -584,6 +584,7 @@ private enum SharedDataKeyValues: Int32 {
|
||||
case countriesList = 7
|
||||
case wallapersState = 8
|
||||
case chatThemes = 10
|
||||
case deviceContacts = 11
|
||||
}
|
||||
|
||||
public struct SharedDataKeys {
|
||||
@ -640,6 +641,12 @@ public struct SharedDataKeys {
|
||||
key.setInt32(0, value: SharedDataKeyValues.chatThemes.rawValue)
|
||||
return key
|
||||
}()
|
||||
|
||||
public static let deviceContacts: ValueBoxKey = {
|
||||
let key = ValueBoxKey(length: 4)
|
||||
key.setInt32(0, value: SharedDataKeyValues.deviceContacts.rawValue)
|
||||
return key
|
||||
}()
|
||||
}
|
||||
|
||||
public func applicationSpecificItemCacheCollectionId(_ value: Int8) -> Int8 {
|
||||
|
@ -4,7 +4,6 @@ import SwiftSignalKit
|
||||
import Postbox
|
||||
import TelegramCore
|
||||
import Contacts
|
||||
import AddressBook
|
||||
import Display
|
||||
import TelegramUIPreferences
|
||||
import AppBundle
|
||||
@ -201,19 +200,11 @@ private func currentDateTimeFormat() -> PresentationDateTimeFormat {
|
||||
}
|
||||
|
||||
private func currentPersonNameSortOrder() -> PresentationPersonNameOrder {
|
||||
if #available(iOSApplicationExtension 9.0, iOS 9.0, *) {
|
||||
switch CNContactsUserDefaults.shared().sortOrder {
|
||||
case .givenName:
|
||||
return .firstLast
|
||||
default:
|
||||
return .lastFirst
|
||||
}
|
||||
} else {
|
||||
if ABPersonGetSortOrdering() == kABPersonSortByFirstName {
|
||||
switch CNContactsUserDefaults.shared().sortOrder {
|
||||
case .givenName:
|
||||
return .firstLast
|
||||
} else {
|
||||
default:
|
||||
return .lastFirst
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -482,6 +482,7 @@ swift_library(
|
||||
"//submodules/TelegramUI/Components/ComposeTodoScreen",
|
||||
"//submodules/TelegramUI/Components/SuggestedPostApproveAlert",
|
||||
"//submodules/TelegramUI/Components/Stars/BalanceNeededScreen",
|
||||
"//submodules/ContactsHelper",
|
||||
] + select({
|
||||
"@build_bazel_rules_apple//apple:ios_arm64": appcenter_targets,
|
||||
"//build-system:ios_sim_arm64": [],
|
||||
|
@ -359,6 +359,10 @@ public final class AccountContextImpl: AccountContext {
|
||||
self.appConfigurationDisposable = (self._appConfiguration.get()
|
||||
|> deliverOnMainQueue).start(next: { value in
|
||||
let _ = currentAppConfiguration.swap(value)
|
||||
|
||||
if let data = appConfiguration.data, data["ios_killswitch_contact_diffing"] != nil {
|
||||
sharedDisableDeviceContactDataDiffing = true
|
||||
}
|
||||
})
|
||||
|
||||
let langCode = sharedContext.currentPresentationData.with { $0 }.strings.baseLanguageCode
|
||||
|
@ -7,6 +7,8 @@ import TelegramUIPreferences
|
||||
import DeviceAccess
|
||||
import AccountContext
|
||||
import PhoneNumberFormat
|
||||
import ContactsHelper
|
||||
import AccountContext
|
||||
|
||||
private protocol DeviceContactDataContext {
|
||||
func personNameDisplayOrder() -> PresentationPersonNameOrder
|
||||
@ -17,62 +19,222 @@ private protocol DeviceContactDataContext {
|
||||
func deleteContactWithAppSpecificReference(peerId: PeerId)
|
||||
}
|
||||
|
||||
@available(iOSApplicationExtension 9.0, iOS 9.0, *)
|
||||
private final class DeviceContactDataModernContext: DeviceContactDataContext {
|
||||
let queue: Queue
|
||||
let accountManager: AccountManager<TelegramAccountManagerTypes>
|
||||
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)
|
||||
}
|
||||
private var retrieveContactsDisposable: Disposable?
|
||||
|
||||
init(queue: Queue, accountManager: AccountManager<TelegramAccountManagerTypes>, updated: @escaping ([DeviceContactStableId: DeviceContactBasicData]) -> Void, appSpecificReferencesUpdated: @escaping ([PeerId: DeviceContactBasicDataWithReference]) -> Void) {
|
||||
self.queue = queue
|
||||
self.accountManager = accountManager
|
||||
|
||||
self.retrieveContactsDisposable?.dispose()
|
||||
self.retrieveContactsDisposable = (self.retrieveContacts()
|
||||
|> deliverOn(self.queue)).startStrict(next: { [weak self] contacts, references in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
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 self else {
|
||||
return
|
||||
}
|
||||
self.retrieveContactsDisposable?.dispose()
|
||||
self.retrieveContactsDisposable = (self.retrieveContacts()
|
||||
|> deliverOn(self.queue)).startStrict(next: { [weak self] contacts, references in
|
||||
guard let self else {
|
||||
return
|
||||
}
|
||||
|
||||
if self.currentContacts != contacts {
|
||||
self.currentContacts = contacts
|
||||
updated(self.currentContacts)
|
||||
}
|
||||
if self.currentAppSpecificReferences != references {
|
||||
self.currentAppSpecificReferences = references
|
||||
appSpecificReferencesUpdated(self.currentAppSpecificReferences)
|
||||
}
|
||||
})
|
||||
}
|
||||
})
|
||||
self.updateHandle = handle
|
||||
})
|
||||
self.updateHandle = handle
|
||||
}
|
||||
|
||||
deinit {
|
||||
if let updateHandle = updateHandle {
|
||||
self.retrieveContactsDisposable?.dispose()
|
||||
if let updateHandle = self.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)
|
||||
private func retrieveContacts() -> Signal<([DeviceContactStableId: DeviceContactBasicData], [PeerId: DeviceContactBasicDataWithReference]), NoError> {
|
||||
return self.accountManager.transaction { transaction -> DeviceContactDataState? in
|
||||
return transaction.getSharedData(SharedDataKeys.deviceContacts)?.get(DeviceContactDataState.self)
|
||||
}
|
||||
|> deliverOn(self.queue)
|
||||
|> map { currentData -> ([DeviceContactStableId: DeviceContactBasicData], [PeerId: DeviceContactBasicDataWithReference]) in
|
||||
if let currentData, let stateToken = currentData.stateToken, !sharedDisableDeviceContactDataDiffing {
|
||||
final class ChangeVisitor: NSObject, CNChangeHistoryEventVisitor {
|
||||
let onDropEverything: () -> Void
|
||||
let onAdd: (CNContact) -> Void
|
||||
let onUpdate: (CNContact) -> Void
|
||||
let onDelete: (String) -> Void
|
||||
|
||||
init(onDropEverything: @escaping () -> Void, onAdd: @escaping (CNContact) -> Void, onUpdate: @escaping (CNContact) -> Void, onDelete: @escaping (String) -> Void) {
|
||||
self.onDropEverything = onDropEverything
|
||||
self.onAdd = onAdd
|
||||
self.onUpdate = onUpdate
|
||||
self.onDelete = onDelete
|
||||
|
||||
super.init()
|
||||
}
|
||||
|
||||
func visit(_ event: CNChangeHistoryDropEverythingEvent) {
|
||||
self.onDropEverything()
|
||||
}
|
||||
|
||||
func visit(_ event: CNChangeHistoryAddContactEvent) {
|
||||
self.onAdd(event.contact)
|
||||
}
|
||||
|
||||
func visit(_ event: CNChangeHistoryUpdateContactEvent) {
|
||||
self.onUpdate(event.contact)
|
||||
}
|
||||
|
||||
func visit(_ event: CNChangeHistoryDeleteContactEvent) {
|
||||
self.onDelete(event.contactIdentifier)
|
||||
}
|
||||
}
|
||||
|
||||
let changeRequest = CNChangeHistoryFetchRequest()
|
||||
changeRequest.startingToken = stateToken
|
||||
changeRequest.additionalContactKeyDescriptors = [
|
||||
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
|
||||
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
||||
CNContactUrlAddressesKey as CNKeyDescriptor
|
||||
]
|
||||
|
||||
var contacts: [DeviceContactStableId: DeviceContactBasicData] = currentData.contacts
|
||||
var telegramReferences: [PeerId: String] = currentData.telegramReferences
|
||||
var reverseTelegramReferences: [String: Set<PeerId>] = [:]
|
||||
for (peerId, id) in telegramReferences {
|
||||
if reverseTelegramReferences[id] == nil {
|
||||
reverseTelegramReferences[id] = Set()
|
||||
}
|
||||
reverseTelegramReferences[id]?.insert(peerId)
|
||||
}
|
||||
|
||||
let visitor = ChangeVisitor(
|
||||
onDropEverything: {
|
||||
contacts.removeAll()
|
||||
telegramReferences.removeAll()
|
||||
reverseTelegramReferences.removeAll()
|
||||
},
|
||||
onAdd: { contact in
|
||||
let stableIdAndContact = DeviceContactDataModernContext.parseContact(contact)
|
||||
contacts[stableIdAndContact.0] = stableIdAndContact.1
|
||||
for address in contact.urlAddresses {
|
||||
if address.label == "Telegram", let peerId = parseAppSpecificContactReference(address.value as String) {
|
||||
telegramReferences[peerId] = stableIdAndContact.0
|
||||
if reverseTelegramReferences[stableIdAndContact.0] == nil {
|
||||
reverseTelegramReferences[stableIdAndContact.0] = Set()
|
||||
}
|
||||
reverseTelegramReferences[stableIdAndContact.0]?.insert(peerId)
|
||||
}
|
||||
}
|
||||
},
|
||||
onUpdate: { contact in
|
||||
let stableIdAndContact = DeviceContactDataModernContext.parseContact(contact)
|
||||
contacts[stableIdAndContact.0] = stableIdAndContact.1
|
||||
for address in contact.urlAddresses {
|
||||
if address.label == "Telegram", let peerId = parseAppSpecificContactReference(address.value as String) {
|
||||
telegramReferences[peerId] = stableIdAndContact.0
|
||||
telegramReferences[peerId] = stableIdAndContact.0
|
||||
if reverseTelegramReferences[stableIdAndContact.0] == nil {
|
||||
reverseTelegramReferences[stableIdAndContact.0] = Set()
|
||||
}
|
||||
reverseTelegramReferences[stableIdAndContact.0]?.insert(peerId)
|
||||
}
|
||||
}
|
||||
},
|
||||
onDelete: { contactId in
|
||||
contacts.removeValue(forKey: contactId)
|
||||
if let peerIds = reverseTelegramReferences[contactId] {
|
||||
for peerId in peerIds {
|
||||
telegramReferences.removeValue(forKey: peerId)
|
||||
}
|
||||
reverseTelegramReferences.removeValue(forKey: contactId)
|
||||
}
|
||||
}
|
||||
)
|
||||
|
||||
let resultState = ContactsEnumerateChangeRequest(self.store, changeRequest, visitor)
|
||||
|
||||
let _ = self.accountManager.transaction({ transaction -> Void in
|
||||
transaction.updateSharedData(SharedDataKeys.deviceContacts, { _ in
|
||||
return PreferencesEntry(DeviceContactDataState(
|
||||
contacts: contacts, telegramReferences: telegramReferences, stateToken: resultState?.stateToken))
|
||||
})
|
||||
}).startStandalone()
|
||||
|
||||
var references: [PeerId: DeviceContactBasicDataWithReference] = [:]
|
||||
for (peerId, id) in telegramReferences {
|
||||
if let basicData = contacts[id] {
|
||||
references[peerId] = DeviceContactBasicDataWithReference(stableId: id, basicData: basicData)
|
||||
}
|
||||
}
|
||||
|
||||
return (contacts, references)
|
||||
} else {
|
||||
let keysToFetch: [CNKeyDescriptor] = [
|
||||
CNContactFormatter.descriptorForRequiredKeys(for: .fullName),
|
||||
CNContactPhoneNumbersKey as CNKeyDescriptor,
|
||||
CNContactUrlAddressesKey as CNKeyDescriptor
|
||||
]
|
||||
|
||||
let request = CNContactFetchRequest(keysToFetch: keysToFetch)
|
||||
request.unifyResults = true
|
||||
|
||||
var contacts: [String: DeviceContactBasicData] = [:]
|
||||
var telegramReferences: [EnginePeer.Id: String] = [:]
|
||||
let resultState = ContactsEnumerateRequest(self.store, request, { contact in
|
||||
let stableIdAndContact = DeviceContactDataModernContext.parseContact(contact)
|
||||
contacts[stableIdAndContact.0] = stableIdAndContact.1
|
||||
for address in contact.urlAddresses {
|
||||
if address.label == "Telegram", let peerId = parseAppSpecificContactReference(address.value as String) {
|
||||
telegramReferences[peerId] = stableIdAndContact.0
|
||||
}
|
||||
}
|
||||
})
|
||||
|
||||
let _ = self.accountManager.transaction({ transaction -> Void in
|
||||
transaction.updateSharedData(SharedDataKeys.deviceContacts, { _ in
|
||||
return PreferencesEntry(DeviceContactDataState(
|
||||
contacts: contacts, telegramReferences: telegramReferences, stateToken: resultState?.stateToken))
|
||||
})
|
||||
}).startStandalone()
|
||||
|
||||
var references: [PeerId: DeviceContactBasicDataWithReference] = [:]
|
||||
for (peerId, id) in telegramReferences {
|
||||
if let basicData = contacts[id] {
|
||||
references[peerId] = DeviceContactBasicDataWithReference(stableId: id, basicData: basicData)
|
||||
}
|
||||
}
|
||||
|
||||
return (contacts, references)
|
||||
}
|
||||
})
|
||||
return (result, references)
|
||||
}
|
||||
}
|
||||
|
||||
private static func parseContact(_ contact: CNContact) -> (DeviceContactStableId, DeviceContactBasicData) {
|
||||
@ -323,6 +485,7 @@ private final class BasicDataForNormalizedNumberContext {
|
||||
|
||||
private final class DeviceContactDataManagerPrivateImpl {
|
||||
private let queue: Queue
|
||||
private let accountManager: AccountManager<TelegramAccountManagerTypes>
|
||||
|
||||
private var accessInitialized = false
|
||||
|
||||
@ -345,8 +508,9 @@ private final class DeviceContactDataManagerPrivateImpl {
|
||||
private let importableContactsSubscribers = Bag<([DeviceContactNormalizedPhoneNumber: ImportableDeviceContactData]) -> Void>()
|
||||
private let appSpecificReferencesSubscribers = Bag<([PeerId: DeviceContactBasicDataWithReference]) -> Void>()
|
||||
|
||||
init(queue: Queue) {
|
||||
init(queue: Queue, accountManager: AccountManager<TelegramAccountManagerTypes>) {
|
||||
self.queue = queue
|
||||
self.accountManager = accountManager
|
||||
self.accessDisposable = (DeviceAccess.authorizationStatus(subject: .contacts)
|
||||
|> delay(2.0, queue: .mainQueue())
|
||||
|> deliverOn(self.queue)).startStrict(next: { [weak self] authorizationStatus in
|
||||
@ -355,7 +519,7 @@ private final class DeviceContactDataManagerPrivateImpl {
|
||||
}
|
||||
strongSelf.accessInitialized = true
|
||||
if authorizationStatus == .allowed {
|
||||
let dataContext = DeviceContactDataModernContext(queue: strongSelf.queue, updated: { stableIdToBasicContactData in
|
||||
let dataContext = DeviceContactDataModernContext(queue: strongSelf.queue, accountManager: strongSelf.accountManager, updated: { stableIdToBasicContactData in
|
||||
guard let strongSelf = self else {
|
||||
return
|
||||
}
|
||||
@ -605,10 +769,10 @@ public final class DeviceContactDataManagerImpl: DeviceContactDataManager {
|
||||
private let queue = Queue()
|
||||
private let impl: QueueLocalObject<DeviceContactDataManagerPrivateImpl>
|
||||
|
||||
init() {
|
||||
init(accountManager: AccountManager<TelegramAccountManagerTypes>) {
|
||||
let queue = self.queue
|
||||
self.impl = QueueLocalObject(queue: queue, generate: {
|
||||
return DeviceContactDataManagerPrivateImpl(queue: queue)
|
||||
return DeviceContactDataManagerPrivateImpl(queue: queue, accountManager: accountManager)
|
||||
})
|
||||
}
|
||||
|
||||
|
@ -316,7 +316,7 @@ public final class SharedAccountContextImpl: SharedAccountContext {
|
||||
|
||||
if applicationBindings.isMainApp {
|
||||
self.locationManager = DeviceLocationManager(queue: Queue.mainQueue())
|
||||
self.contactDataManager = DeviceContactDataManagerImpl()
|
||||
self.contactDataManager = DeviceContactDataManagerImpl(accountManager: accountManager)
|
||||
} else {
|
||||
self.locationManager = nil
|
||||
self.contactDataManager = nil
|
||||
|
Loading…
x
Reference in New Issue
Block a user