mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Use FTS for compose picker search
This commit is contained in:
parent
1d24fa7c50
commit
b4908e71e9
2 changed files with 174 additions and 41 deletions
|
@ -54,6 +54,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
@property (nonatomic, readonly) UILocalizedIndexedCollation *collation;
|
||||
|
||||
@property (nonatomic, readonly) UISearchBar *searchBar;
|
||||
@property (nonatomic) ComposeScreenSearchResultSet *searchResults;
|
||||
|
||||
// A list of possible phone numbers parsed from the search text as
|
||||
// E164 values.
|
||||
|
@ -71,12 +72,32 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
@implementation NewContactThreadViewController
|
||||
|
||||
|
||||
#pragma mark - Dependencies
|
||||
|
||||
- (ConversationSearcher *)conversationSearcher
|
||||
{
|
||||
return ConversationSearcher.shared;
|
||||
}
|
||||
|
||||
- (YapDatabaseConnection *)uiDatabaseConnection
|
||||
{
|
||||
return OWSPrimaryStorage.sharedManager.uiDatabaseConnection;
|
||||
}
|
||||
|
||||
- (OWSContactsManager *)contactsManager
|
||||
{
|
||||
return Environment.shared.contactsManager;
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (void)loadView
|
||||
{
|
||||
[super loadView];
|
||||
|
||||
_searchResults = ComposeScreenSearchResultSet.empty;
|
||||
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
|
||||
_conversationSearcher = [ConversationSearcher shared];
|
||||
_nonContactAccountSet = [NSMutableSet set];
|
||||
_collation = [UILocalizedIndexedCollation currentCollation];
|
||||
|
||||
|
@ -155,13 +176,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
{
|
||||
OWSAssertIsOnMainThread();
|
||||
|
||||
[self.contactsViewHelper.contactsManager
|
||||
userRequestedSystemContactsRefreshWithCompletion:^(NSError *_Nullable error) {
|
||||
if (error) {
|
||||
OWSLogError(@"refreshing contacts failed with error: %@", error);
|
||||
}
|
||||
[refreshControl endRefreshing];
|
||||
}];
|
||||
[self.contactsManager userRequestedSystemContactsRefreshWithCompletion:^(NSError *_Nullable error) {
|
||||
if (error) {
|
||||
OWSLogError(@"refreshing contacts failed with error: %@", error);
|
||||
}
|
||||
[refreshControl endRefreshing];
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)showSearchBar:(BOOL)isVisible
|
||||
|
@ -268,7 +288,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// Make sure we have requested contact access at this point if, e.g.
|
||||
// the user has no messages in their inbox and they choose to compose
|
||||
// a message.
|
||||
[self.contactsViewHelper.contactsManager requestSystemContactsOnce];
|
||||
[self.contactsManager requestSystemContactsOnce];
|
||||
|
||||
[self showContactAppropriateViews];
|
||||
}
|
||||
|
@ -295,7 +315,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
// App is killed and restarted when the user changes their contact permissions, so need need to "observe" anything
|
||||
// to re-render this.
|
||||
if (self.contactsViewHelper.contactsManager.isSystemContactsDenied) {
|
||||
if (self.contactsManager.isSystemContactsDenied) {
|
||||
OWSTableItem *contactReminderItem = [OWSTableItem
|
||||
itemWithCustomCellBlock:^{
|
||||
UITableViewCell *newCell = [OWSTableItem newCell];
|
||||
|
@ -334,7 +354,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
animated:YES];
|
||||
}]];
|
||||
|
||||
if (self.contactsViewHelper.contactsManager.isSystemContactsAuthorized) {
|
||||
if (self.contactsManager.isSystemContactsAuthorized) {
|
||||
// Invite Contacts
|
||||
[staticSection
|
||||
addItem:[OWSTableItem
|
||||
|
@ -347,7 +367,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
}
|
||||
[contents addSection:staticSection];
|
||||
|
||||
BOOL hasSearchText = [self.searchBar text].length > 0;
|
||||
BOOL hasSearchText = self.searchText.length > 0;
|
||||
|
||||
if (hasSearchText) {
|
||||
for (OWSTableSection *section in [self contactsSectionsForSearch]) {
|
||||
|
@ -404,7 +424,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
// No Contacts
|
||||
OWSTableSection *contactsSection = [OWSTableSection new];
|
||||
|
||||
if (self.contactsViewHelper.contactsManager.isSystemContactsAuthorized) {
|
||||
if (self.contactsManager.isSystemContactsAuthorized) {
|
||||
if (self.contactsViewHelper.hasUpdatedContactsAtLeastOnce) {
|
||||
|
||||
[contactsSection
|
||||
|
@ -433,7 +453,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
[contactsSection addItem:loadingItem];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
return @[ contactsSection ];
|
||||
}
|
||||
__weak NewContactThreadViewController *weakSelf = self;
|
||||
|
@ -650,25 +670,12 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (NSArray<SignalAccount *> *)filteredSignalAccounts
|
||||
{
|
||||
NSString *searchString = self.searchBar.text;
|
||||
|
||||
ContactsViewHelper *helper = self.contactsViewHelper;
|
||||
return [helper signalAccountsMatchingSearchString:searchString];
|
||||
return self.searchResults.signalAccounts;
|
||||
}
|
||||
|
||||
- (NSArray<TSGroupThread *> *)filteredGroupThreads
|
||||
{
|
||||
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;
|
||||
[groupThreads addObject:groupThread];
|
||||
}];
|
||||
|
||||
return [self.conversationSearcher filterGroupThreads:groupThreads withSearchText:self.searchBar.text];
|
||||
return self.searchResults.groupThreads;
|
||||
}
|
||||
|
||||
#pragma mark - No Contacts Mode
|
||||
|
@ -683,14 +690,13 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
- (void)presentInviteFlow
|
||||
{
|
||||
OWSInviteFlow *inviteFlow =
|
||||
[[OWSInviteFlow alloc] initWithPresentingViewController:self
|
||||
contactsManager:self.contactsViewHelper.contactsManager];
|
||||
[[OWSInviteFlow alloc] initWithPresentingViewController:self contactsManager:self.contactsManager];
|
||||
[self presentViewController:inviteFlow.actionSheetController animated:YES completion:nil];
|
||||
}
|
||||
|
||||
- (void)showContactAppropriateViews
|
||||
{
|
||||
if (self.contactsViewHelper.contactsManager.isSystemContactsAuthorized) {
|
||||
if (self.contactsManager.isSystemContactsAuthorized) {
|
||||
if (self.contactsViewHelper.hasUpdatedContactsAtLeastOnce && self.contactsViewHelper.signalAccounts.count < 1
|
||||
&& ![Environment.shared.preferences hasDeclinedNoContactsView]) {
|
||||
self.isNoContactsModeActive = YES;
|
||||
|
@ -733,8 +739,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
{
|
||||
|
||||
OWSInviteFlow *inviteFlow =
|
||||
[[OWSInviteFlow alloc] initWithPresentingViewController:self
|
||||
contactsManager:self.contactsViewHelper.contactsManager];
|
||||
[[OWSInviteFlow alloc] initWithPresentingViewController:self contactsManager:self.contactsManager];
|
||||
|
||||
OWSAssertDebug([phoneNumber length] > 0);
|
||||
NSString *confirmMessage = NSLocalizedString(@"SEND_SMS_CONFIRM_TITLE", @"");
|
||||
|
@ -866,6 +871,7 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
|
||||
{
|
||||
[BenchManager startEventWithTitle:@"Compose Search" eventId:@"Compose Search"];
|
||||
[self searchTextDidChange];
|
||||
}
|
||||
|
||||
|
@ -891,11 +897,22 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
|
||||
- (void)searchTextDidChange
|
||||
{
|
||||
[self updateSearchPhoneNumbers];
|
||||
|
||||
[self updateTableContents];
|
||||
NSString *searchText = self.searchText;
|
||||
[self.uiDatabaseConnection
|
||||
asyncReadWithBlock:^(YapDatabaseReadTransaction *_Nonnull transaction) {
|
||||
self.searchResults = [self.conversationSearcher searchForComposeScreenWithSearchText:searchText
|
||||
transaction:transaction
|
||||
contactsManager:self.contactsManager];
|
||||
}
|
||||
completionBlock:^{
|
||||
[self updateSearchPhoneNumbers];
|
||||
[self updateTableContents];
|
||||
[BenchManager completeEventWithEventId:@"Compose Search"];
|
||||
}];
|
||||
}
|
||||
|
||||
#pragma mark -
|
||||
|
||||
- (NSDictionary<NSString *, NSString *> *)callingCodesToCountryCodeMap
|
||||
{
|
||||
static NSDictionary<NSString *, NSString *> *result = nil;
|
||||
|
@ -928,9 +945,20 @@ NS_ASSUME_NONNULL_BEGIN
|
|||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)searchText
|
||||
{
|
||||
NSString *rawText = self.searchBar.text;
|
||||
NSString *stripped = rawText.ows_stripped;
|
||||
if (stripped.length == 0) {
|
||||
return @"";
|
||||
} else {
|
||||
return stripped;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSArray<NSString *> *)parsePossibleSearchPhoneNumbers
|
||||
{
|
||||
NSString *searchText = self.searchBar.text;
|
||||
NSString *searchText = self.searchText;
|
||||
|
||||
if (searchText.length < 8) {
|
||||
return @[];
|
||||
|
|
|
@ -51,7 +51,8 @@ public class ConversationSearchResult<SortKey>: Comparable where SortKey: Compar
|
|||
}
|
||||
}
|
||||
|
||||
public class ContactSearchResult: Comparable {
|
||||
@objc
|
||||
public class ContactSearchResult: NSObject, Comparable {
|
||||
public let signalAccount: SignalAccount
|
||||
public let contactsManager: ContactsManagerProtocol
|
||||
|
||||
|
@ -77,7 +78,8 @@ public class ContactSearchResult: Comparable {
|
|||
}
|
||||
}
|
||||
|
||||
public class SearchResultSet {
|
||||
@objc
|
||||
public class SearchResultSet: NSObject {
|
||||
public let searchText: String
|
||||
public let conversations: [ConversationSearchResult<ConversationSortKey>]
|
||||
public let contacts: [ContactSearchResult]
|
||||
|
@ -99,6 +101,67 @@ public class SearchResultSet {
|
|||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public class GroupSearchResult: NSObject, Comparable {
|
||||
public let thread: ThreadViewModel
|
||||
|
||||
private let sortKey: ConversationSortKey
|
||||
|
||||
init(thread: ThreadViewModel, sortKey: ConversationSortKey) {
|
||||
self.thread = thread
|
||||
self.sortKey = sortKey
|
||||
}
|
||||
|
||||
// MARK: Comparable
|
||||
|
||||
public static func < (lhs: GroupSearchResult, rhs: GroupSearchResult) -> Bool {
|
||||
return lhs.sortKey < rhs.sortKey
|
||||
}
|
||||
|
||||
// MARK: Equatable
|
||||
|
||||
public static func == (lhs: GroupSearchResult, rhs: GroupSearchResult) -> Bool {
|
||||
return lhs.thread.threadRecord.uniqueId == rhs.thread.threadRecord.uniqueId
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public class ComposeScreenSearchResultSet: NSObject {
|
||||
|
||||
@objc
|
||||
public let searchText: String
|
||||
|
||||
@objc
|
||||
public let groups: [GroupSearchResult]
|
||||
|
||||
@objc
|
||||
public var groupThreads: [TSGroupThread] {
|
||||
return groups.compactMap { $0.thread.threadRecord as? TSGroupThread }
|
||||
}
|
||||
|
||||
@objc
|
||||
public let signalContacts: [ContactSearchResult]
|
||||
|
||||
@objc
|
||||
public var signalAccounts: [SignalAccount] {
|
||||
return signalContacts.map { $0.signalAccount }
|
||||
}
|
||||
|
||||
public init(searchText: String, groups: [GroupSearchResult], signalContacts: [ContactSearchResult]) {
|
||||
self.searchText = searchText
|
||||
self.groups = groups
|
||||
self.signalContacts = signalContacts
|
||||
}
|
||||
|
||||
@objc
|
||||
public static let empty = ComposeScreenSearchResultSet(searchText: "", groups: [], signalContacts: [])
|
||||
|
||||
@objc
|
||||
public var isEmpty: Bool {
|
||||
return groups.isEmpty && signalContacts.isEmpty
|
||||
}
|
||||
}
|
||||
|
||||
@objc
|
||||
public class ConversationSearcher: NSObject {
|
||||
|
||||
|
@ -119,6 +182,48 @@ public class ConversationSearcher: NSObject {
|
|||
super.init()
|
||||
}
|
||||
|
||||
@objc
|
||||
public func searchForComposeScreen(searchText: String,
|
||||
transaction: YapDatabaseReadTransaction,
|
||||
contactsManager: ContactsManagerProtocol) -> ComposeScreenSearchResultSet {
|
||||
|
||||
var signalContacts: [ContactSearchResult] = []
|
||||
var groups: [GroupSearchResult] = []
|
||||
|
||||
self.finder.enumerateObjects(searchText: searchText, transaction: transaction) { (match: Any, snippet: String?) in
|
||||
|
||||
switch match {
|
||||
case let signalAccount as SignalAccount:
|
||||
let searchResult = ContactSearchResult(signalAccount: signalAccount, contactsManager: contactsManager)
|
||||
signalContacts.append(searchResult)
|
||||
case let groupThread as TSGroupThread:
|
||||
let sortKey = ConversationSortKey(creationDate: groupThread.creationDate,
|
||||
lastMessageReceivedAtDate: groupThread.lastInteractionForInbox(transaction: transaction)?.receivedAtDate())
|
||||
let threadViewModel = ThreadViewModel(thread: groupThread, transaction: transaction)
|
||||
let searchResult = GroupSearchResult(thread: threadViewModel, sortKey: sortKey)
|
||||
groups.append(searchResult)
|
||||
case is TSContactThread:
|
||||
// not included in compose screen results
|
||||
break
|
||||
case is TSMessage:
|
||||
// not included in compose screen results
|
||||
break
|
||||
default:
|
||||
owsFailDebug("unhandled item: \(match)")
|
||||
}
|
||||
}
|
||||
|
||||
// Order "contact results by display name.
|
||||
signalContacts.sort()
|
||||
|
||||
// Order the conversation and message results in reverse chronological order.
|
||||
// The contact results are pre-sorted by display name.
|
||||
groups.sort(by: >)
|
||||
|
||||
return ComposeScreenSearchResultSet(searchText: searchText, groups: groups, signalContacts: signalContacts)
|
||||
}
|
||||
|
||||
@objc
|
||||
public func results(searchText: String,
|
||||
transaction: YapDatabaseReadTransaction,
|
||||
contactsManager: ContactsManagerProtocol) -> SearchResultSet {
|
||||
|
@ -223,7 +328,7 @@ public class ConversationSearcher: NSObject {
|
|||
let groupName = groupThread.groupModel.groupName
|
||||
let memberStrings = groupThread.groupModel.groupMemberIds.map { recipientId in
|
||||
self.indexingString(recipientId: recipientId)
|
||||
}.joined(separator: " ")
|
||||
}.joined(separator: " ")
|
||||
|
||||
return "\(memberStrings) \(groupName ?? "")"
|
||||
}
|
||||
|
|
Loading…
Reference in a new issue