From 06f52deaf9114971e76c0d5971ffbb77fd8f6961 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 1 Dec 2017 16:25:33 -0800 Subject: [PATCH 1/8] address some additional compiler warnings --- SignalMessaging/utils/DebugLogger.m | 2 +- SignalServiceKit/src/Util/MIMETypeUtil.m | 2 -- 2 files changed, 1 insertion(+), 3 deletions(-) diff --git a/SignalMessaging/utils/DebugLogger.m b/SignalMessaging/utils/DebugLogger.m index a4be908e9..5e2fb16ad 100644 --- a/SignalMessaging/utils/DebugLogger.m +++ b/SignalMessaging/utils/DebugLogger.m @@ -106,7 +106,7 @@ // This should be redundant with the logic above. [logPathSet addObjectsFromArray:self.fileLogger.logFileManager.unsortedLogFilePaths]; NSArray *logPaths = logPathSet.allObjects; - return [logPaths sortedArrayUsingSelector:@selector(compare:)]; + return [logPaths sortedArrayUsingSelector:@selector((compare:))]; } - (void)wipeLogs diff --git a/SignalServiceKit/src/Util/MIMETypeUtil.m b/SignalServiceKit/src/Util/MIMETypeUtil.m index ef09171cc..e779d2dbb 100644 --- a/SignalServiceKit/src/Util/MIMETypeUtil.m +++ b/SignalServiceKit/src/Util/MIMETypeUtil.m @@ -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; } From 71bafcc8f06adf1e1741f97d16818fb7257b5282 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Tue, 28 Nov 2017 16:23:54 -0800 Subject: [PATCH 2/8] Search SignalAccounts by profile name ...and fixup some tests --- Signal.xcodeproj/project.pbxproj | 4 -- .../src/ViewControllers/ContactsViewHelper.m | 54 ++++++++----------- Signal/src/util/Searcher.swift | 11 +++- Signal/test/Models/AccountManagerTest.swift | 1 + .../MesssagesBubblesSizeCalculatorTest.swift | 8 ++- .../ConversationViewItemTest.m | 12 ++++- Signal/test/call/CallAudioSessionTest.swift | 35 ------------ Signal/test/util/SearcherTest.swift | 25 +++++++-- 8 files changed, 71 insertions(+), 79 deletions(-) delete mode 100644 Signal/test/call/CallAudioSessionTest.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index d79e7e515..69bb72832 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -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 */; }; @@ -866,7 +865,6 @@ 45E615151E8C590B0018AD52 /* DisplayableText.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableText.swift; sourceTree = ""; }; 45E7A6A61E71CA7E00D44FB5 /* DisplayableTextFilterTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = DisplayableTextFilterTest.swift; sourceTree = ""; }; 45F170AB1E2F0351003FC1F2 /* CallAudioSession.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSession.swift; sourceTree = ""; }; - 45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioSessionTest.swift; sourceTree = ""; }; 45F170B31E2F0A6A003FC1F2 /* RTCAudioSession.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = RTCAudioSession.h; sourceTree = ""; }; 45F170BA1E2FC5D3003FC1F2 /* CallAudioService.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CallAudioService.swift; sourceTree = ""; }; 45F170CB1E310E22003FC1F2 /* WeakTimer.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = WeakTimer.swift; sourceTree = ""; }; @@ -1837,7 +1835,6 @@ B660F6731C29867F00687D6E /* call */ = { isa = PBXGroup; children = ( - 45F170AE1E2F0393003FC1F2 /* CallAudioSessionTest.swift */, 456F6E2E1E261D1000FD2210 /* PeerConnectionClientTest.swift */, ); path = call; @@ -2928,7 +2925,6 @@ 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 */, diff --git a/Signal/src/ViewControllers/ContactsViewHelper.m b/Signal/src/ViewControllers/ContactsViewHelper.m index 1a3f1b3fd..c30d33992 100644 --- a/Signal/src/ViewControllers/ContactsViewHelper.m +++ b/Signal/src/ViewControllers/ContactsViewHelper.m @@ -31,6 +31,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) BOOL shouldNotifyDelegateOfUpdatedContacts; @property (nonatomic) BOOL hasUpdatedContactsAtLeastOnce; @property (nonatomic) OWSProfileManager *profileManager; +@property (nonatomic, readonly) AnySearcher *signalAccountSearcher; @end @@ -59,6 +60,8 @@ NS_ASSUME_NONNULL_BEGIN [self updateContacts]; self.shouldNotifyDelegateOfUpdatedContacts = NO; + _signalAccountSearcher = [self buildAccountSearcher]; + [self observeNotifications]; return self; @@ -99,6 +102,24 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Contacts +- (AnySearcher *)buildAccountSearcher +{ + return [[AnySearcher alloc] initWithIndexer:^NSString *_Nonnull(id _Nonnull obj) { + if (![obj isKindOfClass:[SignalAccount class]]) { + OWSFail(@"unexpected item in searcher"); + return @""; + } + + SignalAccount *signalAccount = (SignalAccount *)obj; + + NSString *recipientId = signalAccount.recipientId; + NSString *contactName = [self.contactsManager displayNameForPhoneIdentifier:recipientId]; + NSString *profileName = [self.contactsManager profileNameForRecipientId:recipientId]; + + return [NSString stringWithFormat:@"%@ %@ %@", recipientId, contactName, profileName]; + }]; +} + - (nullable SignalAccount *)signalAccountForRecipientId:(NSString *)recipientId { OWSAssert([NSThread isMainThread]); @@ -173,37 +194,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 *)searchTerms -{ - OWSAssert(signalAccount); - OWSAssert(searchTerms.count > 0); - - for (NSString *searchTerm in searchTerms) { - if (![self doesSignalAccount:signalAccount matchSearchTerm:searchTerm]) { - return NO; - } - } - - return YES; -} - - (NSArray *)searchTermsForSearchString:(NSString *)searchText { return [[[searchText ows_stripped] @@ -225,7 +215,7 @@ NS_ASSUME_NONNULL_BEGIN return [self.signalAccounts filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SignalAccount *signalAccount, NSDictionary *_Nullable bindings) { - return [self doesSignalAccount:signalAccount matchSearchTerms:searchTerms]; + return [self.signalAccountSearcher item:signalAccount doesMatchQuery:searchText]; }]]; } diff --git a/Signal/src/util/Searcher.swift b/Signal/src/util/Searcher.swift index 0cd260025..aab5fa7fb 100644 --- a/Signal/src/util/Searcher.swift +++ b/Signal/src/util/Searcher.swift @@ -36,7 +36,16 @@ class Searcher { } 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 { diff --git a/Signal/test/Models/AccountManagerTest.swift b/Signal/test/Models/AccountManagerTest.swift index 3ac8a62d0..7a9589ffb 100644 --- a/Signal/test/Models/AccountManagerTest.swift +++ b/Signal/test/Models/AccountManagerTest.swift @@ -4,6 +4,7 @@ import XCTest import PromiseKit +import SignalServiceKit struct VerificationFailedError: Error { } struct FailedToGetRPRegistrationTokenError: Error { } diff --git a/Signal/test/Models/MesssagesBubblesSizeCalculatorTest.swift b/Signal/test/Models/MesssagesBubblesSizeCalculatorTest.swift index f4e96934b..9ee7327ec 100644 --- a/Signal/test/Models/MesssagesBubblesSizeCalculatorTest.swift +++ b/Signal/test/Models/MesssagesBubblesSizeCalculatorTest.swift @@ -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 diff --git a/Signal/test/ViewControllers/ConversationViewItemTest.m b/Signal/test/ViewControllers/ConversationViewItemTest.m index 3914015e8..df293aaec 100644 --- a/Signal/test/ViewControllers/ConversationViewItemTest.m +++ b/Signal/test/ViewControllers/ConversationViewItemTest.m @@ -8,6 +8,7 @@ #import #import #import +#import @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; } diff --git a/Signal/test/call/CallAudioSessionTest.swift b/Signal/test/call/CallAudioSessionTest.swift deleted file mode 100644 index 7e3e6d042..000000000 --- a/Signal/test/call/CallAudioSessionTest.swift +++ /dev/null @@ -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) - } -} diff --git a/Signal/test/util/SearcherTest.swift b/Signal/test/util/SearcherTest.swift index 0be0cdb44..8e12ac952 100644 --- a/Signal/test/util/SearcherTest.swift +++ b/Signal/test/util/SearcherTest.swift @@ -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 { @@ -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")) + } } From 286463bb2251366d2e238cf55c653656d5ea387a Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Fri, 1 Dec 2017 19:17:47 -0800 Subject: [PATCH 3/8] Thread picker adds sections for threads vs other contacts --- .../src/ViewControllers/ContactsViewHelper.m | 2 +- .../SelectThreadViewController.m | 46 ++++++++++++------- .../translations/en.lproj/Localizable.strings | 6 +++ 3 files changed, 37 insertions(+), 17 deletions(-) diff --git a/Signal/src/ViewControllers/ContactsViewHelper.m b/Signal/src/ViewControllers/ContactsViewHelper.m index c30d33992..4d7cbed81 100644 --- a/Signal/src/ViewControllers/ContactsViewHelper.m +++ b/Signal/src/ViewControllers/ContactsViewHelper.m @@ -3,7 +3,7 @@ // #import "ContactsViewHelper.h" -#import "ContactTableViewCell.h" +//#import "ContactTableViewCell.h" #import "Environment.h" #import "NSString+OWS.h" #import "OWSProfileManager.h" diff --git a/Signal/src/ViewControllers/SelectThreadViewController.m b/Signal/src/ViewControllers/SelectThreadViewController.m index a68ce4151..db3e27cbe 100644 --- a/Signal/src/ViewControllers/SelectThreadViewController.m +++ b/Signal/src/ViewControllers/SelectThreadViewController.m @@ -131,11 +131,13 @@ NS_ASSUME_NONNULL_BEGIN __weak SelectThreadViewController *weakSelf = self; ContactsViewHelper *helper = self.contactsViewHelper; OWSTableContents *contents = [OWSTableContents new]; - OWSTableSection *section = [OWSTableSection new]; - // Threads + // Threads are listed, most recent first. + 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 +147,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 *filteredSignalAccounts = [self filteredSignalAccountsWithSearchText]; for (SignalAccount *signalAccount in filteredSignalAccounts) { - [section addItem:[OWSTableItem itemWithCustomCellBlock:^{ + [otherContactsSection addItem:[OWSTableItem itemWithCustomCellBlock:^{ SelectThreadViewController *strongSelf = weakSelf; OWSCAssert(strongSelf); @@ -169,19 +178,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; } diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index c1dd815d5..5c8334918 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -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"; From 3ed52b6d5a718df4b591db307667706b3e838f17 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 4 Dec 2017 11:26:17 -0500 Subject: [PATCH 4/8] Fix profile label for share context // FREEBIE --- Signal/src/views/ContactTableViewCell.m | 3 +++ 1 file changed, 3 insertions(+) diff --git a/Signal/src/views/ContactTableViewCell.m b/Signal/src/views/ContactTableViewCell.m index 0f81b7774..f32996405 100644 --- a/Signal/src/views/ContactTableViewCell.m +++ b/Signal/src/views/ContactTableViewCell.m @@ -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; From 766e579961f391c2b71aeed91805796b0221c246 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 4 Dec 2017 12:24:04 -0500 Subject: [PATCH 5/8] Share picker searches by profile name Consolidate some of the share logic // FREEBIE --- .../src/ViewControllers/ContactsViewHelper.m | 4 +- .../SelectThreadViewController.m | 29 ++------ Signal/src/ViewControllers/ThreadViewHelper.h | 7 +- Signal/src/ViewControllers/ThreadViewHelper.m | 73 +++++++++++++++++++ 4 files changed, 86 insertions(+), 27 deletions(-) diff --git a/Signal/src/ViewControllers/ContactsViewHelper.m b/Signal/src/ViewControllers/ContactsViewHelper.m index 4d7cbed81..f1bfdb0f4 100644 --- a/Signal/src/ViewControllers/ContactsViewHelper.m +++ b/Signal/src/ViewControllers/ContactsViewHelper.m @@ -60,7 +60,7 @@ NS_ASSUME_NONNULL_BEGIN [self updateContacts]; self.shouldNotifyDelegateOfUpdatedContacts = NO; - _signalAccountSearcher = [self buildAccountSearcher]; + _signalAccountSearcher = [self buildSignalAccountSearcher]; [self observeNotifications]; @@ -102,7 +102,7 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Contacts -- (AnySearcher *)buildAccountSearcher +- (AnySearcher *)buildSignalAccountSearcher { return [[AnySearcher alloc] initWithIndexer:^NSString *_Nonnull(id _Nonnull obj) { if (![obj isKindOfClass:[SignalAccount class]]) { diff --git a/Signal/src/ViewControllers/SelectThreadViewController.m b/Signal/src/ViewControllers/SelectThreadViewController.m index db3e27cbe..c39e374f9 100644 --- a/Signal/src/ViewControllers/SelectThreadViewController.m +++ b/Signal/src/ViewControllers/SelectThreadViewController.m @@ -236,28 +236,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *)filteredThreadsWithSearchText { - NSArray *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.threadViewHelper threadsMatchingSearchString:searchTerm]; } - (NSArray *)filteredSignalAccountsWithSearchText @@ -273,10 +253,11 @@ NS_ASSUME_NONNULL_BEGIN } } - NSString *searchString = [self.searchBar text]; + NSString *searchString = self.searchBar.text; + NSArray *matchingAccounts = + [self.contactsViewHelper signalAccountsMatchingSearchString:searchString]; - ContactsViewHelper *helper = self.contactsViewHelper; - return [[helper signalAccountsMatchingSearchString:searchString] + return [matchingAccounts filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SignalAccount *signalAccount, NSDictionary *_Nullable bindings) { return ![contactIdsToIgnore containsObject:signalAccount.recipientId]; diff --git a/Signal/src/ViewControllers/ThreadViewHelper.h b/Signal/src/ViewControllers/ThreadViewHelper.h index d6d98b638..917ccf3de 100644 --- a/Signal/src/ViewControllers/ThreadViewHelper.h +++ b/Signal/src/ViewControllers/ThreadViewHelper.h @@ -4,6 +4,8 @@ NS_ASSUME_NONNULL_BEGIN +@class AnySearcher; + @protocol ThreadViewHelperDelegate - (void)threadListDidChange; @@ -22,8 +24,11 @@ NS_ASSUME_NONNULL_BEGIN @interface ThreadViewHelper : NSObject @property (nonatomic, weak) id delegate; - @property (nonatomic, readonly) NSMutableArray *threads; +@property (nonatomic, readonly) AnySearcher *groupThreadSearcher; +@property (nonatomic, readonly) AnySearcher *contactThreadSearcher; + +- (NSArray *)threadsMatchingSearchString:(NSString *)searchString; @end diff --git a/Signal/src/ViewControllers/ThreadViewHelper.m b/Signal/src/ViewControllers/ThreadViewHelper.m index 5af480c06..9bae7c91f 100644 --- a/Signal/src/ViewControllers/ThreadViewHelper.m +++ b/Signal/src/ViewControllers/ThreadViewHelper.m @@ -3,6 +3,7 @@ // #import "ThreadViewHelper.h" +#import "Signal-Swift.h" #import #import #import @@ -29,6 +30,8 @@ NS_ASSUME_NONNULL_BEGIN } [self initializeMapping]; + _groupThreadSearcher = [self buildGroupThreadSearcher]; + _contactThreadSearcher = [self buildContactThreadSearcher]; return self; } @@ -123,6 +126,76 @@ NS_ASSUME_NONNULL_BEGIN _threads = [threads copy]; } +#pragma mark - Searching + +- (OWSContactsManager *)contactsManager +{ + return [Environment getCurrent].contactsManager; +} + +- (NSString *)searchIndexStringForRecipientId:(NSString *)recipientId +{ + NSString *contactName = [self.contactsManager displayNameForPhoneIdentifier:recipientId]; + NSString *profileName = [self.contactsManager profileNameForRecipientId:recipientId]; + + return [NSString stringWithFormat:@"%@ %@ %@", recipientId, contactName, profileName]; +} + +- (AnySearcher *)buildContactThreadSearcher +{ + AnySearcher *searcher = [[AnySearcher alloc] initWithIndexer:^NSString *_Nonnull(id _Nonnull obj) { + if (![obj isKindOfClass:[TSContactThread class]]) { + OWSFail(@"unexpected item in searcher"); + return @""; + } + TSContactThread *contactThread = (TSContactThread *)obj; + + NSString *recipientId = contactThread.contactIdentifier; + return [self searchIndexStringForRecipientId:recipientId]; + }]; + + return searcher; +} + +- (AnySearcher *)buildGroupThreadSearcher +{ + 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 *groupMemberStrings = [NSMutableString new]; + for (NSString *recipientId in groupThread.groupModel.groupMemberIds) { + NSString *recipientString = [self searchIndexStringForRecipientId:recipientId]; + [groupMemberStrings appendFormat:@" %@", recipientString]; + } + + return [NSString stringWithFormat:@"%@ %@", groupName, groupMemberStrings]; + }]; + + return searcher; +} + +- (NSArray *)threadsMatchingSearchString:(NSString *)searchString +{ + if (searchString.length == 0) { + return self.threads; + } + + NSMutableArray *result = [NSMutableArray new]; + for (TSThread *thread in self.threads) { + AnySearcher *searcher = + [thread isKindOfClass:[TSContactThread class]] ? self.contactThreadSearcher : self.groupThreadSearcher; + if ([searcher item:thread doesMatchQuery:searchString]) { + [result addObject:thread]; + } + } + return result; +} + + @end NS_ASSUME_NONNULL_END From cd440b839f26d16f9b4e266b293f7b24eddb6e8c Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 4 Dec 2017 13:52:16 -0500 Subject: [PATCH 6/8] Consolidate search logic // FREEBIE --- Signal.xcodeproj/project.pbxproj | 8 -- .../src/ViewControllers/ContactsViewHelper.m | 35 +------- .../NewContactThreadViewController.m | 27 ++---- .../SelectThreadViewController.m | 10 ++- Signal/src/ViewControllers/ThreadViewHelper.h | 6 -- Signal/src/ViewControllers/ThreadViewHelper.m | 74 +--------------- Signal/src/contact/OWSContactsSearcher.h | 16 ---- Signal/src/contact/OWSContactsSearcher.m | 40 --------- Signal/src/util/Searcher.swift | 86 +++++++++++++++++++ 9 files changed, 102 insertions(+), 200 deletions(-) delete mode 100644 Signal/src/contact/OWSContactsSearcher.h delete mode 100644 Signal/src/contact/OWSContactsSearcher.m diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 69bb72832..ef904fa3e 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -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 */; }; @@ -815,8 +813,6 @@ 4579431C1E7C8CE9008ED0C0 /* Pastelog.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = Pastelog.h; sourceTree = ""; }; 4579431D1E7C8CE9008ED0C0 /* Pastelog.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = Pastelog.m; sourceTree = ""; }; 45794E851E00620000066731 /* CallUIAdapter.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = CallUIAdapter.swift; path = UserInterface/CallUIAdapter.swift; sourceTree = ""; }; - 45843D1D1D2236B30013E85A /* OWSContactsSearcher.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = OWSContactsSearcher.h; sourceTree = ""; }; - 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactsSearcher.m; sourceTree = ""; }; 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 = ""; }; 45855F361D9498A40084F340 /* OWSContactAvatarBuilder.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = OWSContactAvatarBuilder.m; sourceTree = ""; }; @@ -1642,8 +1638,6 @@ children = ( 76EB040818170B33006006FC /* OWSContactsManager.h */, 76EB040918170B33006006FC /* OWSContactsManager.m */, - 45843D1D1D2236B30013E85A /* OWSContactsSearcher.h */, - 45843D1E1D2236B30013E85A /* OWSContactsSearcher.m */, 4542F0931EB9372700C7EE92 /* SystemContactsFetcher.swift */, ); path = contact; @@ -2770,7 +2764,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,7 +2921,6 @@ 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 */, diff --git a/Signal/src/ViewControllers/ContactsViewHelper.m b/Signal/src/ViewControllers/ContactsViewHelper.m index f1bfdb0f4..50dafced6 100644 --- a/Signal/src/ViewControllers/ContactsViewHelper.m +++ b/Signal/src/ViewControllers/ContactsViewHelper.m @@ -31,7 +31,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) BOOL shouldNotifyDelegateOfUpdatedContacts; @property (nonatomic) BOOL hasUpdatedContactsAtLeastOnce; @property (nonatomic) OWSProfileManager *profileManager; -@property (nonatomic, readonly) AnySearcher *signalAccountSearcher; +@property (nonatomic, readonly) ConversationSearcher *conversationSearcher; @end @@ -51,6 +51,7 @@ NS_ASSUME_NONNULL_BEGIN _blockingManager = [OWSBlockingManager sharedManager]; _blockedPhoneNumbers = [_blockingManager blockedPhoneNumbers]; + _conversationSearcher = ConversationSearcher.shared; _contactsManager = [Environment getCurrent].contactsManager; _profileManager = [OWSProfileManager sharedManager]; @@ -60,8 +61,6 @@ NS_ASSUME_NONNULL_BEGIN [self updateContacts]; self.shouldNotifyDelegateOfUpdatedContacts = NO; - _signalAccountSearcher = [self buildSignalAccountSearcher]; - [self observeNotifications]; return self; @@ -102,24 +101,6 @@ NS_ASSUME_NONNULL_BEGIN #pragma mark - Contacts -- (AnySearcher *)buildSignalAccountSearcher -{ - return [[AnySearcher alloc] initWithIndexer:^NSString *_Nonnull(id _Nonnull obj) { - if (![obj isKindOfClass:[SignalAccount class]]) { - OWSFail(@"unexpected item in searcher"); - return @""; - } - - SignalAccount *signalAccount = (SignalAccount *)obj; - - NSString *recipientId = signalAccount.recipientId; - NSString *contactName = [self.contactsManager displayNameForPhoneIdentifier:recipientId]; - NSString *profileName = [self.contactsManager profileNameForRecipientId:recipientId]; - - return [NSString stringWithFormat:@"%@ %@ %@", recipientId, contactName, profileName]; - }]; -} - - (nullable SignalAccount *)signalAccountForRecipientId:(NSString *)recipientId { OWSAssert([NSThread isMainThread]); @@ -206,17 +187,7 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *)signalAccountsMatchingSearchString:(NSString *)searchText { - NSArray *searchTerms = [self searchTermsForSearchString:searchText]; - - if (searchTerms.count < 1) { - return self.signalAccounts; - } - - return [self.signalAccounts - filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SignalAccount *signalAccount, - NSDictionary *_Nullable bindings) { - return [self.signalAccountSearcher item:signalAccount doesMatchQuery:searchText]; - }]]; + return [self.conversationSearcher filterSignalAccounts:self.signalAccounts withSearchText:searchText]; } - (BOOL)doesContact:(Contact *)contact matchSearchTerm:(NSString *)searchTerm diff --git a/Signal/src/ViewControllers/NewContactThreadViewController.m b/Signal/src/ViewControllers/NewContactThreadViewController.m index 9b65ec0a6..b02aa5977 100644 --- a/Signal/src/ViewControllers/NewContactThreadViewController.m +++ b/Signal/src/ViewControllers/NewContactThreadViewController.m @@ -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 *)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 *matchingThreads = [NSMutableArray new]; + NSMutableArray *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 diff --git a/Signal/src/ViewControllers/SelectThreadViewController.m b/Signal/src/ViewControllers/SelectThreadViewController.m index c39e374f9..dfbc5a158 100644 --- a/Signal/src/ViewControllers/SelectThreadViewController.m +++ b/Signal/src/ViewControllers/SelectThreadViewController.m @@ -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; @@ -132,7 +133,7 @@ NS_ASSUME_NONNULL_BEGIN ContactsViewHelper *helper = self.contactsViewHelper; OWSTableContents *contents = [OWSTableContents new]; - // Threads are listed, most recent first. + // 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"); @@ -237,7 +238,8 @@ NS_ASSUME_NONNULL_BEGIN - (NSArray *)filteredThreadsWithSearchText { NSString *searchTerm = [[self.searchBar text] ows_stripped]; - return [self.threadViewHelper threadsMatchingSearchString:searchTerm]; + + return [self.conversationSearcher filterThreads:self.threadViewHelper.threads withSearchText:searchTerm]; } - (NSArray *)filteredSignalAccountsWithSearchText diff --git a/Signal/src/ViewControllers/ThreadViewHelper.h b/Signal/src/ViewControllers/ThreadViewHelper.h index 917ccf3de..6b73af8c4 100644 --- a/Signal/src/ViewControllers/ThreadViewHelper.h +++ b/Signal/src/ViewControllers/ThreadViewHelper.h @@ -4,8 +4,6 @@ NS_ASSUME_NONNULL_BEGIN -@class AnySearcher; - @protocol ThreadViewHelperDelegate - (void)threadListDidChange; @@ -25,10 +23,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic, weak) id delegate; @property (nonatomic, readonly) NSMutableArray *threads; -@property (nonatomic, readonly) AnySearcher *groupThreadSearcher; -@property (nonatomic, readonly) AnySearcher *contactThreadSearcher; - -- (NSArray *)threadsMatchingSearchString:(NSString *)searchString; @end diff --git a/Signal/src/ViewControllers/ThreadViewHelper.m b/Signal/src/ViewControllers/ThreadViewHelper.m index 9bae7c91f..3df98fdfe 100644 --- a/Signal/src/ViewControllers/ThreadViewHelper.m +++ b/Signal/src/ViewControllers/ThreadViewHelper.m @@ -17,6 +17,7 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) YapDatabaseConnection *uiDatabaseConnection; @property (nonatomic) YapDatabaseViewMappings *threadMappings; +@property (nonatomic) ConversationSearcher *conversationSearcher; @end @@ -30,8 +31,7 @@ NS_ASSUME_NONNULL_BEGIN } [self initializeMapping]; - _groupThreadSearcher = [self buildGroupThreadSearcher]; - _contactThreadSearcher = [self buildContactThreadSearcher]; + _conversationSearcher = ConversationSearcher.shared; return self; } @@ -126,76 +126,6 @@ NS_ASSUME_NONNULL_BEGIN _threads = [threads copy]; } -#pragma mark - Searching - -- (OWSContactsManager *)contactsManager -{ - return [Environment getCurrent].contactsManager; -} - -- (NSString *)searchIndexStringForRecipientId:(NSString *)recipientId -{ - NSString *contactName = [self.contactsManager displayNameForPhoneIdentifier:recipientId]; - NSString *profileName = [self.contactsManager profileNameForRecipientId:recipientId]; - - return [NSString stringWithFormat:@"%@ %@ %@", recipientId, contactName, profileName]; -} - -- (AnySearcher *)buildContactThreadSearcher -{ - AnySearcher *searcher = [[AnySearcher alloc] initWithIndexer:^NSString *_Nonnull(id _Nonnull obj) { - if (![obj isKindOfClass:[TSContactThread class]]) { - OWSFail(@"unexpected item in searcher"); - return @""; - } - TSContactThread *contactThread = (TSContactThread *)obj; - - NSString *recipientId = contactThread.contactIdentifier; - return [self searchIndexStringForRecipientId:recipientId]; - }]; - - return searcher; -} - -- (AnySearcher *)buildGroupThreadSearcher -{ - 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 *groupMemberStrings = [NSMutableString new]; - for (NSString *recipientId in groupThread.groupModel.groupMemberIds) { - NSString *recipientString = [self searchIndexStringForRecipientId:recipientId]; - [groupMemberStrings appendFormat:@" %@", recipientString]; - } - - return [NSString stringWithFormat:@"%@ %@", groupName, groupMemberStrings]; - }]; - - return searcher; -} - -- (NSArray *)threadsMatchingSearchString:(NSString *)searchString -{ - if (searchString.length == 0) { - return self.threads; - } - - NSMutableArray *result = [NSMutableArray new]; - for (TSThread *thread in self.threads) { - AnySearcher *searcher = - [thread isKindOfClass:[TSContactThread class]] ? self.contactThreadSearcher : self.groupThreadSearcher; - if ([searcher item:thread doesMatchQuery:searchString]) { - [result addObject:thread]; - } - } - return result; -} - - @end NS_ASSUME_NONNULL_END diff --git a/Signal/src/contact/OWSContactsSearcher.h b/Signal/src/contact/OWSContactsSearcher.h deleted file mode 100644 index 09f96e0d2..000000000 --- a/Signal/src/contact/OWSContactsSearcher.h +++ /dev/null @@ -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 *)contacts; -- (NSArray *)filterWithString:(NSString *)string; - -@end diff --git a/Signal/src/contact/OWSContactsSearcher.m b/Signal/src/contact/OWSContactsSearcher.m deleted file mode 100644 index 774f1038c..000000000 --- a/Signal/src/contact/OWSContactsSearcher.m +++ /dev/null @@ -1,40 +0,0 @@ -// -// Copyright (c) 2017 Open Whisper Systems. All rights reserved. -// - -#import "OWSContactsSearcher.h" -#import "NSString+OWS.h" -#import - -@interface OWSContactsSearcher () - -@property (copy) NSArray *contacts; - -@end - -@implementation OWSContactsSearcher - -- (instancetype)initWithContacts:(NSArray *)contacts { - self = [super init]; - if (!self) return self; - - _contacts = contacts; - return self; -} - -- (NSArray *)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 diff --git a/Signal/src/util/Searcher.swift b/Signal/src/util/Searcher.swift index aab5fa7fb..7adf75fc7 100644 --- a/Signal/src/util/Searcher.swift +++ b/Signal/src/util/Searcher.swift @@ -3,6 +3,91 @@ // 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 = 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 = Searcher { (contactThread: TSContactThread) in + let recipientId = contactThread.contactIdentifier() + return self.indexingString(recipientId: recipientId) + } + + private lazy var signalAccountSearcher: Searcher = 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 ?? "")" + } +} // ObjC compatible searcher @objc class AnySearcher: NSObject { @@ -19,6 +104,7 @@ import Foundation } } +// A generic searching class, configurable with an indexing block class Searcher { private let indexer: (T) -> String From 27ddf4a352d313b87831276bdc6107a8db855c2c Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 4 Dec 2017 13:57:11 -0500 Subject: [PATCH 7/8] Cleanup before PR --- Signal/src/ViewControllers/ContactsViewHelper.m | 1 - Signal/src/ViewControllers/ThreadViewHelper.h | 1 + Signal/src/ViewControllers/ThreadViewHelper.m | 3 --- 3 files changed, 1 insertion(+), 4 deletions(-) diff --git a/Signal/src/ViewControllers/ContactsViewHelper.m b/Signal/src/ViewControllers/ContactsViewHelper.m index 50dafced6..7fc14b62a 100644 --- a/Signal/src/ViewControllers/ContactsViewHelper.m +++ b/Signal/src/ViewControllers/ContactsViewHelper.m @@ -3,7 +3,6 @@ // #import "ContactsViewHelper.h" -//#import "ContactTableViewCell.h" #import "Environment.h" #import "NSString+OWS.h" #import "OWSProfileManager.h" diff --git a/Signal/src/ViewControllers/ThreadViewHelper.h b/Signal/src/ViewControllers/ThreadViewHelper.h index 6b73af8c4..d6d98b638 100644 --- a/Signal/src/ViewControllers/ThreadViewHelper.h +++ b/Signal/src/ViewControllers/ThreadViewHelper.h @@ -22,6 +22,7 @@ NS_ASSUME_NONNULL_BEGIN @interface ThreadViewHelper : NSObject @property (nonatomic, weak) id delegate; + @property (nonatomic, readonly) NSMutableArray *threads; @end diff --git a/Signal/src/ViewControllers/ThreadViewHelper.m b/Signal/src/ViewControllers/ThreadViewHelper.m index 3df98fdfe..5af480c06 100644 --- a/Signal/src/ViewControllers/ThreadViewHelper.m +++ b/Signal/src/ViewControllers/ThreadViewHelper.m @@ -3,7 +3,6 @@ // #import "ThreadViewHelper.h" -#import "Signal-Swift.h" #import #import #import @@ -17,7 +16,6 @@ NS_ASSUME_NONNULL_BEGIN @property (nonatomic) YapDatabaseConnection *uiDatabaseConnection; @property (nonatomic) YapDatabaseViewMappings *threadMappings; -@property (nonatomic) ConversationSearcher *conversationSearcher; @end @@ -31,7 +29,6 @@ NS_ASSUME_NONNULL_BEGIN } [self initializeMapping]; - _conversationSearcher = ConversationSearcher.shared; return self; } From e3b0333b974873e686bc61556315bc324a1ad0e8 Mon Sep 17 00:00:00 2001 From: Michael Kirk Date: Mon, 4 Dec 2017 15:28:49 -0500 Subject: [PATCH 8/8] CR: Separate class files // FREEBIE --- Signal.xcodeproj/project.pbxproj | 6 ++ Signal/src/util/ConversationSearcher.swift | 90 ++++++++++++++++++++++ Signal/src/util/Searcher.swift | 85 -------------------- 3 files changed, 96 insertions(+), 85 deletions(-) create mode 100644 Signal/src/util/ConversationSearcher.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index ef904fa3e..e140ffea0 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -255,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, ); }; }; @@ -844,6 +846,7 @@ 45A6DAD51EBBF85500893231 /* ReminderView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ReminderView.swift; sourceTree = ""; }; 45AE48501E0732D6004D96C2 /* TurnServerInfo.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = TurnServerInfo.swift; sourceTree = ""; }; 45B201741DAECBFD00C461E0 /* Signal-Bridging-Header.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = "Signal-Bridging-Header.h"; sourceTree = ""; }; + 45B72DD91FD5E70600151AF6 /* ConversationSearcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ConversationSearcher.swift; sourceTree = ""; }; 45BB93371E688E14001E3939 /* UIDevice+featureSupport.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIDevice+featureSupport.swift"; sourceTree = ""; }; 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 = ""; }; @@ -1717,6 +1720,7 @@ 76EB04FB18170B33006006FC /* Util.h */, 45F170D51E315310003FC1F2 /* Weak.swift */, 45F170CB1E310E22003FC1F2 /* WeakTimer.swift */, + 45B72DD91FD5E70600151AF6 /* ConversationSearcher.swift */, ); path = util; sourceTree = ""; @@ -2710,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 */, @@ -2966,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 */, ); diff --git a/Signal/src/util/ConversationSearcher.swift b/Signal/src/util/ConversationSearcher.swift new file mode 100644 index 000000000..7464850de --- /dev/null +++ b/Signal/src/util/ConversationSearcher.swift @@ -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 = 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 = Searcher { (contactThread: TSContactThread) in + let recipientId = contactThread.contactIdentifier() + return self.indexingString(recipientId: recipientId) + } + + private lazy var signalAccountSearcher: Searcher = 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 ?? "")" + } +} diff --git a/Signal/src/util/Searcher.swift b/Signal/src/util/Searcher.swift index 7adf75fc7..f38956c50 100644 --- a/Signal/src/util/Searcher.swift +++ b/Signal/src/util/Searcher.swift @@ -3,91 +3,6 @@ // 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 = 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 = Searcher { (contactThread: TSContactThread) in - let recipientId = contactThread.contactIdentifier() - return self.indexingString(recipientId: recipientId) - } - - private lazy var signalAccountSearcher: Searcher = 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 ?? "")" - } -} // ObjC compatible searcher @objc class AnySearcher: NSObject {