// _____ _ // |_ _| |_ _ _ ___ ___ _ __ __ _ // | | | ' \| '_/ -_) -_) ' \/ _` |_ // |_| |_||_|_| \___\___|_|_|_\__,_(_) // // Threema iOS Client // Copyright (c) 2012-2020 Threema GmbH // // This program is free software: you can redistribute it and/or modify // it under the terms of the GNU Affero General Public License, version 3, // as published by the Free Software Foundation. // // This program is distributed in the hope that it will be useful, // but WITHOUT ANY WARRANTY; without even the implied warranty of // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the // GNU Affero General Public License for more details. // // You should have received a copy of the GNU Affero General Public License // along with this program. If not, see . #import "ContactPickerViewController.h" #import "ContactCell.h" #import "GroupCell.h" #import "CreateGroupCell.h" #import "Contact.h" #import "ProtocolDefines.h" #import "AppDelegate.h" #import "ServerAPIConnector.h" #import "ErrorHandler.h" #import "EntityManager.h" #import "BundleUtil.h" #import "ContactTableDataSource.h" #import "GroupTableDataSource.h" #import "WorkContactTableDataSource.h" #import "ModalPresenter.h" #import "UserSettings.h" #import "LicenseStore.h" #import "ThemedTableViewController.h" #import "ModalNavigationController.h" #import "Threema-Swift.h" typedef enum : NSUInteger { ModeContacts, ModeGroups, ModeWorkContacts } Mode; @interface ContactPickerViewController () @property Mode mode; @property id currentDataSource; @property (nonatomic) ContactTableDataSource *contactsDataSource; @property (nonatomic) GroupTableDataSource *groupsDataSource; @property (nonatomic) WorkContactTableDataSource *workContactsDataSource; @property (strong, nonatomic) UIView *statusBarView; @end @implementation ContactPickerViewController { NSMutableSet *groupContacts; BOOL groupMode; NSArray *filteredContacts; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { groupContacts = [NSMutableSet set]; } return self; } - (void)viewDidLoad { [super viewDidLoad]; _mode = ModeContacts; _currentDataSource = [self contactsDataSource]; [self.segmentedControl setTitle:@"contacts" forSegmentAtIndex:ModeContacts]; [self.segmentedControl setTitle:@"groups" forSegmentAtIndex:ModeGroups]; if ([LicenseStore requiresLicenseKey]) { [self.segmentedControl insertSegmentWithTitle:@"work" atIndex:ModeWorkContacts animated:NO]; if ([[self workContactsDataSource] numberOfSectionsInTableView:self.tableView] > 0) { // No regular contacts, so show Work contacts by default _mode = ModeWorkContacts; [self.segmentedControl setSelectedSegmentIndex:ModeWorkContacts]; _currentDataSource = [self workContactsDataSource]; } } for (int i = 0; i < self.segmentedControl.numberOfSegments; i++) { UIView *segment = self.segmentedControl.subviews[i]; for (id subview in segment.subviews) { if ([subview isKindOfClass:[UILabel class]]) { UILabel *label = (UILabel *)subview; if ([label.text isEqualToString:@"contacts"]) { segment.accessibilityLabel = NSLocalizedString(@"segmentcontrol_contacts", @""); } else if ([label.text isEqualToString:@"groups"]) { segment.accessibilityLabel = NSLocalizedString(@"segmentcontrol_groups", @""); } else if ([label.text isEqualToString:@"work"]) { segment.accessibilityLabel = NSLocalizedString(@"segmentcontrol_work_contacts", @""); } } } } [self.segmentedControl setTitle:nil forSegmentAtIndex:ModeContacts]; [self.segmentedControl setTitle:nil forSegmentAtIndex:ModeGroups]; [self.segmentedControl setImage:[BundleUtil imageNamed:@"Contact"] forSegmentAtIndex:ModeContacts]; [self.segmentedControl setImage:[BundleUtil imageNamed:@"Group"] forSegmentAtIndex:ModeGroups]; if ([LicenseStore requiresLicenseKey]) { [self.segmentedControl setTitle:nil forSegmentAtIndex:ModeWorkContacts]; [self.segmentedControl setImage:[BundleUtil imageNamed:@"Case"] forSegmentAtIndex:ModeWorkContacts]; } self.searchController = [[UISearchController alloc]initWithSearchResultsController:nil]; self.searchController.delegate = self; self.searchController.searchBar.showsScopeBar = NO; self.searchController.searchBar.scopeButtonTitles = nil; self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag; self.searchController.searchBar.delegate = self; self.searchController.searchResultsUpdater = self; self.searchController.searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth; [self.searchController.searchBar sizeToFit]; self.searchController.searchBar.barStyle = UISearchBarStyleMinimal; self.searchController.dimsBackgroundDuringPresentation = NO; self.definesPresentationContext = YES; self.searchController.hidesNavigationBarDuringPresentation = YES; if (@available(iOS 11.0, *)) { self.navigationItem.searchController = _searchController; } else { [self.view addSubview:self.searchController.searchBar]; self.tableView.contentInset = UIEdgeInsetsMake(self.searchController.searchBar.frame.size.height, 0, 0, 0); self.statusBarView = [[UIView alloc] initWithFrame:[[UIApplication sharedApplication] statusBarFrame]]; } [self setupColors]; self.tableView.rowHeight = UITableViewAutomaticDimension; if (@available(iOS 11.0, *)) { self.tableView.estimatedRowHeight = UITableViewAutomaticDimension; } else { self.tableView.estimatedRowHeight = 44.0; } } - (void)setupColors { [self.view setBackgroundColor:[Colors backgroundLight]]; [self.navigationController.view setBackgroundColor:[Colors background]]; [Colors updateTableView:self.tableView]; if (@available(iOS 11.0, *)) { self.searchController.searchBar.barTintColor = [UIColor clearColor]; self.searchController.searchBar.backgroundColor = [UIColor clearColor]; UINavigationBar *navigationBar = self.navigationController.navigationBar; if (navigationBar) { navigationBar.barTintColor = [Colors backgroundBaseColor]; } } else { self.searchController.searchBar.backgroundColor = [Colors backgroundBaseColor]; UINavigationBar *navigationBar = self.navigationController.navigationBar; if (navigationBar) { navigationBar.barTintColor = [Colors backgroundBaseColor]; } } [Colors updateSearchBar:_searchController.searchBar]; if (@available(iOS 11.0, *)) { self.searchController.searchBar.searchFieldBackgroundPositionAdjustment = UIOffsetMake(0.0, 7.0); self.navigationItem.largeTitleDisplayMode = [UserSettings sharedUserSettings].largeTitleDisplayMode; } else { [_statusBarView setBackgroundColor:[Colors searchBarStatusBar]]; } } - (BOOL)shouldAutorotate { return YES; } -(UIInterfaceOrientationMask)supportedInterfaceOrientations { if (SYSTEM_IS_IPAD) { return UIInterfaceOrientationMaskAll; } return UIInterfaceOrientationMaskAllButUpsideDown; } #pragma mark - Table view - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { [Colors updateTableViewCell:cell]; } - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(nonnull UIView *)view forSection:(NSInteger)section { UITableViewHeaderFooterView *headerView = (UITableViewHeaderFooterView*)view; [headerView.contentView setBackgroundColor:[Colors backgroundDark]]; [headerView.textLabel setTextColor:[Colors fontNormal]]; } - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath { return UITableViewAutomaticDimension; } -(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath { return UITableViewAutomaticDimension; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { if (_mode == ModeGroups) { return [self.currentDataSource numberOfSectionsInTableView:tableView] + 1; } if (_mode == ModeWorkContacts && [[UserSettings sharedUserSettings] companyDirectory] == true) { return [self.currentDataSource numberOfSectionsInTableView:tableView] + 1; } return [self.currentDataSource numberOfSectionsInTableView:tableView]; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (_mode == ModeGroups) { if (section == 0) { if (_searchController.searchBar.isFirstResponder) { return 0; } return 1; } return [self.currentDataSource tableView:tableView numberOfRowsInSection:section - 1]; } if (_mode == ModeWorkContacts && [[UserSettings sharedUserSettings] companyDirectory] == true) { if (section == 0) { return 1; } return [self.currentDataSource tableView:tableView numberOfRowsInSection:section - 1]; } return [self.currentDataSource tableView:tableView numberOfRowsInSection:section]; } - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { return [self.currentDataSource sectionIndexTitlesForTableView:tableView]; } - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { return [self.currentDataSource tableView:tableView sectionForSectionIndexTitle:title atIndex:index]; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { if (_mode == ModeGroups) { if (section == 1 && _searchController.searchBar.isFirstResponder == NO) { return NSLocalizedString(@"existing groups", nil); } return nil; } if (_mode == ModeWorkContacts && [[UserSettings sharedUserSettings] companyDirectory] == true) { return nil; } return [self.currentDataSource tableView:tableView titleForHeaderInSection:section]; } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell; if (_mode == ModeGroups) { if (indexPath.section == 0) { cell = [self.tableView dequeueReusableCellWithIdentifier:@"CreateGroupCell"]; } else { NSIndexPath *convertedIndex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1]; cell = [self tableView:tableView groupCellForIndexPath:convertedIndex]; } } else if (_mode == ModeWorkContacts) { if ([[UserSettings sharedUserSettings] companyDirectory] == true) { if (indexPath.section == 0) { cell = [self.tableView dequeueReusableCellWithIdentifier:@"CompanyDirectoryCell"]; [((CompanyDirectoryCell *)cell) setupColors]; } else { NSIndexPath *convertedIndex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1]; cell = [self tableView:tableView workContactCellForIndexPath:convertedIndex]; } } else { NSIndexPath *convertedIndex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section]; cell = [self tableView:tableView workContactCellForIndexPath:convertedIndex]; } } else { cell = [self tableView:tableView contactCellForIndexPath:indexPath]; } return cell; } - (UITableViewCell *)tableView:(UITableView *)tableView contactCellForIndexPath:(NSIndexPath *)indexPath { ContactCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactCell"]; Contact *contact = [self.contactsDataSource contactAtIndexPath:indexPath]; cell.contact = contact; return cell; } - (UITableViewCell *)tableView:(UITableView *)tableView groupCellForIndexPath:(NSIndexPath *)indexPath { GroupCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"GroupCell"]; GroupProxy *group = [self.groupsDataSource groupAtIndexPath:indexPath]; cell.group = group; return cell; } - (UITableViewCell *)tableView:(UITableView *)tableView workContactCellForIndexPath:(NSIndexPath *)indexPath { ContactCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactCell"]; Contact *contact = [self.workContactsDataSource workContactAtIndexPath:indexPath]; cell.contact = contact; return cell; } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { UIViewController *presentingVC = self.presentingViewController; GroupProxy *group = nil; Contact *contact = nil; if (_mode == ModeGroups) { if (indexPath.section != 0) { NSIndexPath *convertedIndex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1]; group = [((GroupTableDataSource *)_currentDataSource) groupAtIndexPath:convertedIndex]; } } else if (_mode == ModeWorkContacts) { if ([[UserSettings sharedUserSettings] companyDirectory] == true) { if (indexPath.section != 0) { NSIndexPath *convertedIndex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1]; contact = [((WorkContactTableDataSource *)_currentDataSource) workContactAtIndexPath:convertedIndex]; } } else { contact = [((WorkContactTableDataSource *)_currentDataSource) workContactAtIndexPath:indexPath]; } } else { contact = [((ContactTableDataSource *)_currentDataSource) contactAtIndexPath:indexPath]; } [_searchController setActive:NO]; [self dismissViewControllerAnimated:YES completion:^{ if (_mode == ModeGroups) { if (indexPath.section == 0) { MDMSetup *mdmSetup = [[MDMSetup alloc] initWithSetup:NO]; if ([mdmSetup disableCreateGroup]) { [UIAlertTemplate showAlertWithOwner:presentingVC title:@"" message:NSLocalizedString(@"disabled_by_device_policy", nil) actionOk:nil]; } else { UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"CreateGroup" bundle:nil]; UINavigationController *navVC = [storyboard instantiateInitialViewController]; [presentingVC presentViewController:navVC animated:YES completion:nil]; } } else { [self showConversationForGroup:group]; } } else if (_mode == ModeWorkContacts && [[UserSettings sharedUserSettings] companyDirectory] == true) { if (indexPath.section == 0) { CompanyDirectoryViewController *companyDirectoryViewController = (CompanyDirectoryViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"CompanyDirectoryViewController"]; companyDirectoryViewController.addContactActive = false; ModalNavigationController *nav = [[ModalNavigationController alloc] initWithRootViewController:companyDirectoryViewController]; nav.showDoneButton = true; nav.showFullScreenOnIPad = false; [presentingVC presentViewController:nav animated:YES completion:nil]; } else { [self showConversationForContact:contact]; } } else { [self showConversationForContact:contact]; } }]; } - (void)showConversationForGroup:(GroupProxy *)group { Conversation *conversation = group.conversation; NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys: conversation, kKeyConversation, [NSNumber numberWithBool:YES], kKeyForceCompose, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowConversation object:nil userInfo:info]; } - (void)showConversationForContact:(Contact *)contact { NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys: contact, kKeyContact, [NSNumber numberWithBool:YES], kKeyForceCompose, nil]; [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowConversation object:nil userInfo:info]; } - (IBAction)cancelAction:(id)sender { [self dismissViewControllerAnimated:YES completion:nil]; } - (ContactTableDataSource *)contactsDataSource { if (_contactsDataSource == nil) { _contactsDataSource = [ContactTableDataSource contactTableDataSource]; } return _contactsDataSource; } - (GroupTableDataSource *)groupsDataSource { if (_groupsDataSource == nil) { _groupsDataSource = [GroupTableDataSource groupTableDataSource]; } return _groupsDataSource; } - (WorkContactTableDataSource *)workContactsDataSource { if (_workContactsDataSource == nil) { _workContactsDataSource = [WorkContactTableDataSource workContactTableDataSource]; } return _workContactsDataSource; } #pragma mark - Search controller delegate - (void)willPresentSearchController:(UISearchController *)searchController { if (@available(iOS 11.0, *)) { self.searchController.searchBar.searchFieldBackgroundPositionAdjustment = UIOffsetMake(0.0, 0.0); } else { [self.searchController.view addSubview:_statusBarView]; } } - (void)willDismissSearchController:(UISearchController *)searchController { if (@available(iOS 11.0, *)) { self.searchController.searchBar.searchFieldBackgroundPositionAdjustment = UIOffsetMake(0.0, 7.0); } else { [_statusBarView removeFromSuperview]; } } -(void)updateSearchResultsForSearchController:(UISearchController *)searchController { [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]]; [self.tableView reloadData]; } - (NSArray *)searchWordsForText:(NSString *)text { NSArray *searchWords = nil; if (text && [text length] > 0) { searchWords = [text componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]]; } return searchWords; } #pragma mark - Actions - (IBAction)segmentedControlChanged:(id)sender { _mode = self.segmentedControl.selectedSegmentIndex; switch (_mode) { case ModeContacts: _currentDataSource = [self contactsDataSource]; [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]]; break; case ModeGroups: _currentDataSource = [self groupsDataSource]; [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]]; break; case ModeWorkContacts: _currentDataSource = [self workContactsDataSource]; [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]]; break; default: break; } [self.tableView reloadData]; } @end