From 86fb08307992957cb6342769e752fdf0d614745d Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Wed, 24 May 2017 11:06:46 -0400 Subject: [PATCH] Rationalize the attributed and unattributed display name formatting and caching. // FREEBIE --- Signal/src/contact/OWSContactsManager.m | 288 ++++++++++++++---------- 1 file changed, 168 insertions(+), 120 deletions(-) diff --git a/Signal/src/contact/OWSContactsManager.m b/Signal/src/contact/OWSContactsManager.m index 0659a46722..fd177a5c13 100644 --- a/Signal/src/contact/OWSContactsManager.m +++ b/Signal/src/contact/OWSContactsManager.m @@ -16,7 +16,9 @@ NSString *const OWSContactsManagerSignalAccountsDidChangeNotification = @"OWSContactsManagerSignalAccountsDidChangeNotification"; -NSString *const kTSStorageManager_ContactNames = @"kTSStorageManager_ContactNames"; +NSString *const kTSStorageManager_AccountDisplayNames = @"kTSStorageManager_AccountDisplayNames"; +NSString *const kTSStorageManager_AccountFirstNames = @"kTSStorageManager_AccountFirstNames"; +NSString *const kTSStorageManager_AccountLastNames = @"kTSStorageManager_AccountLastNames"; @interface OWSContactsManager () @@ -30,6 +32,11 @@ NSString *const kTSStorageManager_ContactNames = @"kTSStorageManager_ContactName @property (atomic) NSArray *signalAccounts; @property (atomic) NSDictionary *signalAccountMap; @property (nonatomic, readonly) SystemContactsFetcher *systemContactsFetcher; + +@property (atomic) NSDictionary *cachedAccountNameMap; +@property (atomic) NSDictionary *cachedFirstNameMap; +@property (atomic) NSDictionary *cachedLastNameMap; + @end @implementation OWSContactsManager @@ -49,6 +56,8 @@ NSString *const kTSStorageManager_ContactNames = @"kTSStorageManager_ContactName OWSSingletonAssert(); + [self loadCachedDisplayNames]; + return self; } @@ -191,11 +200,12 @@ NSString *const kTSStorageManager_ContactNames = @"kTSStorageManager_ContactName dispatch_async(dispatch_get_main_queue(), ^{ self.signalAccountMap = [signalAccountMap copy]; self.signalAccounts = [signalAccounts copy]; + + [self updateCachedDisplayNames]; + [[NSNotificationCenter defaultCenter] postNotificationName:OWSContactsManagerSignalAccountsDidChangeNotification object:nil]; - - [self updateCachedDisplayNames]; }); }); } @@ -204,22 +214,87 @@ NSString *const kTSStorageManager_ContactNames = @"kTSStorageManager_ContactName { OWSAssert([NSThread isMainThread]); - NSMutableDictionary *accountNameMap = [NSMutableDictionary new]; + NSMutableDictionary *cachedAccountNameMap = [NSMutableDictionary new]; + NSMutableDictionary *cachedFirstNameMap = [NSMutableDictionary new]; + NSMutableDictionary *cachedLastNameMap = [NSMutableDictionary new]; for (SignalAccount *signalAccount in self.signalAccounts) { - NSString *displayName = [self displayNameForSignalAccount:signalAccount]; + NSString *baseName + = (signalAccount.contact.fullName.length > 0 ? signalAccount.contact.fullName : signalAccount.recipientId); + OWSAssert(signalAccount.hasMultipleAccountContact == (signalAccount.multipleAccountLabelText != nil)); + NSString *displayName = (signalAccount.multipleAccountLabelText + ? [NSString stringWithFormat:@"%@ (%@)", baseName, signalAccount.multipleAccountLabelText] + : baseName); if (![displayName isEqualToString:signalAccount.recipientId]) { - accountNameMap[signalAccount.recipientId] = displayName; + cachedAccountNameMap[signalAccount.recipientId] = displayName; + } + + if (signalAccount.contact.firstName.length > 0) { + cachedFirstNameMap[signalAccount.recipientId] = signalAccount.contact.firstName; + } + if (signalAccount.contact.lastName.length > 0) { + cachedLastNameMap[signalAccount.recipientId] = signalAccount.contact.lastName; } } - dispatch_async(dispatch_get_main_queue(), ^{ - [TSStorageManager.sharedManager.newDatabaseConnection - readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) { - for (NSString *recipientId in accountNameMap) { - NSString *displayName = accountNameMap[recipientId]; - [transaction setObject:displayName forKey:recipientId inCollection:kTSStorageManager_ContactNames]; - } - }]; + self.cachedAccountNameMap = [cachedAccountNameMap copy]; + self.cachedFirstNameMap = [cachedFirstNameMap copy]; + self.cachedLastNameMap = [cachedLastNameMap copy]; + + // Write to database off the main thread. + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + [TSStorageManager.sharedManager.newDatabaseConnection readWriteWithBlock:^( + YapDatabaseReadWriteTransaction *_Nonnull transaction) { + for (NSString *recipientId in cachedAccountNameMap) { + NSString *displayName = cachedAccountNameMap[recipientId]; + [transaction setObject:displayName + forKey:recipientId + inCollection:kTSStorageManager_AccountDisplayNames]; + } + for (NSString *recipientId in cachedFirstNameMap) { + NSString *firstName = cachedFirstNameMap[recipientId]; + [transaction setObject:firstName forKey:recipientId inCollection:kTSStorageManager_AccountFirstNames]; + } + for (NSString *recipientId in cachedLastNameMap) { + NSString *lastName = cachedLastNameMap[recipientId]; + [transaction setObject:lastName forKey:recipientId inCollection:kTSStorageManager_AccountLastNames]; + } + }]; + }); +} + +- (void)loadCachedDisplayNames +{ + // Read from database off the main thread. + dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{ + NSMutableDictionary *cachedAccountNameMap = [NSMutableDictionary new]; + NSMutableDictionary *cachedFirstNameMap = [NSMutableDictionary new]; + NSMutableDictionary *cachedLastNameMap = [NSMutableDictionary new]; + + [TSStorageManager.sharedManager.newDatabaseConnection readWithBlock:^( + YapDatabaseReadTransaction *_Nonnull transaction) { + [transaction + enumerateKeysAndObjectsInCollection:kTSStorageManager_AccountDisplayNames + usingBlock:^( + NSString *_Nonnull key, NSString *_Nonnull object, BOOL *_Nonnull stop) { + cachedAccountNameMap[key] = object; + }]; + [transaction + enumerateKeysAndObjectsInCollection:kTSStorageManager_AccountFirstNames + usingBlock:^( + NSString *_Nonnull key, NSString *_Nonnull object, BOOL *_Nonnull stop) { + cachedFirstNameMap[key] = object; + }]; + [transaction + enumerateKeysAndObjectsInCollection:kTSStorageManager_AccountLastNames + usingBlock:^( + NSString *_Nonnull key, NSString *_Nonnull object, BOOL *_Nonnull stop) { + cachedLastNameMap[key] = object; + }]; + }]; + + self.cachedAccountNameMap = [cachedAccountNameMap copy]; + self.cachedFirstNameMap = [cachedFirstNameMap copy]; + self.cachedLastNameMap = [cachedLastNameMap copy]; }); } @@ -227,7 +302,21 @@ NSString *const kTSStorageManager_ContactNames = @"kTSStorageManager_ContactName { OWSAssert(recipientId.lastPathComponent > 0); - return [[TSStorageManager sharedManager] objectForKey:recipientId inCollection:kTSStorageManager_ContactNames]; + return self.cachedAccountNameMap[recipientId]; +} + +- (NSString *_Nullable)cachedFirstNameForRecipientId:(NSString *)recipientId +{ + OWSAssert(recipientId.lastPathComponent > 0); + + return self.cachedFirstNameMap[recipientId]; +} + +- (NSString *_Nullable)cachedLastNameForRecipientId:(NSString *)recipientId +{ + OWSAssert(recipientId.lastPathComponent > 0); + + return self.cachedLastNameMap[recipientId]; } #pragma mark - View Helpers @@ -287,28 +376,10 @@ NSString *const kTSStorageManager_ContactNames = @"kTSStorageManager_ContactName return self.unknownContactName; } - // When viewing an old thread with someone who is no longer a Signal user, they won't have a SignalAccount - // so we get the name from `allContactsMap` as opposed to `signalAccountForRecipientId`. - Contact *contact = self.allContactsMap[recipientId]; - - NSString *displayName = contact.fullName; - if (displayName.length < 1) { - displayName = [self cachedDisplayNameForRecipientId:recipientId]; - } + NSString *displayName = [self cachedDisplayNameForRecipientId:recipientId]; if (displayName.length < 1) { displayName = recipientId; } - - return displayName; -} - -// TODO move into Contact class. -- (NSString *_Nonnull)displayNameForContact:(Contact *)contact -{ - OWSAssert(contact); - - NSString *displayName = (contact.fullName.length > 0) ? contact.fullName : self.unknownContactName; - return displayName; } @@ -316,14 +387,7 @@ NSString *const kTSStorageManager_ContactNames = @"kTSStorageManager_ContactName { OWSAssert(signalAccount); - NSString *baseName = (signalAccount.contact ? [self displayNameForContact:signalAccount.contact] - : [self displayNameForPhoneIdentifier:signalAccount.recipientId]); - OWSAssert(signalAccount.hasMultipleAccountContact == (signalAccount.multipleAccountLabelText != nil)); - if (signalAccount.multipleAccountLabelText) { - return [NSString stringWithFormat:@"%@ (%@)", baseName, signalAccount.multipleAccountLabelText]; - } else { - return baseName; - } + return [self displayNameForPhoneIdentifier:signalAccount.recipientId]; } - (NSAttributedString *_Nonnull)formattedDisplayNameForSignalAccount:(SignalAccount *)signalAccount @@ -332,91 +396,75 @@ NSString *const kTSStorageManager_ContactNames = @"kTSStorageManager_ContactName OWSAssert(signalAccount); OWSAssert(font); - NSAttributedString *baseName = [self formattedFullNameForContact:signalAccount.contact font:font]; - - if (baseName.length == 0) { - baseName = [self formattedFullNameForRecipientId:signalAccount.recipientId font:font]; - } - - OWSAssert(signalAccount.hasMultipleAccountContact == (signalAccount.multipleAccountLabelText != nil)); - if (signalAccount.multipleAccountLabelText) { - NSMutableAttributedString *result = [NSMutableAttributedString new]; - [result appendAttributedString:baseName]; - [result appendAttributedString:[[NSAttributedString alloc] initWithString:@" (" - attributes:@{ - NSFontAttributeName : font, - }]]; - [result - appendAttributedString:[[NSAttributedString alloc] initWithString:signalAccount.multipleAccountLabelText]]; - [result appendAttributedString:[[NSAttributedString alloc] initWithString:@")" - attributes:@{ - NSFontAttributeName : font, - }]]; - return result; - } else { - return baseName; - } -} - -// TODO move into Contact class. -- (NSAttributedString *_Nonnull)formattedFullNameForContact:(Contact *)contact font:(UIFont *_Nonnull)font -{ - UIFont *boldFont = [UIFont ows_mediumFontWithSize:font.pointSize]; - - NSDictionary *boldFontAttributes = - @{ NSFontAttributeName : boldFont, NSForegroundColorAttributeName : [UIColor blackColor] }; - - NSDictionary *normalFontAttributes = - @{ NSFontAttributeName : font, NSForegroundColorAttributeName : [UIColor ows_darkGrayColor] }; - - NSAttributedString *_Nullable firstName, *_Nullable lastName; - if (ABPersonGetSortOrdering() == kABPersonSortByFirstName) { - if (contact.firstName) { - firstName = [[NSAttributedString alloc] initWithString:contact.firstName attributes:boldFontAttributes]; - } - if (contact.lastName) { - lastName = [[NSAttributedString alloc] initWithString:contact.lastName attributes:normalFontAttributes]; - } - } else { - if (contact.firstName) { - firstName = [[NSAttributedString alloc] initWithString:contact.firstName attributes:normalFontAttributes]; - } - if (contact.lastName) { - lastName = [[NSAttributedString alloc] initWithString:contact.lastName attributes:boldFontAttributes]; - } - } - - NSAttributedString *_Nullable leftName, *_Nullable rightName; - if (ABPersonGetCompositeNameFormat() == kABPersonCompositeNameFormatFirstNameFirst) { - leftName = firstName; - rightName = lastName; - } else { - leftName = lastName; - rightName = firstName; - } - - NSMutableAttributedString *fullNameString = [NSMutableAttributedString new]; - if (leftName.length > 0) { - [fullNameString appendAttributedString:leftName]; - } - if (leftName.length > 0 && rightName.length > 0) { - [fullNameString appendAttributedString:[[NSAttributedString alloc] initWithString:@" "]]; - } - if (rightName.length > 0) { - [fullNameString appendAttributedString:rightName]; - } - - return fullNameString; + return [self formattedFullNameForRecipientId:signalAccount.recipientId font:font]; } - (NSAttributedString *)formattedFullNameForRecipientId:(NSString *)recipientId font:(UIFont *)font { + OWSAssert(recipientId.length > 0); + OWSAssert(font); + + UIFont *boldFont = [UIFont ows_mediumFontWithSize:font.pointSize]; + + NSDictionary *boldFontAttributes = + @{ NSFontAttributeName : boldFont, NSForegroundColorAttributeName : [UIColor blackColor] }; NSDictionary *normalFontAttributes = @{ NSFontAttributeName : font, NSForegroundColorAttributeName : [UIColor ows_darkGrayColor] }; + NSDictionary *firstNameAttributes + = (ABPersonGetSortOrdering() == kABPersonSortByFirstName ? boldFontAttributes : normalFontAttributes); + NSDictionary *lastNameAttributes + = (ABPersonGetSortOrdering() == kABPersonSortByFirstName ? normalFontAttributes : boldFontAttributes); - return [[NSAttributedString alloc] - initWithString:[PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:recipientId] - attributes:normalFontAttributes]; + NSString *cachedFirstName = [self cachedFirstNameForRecipientId:recipientId]; + NSString *cachedLastName = [self cachedLastNameForRecipientId:recipientId]; + + NSMutableAttributedString *formattedName = [NSMutableAttributedString new]; + + if (cachedFirstName.length > 0 && cachedLastName.length > 0) { + NSAttributedString *firstName = + [[NSAttributedString alloc] initWithString:cachedFirstName attributes:firstNameAttributes]; + NSAttributedString *lastName = + [[NSAttributedString alloc] initWithString:cachedLastName attributes:lastNameAttributes]; + + NSAttributedString *_Nullable leftName, *_Nullable rightName; + if (ABPersonGetCompositeNameFormat() == kABPersonCompositeNameFormatFirstNameFirst) { + leftName = firstName; + rightName = lastName; + } else { + leftName = lastName; + rightName = firstName; + } + + [formattedName appendAttributedString:leftName]; + [formattedName + appendAttributedString:[[NSAttributedString alloc] initWithString:@" " attributes:normalFontAttributes]]; + [formattedName appendAttributedString:rightName]; + } else if (cachedFirstName.length > 0) { + [formattedName appendAttributedString:[[NSAttributedString alloc] initWithString:cachedFirstName + attributes:firstNameAttributes]]; + } else if (cachedLastName.length > 0) { + [formattedName appendAttributedString:[[NSAttributedString alloc] initWithString:cachedLastName + attributes:lastNameAttributes]]; + } else { + return [[NSAttributedString alloc] + initWithString:[PhoneNumber bestEffortFormatPartialUserSpecifiedTextToLookLikeAPhoneNumber:recipientId] + attributes:normalFontAttributes]; + } + + SignalAccount *signalAccount = [self signalAccountForRecipientId:recipientId]; + if (signalAccount && signalAccount.multipleAccountLabelText) { + OWSAssert(signalAccount.multipleAccountLabelText.length > 0); + + [formattedName + appendAttributedString:[[NSAttributedString alloc] initWithString:@" (" attributes:normalFontAttributes]]; + [formattedName + appendAttributedString:[[NSAttributedString alloc] initWithString:signalAccount.multipleAccountLabelText + attributes:normalFontAttributes]]; + [formattedName + appendAttributedString:[[NSAttributedString alloc] initWithString:@")" attributes:normalFontAttributes]]; + } + + return formattedName; } - (nullable SignalAccount *)signalAccountForRecipientId:(NSString *)recipientId