Add “new contact” and “add to existing contact” buttons in 1:1 conversation settings view.

// FREEBIE
This commit is contained in:
Matthew Chen 2017-07-04 16:40:28 -04:00
parent 1e67bb52e3
commit 81555d1225
17 changed files with 450 additions and 39 deletions

View File

@ -76,6 +76,7 @@
34B3F89F1E8DF5490035BE1A /* OWSTableViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F89E1E8DF5490035BE1A /* OWSTableViewController.m */; };
34B3F8A21E8EA6040035BE1A /* ViewControllerUtils.m in Sources */ = {isa = PBXBuildFile; fileRef = 34B3F8A11E8EA6040035BE1A /* ViewControllerUtils.m */; };
34CCAF381F0C0599004084F4 /* AppUpdateNag.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF371F0C0599004084F4 /* AppUpdateNag.m */; };
34CCAF3B1F0C2748004084F4 /* OWSAddToContactViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */; };
34D5CC961EA6AFAD005515DB /* OWSContactsSyncing.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */; };
34D5CCA91EAE3D30005515DB /* GroupViewHelper.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCA81EAE3D30005515DB /* GroupViewHelper.m */; };
34D5CCB11EAE7E7F005515DB /* SelectRecipientViewController.m in Sources */ = {isa = PBXBuildFile; fileRef = 34D5CCB01EAE7E7F005515DB /* SelectRecipientViewController.m */; };
@ -494,6 +495,8 @@
34B3F8A11E8EA6040035BE1A /* ViewControllerUtils.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = ViewControllerUtils.m; sourceTree = "<group>"; };
34CCAF361F0C0599004084F4 /* AppUpdateNag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppUpdateNag.h; sourceTree = "<group>"; };
34CCAF371F0C0599004084F4 /* AppUpdateNag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppUpdateNag.m; sourceTree = "<group>"; };
34CCAF391F0C2748004084F4 /* OWSAddToContactViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAddToContactViewController.h; sourceTree = "<group>"; };
34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAddToContactViewController.m; sourceTree = "<group>"; };
34D5CC941EA6AFAD005515DB /* OWSContactsSyncing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSyncing.h; sourceTree = "<group>"; };
34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSyncing.m; sourceTree = "<group>"; };
34D5CC981EA6EB79005515DB /* OWSMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageCollectionViewCell.h; sourceTree = "<group>"; };
@ -980,6 +983,8 @@
34B3F8581E8DF1700035BE1A /* NotificationSettingsViewController.h */,
34B3F8591E8DF1700035BE1A /* NotificationSettingsViewController.m */,
34B3F85A1E8DF1700035BE1A /* OversizeTextMessageViewController.swift */,
34CCAF391F0C2748004084F4 /* OWSAddToContactViewController.h */,
34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */,
34533F161EA8D2070006114F /* OWSAudioAttachmentPlayer.h */,
34533F171EA8D2070006114F /* OWSAudioAttachmentPlayer.m */,
34B3F85B1E8DF1700035BE1A /* OWSConversationSettingsTableViewController.h */,
@ -2229,6 +2234,7 @@
34B3F8811E8DF1700035BE1A /* LockInteractionController.m in Sources */,
3448BFCC1EDF0EA7005B2D69 /* OWSMessagesToolbarContentView.m in Sources */,
3448BFD01EDF0EA7005B2D69 /* MessagesViewController.m in Sources */,
34CCAF3B1F0C2748004084F4 /* OWSAddToContactViewController.m in Sources */,
45F659731E1BD99C00444429 /* CallKitCallUIAdaptee.swift in Sources */,
45BB93381E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */,
458DE9D61DEE3FD00071BB03 /* PeerConnectionClient.swift in Sources */,

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "table_ic_add_to_existing_contact@1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "table_ic_add_to_existing_contact@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "table_ic_add_to_existing_contact@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.0 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.4 KiB

View File

@ -0,0 +1,23 @@
{
"images" : [
{
"idiom" : "universal",
"filename" : "table_ic_new_contact@1x.png",
"scale" : "1x"
},
{
"idiom" : "universal",
"filename" : "table_ic_new_contact@2x.png",
"scale" : "2x"
},
{
"idiom" : "universal",
"filename" : "table_ic_new_contact@3x.png",
"scale" : "3x"
}
],
"info" : {
"version" : 1,
"author" : "xcode"
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

View File

@ -29,6 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
@class OWSContactsManager;
@class OWSBlockingManager;
@class CNContact;
@interface ContactsViewHelper : NSObject
@ -70,6 +71,12 @@ NS_ASSUME_NONNULL_BEGIN
fromViewController:(UIViewController<ContactEditingDelegate> *)fromViewController
editImmediately:(BOOL)shouldEditImmediately;
// This method can be used to edit existing contacts.
- (void)presentContactViewControllerForRecipientId:(NSString *)recipientId
fromViewController:(UIViewController<ContactEditingDelegate> *)fromViewController
editImmediately:(BOOL)shouldEditImmediately
addToExistingCnContact:(CNContact *_Nullable)cnContact;
@end
NS_ASSUME_NONNULL_END

View File

@ -300,6 +300,17 @@ NS_ASSUME_NONNULL_BEGIN
- (void)presentContactViewControllerForRecipientId:(NSString *)recipientId
fromViewController:(UIViewController<ContactEditingDelegate> *)fromViewController
editImmediately:(BOOL)shouldEditImmediately
{
[self presentContactViewControllerForRecipientId:recipientId
fromViewController:fromViewController
editImmediately:shouldEditImmediately
addToExistingCnContact:nil];
}
- (void)presentContactViewControllerForRecipientId:(NSString *)recipientId
fromViewController:(UIViewController<ContactEditingDelegate> *)fromViewController
editImmediately:(BOOL)shouldEditImmediately
addToExistingCnContact:(CNContact *_Nullable)addToExistingCnContact
{
SignalAccount *signalAccount = [self signalAccountForRecipientId:recipientId];
@ -340,21 +351,49 @@ NS_ASSUME_NONNULL_BEGIN
}
CNContactViewController *_Nullable contactViewController;
if (signalAccount) {
CNContact *_Nullable cnContact = signalAccount.contact.cnContact;
if (cnContact) {
if (shouldEditImmediately) {
// 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.
contactViewController = [CNContactViewController viewControllerForNewContact:cnContact];
// Default title is "New Contact". We could give a more descriptive title, but anything
// seems redundant - the context is sufficiently clear.
contactViewController.title = @"";
} else {
contactViewController = [CNContactViewController viewControllerForContact:cnContact];
CNContact *_Nullable cnContact = nil;
if (addToExistingCnContact) {
CNMutableContact *updatedContact = [addToExistingCnContact mutableCopy];
NSMutableArray<CNLabeledValue *> *phoneNumbers
= (updatedContact.phoneNumbers ? [updatedContact.phoneNumbers mutableCopy] : [NSMutableArray new]);
// Only add recipientId as a phone number for the existing contact
// if its not already present.
BOOL hasPhoneNumber = NO;
for (CNLabeledValue *existingPhoneNumber in phoneNumbers) {
CNPhoneNumber *phoneNumber = existingPhoneNumber.value;
if ([phoneNumber.stringValue isEqualToString:recipientId]) {
hasPhoneNumber = YES;
break;
}
}
if (!hasPhoneNumber) {
CNPhoneNumber *phoneNumber = [CNPhoneNumber phoneNumberWithStringValue:recipientId];
CNLabeledValue<CNPhoneNumber *> *labeledPhoneNumber =
[CNLabeledValue labeledValueWithLabel:CNLabelPhoneNumberMain value:phoneNumber];
[phoneNumbers addObject:labeledPhoneNumber];
updatedContact.phoneNumbers = phoneNumbers;
// When adding a phone number to an existing contact, immediately enter
// "edit" mode.
shouldEditImmediately = YES;
}
cnContact = updatedContact;
}
if (signalAccount && !cnContact) {
cnContact = signalAccount.contact.cnContact;
}
if (cnContact) {
if (shouldEditImmediately) {
// 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.
contactViewController = [CNContactViewController viewControllerForNewContact:cnContact];
// Default title is "New Contact". We could give a more descriptive title, but anything
// seems redundant - the context is sufficiently clear.
contactViewController.title = @"";
} else {
contactViewController = [CNContactViewController viewControllerForContact:cnContact];
}
}
if (!contactViewController) {

View File

@ -0,0 +1,15 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSTableViewController.h"
NS_ASSUME_NONNULL_BEGIN
@interface OWSAddToContactViewController : OWSTableViewController
- (void)configureWithRecipientId:(NSString *)recipientId;
@end
NS_ASSUME_NONNULL_END

View File

@ -0,0 +1,250 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSAddToContactViewController.h"
#import "ContactsViewHelper.h"
#import "Environment.h"
#import "OWSContactsManager.h"
#import "UIUtil.h"
@import ContactsUI;
NS_ASSUME_NONNULL_BEGIN
@interface NamedContact : NSObject
@property (nonatomic) Contact *contact;
@property (nonatomic) NSString *displayName;
@end
#pragma mark -
@implementation NamedContact
@end
#pragma mark -
@interface OWSAddToContactViewController () <ContactEditingDelegate, ContactsViewHelperDelegate>
@property (nonatomic) NSString *recipientId;
@property (nonatomic, readonly) OWSContactsManager *contactsManager;
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@end
#pragma mark -
@implementation OWSAddToContactViewController
- (instancetype)init
{
self = [super init];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (nullable instancetype)initWithCoder:(NSCoder *)aDecoder
{
self = [super initWithCoder:aDecoder];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (instancetype)initWithNibName:(nullable NSString *)nibNameOrNil bundle:(nullable NSBundle *)nibBundleOrNil
{
self = [super initWithNibName:nibNameOrNil bundle:nibBundleOrNil];
if (!self) {
return self;
}
[self commonInit];
return self;
}
- (void)commonInit
{
_contactsManager = [Environment getCurrent].contactsManager;
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
}
- (void)configureWithRecipientId:(NSString *)recipientId
{
OWSAssert(recipientId.length > 0);
_recipientId = recipientId;
}
#pragma mark - ContactEditingDelegate
- (void)didFinishEditingContact
{
DDLogDebug(@"%@ %s", self.tag, __PRETTY_FUNCTION__);
[self dismissViewControllerAnimated:NO
completion:^{
[self.navigationController popViewControllerAnimated:YES];
}];
}
#pragma mark - CNContactViewControllerDelegate
- (void)contactViewController:(CNContactViewController *)viewController
didCompleteWithContact:(nullable CNContact *)contact
{
if (contact) {
// Saving normally returns you to the "Show Contact" view
// which we're not interested in, so we skip it here. There is
// an unfortunate blip of the "Show Contact" view on slower devices.
DDLogDebug(@"%@ completed editing contact.", self.tag);
[self dismissViewControllerAnimated:NO
completion:^{
[self.navigationController popViewControllerAnimated:YES];
}];
} else {
DDLogDebug(@"%@ canceled editing contact.", self.tag);
[self dismissViewControllerAnimated:YES
completion:^{
[self.navigationController popViewControllerAnimated:YES];
}];
}
}
#pragma mark - ContactsViewHelperDelegate
- (void)contactsViewHelperDidUpdateContacts
{
[self updateTableContents];
}
#pragma mark - View Lifecycle
- (void)viewDidLoad
{
[super viewDidLoad];
self.title = NSLocalizedString(@"CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT",
@"Label for 'new contact' button in conversation settings view.");
[self updateTableContents];
}
- (nullable NSString *)displayNameForContact:(Contact *)contact
{
OWSAssert(contact);
if (contact.fullName.length > 0) {
return contact.fullName;
}
for (NSString *email in contact.emails) {
if (email.length > 0) {
return email;
}
}
for (NSString *phoneNumber in contact.userTextPhoneNumbers) {
if (phoneNumber.length > 0) {
return phoneNumber;
}
}
return nil;
}
- (void)updateTableContents
{
OWSTableContents *contents = [OWSTableContents new];
contents.title = NSLocalizedString(@"CONVERSATION_SETTINGS", @"title for conversation settings screen");
__weak OWSAddToContactViewController *weakSelf = self;
OWSTableSection *section = [OWSTableSection new];
section.headerTitle = NSLocalizedString(
@"EDIT_GROUP_CONTACTS_SECTION_TITLE", @"a title for the contacts section of the 'new/update group' view.");
NSMutableArray<NamedContact *> *namedContacts = [NSMutableArray new];
for (Contact *contact in self.contactsViewHelper.contactsManager.allContacts) {
OWSAssert(contact.cnContact);
NSString *_Nullable displayName = [self displayNameForContact:contact];
if (displayName.length < 1) {
continue;
}
NamedContact *namedContact = [NamedContact new];
namedContact.contact = contact;
namedContact.displayName = displayName;
[namedContacts addObject:namedContact];
}
[namedContacts sortUsingComparator:^NSComparisonResult(NamedContact *_Nonnull left, NamedContact *_Nonnull right) {
return [left.displayName caseInsensitiveCompare:right.displayName];
}];
for (NamedContact *namedContact in namedContacts) {
[section addItem:[OWSTableItem disclosureItemWithText:namedContact.displayName
actionBlock:^{
[weakSelf
presentContactViewControllerForContact:namedContact.contact];
}]];
}
[contents addSection:section];
self.contents = contents;
[self.tableView reloadData];
}
- (void)viewWillAppear:(BOOL)animated
{
[super viewWillAppear:animated];
// In case we're dismissing a CNContactViewController which requires default system appearance
[UIUtil applySignalAppearence];
}
#pragma mark - Actions
- (void)presentContactViewControllerForContact:(Contact *)contact
{
OWSAssert(contact);
OWSAssert(self.recipientId);
if (!self.contactsManager.supportsContactEditing) {
DDLogError(@"%@ Contact editing not supported", self.tag);
OWSAssert(NO);
return;
}
[self.contactsViewHelper presentContactViewControllerForRecipientId:self.recipientId
fromViewController:self
editImmediately:YES
addToExistingCnContact:contact.cnContact];
}
#pragma mark - Logging
+ (NSString *)tag
{
return [NSString stringWithFormat:@"[%@]", self.class];
}
- (NSString *)tag
{
return self.class.tag;
}
@end
NS_ASSUME_NONNULL_END

View File

@ -7,6 +7,7 @@
#import "ContactsViewHelper.h"
#import "Environment.h"
#import "FingerprintViewController.h"
#import "OWSAddToContactViewController.h"
#import "OWSAvatarBuilder.h"
#import "OWSBlockingManager.h"
#import "OWSContactsManager.h"
@ -149,7 +150,8 @@ NS_ASSUME_NONNULL_BEGIN
{
OWSAssert(self.thread);
if ([self.thread isKindOfClass:[TSContactThread class]] && self.contactsManager.supportsContactEditing) {
if ([self.thread isKindOfClass:[TSContactThread class]] && self.contactsManager.supportsContactEditing
&& self.hasExistingContact) {
self.navigationItem.rightBarButtonItem =
[[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"EDIT_TXT", nil)
style:UIBarButtonItemStylePlain
@ -158,10 +160,22 @@ NS_ASSUME_NONNULL_BEGIN
}
}
- (BOOL)hasExistingContact
{
OWSAssert([self.thread isKindOfClass:[TSContactThread class]]);
TSContactThread *contactThread = (TSContactThread *)self.thread;
NSString *recipientId = contactThread.contactIdentifier;
SignalAccount *signalAccount = [self.contactsViewHelper signalAccountForRecipientId:recipientId];
return signalAccount.contact;
}
#pragma mark - ContactEditingDelegate
- (void)didFinishEditingContact
{
[self updateTableContents];
DDLogDebug(@"%@ %s", self.tag, __PRETTY_FUNCTION__);
[self dismissViewControllerAnimated:NO completion:nil];
}
@ -171,6 +185,8 @@ NS_ASSUME_NONNULL_BEGIN
- (void)contactViewController:(CNContactViewController *)viewController
didCompleteWithContact:(nullable CNContact *)contact
{
[self updateTableContents];
if (contact) {
// Saving normally returns you to the "Show Contact" view
// which we're not interested in, so we skip it here. There is
@ -232,27 +248,53 @@ NS_ASSUME_NONNULL_BEGIN
__weak OWSConversationSettingsTableViewController *weakSelf = self;
// First section.
// Main section.
OWSTableSection *firstSection = [OWSTableSection new];
OWSTableSection *mainSection = [OWSTableSection new];
firstSection.customHeaderView = [self firstSectionHeader];
firstSection.customHeaderHeight = @(100.f);
mainSection.customHeaderView = [self mainSectionHeader];
mainSection.customHeaderHeight = @(100.f);
if ([self.thread isKindOfClass:[TSContactThread class]] && self.contactsManager.supportsContactEditing
&& !self.hasExistingContact) {
[mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
return
[weakSelf disclosureCellWithName:NSLocalizedString(@"CONVERSATION_SETTINGS_NEW_CONTACT",
@"Label for 'new contact' button in conversation settings view.")
iconName:@"table_ic_new_contact"];
}
actionBlock:^{
[weakSelf presentContactViewController];
}]];
[mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
return
[weakSelf disclosureCellWithName:NSLocalizedString(@"CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT",
@"Label for 'new contact' button in conversation settings view.")
iconName:@"table_ic_add_to_existing_contact"];
}
actionBlock:^{
TSContactThread *contactThread = (TSContactThread *)self.thread;
NSString *recipientId = contactThread.contactIdentifier;
OWSAddToContactViewController *view = [OWSAddToContactViewController new];
[view configureWithRecipientId:recipientId];
[weakSelf.navigationController pushViewController:view animated:YES];
}]];
}
if (!self.isGroupThread && self.thread.hasSafetyNumbers) {
[firstSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
[mainSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
return [weakSelf
disclosureCellWithName:
NSLocalizedString(@"VERIFY_PRIVACY",
@"Label for button or row which allows users to verify the safety number of another user.")
iconName:@"table_ic_not_verified"];
}
actionBlock:^{
[weakSelf showVerificationView];
}]];
actionBlock:^{
[weakSelf showVerificationView];
}]];
}
[firstSection
[mainSection
addItem:[OWSTableItem itemWithCustomCellBlock:^{
UITableViewCell *cell = [UITableViewCell new];
cell.selectionStyle = UITableViewCellSelectionStyleNone;
@ -305,7 +347,7 @@ NS_ASSUME_NONNULL_BEGIN
actionBlock:nil]];
if (self.disappearingMessagesConfiguration.isEnabled) {
[firstSection
[mainSection
addItem:[OWSTableItem
itemWithCustomCellBlock:^{
UITableViewCell *cell = [UITableViewCell new];
@ -351,7 +393,7 @@ NS_ASSUME_NONNULL_BEGIN
actionBlock:nil]];
}
[contents addSection:firstSection];
[contents addSection:mainSection];
// Group settings section.
@ -513,11 +555,11 @@ NS_ASSUME_NONNULL_BEGIN
return cell;
}
- (UIView *)firstSectionHeader
- (UIView *)mainSectionHeader
{
UIView *firstSectionHeader = [UIView new];
UIView *mainSectionHeader = [UIView new];
UIView *threadInfoView = [UIView new];
[firstSectionHeader addSubview:threadInfoView];
[mainSectionHeader addSubview:threadInfoView];
[threadInfoView autoPinWidthToSuperviewWithMargin:16.f];
[threadInfoView autoPinHeightToSuperviewWithMargin:16.f];
@ -596,12 +638,12 @@ NS_ASSUME_NONNULL_BEGIN
[lastTitleView autoPinEdgeToSuperviewEdge:ALEdgeBottom];
[firstSectionHeader
[mainSectionHeader
addGestureRecognizer:[[UITapGestureRecognizer alloc] initWithTarget:self
action:@selector(conversationNameTouched:)]];
firstSectionHeader.userInteractionEnabled = YES;
mainSectionHeader.userInteractionEnabled = YES;
return firstSectionHeader;
return mainSectionHeader;
}
- (void)conversationNameTouched:(UIGestureRecognizer *)sender
@ -645,6 +687,8 @@ NS_ASSUME_NONNULL_BEGIN
// HACK to unselect rows when swiping back
// http://stackoverflow.com/questions/19379510/uitableviewcell-doesnt-get-deselected-when-swiping-back-quickly
[self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:animated];
[self updateTableContents];
}
- (void)viewWillDisappear:(BOOL)animated

View File

@ -19,6 +19,8 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification;
@property (nonnull, readonly) NSCache<NSString *, UIImage *> *avatarCache;
@property (atomic, readonly) NSArray<Contact *> *allContacts;
@property (atomic, readonly) NSDictionary<NSString *, Contact *> *allContactsMap;
// signalAccountMap and signalAccounts hold the same data.

View File

@ -215,14 +215,10 @@ NSString *const kTSStorageManager_AccountLastNames = @"kTSStorageManager_Account
{
OWSAssert([NSThread isMainThread]);
// Preserve any existing values, so that contacts that have been removed
// from system contacts still show up properly in the app.
NSMutableDictionary<NSString *, NSString *> *cachedAccountNameMap
= (self.cachedAccountNameMap ? [self.cachedAccountNameMap mutableCopy] : [NSMutableDictionary new]);
NSMutableDictionary<NSString *, NSString *> *cachedFirstNameMap
= (self.cachedFirstNameMap ? [self.cachedFirstNameMap mutableCopy] : [NSMutableDictionary new]);
NSMutableDictionary<NSString *, NSString *> *cachedLastNameMap
= (self.cachedLastNameMap ? [self.cachedLastNameMap mutableCopy] : [NSMutableDictionary new]);
NSMutableDictionary<NSString *, NSString *> *cachedAccountNameMap = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSString *> *cachedFirstNameMap = [NSMutableDictionary new];
NSMutableDictionary<NSString *, NSString *> *cachedLastNameMap = [NSMutableDictionary new];
for (SignalAccount *signalAccount in self.signalAccounts) {
NSString *baseName
= (signalAccount.contact.fullName.length > 0 ? signalAccount.contact.fullName : signalAccount.recipientId);

View File

@ -292,6 +292,9 @@
/* title for conversation settings screen */
"CONVERSATION_SETTINGS" = "Conversation Settings";
/* Label for 'new contact' button in conversation settings view. */
"CONVERSATION_SETTINGS_ADD_TO_EXISTING_CONTACT" = "Add to Existing Contact";
/* table cell label in conversation settings */
"CONVERSATION_SETTINGS_BLOCK_THIS_USER" = "Block this user";
@ -328,6 +331,9 @@
/* Indicates that this thread is muted until a given date or time. Embeds {{The date or time which the thread is muted until}}. */
"CONVERSATION_SETTINGS_MUTED_UNTIL_FORMAT" = "until %@";
/* Label for 'new contact' button in conversation settings view. */
"CONVERSATION_SETTINGS_NEW_CONTACT" = "New Contact";
/* Label for button to unmute a thread. */
"CONVERSATION_SETTINGS_UNMUTE_ACTION" = "Unmute";