diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index eeda565f1..d9c475323 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -881,7 +881,6 @@ static NSString *const kURLHostVerifyPrefix = @"verify"; [OWSProfileManager.sharedManager fetchLocalUsersProfile]; [[OWSReadReceiptManager sharedManager] prepareCachedValues]; - [[Environment getCurrent].contactsManager loadLastKnownContactRecipientIds]; } - (void)registrationStateDidChange diff --git a/Signal/src/ViewControllers/OWSConversationSettingsViewController.m b/Signal/src/ViewControllers/OWSConversationSettingsViewController.m index 73e7b1ee7..4eaece5fb 100644 --- a/Signal/src/ViewControllers/OWSConversationSettingsViewController.m +++ b/Signal/src/ViewControllers/OWSConversationSettingsViewController.m @@ -171,7 +171,7 @@ NS_ASSUME_NONNULL_BEGIN OWSAssert([self.thread isKindOfClass:[TSContactThread class]]); TSContactThread *contactThread = (TSContactThread *)self.thread; NSString *recipientId = contactThread.contactIdentifier; - return [self.contactsManager.lastKnownContactRecipientIds containsObject:recipientId]; + return [self.contactsManager signalAccountForRecipientId:recipientId] != nil; } #pragma mark - ContactEditingDelegate diff --git a/Signal/src/contact/OWSContactsManager.h b/Signal/src/contact/OWSContactsManager.h index fadb35f2b..aafde01aa 100644 --- a/Signal/src/contact/OWSContactsManager.h +++ b/Signal/src/contact/OWSContactsManager.h @@ -30,33 +30,10 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification; @property (atomic, readonly) NSDictionary *allContactsMap; -// signalAccountMap and signalAccounts hold the same data. -// signalAccountMap is for lookup. signalAccounts contains the accounts -// ordered by display order. -@property (atomic, readonly) NSDictionary *signalAccountMap; +// order of the signalAccounts array respects the systems contact sorting preference @property (atomic, readonly) NSArray *signalAccounts; - -// This value is cached and is available immediately, before system contacts -// fetch or contacts intersection. -// -// In some cases, its better if our UI reflects these values -// which haven't been updated yet rather than assume that -// we have no contacts until the first contacts intersection -// successfully completes. -// -// This significantly improves the user experience when: -// -// * No contacts intersection has completed because the app has just launched. -// * Contacts intersection can't complete due to an unreliable connection or -// the contacts intersection rate limit. -@property (atomic, readonly) NSArray *lastKnownContactRecipientIds; - - (nullable SignalAccount *)signalAccountForRecipientId:(NSString *)recipientId; -- (Contact *)getOrBuildContactForPhoneIdentifier:(NSString *)identifier; - -- (void)loadLastKnownContactRecipientIds; - #pragma mark - System Contact Fetching // Must call `requestSystemContactsOnce` before accessing this method diff --git a/Signal/src/contact/OWSContactsManager.m b/Signal/src/contact/OWSContactsManager.m index cc49c0a9d..8909aea8c 100644 --- a/Signal/src/contact/OWSContactsManager.m +++ b/Signal/src/contact/OWSContactsManager.m @@ -23,7 +23,6 @@ NSString *const kTSStorageManager_AccountDisplayNames = @"kTSStorageManager_Acco NSString *const kTSStorageManager_AccountFirstNames = @"kTSStorageManager_AccountFirstNames"; NSString *const kTSStorageManager_AccountLastNames = @"kTSStorageManager_AccountLastNames"; NSString *const kTSStorageManager_OWSContactsManager = @"kTSStorageManager_OWSContactsManager"; -NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownContactRecipientIds"; @interface OWSContactsManager () @@ -34,7 +33,6 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont @property (atomic) NSDictionary *allContactsMap; @property (atomic) NSArray *signalAccounts; @property (atomic) NSDictionary *signalAccountMap; -@property (atomic) NSArray *lastKnownContactRecipientIds; @property (nonatomic, readonly) SystemContactsFetcher *systemContactsFetcher; @property (atomic) NSDictionary *cachedAccountNameMap; @@ -57,7 +55,6 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont _allContactsMap = @{}; _signalAccountMap = @{}; _signalAccounts = @[]; - _lastKnownContactRecipientIds = @[]; _systemContactsFetcher = [SystemContactsFetcher new]; _systemContactsFetcher.delegate = self; @@ -68,18 +65,6 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont return self; } -- (void)loadLastKnownContactRecipientIds -{ - [TSStorageManager.sharedManager.newDatabaseConnection readWithBlock:^( - YapDatabaseReadTransaction *_Nonnull transaction) { - NSArray *_Nullable value = [transaction objectForKey:kTSStorageManager_lastKnownContactRecipientIds - inCollection:kTSStorageManager_OWSContactsManager]; - if (value) { - self.lastKnownContactRecipientIds = value; - } - }]; -} - #pragma mark - System Contact Fetching // Request contacts access if you haven't asked recently. @@ -245,16 +230,18 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont } } - NSArray *lastKnownContactRecipientIds = [signalAccountMap allKeys]; [TSStorageManager.sharedManager.newDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - [transaction setObject:lastKnownContactRecipientIds - forKey:kTSStorageManager_lastKnownContactRecipientIds - inCollection:kTSStorageManager_OWSContactsManager]; + // TODO we can be more efficient here. + // - only save the ones that changed + // - only remove the ones which no longer exist + [transaction removeAllObjectsInCollection:[SignalAccount collection]]; + for (SignalAccount *signalAccount in signalAccounts) { + [signalAccount saveWithTransaction:transaction]; + } }]; dispatch_async(dispatch_get_main_queue(), ^{ - self.lastKnownContactRecipientIds = lastKnownContactRecipientIds; self.signalAccountMap = [signalAccountMap copy]; self.signalAccounts = [signalAccounts copy]; @@ -682,25 +669,22 @@ NSString *const kTSStorageManager_lastKnownContactRecipientIds = @"lastKnownCont { OWSAssert(recipientId.length > 0); - return self.signalAccountMap[recipientId]; -} + SignalAccount *signalAccount = self.signalAccountMap[recipientId]; -- (Contact *)getOrBuildContactForPhoneIdentifier:(NSString *)identifier -{ - Contact *savedContact = self.allContactsMap[identifier]; - if (savedContact) { - return savedContact; - } else { - return [[Contact alloc] initWithContactWithFirstName:self.unknownContactName - andLastName:nil - andUserTextPhoneNumbers:@[ identifier ] - andImage:nil - andContactID:0]; + // If contact intersection hasn't completed, it might exist on disk + // even if it doesn't exist in memory yet. + if (!signalAccount) { + signalAccount = [SignalAccount fetchObjectWithUniqueID:recipientId]; } + + return signalAccount; } - (UIImage * _Nullable)imageForPhoneIdentifier:(NSString * _Nullable)identifier { Contact *contact = self.allContactsMap[identifier]; + if (!contact) { + contact = [self signalAccountForRecipientId:identifier].contact; + } // Prefer the contact image from the local address book if available UIImage *_Nullable image = contact.image; diff --git a/Signal/src/util/ThreadUtil.m b/Signal/src/util/ThreadUtil.m index 048e03a86..eeb0551b7 100644 --- a/Signal/src/util/ThreadUtil.m +++ b/Signal/src/util/ThreadUtil.m @@ -388,7 +388,7 @@ NS_ASSUME_NONNULL_BEGIN shouldHaveAddToProfileWhitelistOffer = NO; } - BOOL isContact = [contactsManager.lastKnownContactRecipientIds containsObject:recipientId]; + BOOL isContact = [contactsManager signalAccountForRecipientId:recipientId] != nil; if (isContact) { // Only create "add to contacts" offers for non-contacts. shouldHaveAddToContactsOffer = NO; diff --git a/SignalServiceKit/src/Contacts/Contact.h b/SignalServiceKit/src/Contacts/Contact.h index 194efe86e..94fbf42b4 100644 --- a/SignalServiceKit/src/Contacts/Contact.h +++ b/SignalServiceKit/src/Contacts/Contact.h @@ -3,6 +3,7 @@ // #import +#import NS_ASSUME_NONNULL_BEGIN @@ -19,7 +20,7 @@ NS_ASSUME_NONNULL_BEGIN @class SignalRecipient; @class YapDatabaseReadTransaction; -@interface Contact : NSObject +@interface Contact : MTLModel @property (nullable, readonly, nonatomic) NSString *firstName; @property (nullable, readonly, nonatomic) NSString *lastName; @@ -30,13 +31,13 @@ NS_ASSUME_NONNULL_BEGIN @property (readonly, nonatomic) NSArray *userTextPhoneNumbers; @property (readonly, nonatomic) NSArray *emails; @property (readonly, nonatomic) NSString *uniqueId; +@property (nonatomic, readonly) BOOL isSignalContact; #if TARGET_OS_IOS @property (nullable, readonly, nonatomic) UIImage *image; @property (readonly, nonatomic) ABRecordID recordID; @property (nullable, nonatomic, readonly) CNContact *cnContact; #endif // TARGET_OS_IOS -- (BOOL)isSignalContact; - (NSArray *)signalRecipientsWithTransaction:(YapDatabaseReadTransaction *)transaction; // TODO: Remove this method. - (NSArray *)textSecureIdentifiers; diff --git a/SignalServiceKit/src/Contacts/SignalAccount.h b/SignalServiceKit/src/Contacts/SignalAccount.h index ea52d17b3..d0be75d07 100644 --- a/SignalServiceKit/src/Contacts/SignalAccount.h +++ b/SignalServiceKit/src/Contacts/SignalAccount.h @@ -2,6 +2,8 @@ // Copyright (c) 2017 Open Whisper Systems. All rights reserved. // +#import "TSYapDatabaseObject.h" + NS_ASSUME_NONNULL_BEGIN @class Contact; @@ -14,10 +16,7 @@ NS_ASSUME_NONNULL_BEGIN // multiple instances of SignalAccount. // * For non-contacts, the contact property will be nil. // -// New instances of SignalAccount for active accounts are -// created every time we do a contacts intersection (e.g. -// in response to a change to the device contacts). -@interface SignalAccount : NSObject +@interface SignalAccount : TSYapDatabaseObject // An E164 value identifying the signal account. // diff --git a/SignalServiceKit/src/Contacts/SignalAccount.m b/SignalServiceKit/src/Contacts/SignalAccount.m index 8f68f9505..9cb948e55 100644 --- a/SignalServiceKit/src/Contacts/SignalAccount.m +++ b/SignalServiceKit/src/Contacts/SignalAccount.m @@ -46,6 +46,11 @@ NS_ASSUME_NONNULL_BEGIN return [SignalRecipient recipientWithTextSecureIdentifier:self.recipientId withTransaction:transaction]; } +- (nullable NSString *)uniqueId +{ + return _recipientId; +} + @end NS_ASSUME_NONNULL_END