session-ios/Signal/src/ViewControllers/SelectThreadViewController.m
Michael Kirk cd440b839f Consolidate search logic
// FREEBIE
2017-12-04 13:53:10 -05:00

306 lines
10 KiB
Objective-C

//
// Copyright (c) 2017 Open Whisper Systems. All rights reserved.
//
#import "SelectThreadViewController.h"
#import "BlockListUIUtils.h"
#import "ContactTableViewCell.h"
#import "ContactsViewHelper.h"
#import "Environment.h"
#import "NSString+OWS.h"
#import "OWSContactsManager.h"
#import "OWSTableViewController.h"
#import "Signal-Swift.h"
#import "ThreadViewHelper.h"
#import "UIColor+OWS.h"
#import "UIFont+OWS.h"
#import "UIView+OWS.h"
#import <SignalServiceKit/PhoneNumber.h>
#import <SignalServiceKit/SignalAccount.h>
#import <SignalServiceKit/TSAccountManager.h>
#import <SignalServiceKit/TSContactThread.h>
#import <SignalServiceKit/TSThread.h>
NS_ASSUME_NONNULL_BEGIN
@interface SelectThreadViewController () <OWSTableViewControllerDelegate,
ThreadViewHelperDelegate,
ContactsViewHelperDelegate,
UISearchBarDelegate>
@property (nonatomic, readonly) ContactsViewHelper *contactsViewHelper;
@property (nonatomic, readonly) ConversationSearcher *conversationSearcher;
@property (nonatomic, readonly) ThreadViewHelper *threadViewHelper;
@property (nonatomic, readonly) OWSTableViewController *tableViewController;
@property (nonatomic, readonly) UISearchBar *searchBar;
@end
#pragma mark -
@implementation SelectThreadViewController
- (void)loadView
{
[super loadView];
self.navigationItem.leftBarButtonItem =
[[UIBarButtonItem alloc] initWithBarButtonSystemItem:UIBarButtonSystemItemStop
target:self
action:@selector(dismissPressed:)];
self.view.backgroundColor = [UIColor whiteColor];
_contactsViewHelper = [[ContactsViewHelper alloc] initWithDelegate:self];
_conversationSearcher = ConversationSearcher.shared;
_threadViewHelper = [ThreadViewHelper new];
_threadViewHelper.delegate = self;
[self createViews];
[self updateTableContents];
}
- (void)viewDidLoad
{
[super viewDidLoad];
[self.navigationController.navigationBar setTranslucent:NO];
}
- (void)createViews
{
OWSAssert(self.delegate);
// Search
UISearchBar *searchBar = [UISearchBar new];
_searchBar = searchBar;
searchBar.searchBarStyle = UISearchBarStyleMinimal;
searchBar.delegate = self;
searchBar.placeholder = NSLocalizedString(@"SEARCH_BYNAMEORNUMBER_PLACEHOLDER_TEXT", @"");
searchBar.backgroundColor = [UIColor whiteColor];
[searchBar sizeToFit];
UIView *header = [self.delegate createHeaderWithSearchBar:searchBar];
// Table
_tableViewController = [OWSTableViewController new];
_tableViewController.delegate = self;
[self.view addSubview:self.tableViewController.view];
[_tableViewController.view autoPinWidthToSuperview];
[_tableViewController.view autoPinToTopLayoutGuideOfViewController:self withInset:0];
if (header) {
_tableViewController.tableView.tableHeaderView = header;
} else {
_tableViewController.tableView.tableHeaderView = searchBar;
}
[self autoPinViewToBottomGuideOrKeyboard:self.tableViewController.view];
}
#pragma mark - UISearchBarDelegate
- (void)searchBar:(UISearchBar *)searchBar textDidChange:(NSString *)searchText
{
[self updateTableContents];
}
- (void)searchBarSearchButtonClicked:(UISearchBar *)searchBar
{
[self updateTableContents];
}
- (void)searchBarCancelButtonClicked:(UISearchBar *)searchBar
{
[self updateTableContents];
}
- (void)searchBarResultsListButtonClicked:(UISearchBar *)searchBar
{
[self updateTableContents];
}
- (void)searchBar:(UISearchBar *)searchBar selectedScopeButtonIndexDidChange:(NSInteger)selectedScope
{
[self updateTableContents];
}
#pragma mark - Table Contents
- (void)updateTableContents
{
__weak SelectThreadViewController *weakSelf = self;
ContactsViewHelper *helper = self.contactsViewHelper;
OWSTableContents *contents = [OWSTableContents new];
// Existing threads are listed first, ordered by most recently active
OWSTableSection *recentChatsSection = [OWSTableSection new];
recentChatsSection.headerTitle = NSLocalizedString(
@"SELECT_THREAD_TABLE_RECENT_CHATS_TITLE", @"Table section header for recently active conversations");
for (TSThread *thread in [self filteredThreadsWithSearchText]) {
[recentChatsSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
SelectThreadViewController *strongSelf = weakSelf;
OWSCAssert(strongSelf);
// To be consistent with the threads (above), we use ContactTableViewCell
// instead of InboxTableViewCell to present contacts and threads.
ContactTableViewCell *cell = [ContactTableViewCell new];
[cell configureWithThread:thread contactsManager:helper.contactsManager];
return cell;
}
customRowHeight:[ContactTableViewCell rowHeight]
actionBlock:^{
[weakSelf.delegate threadWasSelected:thread];
}]];
}
if (recentChatsSection.itemCount > 0) {
[contents addSection:recentChatsSection];
}
// Contacts who don't yet have a thread are listed last
OWSTableSection *otherContactsSection = [OWSTableSection new];
otherContactsSection.headerTitle = NSLocalizedString(
@"SELECT_THREAD_TABLE_OTHER_CHATS_TITLE", @"Table section header for conversations you haven't recently used.");
NSArray<SignalAccount *> *filteredSignalAccounts = [self filteredSignalAccountsWithSearchText];
for (SignalAccount *signalAccount in filteredSignalAccounts) {
[otherContactsSection addItem:[OWSTableItem itemWithCustomCellBlock:^{
SelectThreadViewController *strongSelf = weakSelf;
OWSCAssert(strongSelf);
ContactTableViewCell *cell = [ContactTableViewCell new];
BOOL isBlocked = [helper isRecipientIdBlocked:signalAccount.recipientId];
if (isBlocked) {
cell.accessoryMessage
= NSLocalizedString(@"CONTACT_CELL_IS_BLOCKED", @"An indicator that a contact has been blocked.");
} else {
OWSAssert(cell.accessoryMessage == nil);
}
[cell configureWithSignalAccount:signalAccount contactsManager:helper.contactsManager];
return cell;
}
customRowHeight:[ContactTableViewCell rowHeight]
actionBlock:^{
[weakSelf signalAccountWasSelected:signalAccount];
}]];
}
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];
}
self.tableViewController.contents = contents;
}
- (void)signalAccountWasSelected:(SignalAccount *)signalAccount
{
OWSAssert(signalAccount);
OWSAssert(self.delegate);
ContactsViewHelper *helper = self.contactsViewHelper;
if ([helper isRecipientIdBlocked:signalAccount.recipientId] && ![self.delegate canSelectBlockedContact]) {
__weak SelectThreadViewController *weakSelf = self;
[BlockListUIUtils showUnblockSignalAccountActionSheet:signalAccount
fromViewController:self
blockingManager:helper.blockingManager
contactsManager:helper.contactsManager
completionBlock:^(BOOL isBlocked) {
if (!isBlocked) {
[weakSelf signalAccountWasSelected:signalAccount];
}
}];
return;
}
__block TSThread *thread = nil;
[[TSStorageManager sharedManager].dbReadWriteConnection
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *transaction) {
thread = [TSContactThread getOrCreateThreadWithContactId:signalAccount.recipientId transaction:transaction];
}];
OWSAssert(thread);
[self.delegate threadWasSelected:thread];
}
#pragma mark - Filter
- (NSArray<TSThread *> *)filteredThreadsWithSearchText
{
NSString *searchTerm = [[self.searchBar text] ows_stripped];
return [self.conversationSearcher filterThreads:self.threadViewHelper.threads withSearchText:searchTerm];
}
- (NSArray<SignalAccount *> *)filteredSignalAccountsWithSearchText
{
// We don't want to show a 1:1 thread with Alice and Alice's contact,
// so we de-duplicate by recipientId.
NSArray<TSThread *> *threads = self.threadViewHelper.threads;
NSMutableSet *contactIdsToIgnore = [NSMutableSet new];
for (TSThread *thread in threads) {
if ([thread isKindOfClass:[TSContactThread class]]) {
TSContactThread *contactThread = (TSContactThread *)thread;
[contactIdsToIgnore addObject:contactThread.contactIdentifier];
}
}
NSString *searchString = self.searchBar.text;
NSArray<SignalAccount *> *matchingAccounts =
[self.contactsViewHelper signalAccountsMatchingSearchString:searchString];
return [matchingAccounts
filteredArrayUsingPredicate:[NSPredicate predicateWithBlock:^BOOL(SignalAccount *signalAccount,
NSDictionary<NSString *, id> *_Nullable bindings) {
return ![contactIdsToIgnore containsObject:signalAccount.recipientId];
}]];
}
#pragma mark - Events
- (void)dismissPressed:(id)sender
{
[self.searchBar resignFirstResponder];
[self dismissViewControllerAnimated:YES completion:nil];
}
#pragma mark - OWSTableViewControllerDelegate
- (void)tableViewWillBeginDragging
{
[self.searchBar resignFirstResponder];
}
#pragma mark - ThreadViewHelperDelegate
- (void)threadListDidChange
{
[self updateTableContents];
}
#pragma mark - ContactsViewHelperDelegate
- (void)contactsViewHelperDidUpdateContacts
{
[self updateTableContents];
}
- (BOOL)shouldHideLocalNumber
{
return NO;
}
@end
NS_ASSUME_NONNULL_END