Merge branch 'mkirk/search-profile-names'

This commit is contained in:
Michael Kirk 2017-12-04 15:32:47 -05:00
commit 9ea954bec2
17 changed files with 202 additions and 220 deletions

View File

@ -230,8 +230,6 @@
4574A5D61DD6704700C6B692 /* CallService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4574A5D51DD6704700C6B692 /* CallService.swift */; };
4579431E1E7C8CE9008ED0C0 /* Pastelog.m in Sources */ = {isa = PBXBuildFile; fileRef = 4579431D1E7C8CE9008ED0C0 /* Pastelog.m */; };
45794E861E00620000066731 /* CallUIAdapter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45794E851E00620000066731 /* CallUIAdapter.swift */; };
45843D1F1D2236B30013E85A /* OWSContactsSearcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */; };
45843D201D2236B30013E85A /* OWSContactsSearcher.m in Sources */ = {isa = PBXBuildFile; fileRef = 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */; };
45847E871E4283C30080EAB3 /* Intents.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45847E861E4283C30080EAB3 /* Intents.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
45855F371D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */; };
45855F381D9498A40084F340 /* OWSContactAvatarBuilder.m in Sources */ = {isa = PBXBuildFile; fileRef = 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */; };
@ -257,6 +255,8 @@
45A6DAD71EBBF85500893231 /* ReminderView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45A6DAD51EBBF85500893231 /* ReminderView.swift */; };
45AE48511E0732D6004D96C2 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */; };
45AE48521E0732D6004D96C2 /* TurnServerInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */; };
45B72DDA1FD5E70600151AF6 /* ConversationSearcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B72DD91FD5E70600151AF6 /* ConversationSearcher.swift */; };
45B72DDB1FD5E70600151AF6 /* ConversationSearcher.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45B72DD91FD5E70600151AF6 /* ConversationSearcher.swift */; };
45BB93381E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */; };
45BB93391E688E14001E3939 /* UIDevice+featureSupport.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */; };
45BD60821DE9547E00A8F436 /* Contacts.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 45BD60811DE9547E00A8F436 /* Contacts.framework */; settings = {ATTRIBUTES = (Weak, ); }; };
@ -279,7 +279,6 @@
45E7A6A81E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */; };
45F170AC1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; };
45F170AD1E2F0351003FC1F2 /* CallAudioSession.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */; };
45F170AF1E2F0393003FC1F2 /* CallAudioSessionTest.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */; };
45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */; };
45F170BC1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */; };
45F170CC1E310E22003FC1F2 /* WeakTimer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 45F170CB1E310E22003FC1F2 /* WeakTimer.swift */; };
@ -816,8 +815,6 @@
4579431C1E7C8CE9008ED0C0 /* Pastelog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pastelog.h; sourceTree = "<group>"; };
4579431D1E7C8CE9008ED0C0 /* Pastelog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pastelog.m; sourceTree = "<group>"; };
45794E851E00620000066731 /* CallUIAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallUIAdapter.swift; path = UserInterface/CallUIAdapter.swift; sourceTree = "<group>"; };
45843D1D1D2236B30013E85A /* OWSContactsSearcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSearcher.h; sourceTree = "<group>"; };
45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSearcher.m; sourceTree = "<group>"; };
45847E861E4283C30080EAB3 /* Intents.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Intents.framework; path = System/Library/Frameworks/Intents.framework; sourceTree = SDKROOT; };
45855F351D9498A40084F340 /* OWSContactAvatarBuilder.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactAvatarBuilder.h; sourceTree = "<group>"; };
45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactAvatarBuilder.m; sourceTree = "<group>"; };
@ -849,6 +846,7 @@
45A6DAD51EBBF85500893231 /* ReminderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReminderView.swift; sourceTree = "<group>"; };
45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = "<group>"; };
45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Bridging-Header.h"; sourceTree = "<group>"; };
45B72DD91FD5E70600151AF6 /* ConversationSearcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearcher.swift; sourceTree = "<group>"; };
45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+featureSupport.swift"; sourceTree = "<group>"; };
45BD60811DE9547E00A8F436 /* Contacts.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = Contacts.framework; path = System/Library/Frameworks/Contacts.framework; sourceTree = SDKROOT; };
45C0DC1A1E68FE9000E04C47 /* UIApplication+OWS.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIApplication+OWS.swift"; sourceTree = "<group>"; };
@ -866,7 +864,6 @@
45E615151E8C590B0018AD52 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = "<group>"; };
45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilterTest.swift; sourceTree = "<group>"; };
45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSession.swift; sourceTree = "<group>"; };
45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSessionTest.swift; sourceTree = "<group>"; };
45F170B31E2F0A6A003FC1F2 /* RTCAudioSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTCAudioSession.h; sourceTree = "<group>"; };
45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioService.swift; sourceTree = "<group>"; };
45F170CB1E310E22003FC1F2 /* WeakTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakTimer.swift; sourceTree = "<group>"; };
@ -1644,8 +1641,6 @@
children = (
76EB040818170B33006006FC /* OWSContactsManager.h */,
76EB040918170B33006006FC /* OWSContactsManager.m */,
45843D1D1D2236B30013E85A /* OWSContactsSearcher.h */,
45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */,
4542F0931EB9372700C7EE92 /* SystemContactsFetcher.swift */,
);
path = contact;
@ -1725,6 +1720,7 @@
76EB04FB18170B33006006FC /* Util.h */,
45F170D51E315310003FC1F2 /* Weak.swift */,
45F170CB1E310E22003FC1F2 /* WeakTimer.swift */,
45B72DD91FD5E70600151AF6 /* ConversationSearcher.swift */,
);
path = util;
sourceTree = "<group>";
@ -1837,7 +1833,6 @@
B660F6731C29867F00687D6E /* call */ = {
isa = PBXGroup;
children = (
45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */,
456F6E2E1E261D1000FD2210 /* PeerConnectionClientTest.swift */,
);
path = call;
@ -2719,6 +2714,7 @@
450DF2091E0DD2C6003D14BE /* UserNotificationsAdaptee.swift in Sources */,
34D1F0821F8678AA0066283D /* ConversationHeaderView.m in Sources */,
340CB2241EAC155C0001CAA1 /* ContactsViewHelper.m in Sources */,
45B72DDA1FD5E70600151AF6 /* ConversationSearcher.swift in Sources */,
34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */,
34CE88EC1F3237260098030F /* OWSProfileManager.m in Sources */,
4542F0941EB9372700C7EE92 /* SystemContactsFetcher.swift in Sources */,
@ -2773,7 +2769,6 @@
D221A09A169C9E5E00537ABF /* main.m in Sources */,
345671011E89A5F1006EE662 /* ThreadUtil.m in Sources */,
4585C4601ED4FD0400896AEA /* OWS104CreateRecipientIdentities.m in Sources */,
45843D1F1D2236B30013E85A /* OWSContactsSearcher.m in Sources */,
B6258B331C29E2E60014138E /* NotificationsManager.m in Sources */,
34B3F87B1E8DF1700035BE1A /* ExperienceUpgradesPageViewController.swift in Sources */,
34533F181EA8D2070006114F /* OWSAudioAttachmentPlayer.m in Sources */,
@ -2928,11 +2923,9 @@
458E383A1D6699FA0094BD24 /* OWSDeviceProvisioningURLParserTest.m in Sources */,
452D1EE81DCA90D100A57EC4 /* MesssagesBubblesSizeCalculatorTest.swift in Sources */,
451DE9FE1DC1A28200810E42 /* SyncPushTokensJob.swift in Sources */,
45F170AF1E2F0393003FC1F2 /* CallAudioSessionTest.swift in Sources */,
456F6E231E24133500FD2210 /* Platform.swift in Sources */,
4539B5871F79348F007141FF /* PushRegistrationManager.swift in Sources */,
4504493A1F45EE7D002D1ADA /* NSString+OWS.m in Sources */,
45843D201D2236B30013E85A /* OWSContactsSearcher.m in Sources */,
45AE48521E0732D6004D96C2 /* TurnServerInfo.swift in Sources */,
45360B901F9527DA00FA666C /* SearcherTest.swift in Sources */,
B660F7561C29988E00687D6E /* PushManager.m in Sources */,
@ -2978,6 +2971,7 @@
45A6DAD71EBBF85500893231 /* ReminderView.swift in Sources */,
B660F6D21C29868000687D6E /* PushManagerTest.m in Sources */,
45C0DC1F1E69011F00E04C47 /* UIStoryboard+OWS.swift in Sources */,
45B72DDB1FD5E70600151AF6 /* ConversationSearcher.swift in Sources */,
4505C2C01E648EA300CEBF41 /* ExperienceUpgrade.swift in Sources */,
455AC69E1F4F8B0300134004 /* ImageCacheTest.swift in Sources */,
);

View File

@ -3,7 +3,6 @@
//
#import "ContactsViewHelper.h"
#import "ContactTableViewCell.h"
#import "Environment.h"
#import "NSString+OWS.h"
#import "OWSProfileManager.h"
@ -31,6 +30,7 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic) BOOL shouldNotifyDelegateOfUpdatedContacts;
@property (nonatomic) BOOL hasUpdatedContactsAtLeastOnce;
@property (nonatomic) OWSProfileManager *profileManager;
@property (nonatomic, readonly) ConversationSearcher *conversationSearcher;
@end
@ -50,6 +50,7 @@ NS_ASSUME_NONNULL_BEGIN
_blockingManager = [OWSBlockingManager sharedManager];
_blockedPhoneNumbers = [_blockingManager blockedPhoneNumbers];
_conversationSearcher = ConversationSearcher.shared;
_contactsManager = [Environment getCurrent].contactsManager;
_profileManager = [OWSProfileManager sharedManager];
@ -173,37 +174,6 @@ NS_ASSUME_NONNULL_BEGIN
}
}
- (BOOL)doesSignalAccount:(SignalAccount *)signalAccount matchSearchTerm:(NSString *)searchTerm
{
OWSAssert(signalAccount);
OWSAssert(searchTerm.length > 0);
if ([signalAccount.contact.fullName.lowercaseString containsString:searchTerm.lowercaseString]) {
return YES;
}
NSString *asPhoneNumber = [PhoneNumber removeFormattingCharacters:searchTerm];
if (asPhoneNumber.length > 0 && [signalAccount.recipientId containsString:asPhoneNumber]) {
return YES;
}
return NO;
}
- (BOOL)doesSignalAccount:(SignalAccount *)signalAccount matchSearchTerms:(NSArray<NSString *> *)searchTerms
{
OWSAssert(signalAccount);
OWSAssert(searchTerms.count > 0);
for (NSString *searchTerm in searchTerms) {
if (![self doesSignalAccount:signalAccount matchSearchTerm:searchTerm]) {
return NO;
}
}
return YES;
}
- (NSArray<NSString *> *)searchTermsForSearchString:(NSString *)searchText
{
return [[[searchText ows_stripped]
@ -216,17 +186,7 @@ NS_ASSUME_NONNULL_BEGIN
- (NSArray<SignalAccount *> *)signalAccountsMatchingSearchString:(NSString *)searchText
{
NSArray<NSString *> *searchTerms = [self searchTermsForSearchString:searchText];
if (searchTerms.count < 1) {
return self.signalAccounts;
}
return [self.signalAccounts
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SignalAccount *signalAccount,
NSDictionary<NSString *, id> *_Nullable bindings) {
return [self doesSignalAccount:signalAccount matchSearchTerms:searchTerms];
}]];
return [self.conversationSearcher filterSignalAccounts:self.signalAccounts withSearchText:searchText];
}
- (BOOL)doesContact:(Contact *)contact matchSearchTerm:(NSString *)searchTerm

View File

@ -8,7 +8,6 @@
#import "Environment.h"
#import "NewGroupViewController.h"
#import "NewNonContactConversationViewController.h"
#import "OWSContactsSearcher.h"
#import "OWSTableViewController.h"
#import "Signal-Swift.h"
#import "UIColor+OWS.h"
@ -45,6 +44,7 @@ NS_ASSUME_NONNULL_BEGIN
MFMessageComposeViewControllerDelegate>
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@property (nonatomic, readonly) ConversationSearcher *conversationSearcher;
@property (nonatomic, readonly) UIView *noSignalContactsView;
@ -77,6 +77,7 @@ NS_ASSUME_NONNULL_BEGIN
self.view.backgroundColor = UIColor.whiteColor;
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
_conversationSearcher = [ConversationSearcher shared];
_nonContactAccountSet = [NSMutableSet set];
_collation = [UILocalizedIndexedCollation currentCollation];
@ -630,35 +631,17 @@ NS_ASSUME_NONNULL_BEGIN
- (NSArray<TSGroupThread *> *)filteredGroupThreads
{
AnySearcher *searcher = [[AnySearcher alloc] initWithIndexer:^NSString * _Nonnull(id _Nonnull obj) {
if (![obj isKindOfClass:[TSGroupThread class]]) {
OWSFail(@"unexpected item in searcher");
return @"";
}
TSGroupThread *groupThread = (TSGroupThread *)obj;
NSString *groupName = groupThread.groupModel.groupName;
NSMutableString *groupMemberNames = [NSMutableString new];
for (NSString *recipientId in groupThread.groupModel.groupMemberIds) {
NSString *contactName = [self.contactsViewHelper.contactsManager displayNameForPhoneIdentifier:recipientId];
[groupMemberNames appendFormat:@" %@", contactName];
}
return [NSString stringWithFormat:@"%@ %@", groupName, groupMemberNames];
}];
NSMutableArray<TSGroupThread *> *matchingThreads = [NSMutableArray new];
NSMutableArray<TSGroupThread *> *groupThreads = [NSMutableArray new];
[TSGroupThread enumerateCollectionObjectsUsingBlock:^(id obj, BOOL *stop) {
if (![obj isKindOfClass:[TSGroupThread class]]) {
// group and contact threads are in the same collection.
return;
}
TSGroupThread *groupThread = (TSGroupThread *)obj;
if ([searcher item:groupThread doesMatchQuery:self.searchBar.text]) {
[matchingThreads addObject:groupThread];
}
[groupThreads addObject:groupThread];
}];
return [matchingThreads copy];
return [self.conversationSearcher filterGroupThreads:groupThreads withSearchText:self.searchBar.text];
}
#pragma mark - No Contacts Mode

View File

@ -9,8 +9,8 @@
#import "Environment.h"
#import "NSString+OWS.h"
#import "OWSContactsManager.h"
#import "OWSContactsSearcher.h"
#import "OWSTableViewController.h"
#import "Signal-Swift.h"
#import "ThreadViewHelper.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
@ -29,7 +29,7 @@ NS_ASSUME_NONNULL_BEGIN
UISearchBarDelegate>
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@property (nonatomic, readonly) ConversationSearcher *conversationSearcher;
@property (nonatomic, readonly) ThreadViewHelper *threadViewHelper;
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
@ -54,6 +54,7 @@ NS_ASSUME_NONNULL_BEGIN
self.view.backgroundColor = [UIColor whiteColor];
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
_conversationSearcher = ConversationSearcher.shared;
_threadViewHelper = [ThreadViewHelper new];
_threadViewHelper.delegate = self;
@ -131,11 +132,13 @@ NS_ASSUME_NONNULL_BEGIN
__weak SelectThreadViewController *weakSelf = self;
ContactsViewHelper *helper = self.contactsViewHelper;
OWSTableContents *contents = [OWSTableContents new];
OWSTableSection *section = [OWSTableSection new];
// Threads
// Existing threads are listed first, ordered by most recently active
OWSTableSection *recentChatsSection = [OWSTableSection new];
recentChatsSection.headerTitle = NSLocalizedString(
@"SELECT_THREAD_TABLE_RECENT_CHATS_TITLE", @"Table section header for recently active conversations");
for (TSThread *thread in [self filteredThreadsWithSearchText]) {
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
[recentChatsSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
SelectThreadViewController *strongSelf = weakSelf;
OWSCAssert(strongSelf);
@ -145,16 +148,23 @@ NS_ASSUME_NONNULL_BEGIN
[cell configureWithThread:thread contactsManager:helper.contactsManager];
return cell;
}
customRowHeight:[ContactTableViewCell rowHeight]
actionBlock:^{
[weakSelf.delegate threadWasSelected:thread];
}]];
customRowHeight:[ContactTableViewCell rowHeight]
actionBlock:^{
[weakSelf.delegate threadWasSelected:thread];
}]];
}
// Contacts
if (recentChatsSection.itemCount > 0) {
[contents addSection:recentChatsSection];
}
// Contacts who don't yet have a thread are listed last
OWSTableSection *otherContactsSection = [OWSTableSection new];
otherContactsSection.headerTitle = NSLocalizedString(
@"SELECT_THREAD_TABLE_OTHER_CHATS_TITLE", @"Table section header for conversations you haven't recently used.");
NSArray<SignalAccount *> *filteredSignalAccounts = [self filteredSignalAccountsWithSearchText];
for (SignalAccount *signalAccount in filteredSignalAccounts) {
[section addItem:[OWSTableItem itemWithCustomCellBlock:^{
[otherContactsSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
SelectThreadViewController *strongSelf = weakSelf;
OWSCAssert(strongSelf);
@ -169,19 +179,24 @@ NS_ASSUME_NONNULL_BEGIN
[cell configureWithSignalAccount:signalAccount contactsManager:helper.contactsManager];
return cell;
}
customRowHeight:[ContactTableViewCell rowHeight]
actionBlock:^{
[weakSelf signalAccountWasSelected:signalAccount];
}]];
customRowHeight:[ContactTableViewCell rowHeight]
actionBlock:^{
[weakSelf signalAccountWasSelected:signalAccount];
}]];
}
if (section.itemCount < 1) {
[section
if (otherContactsSection.itemCount > 0) {
[contents addSection:otherContactsSection];
}
if (recentChatsSection.itemCount + otherContactsSection.itemCount < 1) {
OWSTableSection *emptySection = [OWSTableSection new];
[emptySection
addItem:[OWSTableItem
softCenterLabelItemWithText:NSLocalizedString(@"SETTINGS_BLOCK_LIST_NO_CONTACTS",
@"A label that indicates the user has no Signal contacts.")]];
[contents addSection:emptySection];
}
[contents addSection:section];
self.tableViewController.contents = contents;
}
@ -222,28 +237,9 @@ NS_ASSUME_NONNULL_BEGIN
- (NSArray<TSThread *> *)filteredThreadsWithSearchText
{
NSArray<TSThread *> *threads = self.threadViewHelper.threads;
NSString *searchTerm = [[self.searchBar text] ows_stripped];
if ([searchTerm isEqualToString:@""]) {
return threads;
}
NSString *formattedNumber = [PhoneNumber removeFormattingCharacters:searchTerm];
NSMutableArray *result = [NSMutableArray new];
for (TSThread *thread in threads) {
if ([thread.name containsString:searchTerm]) {
[result addObject:thread];
} else if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
if (formattedNumber.length > 0 && [contactThread.contactIdentifier containsString:formattedNumber]) {
[result addObject:thread];
}
}
}
return result;
return [self.conversationSearcher filterThreads:self.threadViewHelper.threads withSearchText:searchTerm];
}
- (NSArray<SignalAccount *> *)filteredSignalAccountsWithSearchText
@ -259,10 +255,11 @@ NS_ASSUME_NONNULL_BEGIN
}
}
NSString *searchString = [self.searchBar text];
NSString *searchString = self.searchBar.text;
NSArray<SignalAccount *> *matchingAccounts =
[self.contactsViewHelper signalAccountsMatchingSearchString:searchString];
ContactsViewHelper *helper = self.contactsViewHelper;
return [[helper signalAccountsMatchingSearchString:searchString]
return [matchingAccounts
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SignalAccount *signalAccount,
NSDictionary<NSString *, id> *_Nullable bindings) {
return ![contactIdsToIgnore containsObject:signalAccount.recipientId];

View File

@ -1,16 +0,0 @@
//
// OWSContactsSearcher.h
// Signal
//
// Created by Michael Kirk on 6/27/16.
// Copyright © 2016 Open Whisper Systems. All rights reserved.
//
#import "Contact.h"
@interface OWSContactsSearcher : NSObject
- (instancetype)initWithContacts:(NSArray<Contact *> *)contacts;
- (NSArray<Contact *> *)filterWithString:(NSString *)string;
@end

View File

@ -1,40 +0,0 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "OWSContactsSearcher.h"
#import "NSString+OWS.h"
#import <SignalServiceKit/PhoneNumber.h>
@interface OWSContactsSearcher ()
@property (copy) NSArray<Contact *> *contacts;
@end
@implementation OWSContactsSearcher
- (instancetype)initWithContacts:(NSArray<Contact *> *)contacts {
self = [super init];
if (!self) return self;
_contacts = contacts;
return self;
}
- (NSArray<Contact *> *)filterWithString:(NSString *)string {
NSString *searchTerm = [string ows_stripped];
if ([searchTerm isEqualToString:@""]) {
return self.contacts;
}
NSString *formattedNumber = [PhoneNumber removeFormattingCharacters:searchTerm];
// TODO: This assumes there's a single search term.
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"(fullName contains[c] %@) OR (ANY parsedPhoneNumbers.toE164 contains[c] %@)", searchTerm, formattedNumber];
return [self.contacts filteredArrayUsingPredicate:predicate];
}
@end

View File

@ -0,0 +1,90 @@
//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
import Foundation
import SignalServiceKit
@objc
class ConversationSearcher: NSObject {
@objc
public static let shared: ConversationSearcher = ConversationSearcher()
override private init() {
super.init()
}
@objc(filterThreads:withSearchText:)
public func filterThreads(_ threads: [TSThread], searchText: String) -> [TSThread] {
guard searchText.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 else {
return threads
}
return threads.filter { thread in
switch thread {
case let groupThread as TSGroupThread:
return self.groupThreadSearcher.matches(item: groupThread, query: searchText)
case let contactThread as TSContactThread:
return self.contactThreadSearcher.matches(item: contactThread, query: searchText)
default:
owsFail("Unexpected thread type: \(thread)")
return false
}
}
}
@objc(filterGroupThreads:withSearchText:)
public func filterGroupThreads(_ groupThreads: [TSGroupThread], searchText: String) -> [TSGroupThread] {
guard searchText.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 else {
return groupThreads
}
return groupThreads.filter { groupThread in
return self.groupThreadSearcher.matches(item: groupThread, query: searchText)
}
}
@objc(filterSignalAccounts:withSearchText:)
public func filterSignalAccounts(_ signalAccounts: [SignalAccount], searchText: String) -> [SignalAccount] {
guard searchText.trimmingCharacters(in: .whitespacesAndNewlines).count > 0 else {
return signalAccounts
}
return signalAccounts.filter { signalAccount in
self.signalAccountSearcher.matches(item: signalAccount, query: searchText)
}
}
// MARK: - Helpers
// MARK: Searchers
private lazy var groupThreadSearcher: Searcher<TSGroupThread> = Searcher { (groupThread: TSGroupThread) in
let groupName = groupThread.groupModel.groupName
let memberStrings = groupThread.groupModel.groupMemberIds.map { recipientId in
self.indexingString(recipientId: recipientId)
}.joined(separator: " ")
return "\(memberStrings) \(groupName ?? "")"
}
private lazy var contactThreadSearcher: Searcher<TSContactThread> = Searcher { (contactThread: TSContactThread) in
let recipientId = contactThread.contactIdentifier()
return self.indexingString(recipientId: recipientId)
}
private lazy var signalAccountSearcher: Searcher<SignalAccount> = Searcher { (signalAccount: SignalAccount) in
let recipientId = signalAccount.recipientId
return self.indexingString(recipientId: recipientId)
}
private var contactsManager: OWSContactsManager {
return Environment.getCurrent().contactsManager
}
private func indexingString(recipientId: String) -> String {
let contactName = contactsManager.displayName(forPhoneIdentifier: recipientId)
let profileName = contactsManager.profileName(forRecipientId: recipientId)
return "\(recipientId) \(contactName) \(profileName ?? "")"
}
}

View File

@ -19,6 +19,7 @@ import Foundation
}
}
// A generic searching class, configurable with an indexing block
class Searcher<T> {
private let indexer: (T) -> String
@ -36,7 +37,16 @@ class Searcher<T> {
}
private func stem(string: String) -> [String] {
return normalize(string: string).components(separatedBy: .whitespaces)
var normalized = normalize(string: string)
// Remove any phone number formatting from the search terms
let nonformattingScalars = normalized.unicodeScalars.lazy.filter {
!CharacterSet.punctuationCharacters.contains($0)
}
normalized = String(String.UnicodeScalarView(nonformattingScalars))
return normalized.components(separatedBy: .whitespacesAndNewlines)
}
private func normalize(string: String) -> String {

View File

@ -170,6 +170,8 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12;
self.nameLabel.attributedText = attributedText;
if ([thread isKindOfClass:[TSContactThread class]]) {
self.recipientId = thread.contactIdentifier;
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(otherUsersProfileDidChange:)
name:kNSNotificationName_OtherUsersProfileDidChange
@ -220,6 +222,7 @@ const CGFloat kContactTableViewCellAvatarTextMargin = 12;
diameter:kContactTableViewCellAvatarSize
contactsManager:contactsManager] build];
}
- (void)updateProfileName
{
OWSContactsManager *contactsManager = self.contactsManager;

View File

@ -4,6 +4,7 @@
import XCTest
import PromiseKit
import SignalServiceKit
struct VerificationFailedError: Error { }
struct FailedToGetRPRegistrationTokenError: Error { }

View File

@ -3,6 +3,7 @@
//
import XCTest
import SignalServiceKit
/**
* This is a brittle test, which will break if our layout changes.
@ -23,7 +24,12 @@ class MesssagesBubblesSizeCalculatorTest: XCTestCase {
func viewItemForText(_ text: String?) -> ConversationViewItem {
let interaction = TSOutgoingMessage(timestamp: 0, in: thread, messageBody: text)
interaction.save()
let viewItem = ConversationViewItem(tsInteraction:interaction, isGroupThread:false)
var viewItem: ConversationViewItem!
interaction.dbReadWriteConnection().readWrite { transaction in
viewItem = ConversationViewItem(interaction: interaction, isGroupThread: false, transaction: transaction)
}
viewItem.shouldShowDate = false
viewItem.shouldHideRecipientStatus = true
return viewItem

View File

@ -8,6 +8,7 @@
#import <SignalServiceKit/TSAttachmentStream.h>
#import <SignalServiceKit/TSOutgoingMessage.h>
#import <XCTest/XCTest.h>
#import <YapDatabase/YapDatabaseConnection.h>
@interface ConversationViewItem (Testing)
@ -64,8 +65,10 @@
OWSAssert([[NSFileManager defaultManager] fileExistsAtPath:filePath]);
TSAttachmentStream *attachment = [[TSAttachmentStream alloc] initWithContentType:mimeType sourceFilename:nil];
DataSource *dataSource = [DataSourcePath dataSourceWithFilePath:filePath];
TSAttachmentStream *attachment = [[TSAttachmentStream alloc] initWithContentType:mimeType
byteCount:(UInt32)dataSource.dataLength
sourceFilename:nil];
BOOL success = [attachment writeDataSource:dataSource];
OWSAssert(success);
[attachment save];
@ -75,7 +78,12 @@
TSOutgoingMessage *message =
[[TSOutgoingMessage alloc] initWithTimestamp:1 inThread:nil messageBody:nil attachmentIds:attachmentIds];
[message save];
ConversationViewItem *viewItem = [[ConversationViewItem alloc] initWithInteraction:message isGroupThread:NO];
__block ConversationViewItem *viewItem = nil;
[TSYapDatabaseObject.dbReadConnection readWithBlock:^(YapDatabaseReadTransaction *transaction) {
viewItem = [[ConversationViewItem alloc] initWithInteraction:message isGroupThread:NO transaction:transaction];
}];
return viewItem;
}

View File

@ -1,35 +0,0 @@
// Copyright © 2017 Open Whisper Systems. All rights reserved.
//
import XCTest
import AVKit
import WebRTC
/**
* These tests are obtuse - they just assert the exact implementation of the methods. Normally I wouldn't include them,
* but these methods make use of a header not included in the standard distribution of the WebRTC.framework. We've
* included the header in our local project, and test the methods here to make sure that they are still available when
* we upgrade the framework.
*
* If they are failing, it's possible the RTCAudioSession header, and our usage of it, need to be updated.
*/
class CallAudioSessionTest: XCTestCase {
func testAudioSession() {
let rtcAudioSession = RTCAudioSession.sharedInstance()
// Sanity Check
XCTAssertFalse(rtcAudioSession.useManualAudio)
CallAudioSession().configure()
XCTAssertTrue(rtcAudioSession.useManualAudio)
XCTAssertFalse(rtcAudioSession.isAudioEnabled)
CallAudioSession().start()
XCTAssertTrue(rtcAudioSession.useManualAudio)
XCTAssertTrue(rtcAudioSession.isAudioEnabled)
CallAudioSession().stop()
XCTAssertTrue(rtcAudioSession.useManualAudio)
XCTAssertFalse(rtcAudioSession.isAudioEnabled)
}
}

View File

@ -9,14 +9,15 @@ class SearcherTest: XCTestCase {
struct TestCharacter {
let name: String
let description: String
let phoneNumber: String?
}
let smerdyakov = TestCharacter(name: "Pavel Fyodorovich Smerdyakov", description: "A rusty hue in the sky")
let stinkingLizaveta = TestCharacter(name: "Stinking Lizaveta", description: "object of pity")
let regularLizaveta = TestCharacter(name: "Lizaveta", description: "")
let smerdyakov = TestCharacter(name: "Pavel Fyodorovich Smerdyakov", description: "A rusty hue in the sky", phoneNumber: nil)
let stinkingLizaveta = TestCharacter(name: "Stinking Lizaveta", description: "object of pity", phoneNumber: "+13235555555")
let regularLizaveta = TestCharacter(name: "Lizaveta", description: "", phoneNumber: "1 (415) 555-5555")
let indexer = { (character: TestCharacter) in
return "\(character.name) \(character.description)"
return "\(character.name) \(character.description) \(character.phoneNumber ?? "")"
}
var searcher: Searcher<TestCharacter> {
@ -57,4 +58,20 @@ class SearcherTest: XCTestCase {
XCTAssert(searcher.matches(item: stinkingLizaveta, query: "Lizaveta St"))
XCTAssert(searcher.matches(item: stinkingLizaveta, query: " Lizaveta St "))
}
func testFormattingChars() {
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"323"))
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"1-323-555-5555"))
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"13235555555"))
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"+1-323"))
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"Liza +1-323"))
// Sanity check, match both by names
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"Liza"))
XCTAssert(searcher.matches(item: regularLizaveta, query:"Liza"))
// Disambiguate the two Liza's by area code
XCTAssert(searcher.matches(item: stinkingLizaveta, query:"Liza 323"))
XCTAssertFalse(searcher.matches(item: regularLizaveta, query:"Liza 323"))
}
}

View File

@ -1342,6 +1342,12 @@
/* Label for 'select gif to attach' action sheet button */
"SELECT_GIF_BUTTON" = "GIF";
/* Table section header for conversations you haven't recently used. */
"SELECT_THREAD_TABLE_OTHER_CHATS_TITLE" = "Other Contacts";
/* Table section header for recently active conversations */
"SELECT_THREAD_TABLE_RECENT_CHATS_TITLE" = "Recent Chats";
/* No comment provided by engineer. */
"SEND_AGAIN_BUTTON" = "Send Again";

View File

@ -106,7 +106,7 @@
// This should be redundant with the logic above.
[logPathSet addObjectsFromArray:self.fileLogger.logFileManager.unsortedLogFilePaths];
NSArray<NSString *> *logPaths = logPathSet.allObjects;
return [logPaths sortedArrayUsingSelector:@selector(compare:)];
return [logPaths sortedArrayUsingSelector:@selector((compare:))];
}
- (void)wipeLogs

View File

@ -320,8 +320,6 @@ NSString *const kSyncMessageFileExtension = @"bin";
// Store the file in a subdirectory whose name is the uniqueId of this attachment,
// to avoid collisions between multiple attachments with the same name.
NSString *attachmentFolderPath = [folder stringByAppendingPathComponent:uniqueId];
NSError *error = nil;
BOOL attachmentFolderPathExists = [[NSFileManager defaultManager] fileExistsAtPath:attachmentFolderPath];
if (![OWSFileSystem ensureDirectoryExists:attachmentFolderPath]) {
return nil;
}