From d0e26a58c311bbf6277eba6e6777038d8ab7e0fc Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 5 May 2017 11:37:17 -0400 Subject: [PATCH 1/2] =?UTF-8?q?Show=20=E2=80=9Cinvite=20by=20SMS=E2=80=9D?= =?UTF-8?q?=20offer=20for=20matching=20non-Signal=20contacts=20when=20sear?= =?UTF-8?q?ching=20in=20=E2=80=9Cnew=201:1:=20conversation=E2=80=9D=20view?= =?UTF-8?q?.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit // FREEBIE --- .../src/ViewControllers/ContactsViewHelper.h | 2 + .../src/ViewControllers/ContactsViewHelper.m | 92 +++++++++++++++++-- .../MessageComposeTableViewController.m | 40 +++++++- Signal/src/contact/OWSContactsManager.h | 2 + 4 files changed, 128 insertions(+), 8 deletions(-) diff --git a/Signal/src/ViewControllers/ContactsViewHelper.h b/Signal/src/ViewControllers/ContactsViewHelper.h index d81382d33..263675d75 100644 --- a/Signal/src/ViewControllers/ContactsViewHelper.h +++ b/Signal/src/ViewControllers/ContactsViewHelper.h @@ -44,6 +44,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *)signalAccountsMatchingSearchString:(NSString *)searchText; +- (NSArray *)nonSignalContactsMatchingSearchString:(NSString *)searchText; + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/ContactsViewHelper.m b/Signal/src/ViewControllers/ContactsViewHelper.m index 756b0ce17..1f0e9c787 100644 --- a/Signal/src/ViewControllers/ContactsViewHelper.m +++ b/Signal/src/ViewControllers/ContactsViewHelper.m @@ -15,6 +15,9 @@ NS_ASSUME_NONNULL_BEGIN @interface ContactsViewHelper () +// This property is a cached value that is lazy-populated. +@property (nonatomic, nullable) NSArray *nonSignalContacts; + @property (nonatomic) NSDictionary *signalAccountMap; @property (nonatomic) NSArray *signalAccounts; @@ -143,6 +146,7 @@ NS_ASSUME_NONNULL_BEGIN } self.signalAccountMap = [signalAccountMap copy]; self.signalAccounts = [signalAccounts copy]; + self.nonSignalContacts = nil; [self.delegate contactsViewHelperDidUpdateContacts]; } @@ -178,15 +182,19 @@ NS_ASSUME_NONNULL_BEGIN return YES; } +- (NSArray *)searchTermsForSearchString:(NSString *)searchText +{ + return [[[searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] + componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] + filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *_Nullable searchTerm, + NSDictionary *_Nullable bindings) { + return searchTerm.length > 0; + }]]; +} + - (NSArray *)signalAccountsMatchingSearchString:(NSString *)searchText { - NSArray *searchTerms = - [[[searchText stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] - componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceAndNewlineCharacterSet]] - filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(NSString *_Nullable searchTerm, - NSDictionary *_Nullable bindings) { - return searchTerm.length > 0; - }]]; + NSArray *searchTerms = [self searchTermsForSearchString:searchText]; if (searchTerms.count < 1) { return self.signalAccounts; @@ -199,6 +207,76 @@ NS_ASSUME_NONNULL_BEGIN }]]; } +- (BOOL)doesContact:(Contact *)contact matchSearchTerm:(NSString *)searchTerm +{ + OWSAssert(contact); + OWSAssert(searchTerm.length > 0); + + if ([contact.fullName.lowercaseString containsString:searchTerm.lowercaseString]) { + return YES; + } + + NSString *asPhoneNumber = [PhoneNumber removeFormattingCharacters:searchTerm]; + if (asPhoneNumber.length > 0) { + for (PhoneNumber *phoneNumber in contact.parsedPhoneNumbers) { + if ([phoneNumber.toE164 containsString:asPhoneNumber]) { + return YES; + } + } + } + + return NO; +} + +- (BOOL)doesContact:(Contact *)contact matchSearchTerms:(NSArray *)searchTerms +{ + OWSAssert(contact); + OWSAssert(searchTerms.count > 0); + + for (NSString *searchTerm in searchTerms) { + if (![self doesContact:contact matchSearchTerm:searchTerm]) { + return NO; + } + } + + return YES; +} + +- (NSArray *)nonSignalContactsMatchingSearchString:(NSString *)searchText +{ + NSArray *searchTerms = [self searchTermsForSearchString:searchText]; + + if (searchTerms.count < 1) { + return [NSArray new]; + } + + return [self.nonSignalContacts filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(Contact *contact, + NSDictionary *_Nullable bindings) { + return [self doesContact:contact matchSearchTerms:searchTerms]; + }]]; +} + +- (nullable NSArray *)nonSignalContacts +{ + if (!_nonSignalContacts) { + NSMutableSet *nonSignalContacts = [NSMutableSet new]; + [[TSStorageManager sharedManager].dbConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) { + for (Contact *contact in self.contactsManager.allContactsMap.allValues) { + NSArray *signalRecipients = [contact signalRecipientsWithTransaction:transaction]; + if (signalRecipients.count < 1) { + [nonSignalContacts addObject:contact]; + } + } + }]; + _nonSignalContacts = [nonSignalContacts.allObjects + sortedArrayUsingComparator:^NSComparisonResult(Contact *_Nonnull left, Contact *_Nonnull right) { + return [left.fullName compare:right.fullName]; + }]; + } + + return _nonSignalContacts; +} + @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/ViewControllers/MessageComposeTableViewController.m b/Signal/src/ViewControllers/MessageComposeTableViewController.m index 5d558d3f6..39e949e1a 100644 --- a/Signal/src/ViewControllers/MessageComposeTableViewController.m +++ b/Signal/src/ViewControllers/MessageComposeTableViewController.m @@ -218,6 +218,10 @@ NS_ASSUME_NONNULL_BEGIN OWSTableSection *section = [OWSTableSection new]; + const CGFloat kActionCellHeight + = ScaleFromIPhone5To7Plus(round((kOWSTable_DefaultCellHeight + [ContactTableViewCell rowHeight]) * 0.5f), + [ContactTableViewCell rowHeight]); + // Find Non-Contacts by Phone Number [section addItem:[OWSTableItem itemWithCustomCellBlock:^{ UITableViewCell *cell = [UITableViewCell new]; @@ -316,6 +320,40 @@ NS_ASSUME_NONNULL_BEGIN } BOOL hasSearchText = [self.searchBar text].length > 0; + BOOL hasSearchResults = filteredSignalAccounts.count > 0; + + // Invitation offers for non-signal contacts + if (hasSearchText) { + for (Contact *contact in [helper nonSignalContactsMatchingSearchString:[self.searchBar text]]) { + hasSearchResults = YES; + + OWSAssert(contact.parsedPhoneNumbers.count > 0); + // TODO: Should we invite all of their phone numbers? + PhoneNumber *phoneNumber = contact.parsedPhoneNumbers[0]; + NSString *displayName = contact.fullName; + if (displayName.length < 1) { + displayName = phoneNumber.toE164; + } + + [section addItem:[OWSTableItem itemWithCustomCellBlock:^{ + UITableViewCell *cell = [UITableViewCell new]; + cell.textLabel.text = + [NSString stringWithFormat:NSLocalizedString(@"SEND_INVITE_VIA_SMS_BUTTON_FORMAT", + @"Text for button to send a Signal invite via SMS. %@ is " + @"placeholder for the receipient's phone number."), + displayName]; + cell.textLabel.font = [UIFont ows_regularFontWithSize:18.f]; + cell.textLabel.textColor = [UIColor blackColor]; + cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; + return cell; + } + customRowHeight:[ContactTableViewCell rowHeight] + actionBlock:^{ + [weakSelf sendTextToPhoneNumber:phoneNumber.toE164]; + }]]; + } + } + if (!hasSearchText && helper.signalAccounts.count < 1) { // No Contacts @@ -333,7 +371,7 @@ NS_ASSUME_NONNULL_BEGIN actionBlock:nil]]; } - if (hasSearchText && filteredSignalAccounts.count < 1) { + if (hasSearchText && !hasSearchResults) { // No Search Results [section addItem:[OWSTableItem itemWithCustomCellBlock:^{ diff --git a/Signal/src/contact/OWSContactsManager.h b/Signal/src/contact/OWSContactsManager.h index 762a6b321..1de096163 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) NSDictionary *allContactsMap; + // signalAccountMap and signalAccounts hold the same data. // signalAccountMap is for lookup. signalAccounts contains the accounts // ordered by display order. From 9f4b8d3b0f428af6ac6d27597cfb6b915eaa0b15 Mon Sep 17 00:00:00 2001 From: Matthew Chen Date: Fri, 5 May 2017 11:42:02 -0400 Subject: [PATCH 2/2] =?UTF-8?q?Slightly=20reduce=20the=20non-contact=20cel?= =?UTF-8?q?l=20heights=20in=20=E2=80=9Cnew=201:1=20conversation=E2=80=9D?= =?UTF-8?q?=20view.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit // FREEBIE --- .../MessageComposeTableViewController.m | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/Signal/src/ViewControllers/MessageComposeTableViewController.m b/Signal/src/ViewControllers/MessageComposeTableViewController.m index 39e949e1a..20570c337 100644 --- a/Signal/src/ViewControllers/MessageComposeTableViewController.m +++ b/Signal/src/ViewControllers/MessageComposeTableViewController.m @@ -232,7 +232,7 @@ NS_ASSUME_NONNULL_BEGIN cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; return cell; } - customRowHeight:[ContactTableViewCell rowHeight] + customRowHeight:kActionCellHeight actionBlock:^{ NewNonContactConversationViewController *viewController = [NewNonContactConversationViewController new]; @@ -250,7 +250,7 @@ NS_ASSUME_NONNULL_BEGIN cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; return cell; } - customRowHeight:[ContactTableViewCell rowHeight] + customRowHeight:kActionCellHeight actionBlock:^{ [weakSelf presentInviteFlow]; }]]; @@ -291,7 +291,7 @@ NS_ASSUME_NONNULL_BEGIN cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; return cell; } - customRowHeight:[ContactTableViewCell rowHeight] + customRowHeight:kActionCellHeight actionBlock:^{ [weakSelf sendTextToPhoneNumber:phoneNumber]; }]]; @@ -347,7 +347,7 @@ NS_ASSUME_NONNULL_BEGIN cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; return cell; } - customRowHeight:[ContactTableViewCell rowHeight] + customRowHeight:kActionCellHeight actionBlock:^{ [weakSelf sendTextToPhoneNumber:phoneNumber.toE164]; }]]; @@ -367,7 +367,7 @@ NS_ASSUME_NONNULL_BEGIN cell.selectionStyle = UITableViewCellSelectionStyleNone; return cell; } - customRowHeight:[ContactTableViewCell rowHeight] + customRowHeight:kActionCellHeight actionBlock:nil]]; } @@ -384,7 +384,7 @@ NS_ASSUME_NONNULL_BEGIN cell.selectionStyle = UITableViewCellSelectionStyleNone; return cell; } - customRowHeight:[ContactTableViewCell rowHeight] + customRowHeight:kActionCellHeight actionBlock:nil]]; }