Don't cache CNContact.

pull/1/head
Matthew Chen 8 years ago
parent e2de8f6ffa
commit 12295bd8c5

@ -55,17 +55,34 @@ class AddContactShareToExistingContactViewController: ContactsPicker, ContactsPi
navigationController.popViewController(animated: true) navigationController.popViewController(animated: true)
} }
func contactsPicker(_: ContactsPicker, didSelectContact contact: Contact) { func contactsPicker(_: ContactsPicker, didSelectContact newContact: Contact) {
Logger.debug("\(self.logTag) in \(#function)") Logger.debug("\(self.logTag) in \(#function)")
guard let mergedContact: CNContact = self.contactShare.cnContact(mergedWithExistingContact: contact) else { let contactsManager = Environment.current().contactsManager
owsFail("\(logTag) in \(#function) mergedContact was unexpectedly nil") contactsManager?.cnContact(withId: self.contactShare.cnContactId,
return success: { [weak self] oldCNContact in
} contactsManager?.cnContact(withId: newContact.cnContactId,
success: { newCNContact in
guard let strongSelf = weakSelf else {
return
}
strongSelf.merge(oldCNContact: oldCNContact, newCNContact: newCNContact)
}, failure: {
// TODO: Alert?
})
}, failure: {
// TODO: Alert?
})
}
func merge(oldCNContact: CNContact, newCNContact: CNContact) {
Logger.debug("\(self.logTag) in \(#function)")
let mergedCNContact: CNContact = Contact.merge(cnContact: oldCNContact, newCNContact: newCNContact)
// Not actually a "new" contact, but this brings up the edit form rather than the "Read" form // Not actually a "new" contact, but this brings up the edit form rather than the "Read" form
// saving our users a tap in some cases when we already know they want to edit. // saving our users a tap in some cases when we already know they want to edit.
let contactViewController: CNContactViewController = CNContactViewController(forNewContact: mergedContact) let contactViewController: CNContactViewController = CNContactViewController(forNewContact: mergedCNContact)
// Default title is "New Contact". We could give a more descriptive title, but anything // Default title is "New Contact". We could give a more descriptive title, but anything
// seems redundant - the context is sufficiently clear. // seems redundant - the context is sufficiently clear.

@ -148,16 +148,20 @@ public class ContactShareViewModel: NSObject {
return dbRecord.isProfileAvatar return dbRecord.isProfileAvatar
} }
@objc // @objc
public func cnContact(mergedWithExistingContact existingContact: Contact) -> CNContact? { // public func cnContact(mergedWithExistingContact existingContact: Contact) -> CNContact? {
// public func cnContact(mergedWithExistingContact existingContact: Contact) -> CNContact? {
guard let newCNContact = OWSContacts.systemContact(for: self.dbRecord, imageData: self.avatarImageData) else { //// success successParam: @escaping (CNContact) -> Void,
owsFail("\(logTag) in \(#function) newCNContact was unexpectedly nil") //// failure failureParam: @escaping () -> Void) {
return nil //
} // guard let newCNContact = OWSContacts.systemContact(for: self.dbRecord, imageData: self.avatarImageData) else {
// owsFail("\(logTag) in \(#function) newCNContact was unexpectedly nil")
return existingContact.buildCNContact(mergedWithNewContact: newCNContact) // return nil
} // }
//
// let mergedCNContact = Contact.merge(cnContact: <#T##CNContact#>, newCNContact: <#T##CNContact#>)
// return existingContact.buildCNContact(mergedWithNewContact: newCNContact)
// }
@objc @objc
public func copy(withName name: OWSContactName) -> ContactShareViewModel { public func copy(withName name: OWSContactName) -> ContactShareViewModel {

@ -149,37 +149,7 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
self.thread = thread; self.thread = thread;
if (self.attachment.isConvertibleToContactShare) { if (self.attachment.isConvertibleToContactShare) {
NSData *data = self.attachment.data; [self showContactShareApproval];
Contact *_Nullable contact = [Contact contactWithVCardData:data];
OWSContact *_Nullable contactShareRecord = [OWSContacts contactForSystemContact:contact.cnContact];
if (!contactShareRecord) {
DDLogError(@"%@ Could not convert system contact.", self.logTag);
return;
}
BOOL isProfileAvatar = NO;
NSData *_Nullable avatarImageData = contact.imageData;
for (NSString *recipientId in contact.textSecureIdentifiers) {
if (avatarImageData) {
break;
}
avatarImageData = [self.contactsManager profileImageDataForPhoneIdentifier:recipientId];
if (avatarImageData) {
isProfileAvatar = YES;
}
}
contactShareRecord.isProfileAvatar = isProfileAvatar;
ContactShareViewModel *contactShare =
[[ContactShareViewModel alloc] initWithContactShareRecord:contactShareRecord
avatarImageData:avatarImageData];
ContactShareApprovalViewController *approvalVC =
[[ContactShareApprovalViewController alloc] initWithContactShare:contactShare
contactsManager:self.contactsManager
delegate:self];
[self.navigationController pushViewController:approvalVC animated:YES];
return; return;
} }
@ -200,6 +170,45 @@ typedef void (^SendMessageBlock)(SendCompletionBlock completion);
} }
} }
- (void)showContactShareApproval
{
OWSAssert(self.attachment);
OWSAssert(self.thread);
OWSAssert(self.attachment.isConvertibleToContactShare);
NSData *data = self.attachment.data;
CNContact *_Nullable cnContact = [Contact cnContactWithVCardData:data];
Contact *_Nullable contact = [[Contact alloc] initWithSystemContact:cnContact];
OWSContact *_Nullable contactShareRecord = [OWSContacts contactForSystemContact:cnContact];
if (!contactShareRecord) {
DDLogError(@"%@ Could not convert system contact.", self.logTag);
return;
}
BOOL isProfileAvatar = NO;
NSData *_Nullable avatarImageData = contact.imageData;
for (NSString *recipientId in contact.textSecureIdentifiers) {
if (avatarImageData) {
break;
}
avatarImageData = [self.contactsManager profileImageDataForPhoneIdentifier:recipientId];
if (avatarImageData) {
isProfileAvatar = YES;
}
}
contactShareRecord.isProfileAvatar = isProfileAvatar;
ContactShareViewModel *contactShare =
[[ContactShareViewModel alloc] initWithContactShareRecord:contactShareRecord avatarImageData:avatarImageData];
ContactShareApprovalViewController *approvalVC =
[[ContactShareApprovalViewController alloc] initWithContactShare:contactShare
contactsManager:self.contactsManager
delegate:self];
[self.navigationController pushViewController:approvalVC animated:YES];
}
// override // override
- (void)dismissPressed:(id)sender - (void)dismissPressed:(id)sender
{ {

@ -9,6 +9,9 @@ NS_ASSUME_NONNULL_BEGIN
extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification; extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification;
typedef void (^CNContactFetchSuccess)(CNContact *cnContact);
typedef void (^CNContactFetchFailure)(void);
@class ImageCache; @class ImageCache;
@class SignalAccount; @class SignalAccount;
@class UIFont; @class UIFont;
@ -59,6 +62,10 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification;
// contacts haven't changed, and will clear out any stale cached SignalAccounts // contacts haven't changed, and will clear out any stale cached SignalAccounts
- (void)userRequestedSystemContactsRefreshWithCompletion:(void (^)(NSError *_Nullable error))completionHandler; - (void)userRequestedSystemContactsRefreshWithCompletion:(void (^)(NSError *_Nullable error))completionHandler;
- (void)cnContactWithId:(NSString *)contactId
success:(CNContactFetchSuccess)success
failure:(CNContactFetchFailure)failure;
#pragma mark - Util #pragma mark - Util
- (BOOL)isSystemContact:(NSString *)recipientId; - (BOOL)isSystemContact:(NSString *)recipientId;

@ -133,6 +133,17 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
return self.systemContactsFetcher.supportsContactEditing; return self.systemContactsFetcher.supportsContactEditing;
} }
- (void)cnContactWithId:(NSString *)contactId
success:(CNContactFetchSuccess)success
failure:(CNContactFetchFailure)failure
{
OWSAssert(contactId.length > 0);
OWSAssert(success);
OWSAssert(failure);
return [self.systemContactsFetcher fetchCNContactWithContactId:contactId success:success failure:failure];
}
#pragma mark - SystemContactsFetcherDelegate #pragma mark - SystemContactsFetcherDelegate
- (void)systemContactsFetcher:(SystemContactsFetcher *)systemsContactsFetcher - (void)systemContactsFetcher:(SystemContactsFetcher *)systemsContactsFetcher
@ -255,13 +266,13 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { [self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
for (Contact *contact in contacts) { for (Contact *contact in contacts) {
NSArray<SignalRecipient *> *signalRecipients = [contact signalRecipientsWithTransaction:transaction]; NSArray<SignalRecipient *> *signalRecipients = [contact signalRecipientsWithTransaction:transaction];
contactIdToSignalRecipientsMap[contact.uniqueId] = signalRecipients; contactIdToSignalRecipientsMap[contact.cnContactId] = signalRecipients;
} }
}]; }];
NSMutableSet<NSString *> *seenRecipientIds = [NSMutableSet new]; NSMutableSet<NSString *> *seenRecipientIds = [NSMutableSet new];
for (Contact *contact in contacts) { for (Contact *contact in contacts) {
NSArray<SignalRecipient *> *signalRecipients = contactIdToSignalRecipientsMap[contact.uniqueId]; NSArray<SignalRecipient *> *signalRecipients = contactIdToSignalRecipientsMap[contact.cnContactId];
for (SignalRecipient *signalRecipient in [signalRecipients sortedArrayUsingSelector:@selector(compare:)]) { for (SignalRecipient *signalRecipient in [signalRecipients sortedArrayUsingSelector:@selector(compare:)]) {
if ([seenRecipientIds containsObject:signalRecipient.recipientId]) { if ([seenRecipientIds containsObject:signalRecipient.recipientId]) {
DDLogDebug(@"Ignoring duplicate contact: %@, %@", signalRecipient.recipientId, contact.fullName); DDLogDebug(@"Ignoring duplicate contact: %@, %@", signalRecipient.recipientId, contact.fullName);
@ -584,7 +595,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
NSAttributedString *lastName = NSAttributedString *lastName =
[[NSAttributedString alloc] initWithString:cachedLastName attributes:lastNameAttributes]; [[NSAttributedString alloc] initWithString:cachedLastName attributes:lastNameAttributes];
CNContact *_Nullable cnContact = self.allContactsMap[recipientId].cnContact; CNContact *_Nullable cnContact = self.allContactsMap[recipientId].cnContactForFormatting;
if (!cnContact) { if (!cnContact) {
// If we don't have a CNContact for this recipient id, make one. // If we don't have a CNContact for this recipient id, make one.
// Presumably [CNContactFormatter nameOrderForContact:] tries // Presumably [CNContactFormatter nameOrderForContact:] tries

@ -17,12 +17,12 @@ protocol ContactStoreAdaptee {
var supportsContactEditing: Bool { get } var supportsContactEditing: Bool { get }
func requestAccess(completionHandler: @escaping (Bool, Error?) -> Void) func requestAccess(completionHandler: @escaping (Bool, Error?) -> Void)
func fetchContacts() -> Result<[Contact], Error> func fetchContacts() -> Result<[Contact], Error>
func fetchCNContact(contactId: String) -> CNContact?
func startObservingChanges(changeHandler: @escaping () -> Void) func startObservingChanges(changeHandler: @escaping () -> Void)
} }
public public
class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee { class ContactsFrameworkContactStoreAdaptee: NSObject, ContactStoreAdaptee {
let TAG = "[ContactsFrameworkContactStoreAdaptee]"
private let contactStore = CNContactStore() private let contactStore = CNContactStore()
private var changeHandler: (() -> Void)? private var changeHandler: (() -> Void)?
private var initializedObserver = false private var initializedObserver = false
@ -72,7 +72,7 @@ class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee {
return return
} }
Logger.info("\(self.TAG) sort order changed: \(String(describing: self.lastSortOrder)) -> \(String(describing: currentSortOrder))") Logger.info("\(self.logTag) sort order changed: \(String(describing: self.lastSortOrder)) -> \(String(describing: currentSortOrder))")
self.lastSortOrder = currentSortOrder self.lastSortOrder = currentSortOrder
self.runChangeHandler() self.runChangeHandler()
} }
@ -81,7 +81,7 @@ class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee {
@objc @objc
func runChangeHandler() { func runChangeHandler() {
guard let changeHandler = self.changeHandler else { guard let changeHandler = self.changeHandler else {
owsFail("\(TAG) trying to run change handler before it was registered") owsFail("\(self.logTag) trying to run change handler before it was registered")
return return
} }
changeHandler() changeHandler()
@ -100,13 +100,35 @@ class ContactsFrameworkContactStoreAdaptee: ContactStoreAdaptee {
systemContacts.append(contact) systemContacts.append(contact)
} }
} catch let error as NSError { } catch let error as NSError {
owsFail("\(self.TAG) Failed to fetch contacts with error:\(error)") owsFail("\(self.logTag) Failed to fetch contacts with error:\(error)")
return .error(error) return .error(error)
} }
let contacts = systemContacts.map { Contact(systemContact: $0) } let contacts = systemContacts.map { Contact(systemContact: $0) }
return .success(contacts) return .success(contacts)
} }
func fetchCNContact(contactId: String) -> CNContact? {
var result: CNContact?
do {
let contactFetchRequest = CNContactFetchRequest(keysToFetch: ContactsFrameworkContactStoreAdaptee.allowedContactKeys)
contactFetchRequest.sortOrder = .userDefault
contactFetchRequest.predicate = CNContact.predicateForContacts(withIdentifiers: [contactId])
try self.contactStore.enumerateContacts(with: contactFetchRequest) { (contact, _) -> Void in
guard result == nil else {
owsFail("\(self.logTag) More than one contact with contact id.")
return
}
result = contact
}
} catch let error as NSError {
owsFail("\(self.logTag) Failed to fetch contact with error:\(error)")
return nil
}
return result
}
} }
@objc @objc
@ -125,8 +147,6 @@ public enum ContactStoreAuthorizationStatus: UInt {
@objc @objc
public class SystemContactsFetcher: NSObject { public class SystemContactsFetcher: NSObject {
private let TAG = "[SystemContactsFetcher]"
private let serialQueue = DispatchQueue(label: "SystemContactsFetcherQueue") private let serialQueue = DispatchQueue(label: "SystemContactsFetcherQueue")
var lastContactUpdateHash: Int? var lastContactUpdateHash: Int?
@ -207,20 +227,20 @@ public class SystemContactsFetcher: NSObject {
switch authorizationStatus { switch authorizationStatus {
case .notDetermined: case .notDetermined:
if CurrentAppContext().isInBackground() { if CurrentAppContext().isInBackground() {
Logger.error("\(self.TAG) do not request contacts permission when app is in background") Logger.error("\(self.logTag) do not request contacts permission when app is in background")
completion(nil) completion(nil)
return return
} }
self.contactStoreAdapter.requestAccess { (granted, error) in self.contactStoreAdapter.requestAccess { (granted, error) in
if let error = error { if let error = error {
Logger.error("\(self.TAG) error fetching contacts: \(error)") Logger.error("\(self.logTag) error fetching contacts: \(error)")
completion(error) completion(error)
return return
} }
guard granted else { guard granted else {
// This case should have been caught by the error guard a few lines up. // This case should have been caught by the error guard a few lines up.
owsFail("\(self.TAG) declined contact access.") owsFail("\(self.logTag) declined contact access.")
completion(nil) completion(nil)
return return
} }
@ -232,7 +252,7 @@ public class SystemContactsFetcher: NSObject {
case .authorized: case .authorized:
self.updateContacts(completion: completion) self.updateContacts(completion: completion)
case .denied, .restricted: case .denied, .restricted:
Logger.debug("\(TAG) contacts were \(self.authorizationStatus)") Logger.debug("\(logTag) contacts were \(self.authorizationStatus)")
self.delegate?.systemContactsFetcher(self, hasAuthorizationStatus: authorizationStatus) self.delegate?.systemContactsFetcher(self, hasAuthorizationStatus: authorizationStatus)
completion(nil) completion(nil)
} }
@ -292,7 +312,7 @@ public class SystemContactsFetcher: NSObject {
guard let _ = self else { guard let _ = self else {
return return
} }
Logger.error("background task time ran out contacts fetch completed.") Logger.error("background task time ran out before contacts fetch completed.")
}) })
// Ensure completion is invoked on main thread. // Ensure completion is invoked on main thread.
@ -310,7 +330,7 @@ public class SystemContactsFetcher: NSObject {
serialQueue.async { serialQueue.async {
Logger.info("\(self.TAG) fetching contacts") Logger.info("\(self.logTag) fetching contacts")
var fetchedContacts: [Contact]? var fetchedContacts: [Contact]?
switch self.contactStoreAdapter.fetchContacts() { switch self.contactStoreAdapter.fetchContacts() {
@ -322,21 +342,21 @@ public class SystemContactsFetcher: NSObject {
} }
guard let contacts = fetchedContacts else { guard let contacts = fetchedContacts else {
owsFail("\(self.TAG) contacts was unexpectedly not set.") owsFail("\(self.logTag) contacts was unexpectedly not set.")
completion(nil) completion(nil)
} }
Logger.info("\(self.TAG) fetched \(contacts.count) contacts.") Logger.info("\(self.logTag) fetched \(contacts.count) contacts.")
let contactsHash = HashableArray(contacts).hashValue let contactsHash = HashableArray(contacts).hashValue
DispatchQueue.main.async { DispatchQueue.main.async {
var shouldNotifyDelegate = false var shouldNotifyDelegate = false
if self.lastContactUpdateHash != contactsHash { if self.lastContactUpdateHash != contactsHash {
Logger.info("\(self.TAG) contact hash changed. new contactsHash: \(contactsHash)") Logger.info("\(self.logTag) contact hash changed. new contactsHash: \(contactsHash)")
shouldNotifyDelegate = true shouldNotifyDelegate = true
} else if isUserRequested { } else if isUserRequested {
Logger.info("\(self.TAG) ignoring debounce due to user request") Logger.info("\(self.logTag) ignoring debounce due to user request")
shouldNotifyDelegate = true shouldNotifyDelegate = true
} else { } else {
@ -346,19 +366,19 @@ public class SystemContactsFetcher: NSObject {
let expiresAtDate = Date(timeInterval: kDebounceInterval, since: lastDelegateNotificationDate) let expiresAtDate = Date(timeInterval: kDebounceInterval, since: lastDelegateNotificationDate)
if Date() > expiresAtDate { if Date() > expiresAtDate {
Logger.info("\(self.TAG) debounce interval expired at: \(expiresAtDate)") Logger.info("\(self.logTag) debounce interval expired at: \(expiresAtDate)")
shouldNotifyDelegate = true shouldNotifyDelegate = true
} else { } else {
Logger.info("\(self.TAG) ignoring since debounce interval hasn't expired") Logger.info("\(self.logTag) ignoring since debounce interval hasn't expired")
} }
} else { } else {
Logger.info("\(self.TAG) first contact fetch. contactsHash: \(contactsHash)") Logger.info("\(self.logTag) first contact fetch. contactsHash: \(contactsHash)")
shouldNotifyDelegate = true shouldNotifyDelegate = true
} }
} }
guard shouldNotifyDelegate else { guard shouldNotifyDelegate else {
Logger.info("\(self.TAG) no reason to notify delegate.") Logger.info("\(self.logTag) no reason to notify delegate.")
completion(nil) completion(nil)
@ -373,6 +393,66 @@ public class SystemContactsFetcher: NSObject {
} }
} }
} }
@objc
public func fetchCNContact(contactId: String,
success successParam: @escaping (CNContact) -> Void,
failure failureParam: @escaping () -> Void) {
SwiftAssertIsOnMainThread(#function)
guard authorizationStatus == .authorized else {
Logger.error("\(logTag) contact fetch failed; no access.")
failureParam()
return
}
var backgroundTask: OWSBackgroundTask? = OWSBackgroundTask(label: "\(#function)", completionBlock: { [weak self] status in
SwiftAssertIsOnMainThread(#function)
guard status == .expired else {
return
}
guard let _ = self else {
return
}
Logger.error("background task time ran out before contact fetch completed.")
})
// Ensure success is invoked on main thread.
let success: (CNContact) -> Void = { contact in
DispatchMainThreadSafe({
successParam(contact)
assert(backgroundTask != nil)
backgroundTask = nil
})
}
// Ensure success is invoked on main thread.
let failure: () -> Void = {
DispatchMainThreadSafe({
failureParam()
assert(backgroundTask != nil)
backgroundTask = nil
})
}
// Don't use the serial queue.
DispatchQueue.global().async {
Logger.info("\(self.logTag) fetching contact")
if let cnContact = self.contactStoreAdapter.fetchCNContact(contactId: contactId) {
Logger.info("\(self.logTag) contact found")
success(cnContact)
} else {
Logger.info("\(self.logTag) contact not found")
failure()
}
}
}
} }
struct HashableArray<Element: Hashable>: Hashable { struct HashableArray<Element: Hashable>: Hashable {

@ -26,12 +26,11 @@ NS_ASSUME_NONNULL_BEGIN
@property (readonly, nonatomic) NSArray<PhoneNumber *> *parsedPhoneNumbers; @property (readonly, nonatomic) NSArray<PhoneNumber *> *parsedPhoneNumbers;
@property (readonly, nonatomic) NSArray<NSString *> *userTextPhoneNumbers; @property (readonly, nonatomic) NSArray<NSString *> *userTextPhoneNumbers;
@property (readonly, nonatomic) NSArray<NSString *> *emails; @property (readonly, nonatomic) NSArray<NSString *> *emails;
@property (readonly, nonatomic) NSString *uniqueId;
@property (nonatomic, readonly) BOOL isSignalContact; @property (nonatomic, readonly) BOOL isSignalContact;
@property (nonatomic, readonly) NSString *cnContactId;
#if TARGET_OS_IOS #if TARGET_OS_IOS
@property (nullable, readonly, nonatomic) UIImage *image; @property (nullable, readonly, nonatomic) UIImage *image;
@property (nullable, readonly, nonatomic) NSData *imageData; @property (nullable, readonly, nonatomic) NSData *imageData;
@property (nullable, nonatomic, readonly) CNContact *cnContact;
#endif // TARGET_OS_IOS #endif // TARGET_OS_IOS
- (NSArray<SignalRecipient *> *)signalRecipientsWithTransaction:(YapDatabaseReadTransaction *)transaction; - (NSArray<SignalRecipient *> *)signalRecipientsWithTransaction:(YapDatabaseReadTransaction *)transaction;
@ -41,7 +40,7 @@ NS_ASSUME_NONNULL_BEGIN
#if TARGET_OS_IOS #if TARGET_OS_IOS
- (instancetype)initWithSystemContact:(CNContact *)contact NS_AVAILABLE_IOS(9_0); - (instancetype)initWithSystemContact:(CNContact *)contact NS_AVAILABLE_IOS(9_0);
+ (nullable Contact *)contactWithVCardData:(NSData *)data; + (nullable CNContact *)cnContactWithVCardData:(NSData *)data;
- (NSString *)nameForPhoneNumber:(NSString *)recipientId; - (NSString *)nameForPhoneNumber:(NSString *)recipientId;
@ -51,7 +50,10 @@ NS_ASSUME_NONNULL_BEGIN
+ (NSString *)formattedFullNameWithCNContact:(CNContact *)cnContact NS_SWIFT_NAME(formattedFullName(cnContact:)); + (NSString *)formattedFullNameWithCNContact:(CNContact *)cnContact NS_SWIFT_NAME(formattedFullName(cnContact:));
+ (nullable NSString *)localizedStringForCNLabel:(nullable NSString *)cnLabel; + (nullable NSString *)localizedStringForCNLabel:(nullable NSString *)cnLabel;
- (CNContact *)buildCNContactMergedWithNewContact:(CNContact *)newCNContact NS_SWIFT_NAME(buildCNContact(mergedWithNewContact:)); + (CNContact *)mergeCNContact:(CNContact *)oldCNContact
newCNContact:(CNContact *)newCNContact NS_SWIFT_NAME(merge(cnContact:newCNContact:));
- (CNContact *)cnContactForFormatting;
@end @end

@ -16,7 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
@interface Contact () @interface Contact ()
@property (readonly, nonatomic) NSMutableDictionary<NSString *, NSString *> *phoneNumberNameMap; @property (nonatomic, readonly) NSMutableDictionary<NSString *, NSString *> *phoneNumberNameMap;
@end @end
@ -37,11 +37,10 @@ NS_ASSUME_NONNULL_BEGIN
return self; return self;
} }
_cnContact = contact; _cnContactId = contact.identifier;
_firstName = contact.givenName.ows_stripped; _firstName = contact.givenName.ows_stripped;
_lastName = contact.familyName.ows_stripped; _lastName = contact.familyName.ows_stripped;
_fullName = [Contact formattedFullNameWithCNContact:contact]; _fullName = [Contact formattedFullNameWithCNContact:contact];
_uniqueId = contact.identifier;
NSMutableArray<NSString *> *phoneNumbers = [NSMutableArray new]; NSMutableArray<NSString *> *phoneNumbers = [NSMutableArray new];
NSMutableDictionary<NSString *, NSString *> *phoneNumberNameMap = [NSMutableDictionary new]; NSMutableDictionary<NSString *, NSString *> *phoneNumberNameMap = [NSMutableDictionary new];
@ -114,17 +113,6 @@ NS_ASSUME_NONNULL_BEGIN
return self; return self;
} }
+ (nullable Contact *)contactWithVCardData:(NSData *)data
{
CNContact *_Nullable cnContact = [self cnContactWithVCardData:data];
if (!cnContact) {
return nil;
}
return [[self alloc] initWithSystemContact:cnContact];
}
- (nullable UIImage *)image - (nullable UIImage *)image
{ {
if (_image) { if (_image) {
@ -326,9 +314,14 @@ NS_ASSUME_NONNULL_BEGIN
return contacts.firstObject; return contacts.firstObject;
} }
- (CNContact *)buildCNContactMergedWithNewContact:(CNContact *)newCNContact + (CNContact *)mergeCNContact:(CNContact *)oldCNContact newCNContact:(CNContact *)newCNContact
{ {
CNMutableContact *_Nullable mergedCNContact = [self.cnContact mutableCopy]; OWSAssert(oldCNContact);
OWSAssert(newCNContact);
Contact *oldContact = [[Contact alloc] initWithSystemContact:oldCNContact];
CNMutableContact *_Nullable mergedCNContact = [oldCNContact mutableCopy];
if (!mergedCNContact) { if (!mergedCNContact) {
OWSFail(@"%@ in %s mergedCNContact was unexpectedly nil", self.logTag, __PRETTY_FUNCTION__); OWSFail(@"%@ in %s mergedCNContact was unexpectedly nil", self.logTag, __PRETTY_FUNCTION__);
return [CNContact new]; return [CNContact new];
@ -351,8 +344,8 @@ NS_ASSUME_NONNULL_BEGIN
} }
// Phone Numbers // Phone Numbers
NSSet<PhoneNumber *> *existingParsedPhoneNumberSet = [NSSet setWithArray:self.parsedPhoneNumbers]; NSSet<PhoneNumber *> *existingParsedPhoneNumberSet = [NSSet setWithArray:oldContact.parsedPhoneNumbers];
NSSet<NSString *> *existingUnparsedPhoneNumberSet = [NSSet setWithArray:self.userTextPhoneNumbers]; NSSet<NSString *> *existingUnparsedPhoneNumberSet = [NSSet setWithArray:oldContact.userTextPhoneNumbers];
NSMutableArray<CNLabeledValue<CNPhoneNumber *> *> *mergedPhoneNumbers = [mergedCNContact.phoneNumbers mutableCopy]; NSMutableArray<CNLabeledValue<CNPhoneNumber *> *> *mergedPhoneNumbers = [mergedCNContact.phoneNumbers mutableCopy];
for (CNLabeledValue<CNPhoneNumber *> *labeledPhoneNumber in newCNContact.phoneNumbers) { for (CNLabeledValue<CNPhoneNumber *> *labeledPhoneNumber in newCNContact.phoneNumbers) {
@ -371,7 +364,7 @@ NS_ASSUME_NONNULL_BEGIN
mergedCNContact.phoneNumbers = mergedPhoneNumbers; mergedCNContact.phoneNumbers = mergedPhoneNumbers;
// Emails // Emails
NSSet<NSString *> *existingEmailSet = [NSSet setWithArray:self.emails]; NSSet<NSString *> *existingEmailSet = [NSSet setWithArray:oldContact.emails];
NSMutableArray<CNLabeledValue<NSString *> *> *mergedEmailAddresses = [mergedCNContact.emailAddresses mutableCopy]; NSMutableArray<CNLabeledValue<NSString *> *> *mergedEmailAddresses = [mergedCNContact.emailAddresses mutableCopy];
for (CNLabeledValue<NSString *> *labeledEmail in newCNContact.emailAddresses) { for (CNLabeledValue<NSString *> *labeledEmail in newCNContact.emailAddresses) {
NSString *normalizedValue = labeledEmail.value.ows_stripped; NSString *normalizedValue = labeledEmail.value.ows_stripped;
@ -416,6 +409,14 @@ NS_ASSUME_NONNULL_BEGIN
return localizedLabel; return localizedLabel;
} }
- (CNContact *)cnContactForFormatting
{
CNMutableContact *cnContact = [CNMutableContact new];
cnContact.givenName = self.firstName;
cnContact.familyName = self.lastName;
return cnContact;
}
@end @end
NS_ASSUME_NONNULL_END NS_ASSUME_NONNULL_END

Loading…
Cancel
Save