mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Merge branch 'hotfix/2.19.3'
This commit is contained in:
commit
157bf00410
7 changed files with 197 additions and 72 deletions
|
@ -149,9 +149,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
{
|
||||
OWSAssert([NSThread isMainThread]);
|
||||
|
||||
[self.contactsViewHelper.contactsManager fetchSystemContactsIfAlreadyAuthorizedAndAlwaysNotify];
|
||||
|
||||
[refreshControl endRefreshing];
|
||||
[self.contactsViewHelper.contactsManager
|
||||
userRequestedSystemContactsRefreshWithCompletion:^(NSError *_Nullable error) {
|
||||
if (error) {
|
||||
DDLogError(@"%@ refreshing contacts failed with error: %@", self.logTag, error);
|
||||
}
|
||||
[refreshControl endRefreshing];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showContactsPermissionReminder:(BOOL)isVisible
|
||||
|
|
|
@ -52,9 +52,11 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification;
|
|||
// Ensure's the app has the latest contacts, but won't prompt the user for contact
|
||||
// access if they haven't granted it.
|
||||
- (void)fetchSystemContactsOnceIfAlreadyAuthorized;
|
||||
|
||||
// This variant will fetch system contacts if contact access has already been granted,
|
||||
// but not prompt for contact access. Also, it will always fire a notification.
|
||||
- (void)fetchSystemContactsIfAlreadyAuthorizedAndAlwaysNotify;
|
||||
// but not prompt for contact access. Also, it will always notify delegates, even if
|
||||
// contacts haven't changed, and will clear out any stale cached SignalAccounts
|
||||
- (void)userRequestedSystemContactsRefreshWithCompletion:(void (^)(NSError *_Nullable error))completionHandler;
|
||||
|
||||
#pragma mark - Util
|
||||
|
||||
|
|
|
@ -69,18 +69,31 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
|||
{
|
||||
__block NSMutableArray<SignalAccount *> *signalAccounts;
|
||||
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
|
||||
signalAccounts = [[NSMutableArray alloc]
|
||||
initWithCapacity:[SignalAccount numberOfKeysInCollectionWithTransaction:transaction]];
|
||||
NSUInteger signalAccountCount = [SignalAccount numberOfKeysInCollectionWithTransaction:transaction];
|
||||
DDLogInfo(@"%@ loading %lu signal accounts from cache.", self.logTag, (unsigned long)signalAccountCount);
|
||||
|
||||
[SignalAccount enumerateCollectionObjectsWithTransaction:transaction
|
||||
usingBlock:^(SignalAccount *signalAccount, BOOL *_Nonnull stop) {
|
||||
[signalAccounts addObject:signalAccount];
|
||||
}];
|
||||
signalAccounts = [[NSMutableArray alloc] initWithCapacity:signalAccountCount];
|
||||
|
||||
[SignalAccount enumerateCollectionObjectsWithTransaction:transaction usingBlock:^(SignalAccount *signalAccount, BOOL * _Nonnull stop) {
|
||||
[signalAccounts addObject:signalAccount];
|
||||
}];
|
||||
}];
|
||||
[signalAccounts sortUsingComparator:self.signalAccountComparator];
|
||||
|
||||
[self updateSignalAccounts:signalAccounts];
|
||||
}
|
||||
|
||||
- (dispatch_queue_t)serialQueue
|
||||
{
|
||||
static dispatch_queue_t _serialQueue;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
_serialQueue = dispatch_queue_create("org.whispersystems.contacts.buildSignalAccount", DISPATCH_QUEUE_SERIAL);
|
||||
});
|
||||
|
||||
return _serialQueue;
|
||||
}
|
||||
|
||||
#pragma mark - System Contact Fetching
|
||||
|
||||
// Request contacts access if you haven't asked recently.
|
||||
|
@ -99,9 +112,9 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
|||
[self.systemContactsFetcher fetchOnceIfAlreadyAuthorized];
|
||||
}
|
||||
|
||||
- (void)fetchSystemContactsIfAlreadyAuthorizedAndAlwaysNotify
|
||||
- (void)userRequestedSystemContactsRefreshWithCompletion:(void (^)(NSError *_Nullable error))completionHandler
|
||||
{
|
||||
[self.systemContactsFetcher fetchIfAlreadyAuthorizedAndAlwaysNotify];
|
||||
[self.systemContactsFetcher userRequestedRefreshWithCompletion:completionHandler];
|
||||
}
|
||||
|
||||
- (BOOL)isSystemContactsAuthorized
|
||||
|
@ -123,27 +136,30 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
|||
|
||||
- (void)systemContactsFetcher:(SystemContactsFetcher *)systemsContactsFetcher
|
||||
updatedContacts:(NSArray<Contact *> *)contacts
|
||||
isUserRequested:(BOOL)isUserRequested
|
||||
{
|
||||
[self updateWithContacts:contacts];
|
||||
[self updateWithContacts:contacts shouldClearStaleCache:isUserRequested];
|
||||
}
|
||||
|
||||
#pragma mark - Intersection
|
||||
|
||||
- (void)intersectContacts
|
||||
- (void)intersectContactsWithCompletion:(void (^)(NSError *_Nullable error))completionBlock
|
||||
{
|
||||
[self intersectContactsWithRetryDelay:1];
|
||||
[self intersectContactsWithRetryDelay:1 completion:completionBlock];
|
||||
}
|
||||
|
||||
- (void)intersectContactsWithRetryDelay:(double)retryDelaySeconds
|
||||
completion:(void (^)(NSError *_Nullable error))completionBlock
|
||||
{
|
||||
void (^success)(void) = ^{
|
||||
DDLogInfo(@"%@ Successfully intersected contacts.", self.logTag);
|
||||
[self buildSignalAccounts];
|
||||
completionBlock(nil);
|
||||
};
|
||||
void (^failure)(NSError *error) = ^(NSError *error) {
|
||||
if ([error.domain isEqualToString:OWSSignalServiceKitErrorDomain]
|
||||
&& error.code == OWSErrorCodeContactsUpdaterRateLimit) {
|
||||
DDLogError(@"Contact intersection hit rate limit with error: %@", error);
|
||||
completionBlock(error);
|
||||
return;
|
||||
}
|
||||
|
||||
|
@ -154,7 +170,7 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
|||
// TODO: Abort if another contact intersection succeeds in the meantime.
|
||||
dispatch_after(
|
||||
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(retryDelaySeconds * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
|
||||
[self intersectContactsWithRetryDelay:retryDelaySeconds * 2];
|
||||
[self intersectContactsWithRetryDelay:retryDelaySeconds * 2 completion:completionBlock];
|
||||
});
|
||||
};
|
||||
[[ContactsUpdater sharedUpdater] updateSignalContactIntersectionWithABContacts:self.allContacts
|
||||
|
@ -180,10 +196,9 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
|||
[self.avatarCache removeAllImagesForKey:recipientId];
|
||||
}
|
||||
|
||||
- (void)updateWithContacts:(NSArray<Contact *> *)contacts
|
||||
- (void)updateWithContacts:(NSArray<Contact *> *)contacts shouldClearStaleCache:(BOOL)shouldClearStaleCache
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
|
||||
dispatch_async(self.serialQueue, ^{
|
||||
NSMutableDictionary<NSString *, Contact *> *allContactsMap = [NSMutableDictionary new];
|
||||
for (Contact *contact in contacts) {
|
||||
for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) {
|
||||
|
@ -200,17 +215,16 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
|||
|
||||
[self.avatarCache removeAllImages];
|
||||
|
||||
[self intersectContacts];
|
||||
|
||||
[self buildSignalAccounts];
|
||||
[self intersectContactsWithCompletion:^(NSError *_Nullable error) {
|
||||
[self buildSignalAccountsAndClearStaleCache:shouldClearStaleCache];
|
||||
}];
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
- (void)buildSignalAccounts
|
||||
- (void)buildSignalAccountsAndClearStaleCache:(BOOL)shouldClearStaleCache;
|
||||
{
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
NSMutableDictionary<NSString *, SignalAccount *> *signalAccountMap = [NSMutableDictionary new];
|
||||
dispatch_async(self.serialQueue, ^{
|
||||
NSMutableArray<SignalAccount *> *signalAccounts = [NSMutableArray new];
|
||||
NSArray<Contact *> *contacts = self.allContacts;
|
||||
|
||||
|
@ -225,10 +239,16 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
|||
}
|
||||
}];
|
||||
|
||||
NSMutableSet<NSString *> *seenRecipientIds = [NSMutableSet new];
|
||||
for (Contact *contact in contacts) {
|
||||
NSArray<SignalRecipient *> *signalRecipients = contactIdToSignalRecipientsMap[contact.uniqueId];
|
||||
for (SignalRecipient *signalRecipient in
|
||||
[signalRecipients sortedArrayUsingSelector:@selector((compare:))]) {
|
||||
for (SignalRecipient *signalRecipient in [signalRecipients sortedArrayUsingSelector:@selector(compare:)]) {
|
||||
if ([seenRecipientIds containsObject:signalRecipient.recipientId]) {
|
||||
DDLogDebug(@"Ignoring duplicate contact: %@, %@", signalRecipient.recipientId, contact.fullName);
|
||||
continue;
|
||||
}
|
||||
[seenRecipientIds addObject:signalRecipient.recipientId];
|
||||
|
||||
SignalAccount *signalAccount = [[SignalAccount alloc] initWithSignalRecipient:signalRecipient];
|
||||
signalAccount.contact = contact;
|
||||
if (signalRecipients.count > 1) {
|
||||
|
@ -236,33 +256,81 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
|||
signalAccount.multipleAccountLabelText =
|
||||
[[self class] accountLabelForContact:contact recipientId:signalRecipient.recipientId];
|
||||
}
|
||||
if (signalAccountMap[signalAccount.recipientId]) {
|
||||
DDLogDebug(@"Ignoring duplicate contact: %@, %@", signalAccount.recipientId, contact.fullName);
|
||||
continue;
|
||||
}
|
||||
[signalAccounts addObject:signalAccount];
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableDictionary<NSString *, SignalAccount *> *oldSignalAccounts = [NSMutableDictionary new];
|
||||
[self.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
|
||||
[SignalAccount
|
||||
enumerateCollectionObjectsWithTransaction:transaction
|
||||
usingBlock:^(id _Nonnull object, BOOL *_Nonnull stop) {
|
||||
OWSAssert([object isKindOfClass:[SignalAccount class]]);
|
||||
SignalAccount *oldSignalAccount = (SignalAccount *)object;
|
||||
|
||||
oldSignalAccounts[oldSignalAccount.uniqueId] = oldSignalAccount;
|
||||
}];
|
||||
}];
|
||||
|
||||
NSMutableArray *accountsToSave = [NSMutableArray new];
|
||||
for (SignalAccount *signalAccount in signalAccounts) {
|
||||
SignalAccount *_Nullable oldSignalAccount = oldSignalAccounts[signalAccount.uniqueId];
|
||||
|
||||
// keep track of which accounts are still relevant, so we can clean up orphans
|
||||
[oldSignalAccounts removeObjectForKey:signalAccount.uniqueId];
|
||||
|
||||
if (oldSignalAccount == nil) {
|
||||
// new Signal Account
|
||||
[accountsToSave addObject:signalAccount];
|
||||
continue;
|
||||
}
|
||||
|
||||
if ([oldSignalAccount isEqual:signalAccount]) {
|
||||
// Same value, no need to save.
|
||||
continue;
|
||||
}
|
||||
|
||||
// value changed, save account
|
||||
[accountsToSave addObject:signalAccount];
|
||||
}
|
||||
|
||||
// Update cached SignalAccounts on disk
|
||||
[self.dbWriteConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
|
||||
NSArray<NSString *> *allKeys = [transaction allKeysInCollection:[SignalAccount collection]];
|
||||
NSMutableSet<NSString *> *orphanedKeys = [NSMutableSet setWithArray:allKeys];
|
||||
|
||||
DDLogInfo(@"%@ Saving %lu SignalAccounts", self.logTag, signalAccounts.count);
|
||||
for (SignalAccount *signalAccount in signalAccounts) {
|
||||
// TODO only save the ones that changed
|
||||
[orphanedKeys removeObject:signalAccount.uniqueId];
|
||||
DDLogInfo(@"%@ Saving %lu SignalAccounts", self.logTag, (unsigned long)accountsToSave.count);
|
||||
for (SignalAccount *signalAccount in accountsToSave) {
|
||||
DDLogVerbose(@"%@ Saving SignalAccount: %@", self.logTag, signalAccount);
|
||||
[signalAccount saveWithTransaction:transaction];
|
||||
}
|
||||
|
||||
if (orphanedKeys.count > 0) {
|
||||
DDLogInfo(@"%@ Removing %lu orphaned SignalAccounts", self.logTag, (unsigned long)orphanedKeys.count);
|
||||
[transaction removeObjectsForKeys:orphanedKeys.allObjects inCollection:[SignalAccount collection]];
|
||||
if (shouldClearStaleCache) {
|
||||
DDLogInfo(@"%@ Removing %lu old SignalAccounts.", self.logTag, (unsigned long)oldSignalAccounts.count);
|
||||
for (SignalAccount *signalAccount in oldSignalAccounts.allValues) {
|
||||
DDLogVerbose(@"%@ Removing old SignalAccount: %@", self.logTag, signalAccount);
|
||||
[signalAccount removeWithTransaction:transaction];
|
||||
}
|
||||
} else {
|
||||
// In theory we want to remove SignalAccounts if the user deletes the corresponding system contact.
|
||||
// However, as of iOS11.2 CNContactStore occasionally gives us only a subset of the system contacts.
|
||||
// Because of that, it's not safe to clear orphaned accounts.
|
||||
// Because we still want to give users a way to clear their stale accounts, if they pull-to-refresh
|
||||
// their contacts we'll clear the cached ones.
|
||||
// RADAR: https://bugreport.apple.com/web/?problemID=36082946
|
||||
if (oldSignalAccounts.allValues.count > 0) {
|
||||
DDLogWarn(@"%@ NOT Removing %lu old SignalAccounts.",
|
||||
self.logTag,
|
||||
(unsigned long)oldSignalAccounts.count);
|
||||
for (SignalAccount *signalAccount in oldSignalAccounts.allValues) {
|
||||
DDLogVerbose(
|
||||
@"%@ Ensuring old SignalAccount is not inadvertently lost: %@", self.logTag, signalAccount);
|
||||
[signalAccounts addObject:signalAccount];
|
||||
}
|
||||
|
||||
// re-sort signal accounts since we've appended some orphans
|
||||
[signalAccounts sortUsingComparator:self.signalAccountComparator];
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
[self updateSignalAccounts:signalAccounts];
|
||||
});
|
||||
|
@ -273,6 +341,11 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
|||
{
|
||||
AssertIsOnMainThread();
|
||||
|
||||
if ([signalAccounts isEqual:self.signalAccounts]) {
|
||||
DDLogDebug(@"%@ SignalAccounts unchanged.", self.logTag);
|
||||
return;
|
||||
}
|
||||
|
||||
NSMutableDictionary<NSString *, SignalAccount *> *signalAccountMap = [NSMutableDictionary new];
|
||||
for (SignalAccount *signalAccount in signalAccounts) {
|
||||
signalAccountMap[signalAccount.recipientId] = signalAccount;
|
||||
|
@ -630,6 +703,16 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification
|
|||
return image;
|
||||
}
|
||||
|
||||
- (NSComparisonResult (^)(SignalAccount *left, SignalAccount *right))signalAccountComparator
|
||||
{
|
||||
return ^NSComparisonResult(SignalAccount *left, SignalAccount *right) {
|
||||
NSString *leftName = [self comparableNameForSignalAccount:left];
|
||||
NSString *rightName = [self comparableNameForSignalAccount:right];
|
||||
|
||||
return [leftName compare:rightName];
|
||||
};
|
||||
}
|
||||
|
||||
- (NSString *)comparableNameForSignalAccount:(SignalAccount *)signalAccount
|
||||
{
|
||||
NSString *_Nullable name;
|
||||
|
|
|
@ -186,11 +186,11 @@ class AddressBookContactStoreAdaptee: ContactStoreAdaptee {
|
|||
}
|
||||
}
|
||||
|
||||
return Contact(contactWithFirstName: firstName,
|
||||
andLastName: lastName,
|
||||
andUserTextPhoneNumbers: phoneNumbers,
|
||||
andImage: addressBookRecord.image,
|
||||
andContactID: addressBookRecord.recordId)
|
||||
return Contact(firstName: firstName,
|
||||
lastName: lastName,
|
||||
userTextPhoneNumbers: phoneNumbers,
|
||||
imageData: addressBookRecord.imageData,
|
||||
contactID: addressBookRecord.recordId)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -244,14 +244,14 @@ struct OWSABRecord {
|
|||
}
|
||||
}
|
||||
|
||||
var image: UIImage? {
|
||||
var imageData: Data? {
|
||||
guard ABPersonHasImageData(abRecord) else {
|
||||
return nil
|
||||
}
|
||||
guard let data = ABPersonCopyImageData(abRecord)?.takeRetainedValue() else {
|
||||
return nil
|
||||
}
|
||||
return UIImage(data: data as Data)
|
||||
return data as Data
|
||||
}
|
||||
|
||||
private func extractProperty<T>(_ propertyName: ABPropertyID) -> T? {
|
||||
|
@ -317,7 +317,7 @@ class ContactStoreAdapter: ContactStoreAdaptee {
|
|||
}
|
||||
|
||||
@objc public protocol SystemContactsFetcherDelegate: class {
|
||||
func systemContactsFetcher(_ systemContactsFetcher: SystemContactsFetcher, updatedContacts contacts: [Contact])
|
||||
func systemContactsFetcher(_ systemContactsFetcher: SystemContactsFetcher, updatedContacts contacts: [Contact], isUserRequested: Bool)
|
||||
}
|
||||
|
||||
@objc
|
||||
|
@ -370,7 +370,7 @@ public class SystemContactsFetcher: NSObject {
|
|||
hasSetupObservation = true
|
||||
self.contactStoreAdapter.startObservingChanges { [weak self] in
|
||||
DispatchQueue.main.async {
|
||||
self?.updateContacts(completion: nil, alwaysNotify: false)
|
||||
self?.updateContacts(completion: nil, isUserRequested: false)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -444,20 +444,21 @@ public class SystemContactsFetcher: NSObject {
|
|||
return
|
||||
}
|
||||
|
||||
updateContacts(completion: nil, alwaysNotify: false)
|
||||
updateContacts(completion: nil, isUserRequested: false)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func fetchIfAlreadyAuthorizedAndAlwaysNotify() {
|
||||
public func userRequestedRefresh(completion: @escaping (Error?) -> Void) {
|
||||
AssertIsOnMainThread()
|
||||
guard authorizationStatus == .authorized else {
|
||||
owsFail("should have already requested contact access")
|
||||
return
|
||||
}
|
||||
|
||||
updateContacts(completion: nil, alwaysNotify: true)
|
||||
updateContacts(completion: completion, isUserRequested: true)
|
||||
}
|
||||
|
||||
private func updateContacts(completion completionParam: ((Error?) -> Void)?, alwaysNotify: Bool = false) {
|
||||
private func updateContacts(completion completionParam: ((Error?) -> Void)?, isUserRequested: Bool = false) {
|
||||
AssertIsOnMainThread()
|
||||
|
||||
// Ensure completion is invoked on main thread.
|
||||
|
@ -497,8 +498,8 @@ public class SystemContactsFetcher: NSObject {
|
|||
if self.lastContactUpdateHash != contactsHash {
|
||||
Logger.info("\(self.TAG) contact hash changed. new contactsHash: \(contactsHash)")
|
||||
shouldNotifyDelegate = true
|
||||
} else if alwaysNotify {
|
||||
Logger.info("\(self.TAG) ignoring debounce.")
|
||||
} else if isUserRequested {
|
||||
Logger.info("\(self.TAG) ignoring debounce due to user request")
|
||||
shouldNotifyDelegate = true
|
||||
} else {
|
||||
|
||||
|
@ -530,7 +531,7 @@ public class SystemContactsFetcher: NSObject {
|
|||
self.lastDelegateNotificationDate = Date()
|
||||
self.lastContactUpdateHash = contactsHash
|
||||
|
||||
self.delegate?.systemContactsFetcher(self, updatedContacts: contacts)
|
||||
self.delegate?.systemContactsFetcher(self, updatedContacts: contacts, isUserRequested: isUserRequested)
|
||||
completion(nil)
|
||||
}
|
||||
}
|
||||
|
|
|
@ -44,11 +44,11 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
#if TARGET_OS_IOS
|
||||
|
||||
- (instancetype)initWithContactWithFirstName:(nullable NSString *)firstName
|
||||
andLastName:(nullable NSString *)lastName
|
||||
andUserTextPhoneNumbers:(NSArray<NSString *> *)phoneNumbers
|
||||
andImage:(nullable UIImage *)image
|
||||
andContactID:(ABRecordID)record;
|
||||
- (instancetype)initWithFirstName:(nullable NSString *)firstName
|
||||
lastName:(nullable NSString *)lastName
|
||||
userTextPhoneNumbers:(NSArray<NSString *> *)phoneNumbers
|
||||
imageData:(nullable NSData *)imageData
|
||||
contactID:(ABRecordID)record;
|
||||
|
||||
- (instancetype)initWithSystemContact:(CNContact *)contact NS_AVAILABLE_IOS(9_0);
|
||||
|
||||
|
|
|
@ -16,6 +16,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@interface Contact ()
|
||||
|
||||
@property (readonly, nonatomic) NSMutableDictionary<NSString *, NSString *> *phoneNumberNameMap;
|
||||
@property (readonly, nonatomic) NSData *imageData;
|
||||
|
||||
@end
|
||||
|
||||
|
@ -26,13 +27,14 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@synthesize fullName = _fullName;
|
||||
@synthesize comparableNameFirstLast = _comparableNameFirstLast;
|
||||
@synthesize comparableNameLastFirst = _comparableNameLastFirst;
|
||||
@synthesize image = _image;
|
||||
|
||||
#if TARGET_OS_IOS
|
||||
- (instancetype)initWithContactWithFirstName:(nullable NSString *)firstName
|
||||
andLastName:(nullable NSString *)lastName
|
||||
andUserTextPhoneNumbers:(NSArray *)phoneNumbers
|
||||
andImage:(nullable UIImage *)image
|
||||
andContactID:(ABRecordID)record
|
||||
- (instancetype)initWithFirstName:(nullable NSString *)firstName
|
||||
lastName:(nullable NSString *)lastName
|
||||
userTextPhoneNumbers:(NSArray<NSString *> *)phoneNumbers
|
||||
imageData:(nullable NSData *)imageData
|
||||
contactID:(ABRecordID)record
|
||||
{
|
||||
self = [super init];
|
||||
if (!self) {
|
||||
|
@ -46,7 +48,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
_userTextPhoneNumbers = phoneNumbers;
|
||||
_phoneNumberNameMap = [NSMutableDictionary new];
|
||||
_parsedPhoneNumbers = [self parsedPhoneNumbersFromUserTextPhoneNumbers:phoneNumbers phoneNumberNameMap:@{}];
|
||||
_image = image;
|
||||
_imageData = imageData;
|
||||
// Not using emails for old AB style contacts.
|
||||
_emails = [NSMutableArray new];
|
||||
|
||||
|
@ -127,12 +129,26 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
_emails = [emailAddresses copy];
|
||||
|
||||
if (contact.thumbnailImageData) {
|
||||
_image = [UIImage imageWithData:contact.thumbnailImageData];
|
||||
_imageData = contact.thumbnailImageData;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (nullable UIImage *)image
|
||||
{
|
||||
if (_image) {
|
||||
return _image;
|
||||
}
|
||||
|
||||
if (!self.imageData) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
_image = [UIImage imageWithData:self.imageData];
|
||||
return _image;
|
||||
}
|
||||
|
||||
- (NSString *)trimName:(NSString *)name
|
||||
{
|
||||
return [name stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]];
|
||||
|
@ -143,6 +159,15 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return [NSString stringWithFormat:@"ABRecordId:%d", recordId];
|
||||
}
|
||||
|
||||
+ (MTLPropertyStorage)storageBehaviorForPropertyWithKey:(NSString *)propertyKey
|
||||
{
|
||||
if ([propertyKey isEqualToString:@"cnContact"] || [propertyKey isEqualToString:@"image"]) {
|
||||
return MTLPropertyStorageTransitory;
|
||||
} else {
|
||||
return [super storageBehaviorForPropertyWithKey:propertyKey];
|
||||
}
|
||||
}
|
||||
|
||||
#endif // TARGET_OS_IOS
|
||||
|
||||
- (NSArray<PhoneNumber *> *)parsedPhoneNumbersFromUserTextPhoneNumbers:(NSArray<NSString *> *)userTextPhoneNumbers
|
||||
|
|
|
@ -377,4 +377,14 @@ static NSString *const RPDefaultsKeyPhoneNumberCanonical = @"RPDefaultsKeyPhoneN
|
|||
return [self.toE164 compare:other.toE164];
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(id)other
|
||||
{
|
||||
if (![other isMemberOfClass:[self class]]) {
|
||||
return NO;
|
||||
}
|
||||
PhoneNumber *otherPhoneNumber = (PhoneNumber *)other;
|
||||
|
||||
return [self.phoneNumber isEqual:otherPhoneNumber.phoneNumber];
|
||||
}
|
||||
|
||||
@end
|
||||
|
|
Loading…
Reference in a new issue