// _____ _ // |_ _| |_ _ _ ___ ___ _ __ __ _ // | | | ' \| '_/ -_) -_) ' \/ _` |_ // |_| |_||_|_| \___\___|_|_|_\__,_(_) // // Threema iOS Client // Copyright (c) 2015-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 "ContactTableDataSource.h" #import #import "EntityManager.h" #import "ErrorHandler.h" #import "PickerContactCell.h" #import "UserSettings.h" #ifdef DEBUG static const DDLogLevel ddLogLevel = DDLogLevelInfo; #else static const DDLogLevel ddLogLevel = DDLogLevelWarning; #endif @interface ContactTableDataSource () @property NSArray *filteredContacts; @property NSFetchedResultsController *fetchedResultsController; @property NSFetchedResultsController *gatewayFetchedResultsController; @property (nonatomic) NSMutableSet *selectedContacts; @property EntityManager *entityManager; @property id fetchedResultsControllerDelegate; @property BOOL ignoreUpdates; @end @implementation ContactTableDataSource + (instancetype)contactTableDataSource { return [[ContactTableDataSource alloc] initWithFetchedResultsControllerDelegate:nil members:nil]; } + (instancetype)contactTableDataSourceWithMembers:(NSMutableSet *)members { return [[ContactTableDataSource alloc] initWithFetchedResultsControllerDelegate:nil members:members]; } + (instancetype)contactTableDataSourceWithFetchedResultsControllerDelegate:(id)delegate members:(NSMutableSet *)members { return [[ContactTableDataSource alloc] initWithFetchedResultsControllerDelegate:delegate members:members]; } - (void)dealloc { _fetchedResultsController.delegate = nil; _gatewayFetchedResultsController.delegate = nil; } - (instancetype)initWithFetchedResultsControllerDelegate:(id)delegate members:(NSMutableSet *)members { self = [super init]; if (self) { _entityManager = [[EntityManager alloc] init]; if (members != nil) { _selectedContacts = members; } else { _selectedContacts = [NSMutableSet set]; } if (delegate) { _fetchedResultsControllerDelegate = delegate; delegate = self; } [self setupFetchedResultsControllerWithDelegate:delegate]; // include gateway contacts by default [self loadGatewayContacts]; } return self; } - (void)setExcludeGatewayContacts:(BOOL)excludeGatewayContacts { _excludeGatewayContacts = excludeGatewayContacts; [self loadGatewayContacts]; } - (void)setExcludeEchoEcho:(BOOL)excludeEchoEcho { _excludeEchoEcho = excludeEchoEcho; if (_excludeEchoEcho && _excludeGatewayContacts) { NSFetchedResultsController *fetchedResultsController = [_entityManager.entityFetcher fetchedResultsControllerForContactTypes:ContactsNoGatewayNoEchoecho list:ContactListContacts members:_selectedContacts]; fetchedResultsController.delegate = _fetchedResultsController.delegate; _fetchedResultsController = fetchedResultsController; NSError *error = nil; if (![_fetchedResultsController performFetch:&error]) { DDLogError(@"Unresolved error %@, %@", error, [error userInfo]); [ErrorHandler abortWithError: error]; } } else if (_excludeEchoEcho) { NSFetchedResultsController *fetchedResultsController = [_entityManager.entityFetcher fetchedResultsControllerForContactTypes:ContactsNoEchoEcho list:ContactListContacts members:_selectedContacts]; fetchedResultsController.delegate = _fetchedResultsController.delegate; _fetchedResultsController = fetchedResultsController; NSError *error = nil; if (![_fetchedResultsController performFetch:&error]) { DDLogError(@"Unresolved error %@, %@", error, [error userInfo]); [ErrorHandler abortWithError: error]; } } } - (void)loadGatewayContacts { if (_excludeGatewayContacts == NO) { NSFetchedResultsController *fetchedResultsController = [_entityManager.entityFetcher fetchedResultsControllerForContactTypes:ContactsGatewayOnly list:ContactListContacts members:nil]; fetchedResultsController.delegate = self; _gatewayFetchedResultsController = fetchedResultsController; NSError *error = nil; if (![_gatewayFetchedResultsController performFetch:&error]) { DDLogError(@"Unresolved error %@, %@", error, [error userInfo]); [ErrorHandler abortWithError: error]; } } else { _gatewayFetchedResultsController.delegate = nil; _gatewayFetchedResultsController = nil; } } - (void)setupFetchedResultsControllerWithDelegate:(id)delegate { NSFetchedResultsController *fetchedResultsController = [_entityManager.entityFetcher fetchedResultsControllerForContactTypes:ContactsNoGateway list:ContactListContacts members:_selectedContacts]; fetchedResultsController.delegate = delegate; _fetchedResultsController = fetchedResultsController; NSError *error = nil; if (![_fetchedResultsController performFetch:&error]) { DDLogError(@"Unresolved error %@, %@", error, [error userInfo]); [ErrorHandler abortWithError: error]; } } - (Contact *)contactAtIndexPath:(NSIndexPath *)indexPath { if (_filteredContacts) { return [_filteredContacts objectAtIndex:indexPath.row]; } if (indexPath.section == self.fetchedResultsController.sections.count) { NSIndexPath *gatewayIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:0]; return [_gatewayFetchedResultsController objectAtIndexPath:gatewayIndexPath]; } return [self.fetchedResultsController objectAtIndexPath:indexPath]; } - (NSIndexPath *)indexPathForObject:(id)object { return [_fetchedResultsController indexPathForObject:object]; } - (NSSet *)getSelectedContacts { return [NSSet setWithSet:_selectedContacts]; } - (void)updateSelectedContacts:(NSSet *)contacts { _selectedContacts = [NSMutableSet setWithSet:contacts]; } - (void)refreshContactSortIndices { for (Contact *contact in _fetchedResultsController.fetchedObjects) { [contact updateSortInitial]; } for (Contact *contact in _gatewayFetchedResultsController.fetchedObjects) { [contact updateSortInitial]; } } - (NSUInteger)countOfContacts { if (_excludeGatewayContacts) { return _fetchedResultsController.fetchedObjects.count; } return _fetchedResultsController.fetchedObjects.count + _gatewayFetchedResultsController.fetchedObjects.count; } #pragma mark - ContactGroupDataSource -(void)filterByWords:(NSArray *)words { if (words) { ContactTypes type = ContactsAll; if (_excludeEchoEcho && _excludeGatewayContacts) { type = ContactsNoGatewayNoEchoecho; } else if (_excludeGatewayContacts) { type = ContactsNoGateway; } else if (_excludeEchoEcho) { type = ContactsNoEchoEcho; } _filteredContacts = [_entityManager.entityFetcher contactsFilteredByWords:words forContactTypes:type list:ContactListContacts members:_selectedContacts]; } else { _filteredContacts = nil; } } - (NSSet *)selectedConversations { NSMutableSet *conversations = [NSMutableSet setWithCapacity:[_selectedContacts count]]; for (Contact *contact in _selectedContacts) { __block Conversation *conversation = [_entityManager.entityFetcher conversationForContact:contact]; if (conversation == nil) { // create & immediately save [_entityManager performSyncBlockAndSafe:^{ conversation = [_entityManager.entityCreator conversation]; conversation.contact = contact; }]; } [conversations addObject:conversation]; } return conversations; } - (void)selectedCellAtIndexPath:(NSIndexPath *)indexPath selected:(BOOL)selected { Contact *contact = [self contactAtIndexPath:indexPath]; if (selected) { if (![_selectedContacts containsObject:contact]) { [_selectedContacts addObject:contact]; } } else { if ([_selectedContacts containsObject:contact]) { [_selectedContacts removeObject:contact]; } } } - (void)setIgnoreFRCUpdates:(BOOL)ignoreFRCUpdates { _ignoreUpdates = ignoreFRCUpdates; } - (BOOL)ignoreFRCUpdates { return _ignoreUpdates; } - (BOOL)isFiltered { return (_filteredContacts != nil); } #pragma mark - Table view - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView { if (_filteredContacts) { return 1; } NSInteger count = [[self.fetchedResultsController sections] count]; if (_gatewayFetchedResultsController.sections.count > 0) { count++; } return count; } - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section { if (_filteredContacts) { return _filteredContacts.count; } NSArray *frcSections = [self.fetchedResultsController sections]; if (section >= frcSections.count) { NSArray *gatewaySections = [_gatewayFetchedResultsController sections]; if (gatewaySections.count > 0) { id sectionInfo = gatewaySections[0]; return [sectionInfo numberOfObjects]; } else { return 0; } } id sectionInfo = frcSections[section]; return [sectionInfo numberOfObjects]; } - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView { if (_filteredContacts) { return nil; } NSMutableArray *sectionTitles = [NSMutableArray arrayWithArray:[[UILocalizedIndexedCollation currentCollation] sectionIndexTitles]]; [sectionTitles addObject:@"*"]; return sectionTitles; } - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index { if (_filteredContacts) { return 0; } if ([title isEqualToString:@"*"]) { return self.fetchedResultsController.sections.count; } int frcIndex = 0; for (id section in self.fetchedResultsController.sections) { if ([section.name intValue] <= index) { frcIndex++; } else { break; } } frcIndex--; if (frcIndex < 0) { frcIndex = 0; } return frcIndex; } - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section { if (_filteredContacts) { return nil; } if (section == self.fetchedResultsController.sections.count) { return @"*"; } id sectionInfo = [[self.fetchedResultsController sections] objectAtIndex:section]; /* the section "name" of the FRC is actually an index into the UILocalizedIndexedCollation sectionIndexTitles */ int sitIdx = [[sectionInfo name] intValue]; NSArray *sectionIndexTitles = [[UILocalizedIndexedCollation currentCollation] sectionIndexTitles]; if (sitIdx >= 0 && sitIdx < [sectionIndexTitles count]) { return [sectionIndexTitles objectAtIndex:sitIdx]; } else { return @" "; } } - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath { Contact *contact = [self contactAtIndexPath:indexPath]; PickerContactCell *cell = [tableView dequeueReusableCellWithIdentifier:@"PickerContactCell"]; cell.contact = contact; return cell; } #pragma mark - Fetched results controller - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller { if (_ignoreUpdates || _filteredContacts != nil) { return; } [_fetchedResultsControllerDelegate controllerWillChangeContent:controller]; } - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id )sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type { if (_ignoreUpdates || _filteredContacts != nil) { return; } if (controller == _fetchedResultsController) { [_fetchedResultsControllerDelegate controller:controller didChangeSection:sectionInfo atIndex:sectionIndex forChangeType:type]; } else { ;//ignore } } - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath { if (_ignoreUpdates || _filteredContacts != nil) { return; } if (controller == _fetchedResultsController) { [_fetchedResultsControllerDelegate controller:controller didChangeObject:anObject atIndexPath:indexPath forChangeType:type newIndexPath:newIndexPath]; } else { ;//ignore } } - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller { if (_ignoreUpdates || _filteredContacts != nil) { return; } [_fetchedResultsControllerDelegate controllerDidChangeContent:controller]; } @end