From 81555d1225a347592caefcc06d7b3240b9234e5f Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Tue, 4 Jul 2017 16:40:28 -0400 Subject: [PATCH] =?UTF-8?q?Add=20=E2=80=9Cnew=20contact=E2=80=9D=20and=20?= =?UTF-8?q?=E2=80=9Cadd=20to=20existing=20contact=E2=80=9D=20buttons=20in?= =?UTF-8?q?=201:1=20conversation=20settings=20view.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit // FREEBIE --- Signal.xcodeproj/project.pbxproj | 6 + .../Contents.json | 23 ++ .../table_ic_add_to_existing_contact@1x.png | Bin 0 -> 1571 bytes .../table_ic_add_to_existing_contact@2x.png | Bin 0 -> 1998 bytes .../table_ic_add_to_existing_contact@3x.png | Bin 0 -> 2485 bytes .../Contents.json | 23 ++ .../table_ic_new_contact@1x.png | Bin 0 -> 1651 bytes .../table_ic_new_contact@2x.png | Bin 0 -> 2194 bytes .../table_ic_new_contact@3x.png | Bin 0 -> 2699 bytes .../src/ViewControllers/ContactsViewHelper.h | 7 + .../src/ViewControllers/ContactsViewHelper.m | 65 ++++- .../OWSAddToContactViewController.h | 15 ++ .../OWSAddToContactViewController.m | 250 ++++++++++++++++++ ...SConversationSettingsTableViewController.m | 80 ++++-- Signal/src/contact/OWSContactsManager.h | 2 + Signal/src/contact/OWSContactsManager.m | 12 +- .../translations/en.lproj/Localizable.strings | 6 + 17 files changed, 450 insertions(+), 39 deletions(-) create mode 100644 Signal/Images.xcassets/table_ic_add_to_existing_contact.imageset/Contents.json create mode 100644 Signal/Images.xcassets/table_ic_add_to_existing_contact.imageset/table_ic_add_to_existing_contact@1x.png create mode 100644 Signal/Images.xcassets/table_ic_add_to_existing_contact.imageset/table_ic_add_to_existing_contact@2x.png create mode 100644 Signal/Images.xcassets/table_ic_add_to_existing_contact.imageset/table_ic_add_to_existing_contact@3x.png create mode 100644 Signal/Images.xcassets/table_ic_new_contact.imageset/Contents.json create mode 100644 Signal/Images.xcassets/table_ic_new_contact.imageset/table_ic_new_contact@1x.png create mode 100644 Signal/Images.xcassets/table_ic_new_contact.imageset/table_ic_new_contact@2x.png create mode 100644 Signal/Images.xcassets/table_ic_new_contact.imageset/table_ic_new_contact@3x.png create mode 100644 Signal/src/ViewControllers/OWSAddToContactViewController.h create mode 100644 Signal/src/ViewControllers/OWSAddToContactViewController.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index a89ba5f33..81d223bfd 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 34CCAF361F0C0599004084F4 /* AppUpdateNag.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = AppUpdateNag.h; sourceTree = ""; }; 34CCAF371F0C0599004084F4 /* AppUpdateNag.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = AppUpdateNag.m; sourceTree = ""; }; + 34CCAF391F0C2748004084F4 /* OWSAddToContactViewController.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSAddToContactViewController.h; sourceTree = ""; }; + 34CCAF3A1F0C2748004084F4 /* OWSAddToContactViewController.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSAddToContactViewController.m; sourceTree = ""; }; 34D5CC941EA6AFAD005515DB /* OWSContactsSyncing.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSyncing.h; sourceTree = ""; }; 34D5CC951EA6AFAD005515DB /* OWSContactsSyncing.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSyncing.m; sourceTree = ""; }; 34D5CC981EA6EB79005515DB /* OWSMessageCollectionViewCell.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSMessageCollectionViewCell.h; sourceTree = ""; }; @@ -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 */, diff --git a/Signal/Images.xcassets/table_ic_add_to_existing_contact.imageset/Contents.json b/Signal/Images.xcassets/table_ic_add_to_existing_contact.imageset/Contents.json new file mode 100644 index 000000000..67d5ff502 --- /dev/null +++ b/Signal/Images.xcassets/table_ic_add_to_existing_contact.imageset/Contents.json @@ -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" + } +} \ No newline at end of file diff --git a/Signal/Images.xcassets/table_ic_add_to_existing_contact.imageset/table_ic_add_to_existing_contact@1x.png b/Signal/Images.xcassets/table_ic_add_to_existing_contact.imageset/table_ic_add_to_existing_contact@1x.png new file mode 100644 index 0000000000000000000000000000000000000000..b63738f79eb04719b400ab1fb0d5b17f42eb20d5 GIT binary patch literal 1571 zcmeAS@N?(olHy`uVBq!ia0vp^3LwnE1|*BCs=fdz#^NA%Cx&(BWL^R}oCO|{#S9GG z!XV7ZFl&wk0|SdvW=KRygs+cPa(=E}VoH8es$NBI0Z=sqgH44MkeQoWlBiITo0C^; zRbi_HR$&EXgM{^!6u?SKvTcwsYk*NEcCio^nlW#B-B_{|37a;u=! z;{2RaP!NRXWtP|(*?>KSE{q5fh%V>++=8Oi;$omSJ5#6@WHEI05eRGS%wcvQ3!-cA zFUkb^G!f)3J42`i$YSW~Be7Y4EQq856!caBnH8xy5iXg)00sNP80adj66hM8^K`Ye5o1R}Ho%5|=ed5=a_TlC6qMld@8iOORp<7-!(L@06IXk0flPk5#n| zFeO;|=BH$)Rk|dWq}mx77@F%E0FgT(W2Go(C2uvpiiKLn1ie zPP5i$a};T_pRS;!$6NT*N=0aCo72L@ANWNUsc`j1xvssysd(|_QT_!NI$a*=E9iB0 zL|6C~?ptFx+t&JiPOAT>w7mCwp5MKBbMDHJQ$OBoUl7^B;CyB_b991WO{4n<9ZGtr?6Ca4an{m`-aVtx@ z)E4QTPT9=HV*9Q->~}r$kjEl7|8kZ?S;ak$nUiNF&zWexV7AWcKP}c1SBV*1I55X~ z!qkZ4{STwUujef1f7W{Ni0YhGk*4Y!#sA4wu>aZ;Rd=DBh0XEdMn5*+HBa3SyYxMePVc_)hr?` zs66JMwDwo42riE~CQ9p56|c0+Nh><-$<=n{{Y42*!$JYYol^JCW-mC^vb=*$?!u>k z3oh-ut*LfGR41%N+2p0Lx3R#Nb2XeowTnBe*4~O){de7)%TtsidQSMQQ-AWRo;k&T gYT&v9^F@C!KD0V#%JTZ=X;8`S>FVdQ&MBb@01!JFng9R* literal 0 HcmV?d00001 diff --git a/Signal/Images.xcassets/table_ic_add_to_existing_contact.imageset/table_ic_add_to_existing_contact@2x.png b/Signal/Images.xcassets/table_ic_add_to_existing_contact.imageset/table_ic_add_to_existing_contact@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..b9a434fd9adb9c75133ee38d25003e53b036011a GIT binary patch literal 1998 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 b;h{is@|wn{D%H)>e~9F*cBH&5)QJB8_sw3j literal 0 HcmV?d00001 diff --git a/Signal/src/ViewControllers/ContactsViewHelper.h b/Signal/src/ViewControllers/ContactsViewHelper.h index ab4d68867..4e49cc72d 100644 --- a/Signal/src/ViewControllers/ContactsViewHelper.h +++ b/Signal/src/ViewControllers/ContactsViewHelper.h @@ -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 *)fromViewController editImmediately:(BOOL)shouldEditImmediately; +// This method can be used to edit existing contacts. +- (void)presentContactViewControllerForRecipientId:(NSString *)recipientId + fromViewController:(UIViewController *)fromViewController + editImmediately:(BOOL)shouldEditImmediately + addToExistingCnContact:(CNContact *_Nullable)cnContact; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ContactsViewHelper.m b/Signal/src/ViewControllers/ContactsViewHelper.m index ba836c769..fab07c621 100644 --- a/Signal/src/ViewControllers/ContactsViewHelper.m +++ b/Signal/src/ViewControllers/ContactsViewHelper.m @@ -300,6 +300,17 @@ NS_ASSUME_NONNULL_BEGIN - (void)presentContactViewControllerForRecipientId:(NSString *)recipientId fromViewController:(UIViewController *)fromViewController editImmediately:(BOOL)shouldEditImmediately +{ + [self presentContactViewControllerForRecipientId:recipientId + fromViewController:fromViewController + editImmediately:shouldEditImmediately + addToExistingCnContact:nil]; +} + +- (void)presentContactViewControllerForRecipientId:(NSString *)recipientId + fromViewController:(UIViewController *)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 *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 *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) { diff --git a/Signal/src/ViewControllers/OWSAddToContactViewController.h b/Signal/src/ViewControllers/OWSAddToContactViewController.h new file mode 100644 index 000000000..505dc4317 --- /dev/null +++ b/Signal/src/ViewControllers/OWSAddToContactViewController.h @@ -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 diff --git a/Signal/src/ViewControllers/OWSAddToContactViewController.m b/Signal/src/ViewControllers/OWSAddToContactViewController.m new file mode 100644 index 000000000..462884d87 --- /dev/null +++ b/Signal/src/ViewControllers/OWSAddToContactViewController.m @@ -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 () + +@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 *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 diff --git a/Signal/src/ViewControllers/OWSConversationSettingsTableViewController.m b/Signal/src/ViewControllers/OWSConversationSettingsTableViewController.m index dcca82967..2227eb978 100644 --- a/Signal/src/ViewControllers/OWSConversationSettingsTableViewController.m +++ b/Signal/src/ViewControllers/OWSConversationSettingsTableViewController.m @@ -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 diff --git a/Signal/src/contact/OWSContactsManager.h b/Signal/src/contact/OWSContactsManager.h index 4e08812cd..ffcbc2df3 100644 --- a/Signal/src/contact/OWSContactsManager.h +++ b/Signal/src/contact/OWSContactsManager.h @@ -19,6 +19,8 @@ extern NSString *const OWSContactsManagerSignalAccountsDidChangeNotification; @property (nonnull, readonly) NSCache *avatarCache; +@property (atomic, readonly) NSArray *allContacts; + @property (atomic, readonly) NSDictionary *allContactsMap; // signalAccountMap and signalAccounts hold the same data. diff --git a/Signal/src/contact/OWSContactsManager.m b/Signal/src/contact/OWSContactsManager.m index e4a2ed32a..65f615be2 100644 --- a/Signal/src/contact/OWSContactsManager.m +++ b/Signal/src/contact/OWSContactsManager.m @@ -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 *cachedAccountNameMap - = (self.cachedAccountNameMap ? [self.cachedAccountNameMap mutableCopy] : [NSMutableDictionary new]); - NSMutableDictionary *cachedFirstNameMap - = (self.cachedFirstNameMap ? [self.cachedFirstNameMap mutableCopy] : [NSMutableDictionary new]); - NSMutableDictionary *cachedLastNameMap - = (self.cachedLastNameMap ? [self.cachedLastNameMap mutableCopy] : [NSMutableDictionary new]); + NSMutableDictionary *cachedAccountNameMap = [NSMutableDictionary new]; + NSMutableDictionary *cachedFirstNameMap = [NSMutableDictionary new]; + NSMutableDictionary *cachedLastNameMap = [NSMutableDictionary new]; + for (SignalAccount *signalAccount in self.signalAccounts) { NSString *baseName = (signalAccount.contact.fullName.length > 0 ? signalAccount.contact.fullName : signalAccount.recipientId); diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 58228ee9a..ef5b9db70 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -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";