Consolidate search logic

// FREEBIE
pull/1/head
Michael Kirk 7 years ago
parent 766e579961
commit cd440b839f

@ -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 = "<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>"; };
@ -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 */,

@ -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<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.signalAccountSearcher item:signalAccount doesMatchQuery:searchText];
}]];
return [self.conversationSearcher filterSignalAccounts:self.signalAccounts withSearchText:searchText];
}
- (BOOL)doesContact:(Contact *)contact matchSearchTerm:(NSString *)searchTerm

@ -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

@ -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<TSThread *> *)filteredThreadsWithSearchText
{
NSString *searchTerm = [[self.searchBar text] ows_stripped];
return [self.threadViewHelper threadsMatchingSearchString:searchTerm];
return [self.conversationSearcher filterThreads:self.threadViewHelper.threads withSearchText:searchTerm];
}
- (NSArray<SignalAccount *> *)filteredSignalAccountsWithSearchText

@ -4,8 +4,6 @@
NS_ASSUME_NONNULL_BEGIN
@class AnySearcher;
@protocol ThreadViewHelperDelegate <NSObject>
- (void)threadListDidChange;
@ -25,10 +23,6 @@ NS_ASSUME_NONNULL_BEGIN
@property (nonatomic, weak) id<ThreadViewHelperDelegate> delegate;
@property (nonatomic, readonly) NSMutableArray<TSThread *> *threads;
@property (nonatomic, readonly) AnySearcher *groupThreadSearcher;
@property (nonatomic, readonly) AnySearcher *contactThreadSearcher;
- (NSArray<TSThread *> *)threadsMatchingSearchString:(NSString *)searchString;
@end

@ -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<TSThread *> *)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

@ -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

@ -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

@ -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<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 ?? "")"
}
}
// ObjC compatible searcher
@objc class AnySearcher: NSObject {
@ -19,6 +104,7 @@ import Foundation
}
}
// A generic searching class, configurable with an indexing block
class Searcher<T> {
private let indexer: (T) -> String

Loading…
Cancel
Save