// _____ _ // |_ _| |_ _ _ ___ ___ _ __ __ _ // | | | ' \| '_/ -_) -_) ' \/ _` |_ // |_| |_||_|_| \___\___|_|_|_\__,_(_) // // 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 "ContactsViewController.h" #import "ContactCell.h" #import "GroupCell.h" #import "Contact.h" #import "ContactStore.h" #import "UserSettings.h" #import "DatabaseManager.h" #import "EntityManager.h" #import "GatewayAvatarMaker.h" #import "ContactTableDataSource.h" #import "GroupTableDataSource.h" #import "WorkContactTableDataSource.h" #import "GroupDetailsViewController.h" #import "ContactDetailsViewController.h" #import "DeleteConversationAction.h" #import "DeleteContactAction.h" #import "ModalPresenter.h" #import "RectUtil.h" #import "WorkDataFetcher.h" #import "LicenseStore.h" #import "BundleUtil.h" #import "ModalNavigationController.h" #import "AvatarMaker.h" #import "MDMSetup.h" #ifdef DEBUG static const DDLogLevel ddLogLevel = DDLogLevelInfo; #else static const DDLogLevel ddLogLevel = DDLogLevelWarning; #endif typedef enum : NSUInteger { ModeContacts, ModeGroups, ModeWorkContacts } Mode; @interface ContactsViewController () @property Mode mode; @property (nonatomic) id currentDataSource; @property EntityManager *entityManager; @property (nonatomic) ContactTableDataSource *contactsDataSource; @property (nonatomic) GroupTableDataSource *groupsDataSource; @property (nonatomic) WorkContactTableDataSource *workContactsDataSource; @property id prevNavigationControllerDelegate; @property (copy) GroupDetailsCompletionBlock groupCompletionBlock; @property (copy) void (^contactCompletionBlock)(ContactDetailsViewController *contactDetailsViewController); @property (strong, nonatomic) UIView *statusBarView; @property NSIndexPath *deletionIndexPath; @property id deleteAction; @property (nonatomic) BOOL isMultipleEditing; @property (nonatomic, strong) UIRefreshControl *rfControl; @end @implementation ContactsViewController { Contact *contactForDetails; GroupProxy *groupForDetails; } - (id)initWithCoder:(NSCoder *)aDecoder { self = [super initWithCoder:aDecoder]; if (self) { /* listen for blacklist change (to refresh contact labels with blocked icon) */ [[UserSettings sharedUserSettings] addObserver:self forKeyPath:@"blacklist" options:0 context:nil]; /* listen for stale contacts setting */ [[UserSettings sharedUserSettings] addObserver:self forKeyPath:@"hideStaleContacts" options:0 context:nil]; _entityManager = [[EntityManager alloc] init]; } return self; } - (void)viewDidLoad { [super viewDidLoad]; _currentDataSource = [self currentDataSource]; // iOS 10 and 12 have different subviews sorting, so we have to check it with name and replace it at the end with the image UIImage *contactImage = [BundleUtil imageNamed:@"Contact"]; contactImage.accessibilityLabel = [BundleUtil localizedStringForKey:@"segmentcontrol_contacts"]; UIImage *groupImage = [BundleUtil imageNamed:@"Group"]; groupImage.accessibilityLabel = [BundleUtil localizedStringForKey:@"segmentcontrol_groups"]; UIImage *workImage = [BundleUtil imageNamed:@"Case"]; workImage.accessibilityLabel = [BundleUtil localizedStringForKey:@"segmentcontrol_work_contacts"]; [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]; if (@available(iOS 11.0, *)) { self.tableView.tableHeaderView = [UserSettings sharedUserSettings].companyDirectory == true ? _companyDirectoryCell : nil; } } } 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:contactImage forSegmentAtIndex:ModeContacts]; [self.segmentedControl setImage:groupImage forSegmentAtIndex:ModeGroups]; if ([LicenseStore requiresLicenseKey]) { [self.segmentedControl setTitle:nil forSegmentAtIndex:ModeWorkContacts]; [self.segmentedControl setImage:workImage forSegmentAtIndex:ModeWorkContacts]; } self.navigationItem.leftBarButtonItem = self.editButtonItem; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showProfilePictureChanged:) name:kNotificationShowProfilePictureChanged object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshWorkContactTableView:) name:kNotificationRefreshWorkContactTableView object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshContactSortIndices:) name:kNotificationRefreshContactSortIndices object:nil]; [self setRefreshControlTitle:NO]; [self registerForPreviewingWithDelegate:self sourceView:self.view]; [self setupColors]; self.isMultipleEditing = NO; [self updateNoContactsView]; 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.tableView.tableHeaderView = self.searchController.searchBar; self.statusBarView = [[UIView alloc] initWithFrame:[[UIApplication sharedApplication] statusBarFrame]]; } UITapGestureRecognizer *companyDirectoryRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(companyDirectoryTapped:)]; [_companyDirectoryCell addGestureRecognizer:companyDirectoryRecognizer]; } - (void)refresh { [self setupColors]; [Colors updateNavigationBar:self.navigationController.navigationBar]; _companyDirectoryCell.titleLabel.text = [MyIdentityStore sharedMyIdentityStore].companyName; [self.tableView reloadData]; } - (BOOL)isWorkActive { return _mode == ModeWorkContacts; } - (void)setupColors { [self.navigationController.view setBackgroundColor:[Colors background]]; [_statusBarView setBackgroundColor:[Colors searchBarStatusBar]]; _noContactsTitleLabel.textColor = [Colors fontNormal]; _noContactsMessageLabel.textColor = [Colors fontLight]; [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, *)) { if (!self.searchController.isActive) { self.searchController.searchBar.searchFieldBackgroundPositionAdjustment = UIOffsetMake(0.0, 7.0); } self.navigationItem.largeTitleDisplayMode = [UserSettings sharedUserSettings].largeTitleDisplayMode; } else { [_statusBarView setBackgroundColor:[Colors searchBarStatusBar]]; } if (!self.rfControl) { self.rfControl = [UIRefreshControl new]; [_rfControl addTarget:self action:@selector(pulledForRefresh:) forControlEvents:UIControlEventValueChanged]; self.refreshControl = _rfControl; self.tableView.refreshControl = _rfControl; } _rfControl.backgroundColor = [UIColor clearColor]; [self setRefreshControlTitle:NO]; _companyDirectoryCell.backgroundColor = [Colors background]; _companyDirectoryCell.selectedBackgroundView = [[UIView alloc] initWithFrame:_companyDirectoryCell.frame]; _companyDirectoryCell.selectedBackgroundView.backgroundColor = [Colors backgroundDark]; _companyDirectoryCell.titleLabel.textColor = [Colors fontNormal]; _companyDirectoryCell.descriptionLabel.textColor = [Colors fontLight]; _companyDirectoryCell.companyAvatar.image = [[AvatarMaker sharedAvatarMaker] companyImage]; [_companyDirectoryCell setTintColor:[Colors main]]; } - (void)viewWillAppear:(BOOL)animated { [super viewWillAppear:animated]; if (SYSTEM_IS_IPAD == NO) { [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES]; } else { if (contactForDetails && _mode == ModeContacts) { NSIndexPath *indexPath = [self.contactsDataSource indexPathForObject:contactForDetails]; [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; } else if (groupForDetails && _mode == ModeGroups) { NSIndexPath *indexPath = [self.groupsDataSource indexPathForObject:groupForDetails]; [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; } else if (contactForDetails && _mode == ModeWorkContacts) { NSIndexPath *indexPath = [self.workContactsDataSource indexPathForObject:contactForDetails]; [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone]; } self.tableView.backgroundView = nil; } [self updateNoContactsView]; [self refresh]; } - (void)viewWillDisappear:(BOOL)animated { [super viewWillDisappear:animated]; } - (BOOL)shouldAutorotate { return YES; } -(UIInterfaceOrientationMask)supportedInterfaceOrientations { if (SYSTEM_IS_IPAD) { return UIInterfaceOrientationMaskAll; } return UIInterfaceOrientationMaskAllButUpsideDown; } - (void)dealloc { [[UserSettings sharedUserSettings] removeObserver:self forKeyPath:@"blacklist"]; [[UserSettings sharedUserSettings] removeObserver:self forKeyPath:@"hideStaleContacts"]; } - (void)updateNoContactsView { if ([self hasData]) { if (_mode == ModeContacts) { [self setFooterView:YES]; } else if (_mode == ModeWorkContacts) { [self setFooterView:YES]; } else { [self setFooterView:NO]; } } else { [self setFooterView:NO]; NSString *messageKey; NSString *titleKey; if (_mode == ModeContacts) { titleKey = @"no_contacts"; messageKey = [UserSettings sharedUserSettings].syncContacts ? @"no_contacts_syncon" : @"no_contacts_syncoff"; } else if (_mode == ModeWorkContacts) { titleKey = @"no_work_contacts"; messageKey = @"no_work_contacts_message"; } else { titleKey = @"no_groups"; messageKey = @"no_groups_message"; } _noContactsTitleLabel.text = NSLocalizedString(titleKey, nil); _noContactsMessageLabel.text = NSLocalizedString(messageKey, nil); self.tableView.tableFooterView = _noContactsView; } } - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context { if ([keyPath isEqualToString:@"blacklist"]) { dispatch_async(dispatch_get_main_queue(), ^{ [self resetData]; }); } else if ([keyPath isEqualToString:@"hideStaleContacts"]) { dispatch_async(dispatch_get_main_queue(), ^{ [self resetData]; }); } } - (void)resetData { _currentDataSource = nil; _contactsDataSource = nil; _groupsDataSource = nil; _workContactsDataSource = nil; [self.tableView reloadData]; } - (void)setSelectionForContact:(Contact *)contact { if (_segmentedControl.selectedSegmentIndex != ModeContacts) { _segmentedControl.selectedSegmentIndex = ModeContacts; [self segmentedControlChanged:self]; } /* fix highlighted cell in our view */ NSIndexPath *selectedRow = [self.tableView indexPathForSelectedRow]; NSIndexPath *indexPath = [self.contactsDataSource indexPathForObject:contact]; [self changeSelectedRow:selectedRow to:indexPath]; contactForDetails = contact; } - (void)setSelectionForGroup:(GroupProxy *)group { if (_segmentedControl.selectedSegmentIndex != ModeGroups) { _segmentedControl.selectedSegmentIndex = ModeGroups; [self segmentedControlChanged:self]; } /* fix highlighted cell in our view */ NSIndexPath *selectedRow = [self.tableView indexPathForSelectedRow]; NSIndexPath *newRow = [self.groupsDataSource indexPathForObject:group]; [self changeSelectedRow:selectedRow to:newRow]; groupForDetails = group; } - (void)setSelectionForWorkContact:(Contact *)contact { if (_segmentedControl.selectedSegmentIndex != ModeWorkContacts) { _segmentedControl.selectedSegmentIndex = ModeWorkContacts; [self segmentedControlChanged:self]; } /* fix highlighted cell in our view */ NSIndexPath *selectedRow = [self.tableView indexPathForSelectedRow]; NSIndexPath *indexPath = [self.workContactsDataSource indexPathForObject:contact]; [self changeSelectedRow:selectedRow to:indexPath]; contactForDetails = contact; } - (void)changeSelectedRow:(NSIndexPath *)selectedRow to:(NSIndexPath *)newRow { DDLogInfo(@"selectedRow: %@, newRow: %@", selectedRow, newRow); if (![selectedRow isEqual:newRow]) { if (selectedRow != nil) [self.tableView deselectRowAtIndexPath:selectedRow animated:NO]; if (newRow != nil) [self.tableView selectRowAtIndexPath:newRow animated:NO scrollPosition:UITableViewScrollPositionNone]; } } - (void)showDetailsForContact:(Contact*)contact { [self setSelectionForContact:contact]; [self displayContact]; } - (void)showDetailsForGroup:(GroupProxy*)group { [self setSelectionForGroup:group]; [self displayGroup]; } - (void)showDetailsForWorkContact:(Contact*)contact { [self setSelectionForWorkContact:contact]; [self displayContact]; } - (BOOL)isEditing { return self.tableView.editing; } - (void)setEditing:(BOOL)editing animated:(BOOL)animated { [super setEditing:editing animated:animated]; [self.tableView setEditing:editing animated:animated]; self.isMultipleEditing = editing; } - (BOOL)showFirstEntryForCurrentMode { if (_mode == ModeContacts) { Contact *contact = [self getFirstContact]; if (contact) { [self showDetailsForContact:contact]; return YES; } } if (_mode == ModeWorkContacts) { Contact *contact = [self getFirstWorkContact]; if (contact) { [self showDetailsForWorkContact:contact]; return YES; } } if (_mode == ModeGroups) { GroupProxy *group = [self getFirstGroup]; if (group) { [self showDetailsForGroup:group]; return YES; } } return NO; } - (Contact *)getFirstContact { if ([self hasContactData]) { if ([self.contactsDataSource tableView:self.tableView numberOfRowsInSection:0] > 0) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; return [self.contactsDataSource contactAtIndexPath:indexPath]; } } return nil; } - (GroupProxy *)getFirstGroup { if ([self hasGroupData]) { if ([self.groupsDataSource tableView:self.tableView numberOfRowsInSection:0] > 0) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; return [self.groupsDataSource groupAtIndexPath:indexPath]; } } return nil; } - (Contact *)getFirstWorkContact { if ([self hasWorkContactData]) { if ([self.workContactsDataSource tableView:self.tableView numberOfRowsInSection:0] > 0) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; return [self.workContactsDataSource workContactAtIndexPath:indexPath]; } } return nil; } - (void)displayContact { if (SYSTEM_IS_IPAD == NO) { [self performSegueWithIdentifier:@"ShowContact" sender:self]; } else { [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowContact object:nil userInfo:[NSDictionary dictionaryWithObject:contactForDetails forKey:kKeyContact]]; } } - (void)displayGroup { if (SYSTEM_IS_IPAD == NO) { GroupDetailsViewController *detailsViewController = [self groupDetailsViewControllerForGroup:groupForDetails]; [self.navigationController pushViewController:detailsViewController animated:YES]; } else { [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowGroup object:nil userInfo:[NSDictionary dictionaryWithObject:groupForDetails forKey:kKeyGroup]]; } } - (GroupDetailsViewController *)groupDetailsViewControllerForGroup:(GroupProxy *)group { GroupDetailsViewController *detailsViewController = (GroupDetailsViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"groupDetailsViewController"]; detailsViewController.group = group; return detailsViewController; } - (ContactDetailsViewController *)contactDetailsViewControllerForContact:(Contact *)contact { ContactDetailsViewController *detailsViewController = (ContactDetailsViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"contactDetailsViewController"]; detailsViewController.contact = contact; return detailsViewController; } - (BOOL)hasContactData { if ([self.contactsDataSource numberOfSectionsInTableView:self.tableView] > 0) { NSInteger count = [self.contactsDataSource tableView:self.tableView numberOfRowsInSection:0]; if (count > 0) { return YES; } } return NO; } - (BOOL)hasGroupData { if ([self.groupsDataSource numberOfSectionsInTableView:self.tableView] > 0) { NSInteger count = [self.groupsDataSource tableView:self.tableView numberOfRowsInSection:0]; if (count > 0) { return YES; } } return NO; } - (BOOL)hasWorkContactData { if ([self.workContactsDataSource numberOfSectionsInTableView:self.tableView] > 0) { NSInteger count = [self.workContactsDataSource tableView:self.tableView numberOfRowsInSection:0]; if (count > 0) { return YES; } } return NO; } - (BOOL)hasData { if (_mode == ModeContacts) { return [self.contactsDataSource numberOfSectionsInTableView:self.tableView] > 0; } else if (_mode == ModeWorkContacts) { return [self.workContactsDataSource numberOfSectionsInTableView:self.tableView] > 0; } else { return [self.groupsDataSource tableView:self.tableView numberOfRowsInSection:0] > 0; } } - (ContactTableDataSource *)contactsDataSource { if (_contactsDataSource == nil) { _contactsDataSource = [ContactTableDataSource contactTableDataSourceWithFetchedResultsControllerDelegate:self members:nil]; // make sure sort indices are up to date [_contactsDataSource refreshContactSortIndices]; } return _contactsDataSource; } - (GroupTableDataSource *)groupsDataSource { if (_groupsDataSource == nil) { _groupsDataSource = [GroupTableDataSource groupTableDataSourceWithFetchedResultsControllerDelegate:self]; } return _groupsDataSource; } - (WorkContactTableDataSource *)workContactsDataSource { if (_workContactsDataSource == nil) { _workContactsDataSource = [WorkContactTableDataSource workContactTableDataSourceWithFetchedResultsControllerDelegate:self members:nil]; // make sure sort indices are up to date [_workContactsDataSource refreshWorkContactSortIndices]; } return _workContactsDataSource; } - (id)currentDataSource { if (_currentDataSource == nil) { if (_mode == ModeContacts) { _contactsDataSource = [self contactsDataSource]; _currentDataSource = _contactsDataSource; } else if (_mode == ModeWorkContacts) { _workContactsDataSource = [self workContactsDataSource]; _currentDataSource = _workContactsDataSource; } else { _groupsDataSource = [self groupsDataSource]; _currentDataSource = _groupsDataSource; } } return _currentDataSource; } - (void)setFooterView:(BOOL)show { if (_mode == ModeGroups) { self.tableView.tableFooterView = nil; } else { if (show) { NSUInteger contactsCount; if (_mode == ModeContacts) { contactsCount = [((ContactTableDataSource *)self.currentDataSource) countOfContacts]; } else { contactsCount = [((WorkContactTableDataSource *)self.currentDataSource) countOfWorkContacts]; } _countContactsFooterLabel.text = [NSString stringWithFormat:@"%lu %@", (unsigned long)contactsCount, [BundleUtil localizedStringForKey:@"contacts"]]; self.tableView.tableFooterView = _countContactsFooterView; } else { self.tableView.tableFooterView = nil; } } } - (void)setRefreshControlTitle:(BOOL)active { NSString *refreshText = nil; UIFont *font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote]; if (active) { refreshText = NSLocalizedString(@"synchronizing", nil); } else { refreshText = NSLocalizedString(@"pull_to_sync", nil); } NSMutableAttributedString *attributedRefreshText = [[NSMutableAttributedString alloc] initWithString:refreshText attributes:@{ NSFontAttributeName: font, NSForegroundColorAttributeName: [Colors fontLight], NSBackgroundColorAttributeName: [UIColor clearColor]}]; _rfControl.attributedTitle = attributedRefreshText; } #pragma mark - Table view - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(nonnull UIView *)view forSection:(NSInteger)section { UITableViewHeaderFooterView *headerView = (UITableViewHeaderFooterView*)view; [headerView.textLabel setTextColor:[Colors fontNormal]]; headerView.backgroundView = [[UIView alloc] initWithFrame:headerView.bounds]; headerView.backgroundView.backgroundColor = [Colors backgroundDark]; } - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath { if ([cell isKindOfClass:[ContactCell class]]) { ContactCell *contactCell = (ContactCell *)cell; [contactCell.identityLabel setTextColor:[Colors fontLight]]; [contactCell.nicknameLabel setTextColor:[Colors fontLight]]; } else if ([cell isKindOfClass:[GroupCell class]]) { GroupCell *groupCell = (GroupCell *)cell; [groupCell.groupNameLabel setTextColor:[Colors fontNormal]]; [groupCell.countMemberLabel setTextColor:[Colors fontLight]]; [groupCell.creatorLabel setTextColor:[Colors fontLight]]; } else { [Colors updateTableViewCell:cell]; } [Colors updateTableViewCellBackground:cell]; } - (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath { [super setEditing:YES animated:YES]; } - (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath { [super setEditing:NO animated:YES]; } - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { if (@available(iOS 11.0, *)) { return [self.currentDataSource numberOfSectionsInTableView:tableView]; } else { NSInteger section = [self.currentDataSource numberOfSectionsInTableView:tableView]; if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) { section++; } return section; } } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (@available(iOS 11.0, *)) { return [self.currentDataSource tableView:tableView numberOfRowsInSection:section]; } else { if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true && section == 0) { return 1; } if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) { 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 (@available(iOS 11.0, *)) { return [self.currentDataSource tableView:tableView titleForHeaderInSection:section]; } else { if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true && section == 0) { return nil; } else if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) { return [self.currentDataSource tableView:tableView titleForHeaderInSection:section - 1]; } else { return [self.currentDataSource tableView:tableView titleForHeaderInSection:section]; } } } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { UITableViewCell *cell; if (_mode == ModeContacts) { cell = [self tableView:tableView contactCellForIndexPath:indexPath]; } else if (_mode == ModeWorkContacts) { cell = [self tableView:tableView workContactCellForIndexPath:indexPath]; } else { cell = [self tableView:tableView groupCellForIndexPath:indexPath]; } if (_searchController.isActive) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } else { cell.accessoryType = UITableViewCellAccessoryNone; } if (@available(iOS 11.0, *)) { // do nothing } else { if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true && indexPath.section == 0) { cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator; } } 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 = nil; if (@available(iOS 11.0, *)) { contact = [self.workContactsDataSource workContactAtIndexPath:indexPath]; } else { if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true && indexPath.section == 0) { return _companyDirectoryCell; } else if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) { NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1]; contact = [self.workContactsDataSource workContactAtIndexPath:newIndexPath]; } else { contact = [self.workContactsDataSource workContactAtIndexPath:indexPath]; } } cell.contact = contact; return cell; } - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath { return YES; } - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath { if (editingStyle == UITableViewCellEditingStyleDelete) { _deletionIndexPath = indexPath; if (_mode == ModeGroups) { GroupProxy *group = [self.groupsDataSource groupAtIndexPath:indexPath]; [self deleteGroup:group]; [self updateNoContactsView]; } else if (_mode == ModeWorkContacts) { Contact *contact = nil; if (@available(iOS 11.0, *)) { contact = [self.workContactsDataSource workContactAtIndexPath:indexPath]; } else { if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) { NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1]; contact = [self.workContactsDataSource workContactAtIndexPath:newIndexPath]; } else { contact = [self.workContactsDataSource workContactAtIndexPath:indexPath]; } } [self deleteContact:contact]; [self updateNoContactsView]; } else { Contact *contact = [self.contactsDataSource contactAtIndexPath:indexPath]; [self deleteContact:contact]; [self updateNoContactsView]; } } } - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0)){ UITableViewCell *contextCell = [tableView cellForRowAtIndexPath:indexPath]; if ([contextCell isKindOfClass:[ContactCell class]]) { ContactCell *contactCell = (ContactCell *)contextCell; ContactDetailsViewController *contactVc = [self contactDetailsViewControllerForContact:contactCell.contact]; contactVc.hideActionButtons = YES; contactVc.delegate = self; UIContextMenuConfiguration *conf = [UIContextMenuConfiguration configurationWithIdentifier:indexPath previewProvider:^UIViewController * _Nullable{ return contactVc; } actionProvider:^UIMenu * _Nullable(NSArray * _Nonnull suggestedActions) { NSMutableArray *actionArray = [NSMutableArray new]; NSString *sendMessageActionTitle = [BundleUtil localizedStringForKey:@"send_message"]; UIImage *sendMessageImage = [[BundleUtil imageNamed:@"SendMessage"] imageWithTintColor:[Colors fontNormal]]; UIAction *sendMessageAction = [UIAction actionWithTitle:sendMessageActionTitle image:sendMessageImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) { contactForDetails = contactCell.contact; [contactVc sendMessageAction]; }]; [actionArray addObject:sendMessageAction]; if ([ScanIdentityController canScan]) { NSString *scanQrCodeActionTitle = [BundleUtil localizedStringForKey:@"scan_qr"]; UIImage *scanQrCodeActionImage = [[BundleUtil imageNamed:@"QRScan"] imageWithTintColor:[Colors fontNormal]]; UIAction *scanQrCodeAction = [UIAction actionWithTitle:scanQrCodeActionTitle image:scanQrCodeActionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) { contactForDetails = contactCell.contact; [self presentWithContactDetailsViewController:contactVc onCompletion:^(ContactDetailsViewController * contactDetailsVc) { [contactDetailsVc scanIdentityAction]; }]; }]; [actionArray addObject:scanQrCodeAction]; } if ([[UserSettings sharedUserSettings] enableThreemaCall] && is64Bit == 1) { NSString *threemaCallActionTitle = [BundleUtil localizedStringForKey:@"call_threema_title"]; UIImage *threemaCallActionImage = [[BundleUtil imageNamed:@"ThreemaPhone"] imageWithTintColor:[Colors fontNormal]]; UIAction *threemaCallAction = [UIAction actionWithTitle:threemaCallActionTitle image:threemaCallActionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) { contactForDetails = contactCell.contact; [self presentWithContactDetailsViewController:contactVc onCompletion:^(ContactDetailsViewController * contactDetailsVc) { [contactDetailsVc startThreemaCallAction:false]; }]; }]; [actionArray addObject:threemaCallAction]; } return [UIMenu menuWithTitle:contactVc.contact.displayName image:nil identifier:nil options:UIMenuOptionsDisplayInline children:actionArray]; }]; return conf; } else if ([contextCell isKindOfClass:[GroupCell class]]) { GroupCell *groupCell = (GroupCell *)contextCell; GroupDetailsViewController *groupVc = [self groupDetailsViewControllerForGroup:groupCell.group]; groupVc.hideActionButtons = YES; groupVc.delegate = self; UIContextMenuConfiguration *conf = [UIContextMenuConfiguration configurationWithIdentifier:indexPath previewProvider:^UIViewController * _Nullable{ return groupVc; } actionProvider:^UIMenu * _Nullable(NSArray * _Nonnull suggestedActions) { NSMutableArray *actionArray = [NSMutableArray new]; NSString *sendMessageActionTitle = [BundleUtil localizedStringForKey:@"send_message"]; UIImage *sendMessageImage = [[BundleUtil imageNamed:@"TabBar-Chats"] imageWithTintColor:[Colors fontNormal]]; UIAction *sendMessageAction = [UIAction actionWithTitle:sendMessageActionTitle image:sendMessageImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) { groupForDetails = groupCell.group; [groupVc sendMessageAction]; }]; [actionArray addObject:sendMessageAction]; if ([groupVc.group isOwnGroup]) { NSString *syncActionTitle = [BundleUtil localizedStringForKey:@"sync_group"]; UIImage *syncActionImage = [[BundleUtil imageNamed:@"Sync"] imageWithTintColor:[Colors fontNormal]]; UIAction *syncAction = [UIAction actionWithTitle:syncActionTitle image:syncActionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) { groupForDetails = groupCell.group; [self presentGroupDetails:groupVc onCompletion:^(GroupDetailsViewController *groupDetailsViewController) { [groupDetailsViewController syncMembers]; }]; }]; [actionArray addObject:syncAction]; } if (![groupVc.group didLeaveGroup]) { NSString *leaveActionTitle = [BundleUtil localizedStringForKey:@"leave_group"]; UIImage *leaveActionImage = [UIImage systemImageNamed:@"minus.circle.fill" compatibleWithTraitCollection:self.traitCollection]; UIAction *leaveAction = [UIAction actionWithTitle:leaveActionTitle image:leaveActionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) { groupForDetails = groupCell.group; [self presentGroupDetails:groupVc onCompletion:^(GroupDetailsViewController *groupDetailsViewController) { [groupDetailsViewController leaveGroup]; }]; }]; leaveAction.attributes = UIMenuElementAttributesDestructive; [actionArray addObject:leaveAction]; } return [UIMenu menuWithTitle:groupVc.group.name image:nil identifier:nil options:UIMenuOptionsDisplayInline children:actionArray]; }]; return conf; } return nil; } - (void)tableView:(UITableView *)tableView willPerformPreviewActionForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration animator:(id)animator API_AVAILABLE(ios(13.0)){ UIViewController *previewVc = animator.previewViewController; if ([previewVc isKindOfClass:[ContactDetailsViewController class]]) { contactForDetails = ((ContactDetailsViewController *)previewVc).contact; [animator addCompletion:^{ [self displayContact]; }]; } else if ([previewVc isKindOfClass:[GroupDetailsViewController class]]) { groupForDetails = ((GroupDetailsViewController *)previewVc).group; [animator addCompletion:^{ [self displayGroup]; }]; } } - (void)updateTableViewAfterDeletion:(BOOL)succeeded { if (succeeded) { if ([_currentDataSource isFiltered]) { [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]]; [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:_deletionIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic]; } // else FRC kicks in } else { if ([_currentDataSource isFiltered]) { [self.tableView setEditing:NO animated:YES]; [super setEditing:NO animated:YES]; } else { [self.tableView setEditing:NO animated:YES]; [super setEditing:NO animated:YES]; } } if (!_isMultipleEditing) { [super setEditing:NO animated:YES]; } } - (void)deleteGroup:(GroupProxy *)group { Conversation *conversation = group.conversation; DeleteConversationAction *deleteAction = [DeleteConversationAction deleteActionForConversation:conversation]; deleteAction.presentingViewController = self; _deleteAction = deleteAction; [deleteAction executeOnCompletion:^(BOOL succeeded) { [self updateTableViewAfterDeletion:succeeded]; }]; } - (void)deleteContact:(Contact *) contact { DeleteContactAction *deleteAction = [DeleteContactAction deleteActionForContact:contact]; deleteAction.presentingViewController = self; _deleteAction = deleteAction; [deleteAction executeOnCompletion:^(BOOL succeeded) { [self updateTableViewAfterDeletion:succeeded]; }]; } - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath { return NO; } - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([[segue identifier] isEqualToString:@"ShowContact"]) { [[segue destinationViewController] setContact:contactForDetails]; } } - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath { if (_mode == ModeGroups) { groupForDetails = [self.groupsDataSource groupAtIndexPath:indexPath]; [self displayGroup]; } else if (_mode == ModeWorkContacts) { if (@available(iOS 11.0, *)) { contactForDetails = [self.workContactsDataSource workContactAtIndexPath:indexPath]; } else { if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) { NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1]; contactForDetails = [self.workContactsDataSource workContactAtIndexPath:newIndexPath]; } else { contactForDetails = [self.workContactsDataSource workContactAtIndexPath:indexPath]; } } [self displayContact]; } else { contactForDetails = [self.contactsDataSource contactAtIndexPath:indexPath]; [self displayContact]; } } #pragma mark - Fetched results controller - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { //nop } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { //nop } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { //nop } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { [self updateNoContactsView]; [self.tableView reloadData]; } #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]; } [self setFooterView:NO]; _rfControl = self.refreshControl; self.refreshControl = nil; } - (void)willDismissSearchController:(UISearchController *)searchController { if (@available(iOS 11.0, *)) { self.searchController.searchBar.searchFieldBackgroundPositionAdjustment = UIOffsetMake(0.0, 7.0); } else { [_statusBarView removeFromSuperview]; } self.refreshControl = _rfControl; [self updateNoContactsView]; } -(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: self.navigationItem.title = NSLocalizedString(@"segmentcontrol_contacts", nil); self.refreshControl = _rfControl; _currentDataSource = [self contactsDataSource]; [_contactsDataSource setIgnoreFRCUpdates:NO]; [_workContactsDataSource setIgnoreFRCUpdates:YES]; [_groupsDataSource setIgnoreFRCUpdates:YES]; [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]]; [self updateNoContactsView]; if (@available(iOS 11.0, *)) { self.tableView.tableHeaderView = nil; } break; case ModeWorkContacts: self.navigationItem.title = NSLocalizedString(@"segmentcontrol_work_contacts", nil); self.refreshControl = _rfControl; _currentDataSource = [self workContactsDataSource]; [_contactsDataSource setIgnoreFRCUpdates:YES]; [_workContactsDataSource setIgnoreFRCUpdates:NO]; [_groupsDataSource setIgnoreFRCUpdates:YES]; [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]]; if (@available(iOS 11.0, *)) { self.tableView.tableHeaderView = [UserSettings sharedUserSettings].companyDirectory == true ? _companyDirectoryCell : nil; } [self updateNoContactsView]; break; case ModeGroups: self.navigationItem.title = NSLocalizedString(@"segmentcontrol_groups", nil); _rfControl = self.refreshControl; self.refreshControl = nil; _currentDataSource = [self groupsDataSource]; [_contactsDataSource setIgnoreFRCUpdates:YES]; [_workContactsDataSource setIgnoreFRCUpdates:YES]; [_groupsDataSource setIgnoreFRCUpdates:NO]; [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]]; if (@available(iOS 11.0, *)) { self.tableView.tableHeaderView = nil; } self.tableView.tableFooterView = nil; break; default: break; } [self.tableView reloadData]; if ([self hasData]) { NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0]; [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES]; } } - (IBAction)addAction:(id)sender { MDMSetup *mdmSetup = [[MDMSetup alloc] initWithSetup:NO]; if (_mode == ModeContacts || _mode == ModeWorkContacts) { if ([mdmSetup disableAddContact]) { [UIAlertTemplate showAlertWithOwner:self title:@"" message:NSLocalizedString(@"disabled_by_device_policy", nil) actionOk:nil]; return; } UIStoryboard *storyboard = self.storyboard; UINavigationController *navVC = [storyboard instantiateViewControllerWithIdentifier:@"AddContactNavigationController"]; [self presentViewController:navVC animated:YES completion:nil]; } else { if ([mdmSetup disableCreateGroup]) { [UIAlertTemplate showAlertWithOwner:self title:@"" message:NSLocalizedString(@"disabled_by_device_policy", nil) actionOk:nil]; return; } UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"CreateGroup" bundle:nil]; UINavigationController *navVC = [storyboard instantiateInitialViewController]; [self presentViewController:navVC animated:YES completion:nil]; } } - (IBAction)pulledForRefresh:(UIRefreshControl *)sender { _mode = self.segmentedControl.selectedSegmentIndex; if ((_mode == ModeContacts || _mode == ModeWorkContacts) && !self.searchController.active) { [self setRefreshControlTitle:YES]; _segmentedControl.userInteractionEnabled = NO; [[GatewayAvatarMaker gatewayAvatarMaker] refreshForced]; if ([UserSettings sharedUserSettings].syncContacts) { [[ContactStore sharedContactStore] synchronizeAddressBookForceFullSync:YES ignoreMinimumInterval:YES onCompletion:^(BOOL addressBookAccessGranted) { [self updateWorkDataAndEndRefreshing:sender]; if (!addressBookAccessGranted) { [UIAlertTemplate showAlertWithOwner:self title:NSLocalizedString(@"no_contacts_permission_title", nil) message:NSLocalizedString(@"no_contacts_permission_message", nil) actionOk:nil]; } } onError:^(NSError *error) { [UIAlertTemplate showAlertWithOwner:self title:error.localizedDescription message:error.localizedFailureReason actionOk:^(UIAlertAction * _Nonnull okAction) { [self updateWorkDataAndEndRefreshing:sender]; }]; }]; } else { [[ContactStore sharedContactStore] synchronizeAddressBookForceFullSync:YES ignoreMinimumInterval:YES onCompletion:^(BOOL addressBookAccessGranted) { [self updateWorkDataAndEndRefreshing:sender]; } onError:^(NSError *error) { [self updateWorkDataAndEndRefreshing:sender]; }]; } } else { self.segmentedControl.userInteractionEnabled = YES; [sender endRefreshing]; [self.rfControl endRefreshing]; } } - (void)updateWorkDataAndEndRefreshing:(UIRefreshControl*)rfSender { [WorkDataFetcher checkUpdateWorkDataForce:YES onCompletion:^{ [self endRefreshingAndScrollUp:rfSender]; } onError:^(NSError *error) { [self endRefreshingAndScrollUp:rfSender]; [UIAlertTemplate showAlertWithOwner:self title:error.localizedDescription message:error.localizedFailureReason actionOk:nil]; }]; } - (void)endRefreshingAndScrollUp:(UIRefreshControl*)rfSender { [self setRefreshControlTitle:NO]; self.segmentedControl.userInteractionEnabled = YES; [rfSender endRefreshing]; [self.rfControl endRefreshing]; if ([self.tableView numberOfSections] > 0 && [self.tableView numberOfRowsInSection:0] > 0) [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES]; } - (void)companyDirectoryTapped:(UITapGestureRecognizer *)recognizer { ThemedTableViewController *companyDirectoryViewController = (ThemedTableViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"CompanyDirectoryViewController"]; ModalNavigationController *nav = [[ModalNavigationController alloc] initWithRootViewController:companyDirectoryViewController]; nav.showDoneButton = true; nav.showFullScreenOnIPad = false; [self presentViewController:nav animated:YES completion:nil]; } #pragma mark - UIViewControllerPreviewingDelegate - (void)previewingContext:(id)previewingContext commitViewController:(UIViewController *)viewControllerToCommit { if ([viewControllerToCommit isKindOfClass:[ContactDetailsViewController class]]) { contactForDetails = ((ContactDetailsViewController *)viewControllerToCommit).contact; [self displayContact]; } else if ([viewControllerToCommit isKindOfClass:[GroupDetailsViewController class]]) { groupForDetails = ((GroupDetailsViewController *)viewControllerToCommit).group; [self displayGroup]; } } - (UIViewController *)previewingContext:(id)previewingContext viewControllerForLocation:(CGPoint)location { UIView *view = [self.view hitTest:location withEvent:nil]; if ([view.superview isKindOfClass:[ContactCell class]]) { ContactCell *contactCell = (ContactCell *)view.superview; ContactDetailsViewController *contactVc = [self contactDetailsViewControllerForContact:contactCell.contact]; contactVc.hideActionButtons = YES; contactVc.delegate = self; return contactVc; } else if ([view.superview isKindOfClass:[GroupCell class]]) { GroupCell *groupCell = (GroupCell *)view.superview; GroupDetailsViewController *groupVc = [self groupDetailsViewControllerForGroup:groupCell.group]; groupVc.hideActionButtons = YES; groupVc.delegate = self; return groupVc; } return nil; } #pragma mark - GroupDetailsViewControllerDelegate - (void)presentGroupDetails:(GroupDetailsViewController *)groupDetailsViewController onCompletion:(void (^)(GroupDetailsViewController *))onCompletion { groupForDetails = groupDetailsViewController.group; _prevNavigationControllerDelegate = self.navigationController.delegate; self.navigationController.delegate = self; [self displayGroup]; _groupCompletionBlock = onCompletion; } #pragma mark - ContactDetailsViewControllerDelegate - (void)presentWithContactDetailsViewController:(ContactDetailsViewController *)contactDetailsViewController onCompletion:(void (^)(ContactDetailsViewController * _Nonnull))onCompletion { contactForDetails = contactDetailsViewController.contact; _prevNavigationControllerDelegate = self.navigationController.delegate; self.navigationController.delegate = self; [self displayContact]; _contactCompletionBlock = onCompletion; } #pragma mark - UINavigationControllerDelegate - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated { if (_groupCompletionBlock) { if ([viewController isKindOfClass:[GroupDetailsViewController class]]) { _groupCompletionBlock((GroupDetailsViewController *)viewController); } _groupCompletionBlock = nil; } if (_contactCompletionBlock) { if ([viewController isKindOfClass:[ContactDetailsViewController class]]) { _contactCompletionBlock((ContactDetailsViewController *)viewController); } _contactCompletionBlock = nil; } self.navigationController.delegate = _prevNavigationControllerDelegate; } #pragma mark - Notifications - (void)showProfilePictureChanged:(NSNotification *)notification { [self refresh]; } - (void)refreshWorkContactTableView:(NSNotification *)notification { if (@available(iOS 11.0, *)) { [self refresh]; self.tableView.tableHeaderView = [UserSettings sharedUserSettings].companyDirectory == true ? _companyDirectoryCell : nil; } else { [self refresh]; } } - (void)refreshContactSortIndices:(NSNotification *)notification { if (_contactsDataSource != nil) { [_contactsDataSource refreshContactSortIndices]; } if (_workContactsDataSource != nil) { [_workContactsDataSource refreshWorkContactSortIndices]; } } @end