ContactsViewController.m 56 KB

12345678910111213141516171819202122232425262728293031323334353637383940414243444546474849505152535455565758596061626364656667686970717273747576777879808182838485868788899091929394959697989910010110210310410510610710810911011111211311411511611711811912012112212312412512612712812913013113213313413513613713813914014114214314414514614714814915015115215315415515615715815916016116216316416516616716816917017117217317417517617717817918018118218318418518618718818919019119219319419519619719819920020120220320420520620720820921021121221321421521621721821922022122222322422522622722822923023123223323423523623723823924024124224324424524624724824925025125225325425525625725825926026126226326426526626726826927027127227327427527627727827928028128228328428528628728828929029129229329429529629729829930030130230330430530630730830931031131231331431531631731831932032132232332432532632732832933033133233333433533633733833934034134234334434534634734834935035135235335435535635735835936036136236336436536636736836937037137237337437537637737837938038138238338438538638738838939039139239339439539639739839940040140240340440540640740840941041141241341441541641741841942042142242342442542642742842943043143243343443543643743843944044144244344444544644744844945045145245345445545645745845946046146246346446546646746846947047147247347447547647747847948048148248348448548648748848949049149249349449549649749849950050150250350450550650750850951051151251351451551651751851952052152252352452552652752852953053153253353453553653753853954054154254354454554654754854955055155255355455555655755855956056156256356456556656756856957057157257357457557657757857958058158258358458558658758858959059159259359459559659759859960060160260360460560660760860961061161261361461561661761861962062162262362462562662762862963063163263363463563663763863964064164264364464564664764864965065165265365465565665765865966066166266366466566666766866967067167267367467567667767867968068168268368468568668768868969069169269369469569669769869970070170270370470570670770870971071171271371471571671771871972072172272372472572672772872973073173273373473573673773873974074174274374474574674774874975075175275375475575675775875976076176276376476576676776876977077177277377477577677777877978078178278378478578678778878979079179279379479579679779879980080180280380480580680780880981081181281381481581681781881982082182282382482582682782882983083183283383483583683783883984084184284384484584684784884985085185285385485585685785885986086186286386486586686786886987087187287387487587687787887988088188288388488588688788888989089189289389489589689789889990090190290390490590690790890991091191291391491591691791891992092192292392492592692792892993093193293393493593693793893994094194294394494594694794894995095195295395495595695795895996096196296396496596696796896997097197297397497597697797897998098198298398498598698798898999099199299399499599699799899910001001100210031004100510061007100810091010101110121013101410151016101710181019102010211022102310241025102610271028102910301031103210331034103510361037103810391040104110421043104410451046104710481049105010511052105310541055105610571058105910601061106210631064106510661067106810691070107110721073107410751076107710781079108010811082108310841085108610871088108910901091109210931094109510961097109810991100110111021103110411051106110711081109111011111112111311141115111611171118111911201121112211231124112511261127112811291130113111321133113411351136113711381139114011411142114311441145114611471148114911501151115211531154115511561157115811591160116111621163116411651166116711681169117011711172117311741175117611771178117911801181118211831184118511861187118811891190119111921193119411951196119711981199120012011202120312041205120612071208120912101211121212131214121512161217121812191220122112221223122412251226122712281229123012311232123312341235123612371238123912401241124212431244124512461247124812491250125112521253125412551256125712581259126012611262126312641265126612671268126912701271127212731274127512761277127812791280128112821283128412851286128712881289129012911292129312941295129612971298129913001301130213031304130513061307130813091310131113121313131413151316131713181319132013211322132313241325132613271328132913301331133213331334133513361337133813391340134113421343134413451346134713481349135013511352135313541355135613571358135913601361136213631364136513661367136813691370137113721373137413751376137713781379138013811382138313841385138613871388138913901391139213931394139513961397
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2012-2020 Threema GmbH
  8. //
  9. // This program is free software: you can redistribute it and/or modify
  10. // it under the terms of the GNU Affero General Public License, version 3,
  11. // as published by the Free Software Foundation.
  12. //
  13. // This program is distributed in the hope that it will be useful,
  14. // but WITHOUT ANY WARRANTY; without even the implied warranty of
  15. // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  16. // GNU Affero General Public License for more details.
  17. //
  18. // You should have received a copy of the GNU Affero General Public License
  19. // along with this program. If not, see <https://www.gnu.org/licenses/>.
  20. #import "ContactsViewController.h"
  21. #import "ContactCell.h"
  22. #import "GroupCell.h"
  23. #import "Contact.h"
  24. #import "ContactStore.h"
  25. #import "UserSettings.h"
  26. #import "DatabaseManager.h"
  27. #import "EntityManager.h"
  28. #import "GatewayAvatarMaker.h"
  29. #import "ContactTableDataSource.h"
  30. #import "GroupTableDataSource.h"
  31. #import "WorkContactTableDataSource.h"
  32. #import "GroupDetailsViewController.h"
  33. #import "ContactDetailsViewController.h"
  34. #import "DeleteConversationAction.h"
  35. #import "DeleteContactAction.h"
  36. #import "ModalPresenter.h"
  37. #import "RectUtil.h"
  38. #import "WorkDataFetcher.h"
  39. #import "LicenseStore.h"
  40. #import "BundleUtil.h"
  41. #import "ModalNavigationController.h"
  42. #import "AvatarMaker.h"
  43. #import "MDMSetup.h"
  44. #ifdef DEBUG
  45. static const DDLogLevel ddLogLevel = DDLogLevelInfo;
  46. #else
  47. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  48. #endif
  49. typedef enum : NSUInteger {
  50. ModeContacts,
  51. ModeGroups,
  52. ModeWorkContacts
  53. } Mode;
  54. @interface ContactsViewController () <UIViewControllerPreviewingDelegate, GroupDetailsViewControllerDelegate, ContactDetailsViewControllerDelegate, UINavigationControllerDelegate>
  55. @property Mode mode;
  56. @property (nonatomic) id<ContactGroupDataSource> currentDataSource;
  57. @property EntityManager *entityManager;
  58. @property (nonatomic) ContactTableDataSource *contactsDataSource;
  59. @property (nonatomic) GroupTableDataSource *groupsDataSource;
  60. @property (nonatomic) WorkContactTableDataSource *workContactsDataSource;
  61. @property id<UINavigationControllerDelegate> prevNavigationControllerDelegate;
  62. @property (copy) GroupDetailsCompletionBlock groupCompletionBlock;
  63. @property (copy) void (^contactCompletionBlock)(ContactDetailsViewController *contactDetailsViewController);
  64. @property (strong, nonatomic) UIView *statusBarView;
  65. @property NSIndexPath *deletionIndexPath;
  66. @property id deleteAction;
  67. @property (nonatomic) BOOL isMultipleEditing;
  68. @property (nonatomic, strong) UIRefreshControl *rfControl;
  69. @end
  70. @implementation ContactsViewController {
  71. Contact *contactForDetails;
  72. GroupProxy *groupForDetails;
  73. }
  74. - (id)initWithCoder:(NSCoder *)aDecoder {
  75. self = [super initWithCoder:aDecoder];
  76. if (self) {
  77. /* listen for blacklist change (to refresh contact labels with blocked icon) */
  78. [[UserSettings sharedUserSettings] addObserver:self forKeyPath:@"blacklist" options:0 context:nil];
  79. /* listen for stale contacts setting */
  80. [[UserSettings sharedUserSettings] addObserver:self forKeyPath:@"hideStaleContacts" options:0 context:nil];
  81. _entityManager = [[EntityManager alloc] init];
  82. }
  83. return self;
  84. }
  85. - (void)viewDidLoad
  86. {
  87. [super viewDidLoad];
  88. _currentDataSource = [self currentDataSource];
  89. // 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
  90. UIImage *contactImage = [BundleUtil imageNamed:@"Contact"];
  91. contactImage.accessibilityLabel = [BundleUtil localizedStringForKey:@"segmentcontrol_contacts"];
  92. UIImage *groupImage = [BundleUtil imageNamed:@"Group"];
  93. groupImage.accessibilityLabel = [BundleUtil localizedStringForKey:@"segmentcontrol_groups"];
  94. UIImage *workImage = [BundleUtil imageNamed:@"Case"];
  95. workImage.accessibilityLabel = [BundleUtil localizedStringForKey:@"segmentcontrol_work_contacts"];
  96. [self.segmentedControl setTitle:@"contacts" forSegmentAtIndex:ModeContacts];
  97. [self.segmentedControl setTitle:@"groups" forSegmentAtIndex:ModeGroups];
  98. if ([LicenseStore requiresLicenseKey]) {
  99. [self.segmentedControl insertSegmentWithTitle:@"work" atIndex:ModeWorkContacts animated:NO];
  100. if ([[self workContactsDataSource] numberOfSectionsInTableView:self.tableView] > 0) {
  101. // No regular contacts, so show Work contacts by default
  102. _mode = ModeWorkContacts;
  103. [self.segmentedControl setSelectedSegmentIndex:ModeWorkContacts];
  104. _currentDataSource = [self workContactsDataSource];
  105. if (@available(iOS 11.0, *)) {
  106. self.tableView.tableHeaderView = [UserSettings sharedUserSettings].companyDirectory == true ? _companyDirectoryCell : nil;
  107. }
  108. }
  109. }
  110. for (int i = 0; i < self.segmentedControl.numberOfSegments; i++) {
  111. UIView *segment = self.segmentedControl.subviews[i];
  112. for (id subview in segment.subviews) {
  113. if ([subview isKindOfClass:[UILabel class]]) {
  114. UILabel *label = (UILabel *)subview;
  115. if ([label.text isEqualToString:@"contacts"]) {
  116. segment.accessibilityLabel = NSLocalizedString(@"segmentcontrol_contacts", @"");
  117. }
  118. else if ([label.text isEqualToString:@"groups"]) {
  119. segment.accessibilityLabel = NSLocalizedString(@"segmentcontrol_groups", @"");
  120. }
  121. else if ([label.text isEqualToString:@"work"]) {
  122. segment.accessibilityLabel = NSLocalizedString(@"segmentcontrol_work_contacts", @"");
  123. }
  124. }
  125. }
  126. }
  127. [self.segmentedControl setTitle:nil forSegmentAtIndex:ModeContacts];
  128. [self.segmentedControl setTitle:nil forSegmentAtIndex:ModeGroups];
  129. [self.segmentedControl setImage:contactImage forSegmentAtIndex:ModeContacts];
  130. [self.segmentedControl setImage:groupImage forSegmentAtIndex:ModeGroups];
  131. if ([LicenseStore requiresLicenseKey]) {
  132. [self.segmentedControl setTitle:nil forSegmentAtIndex:ModeWorkContacts];
  133. [self.segmentedControl setImage:workImage forSegmentAtIndex:ModeWorkContacts];
  134. }
  135. self.navigationItem.leftBarButtonItem = self.editButtonItem;
  136. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showProfilePictureChanged:) name:kNotificationShowProfilePictureChanged object:nil];
  137. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshWorkContactTableView:) name:kNotificationRefreshWorkContactTableView object:nil];
  138. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshContactSortIndices:) name:kNotificationRefreshContactSortIndices object:nil];
  139. [self setRefreshControlTitle:NO];
  140. [self registerForPreviewingWithDelegate:self sourceView:self.view];
  141. [self setupColors];
  142. self.isMultipleEditing = NO;
  143. [self updateNoContactsView];
  144. self.searchController = [[UISearchController alloc]initWithSearchResultsController:nil];
  145. self.searchController.delegate = self;
  146. self.searchController.searchBar.showsScopeBar = NO;
  147. self.searchController.searchBar.scopeButtonTitles = nil;
  148. self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
  149. self.searchController.searchBar.delegate = self;
  150. self.searchController.searchResultsUpdater = self;
  151. self.searchController.searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  152. [self.searchController.searchBar sizeToFit];
  153. self.searchController.searchBar.barStyle = UISearchBarStyleMinimal;
  154. self.searchController.dimsBackgroundDuringPresentation = NO;
  155. self.definesPresentationContext = YES;
  156. self.searchController.hidesNavigationBarDuringPresentation = YES;
  157. if (@available(iOS 11.0, *)) {
  158. self.navigationItem.searchController = _searchController;
  159. } else {
  160. self.tableView.tableHeaderView = self.searchController.searchBar;
  161. self.statusBarView = [[UIView alloc] initWithFrame:[[UIApplication sharedApplication] statusBarFrame]];
  162. }
  163. UITapGestureRecognizer *companyDirectoryRecognizer = [[UITapGestureRecognizer alloc] initWithTarget:self action:@selector(companyDirectoryTapped:)];
  164. [_companyDirectoryCell addGestureRecognizer:companyDirectoryRecognizer];
  165. }
  166. - (void)refresh {
  167. [self setupColors];
  168. [Colors updateNavigationBar:self.navigationController.navigationBar];
  169. _companyDirectoryCell.titleLabel.text = [MyIdentityStore sharedMyIdentityStore].companyName;
  170. [self.tableView reloadData];
  171. }
  172. - (BOOL)isWorkActive {
  173. return _mode == ModeWorkContacts;
  174. }
  175. - (void)setupColors {
  176. [self.navigationController.view setBackgroundColor:[Colors background]];
  177. [_statusBarView setBackgroundColor:[Colors searchBarStatusBar]];
  178. _noContactsTitleLabel.textColor = [Colors fontNormal];
  179. _noContactsMessageLabel.textColor = [Colors fontLight];
  180. [Colors updateTableView:self.tableView];
  181. if (@available(iOS 11.0, *)) {
  182. self.searchController.searchBar.barTintColor = [UIColor clearColor];
  183. self.searchController.searchBar.backgroundColor = [UIColor clearColor];
  184. UINavigationBar *navigationBar = self.navigationController.navigationBar;
  185. if (navigationBar) {
  186. navigationBar.barTintColor = [Colors backgroundBaseColor];
  187. }
  188. } else {
  189. self.searchController.searchBar.backgroundColor = [Colors backgroundBaseColor];
  190. UINavigationBar *navigationBar = self.navigationController.navigationBar;
  191. if (navigationBar) {
  192. navigationBar.barTintColor = [Colors backgroundBaseColor];
  193. }
  194. }
  195. [Colors updateSearchBar:_searchController.searchBar];
  196. if (@available(iOS 11.0, *)) {
  197. if (!self.searchController.isActive) {
  198. self.searchController.searchBar.searchFieldBackgroundPositionAdjustment = UIOffsetMake(0.0, 7.0);
  199. }
  200. self.navigationItem.largeTitleDisplayMode = [UserSettings sharedUserSettings].largeTitleDisplayMode;
  201. } else {
  202. [_statusBarView setBackgroundColor:[Colors searchBarStatusBar]];
  203. }
  204. if (!self.rfControl) {
  205. self.rfControl = [UIRefreshControl new];
  206. [_rfControl addTarget:self action:@selector(pulledForRefresh:) forControlEvents:UIControlEventValueChanged];
  207. self.refreshControl = _rfControl;
  208. self.tableView.refreshControl = _rfControl;
  209. }
  210. _rfControl.backgroundColor = [UIColor clearColor];
  211. [self setRefreshControlTitle:NO];
  212. _companyDirectoryCell.backgroundColor = [Colors background];
  213. _companyDirectoryCell.selectedBackgroundView = [[UIView alloc] initWithFrame:_companyDirectoryCell.frame];
  214. _companyDirectoryCell.selectedBackgroundView.backgroundColor = [Colors backgroundDark];
  215. _companyDirectoryCell.titleLabel.textColor = [Colors fontNormal];
  216. _companyDirectoryCell.descriptionLabel.textColor = [Colors fontLight];
  217. _companyDirectoryCell.companyAvatar.image = [[AvatarMaker sharedAvatarMaker] companyImage];
  218. [_companyDirectoryCell setTintColor:[Colors main]];
  219. }
  220. - (void)viewWillAppear:(BOOL)animated {
  221. [super viewWillAppear:animated];
  222. if (SYSTEM_IS_IPAD == NO) {
  223. [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
  224. } else {
  225. if (contactForDetails && _mode == ModeContacts) {
  226. NSIndexPath *indexPath = [self.contactsDataSource indexPathForObject:contactForDetails];
  227. [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
  228. } else if (groupForDetails && _mode == ModeGroups) {
  229. NSIndexPath *indexPath = [self.groupsDataSource indexPathForObject:groupForDetails];
  230. [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
  231. } else if (contactForDetails && _mode == ModeWorkContacts) {
  232. NSIndexPath *indexPath = [self.workContactsDataSource indexPathForObject:contactForDetails];
  233. [self.tableView selectRowAtIndexPath:indexPath animated:NO scrollPosition:UITableViewScrollPositionNone];
  234. }
  235. self.tableView.backgroundView = nil;
  236. }
  237. [self updateNoContactsView];
  238. [self refresh];
  239. }
  240. - (void)viewWillDisappear:(BOOL)animated {
  241. [super viewWillDisappear:animated];
  242. }
  243. - (BOOL)shouldAutorotate {
  244. return YES;
  245. }
  246. -(UIInterfaceOrientationMask)supportedInterfaceOrientations {
  247. if (SYSTEM_IS_IPAD) {
  248. return UIInterfaceOrientationMaskAll;
  249. }
  250. return UIInterfaceOrientationMaskAllButUpsideDown;
  251. }
  252. - (void)dealloc {
  253. [[UserSettings sharedUserSettings] removeObserver:self forKeyPath:@"blacklist"];
  254. [[UserSettings sharedUserSettings] removeObserver:self forKeyPath:@"hideStaleContacts"];
  255. }
  256. - (void)updateNoContactsView {
  257. if ([self hasData]) {
  258. if (_mode == ModeContacts) {
  259. [self setFooterView:YES];
  260. }
  261. else if (_mode == ModeWorkContacts) {
  262. [self setFooterView:YES];
  263. }
  264. else {
  265. [self setFooterView:NO];
  266. }
  267. } else {
  268. [self setFooterView:NO];
  269. NSString *messageKey;
  270. NSString *titleKey;
  271. if (_mode == ModeContacts) {
  272. titleKey = @"no_contacts";
  273. messageKey = [UserSettings sharedUserSettings].syncContacts ? @"no_contacts_syncon" : @"no_contacts_syncoff";
  274. }
  275. else if (_mode == ModeWorkContacts) {
  276. titleKey = @"no_work_contacts";
  277. messageKey = @"no_work_contacts_message";
  278. }
  279. else {
  280. titleKey = @"no_groups";
  281. messageKey = @"no_groups_message";
  282. }
  283. _noContactsTitleLabel.text = NSLocalizedString(titleKey, nil);
  284. _noContactsMessageLabel.text = NSLocalizedString(messageKey, nil);
  285. self.tableView.tableFooterView = _noContactsView;
  286. }
  287. }
  288. - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context {
  289. if ([keyPath isEqualToString:@"blacklist"]) {
  290. dispatch_async(dispatch_get_main_queue(), ^{
  291. [self resetData];
  292. });
  293. } else if ([keyPath isEqualToString:@"hideStaleContacts"]) {
  294. dispatch_async(dispatch_get_main_queue(), ^{
  295. [self resetData];
  296. });
  297. }
  298. }
  299. - (void)resetData {
  300. _currentDataSource = nil;
  301. _contactsDataSource = nil;
  302. _groupsDataSource = nil;
  303. _workContactsDataSource = nil;
  304. [self.tableView reloadData];
  305. }
  306. - (void)setSelectionForContact:(Contact *)contact {
  307. if (_segmentedControl.selectedSegmentIndex != ModeContacts) {
  308. _segmentedControl.selectedSegmentIndex = ModeContacts;
  309. [self segmentedControlChanged:self];
  310. }
  311. /* fix highlighted cell in our view */
  312. NSIndexPath *selectedRow = [self.tableView indexPathForSelectedRow];
  313. NSIndexPath *indexPath = [self.contactsDataSource indexPathForObject:contact];
  314. [self changeSelectedRow:selectedRow to:indexPath];
  315. contactForDetails = contact;
  316. }
  317. - (void)setSelectionForGroup:(GroupProxy *)group {
  318. if (_segmentedControl.selectedSegmentIndex != ModeGroups) {
  319. _segmentedControl.selectedSegmentIndex = ModeGroups;
  320. [self segmentedControlChanged:self];
  321. }
  322. /* fix highlighted cell in our view */
  323. NSIndexPath *selectedRow = [self.tableView indexPathForSelectedRow];
  324. NSIndexPath *newRow = [self.groupsDataSource indexPathForObject:group];
  325. [self changeSelectedRow:selectedRow to:newRow];
  326. groupForDetails = group;
  327. }
  328. - (void)setSelectionForWorkContact:(Contact *)contact {
  329. if (_segmentedControl.selectedSegmentIndex != ModeWorkContacts) {
  330. _segmentedControl.selectedSegmentIndex = ModeWorkContacts;
  331. [self segmentedControlChanged:self];
  332. }
  333. /* fix highlighted cell in our view */
  334. NSIndexPath *selectedRow = [self.tableView indexPathForSelectedRow];
  335. NSIndexPath *indexPath = [self.workContactsDataSource indexPathForObject:contact];
  336. [self changeSelectedRow:selectedRow to:indexPath];
  337. contactForDetails = contact;
  338. }
  339. - (void)changeSelectedRow:(NSIndexPath *)selectedRow to:(NSIndexPath *)newRow {
  340. DDLogInfo(@"selectedRow: %@, newRow: %@", selectedRow, newRow);
  341. if (![selectedRow isEqual:newRow]) {
  342. if (selectedRow != nil)
  343. [self.tableView deselectRowAtIndexPath:selectedRow animated:NO];
  344. if (newRow != nil)
  345. [self.tableView selectRowAtIndexPath:newRow animated:NO scrollPosition:UITableViewScrollPositionNone];
  346. }
  347. }
  348. - (void)showDetailsForContact:(Contact*)contact {
  349. [self setSelectionForContact:contact];
  350. [self displayContact];
  351. }
  352. - (void)showDetailsForGroup:(GroupProxy*)group {
  353. [self setSelectionForGroup:group];
  354. [self displayGroup];
  355. }
  356. - (void)showDetailsForWorkContact:(Contact*)contact {
  357. [self setSelectionForWorkContact:contact];
  358. [self displayContact];
  359. }
  360. - (BOOL)isEditing {
  361. return self.tableView.editing;
  362. }
  363. - (void)setEditing:(BOOL)editing animated:(BOOL)animated {
  364. [super setEditing:editing animated:animated];
  365. [self.tableView setEditing:editing animated:animated];
  366. self.isMultipleEditing = editing;
  367. }
  368. - (BOOL)showFirstEntryForCurrentMode {
  369. if (_mode == ModeContacts) {
  370. Contact *contact = [self getFirstContact];
  371. if (contact) {
  372. [self showDetailsForContact:contact];
  373. return YES;
  374. }
  375. }
  376. if (_mode == ModeWorkContacts) {
  377. Contact *contact = [self getFirstWorkContact];
  378. if (contact) {
  379. [self showDetailsForWorkContact:contact];
  380. return YES;
  381. }
  382. }
  383. if (_mode == ModeGroups) {
  384. GroupProxy *group = [self getFirstGroup];
  385. if (group) {
  386. [self showDetailsForGroup:group];
  387. return YES;
  388. }
  389. }
  390. return NO;
  391. }
  392. - (Contact *)getFirstContact {
  393. if ([self hasContactData]) {
  394. if ([self.contactsDataSource tableView:self.tableView numberOfRowsInSection:0] > 0) {
  395. NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
  396. return [self.contactsDataSource contactAtIndexPath:indexPath];
  397. }
  398. }
  399. return nil;
  400. }
  401. - (GroupProxy *)getFirstGroup {
  402. if ([self hasGroupData]) {
  403. if ([self.groupsDataSource tableView:self.tableView numberOfRowsInSection:0] > 0) {
  404. NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
  405. return [self.groupsDataSource groupAtIndexPath:indexPath];
  406. }
  407. }
  408. return nil;
  409. }
  410. - (Contact *)getFirstWorkContact {
  411. if ([self hasWorkContactData]) {
  412. if ([self.workContactsDataSource tableView:self.tableView numberOfRowsInSection:0] > 0) {
  413. NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
  414. return [self.workContactsDataSource workContactAtIndexPath:indexPath];
  415. }
  416. }
  417. return nil;
  418. }
  419. - (void)displayContact {
  420. if (SYSTEM_IS_IPAD == NO) {
  421. [self performSegueWithIdentifier:@"ShowContact" sender:self];
  422. } else {
  423. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowContact object:nil userInfo:[NSDictionary dictionaryWithObject:contactForDetails forKey:kKeyContact]];
  424. }
  425. }
  426. - (void)displayGroup {
  427. if (SYSTEM_IS_IPAD == NO) {
  428. GroupDetailsViewController *detailsViewController = [self groupDetailsViewControllerForGroup:groupForDetails];
  429. [self.navigationController pushViewController:detailsViewController animated:YES];
  430. } else {
  431. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowGroup object:nil userInfo:[NSDictionary dictionaryWithObject:groupForDetails forKey:kKeyGroup]];
  432. }
  433. }
  434. - (GroupDetailsViewController *)groupDetailsViewControllerForGroup:(GroupProxy *)group {
  435. GroupDetailsViewController *detailsViewController = (GroupDetailsViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"groupDetailsViewController"];
  436. detailsViewController.group = group;
  437. return detailsViewController;
  438. }
  439. - (ContactDetailsViewController *)contactDetailsViewControllerForContact:(Contact *)contact {
  440. ContactDetailsViewController *detailsViewController = (ContactDetailsViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"contactDetailsViewController"];
  441. detailsViewController.contact = contact;
  442. return detailsViewController;
  443. }
  444. - (BOOL)hasContactData {
  445. if ([self.contactsDataSource numberOfSectionsInTableView:self.tableView] > 0) {
  446. NSInteger count = [self.contactsDataSource tableView:self.tableView numberOfRowsInSection:0];
  447. if (count > 0) {
  448. return YES;
  449. }
  450. }
  451. return NO;
  452. }
  453. - (BOOL)hasGroupData {
  454. if ([self.groupsDataSource numberOfSectionsInTableView:self.tableView] > 0) {
  455. NSInteger count = [self.groupsDataSource tableView:self.tableView numberOfRowsInSection:0];
  456. if (count > 0) {
  457. return YES;
  458. }
  459. }
  460. return NO;
  461. }
  462. - (BOOL)hasWorkContactData {
  463. if ([self.workContactsDataSource numberOfSectionsInTableView:self.tableView] > 0) {
  464. NSInteger count = [self.workContactsDataSource tableView:self.tableView numberOfRowsInSection:0];
  465. if (count > 0) {
  466. return YES;
  467. }
  468. }
  469. return NO;
  470. }
  471. - (BOOL)hasData {
  472. if (_mode == ModeContacts) {
  473. return [self.contactsDataSource numberOfSectionsInTableView:self.tableView] > 0;
  474. }
  475. else if (_mode == ModeWorkContacts) {
  476. return [self.workContactsDataSource numberOfSectionsInTableView:self.tableView] > 0;
  477. }
  478. else {
  479. return [self.groupsDataSource tableView:self.tableView numberOfRowsInSection:0] > 0;
  480. }
  481. }
  482. - (ContactTableDataSource *)contactsDataSource {
  483. if (_contactsDataSource == nil) {
  484. _contactsDataSource = [ContactTableDataSource contactTableDataSourceWithFetchedResultsControllerDelegate:self members:nil];
  485. // make sure sort indices are up to date
  486. [_contactsDataSource refreshContactSortIndices];
  487. }
  488. return _contactsDataSource;
  489. }
  490. - (GroupTableDataSource *)groupsDataSource {
  491. if (_groupsDataSource == nil) {
  492. _groupsDataSource = [GroupTableDataSource groupTableDataSourceWithFetchedResultsControllerDelegate:self];
  493. }
  494. return _groupsDataSource;
  495. }
  496. - (WorkContactTableDataSource *)workContactsDataSource {
  497. if (_workContactsDataSource == nil) {
  498. _workContactsDataSource = [WorkContactTableDataSource workContactTableDataSourceWithFetchedResultsControllerDelegate:self members:nil];
  499. // make sure sort indices are up to date
  500. [_workContactsDataSource refreshWorkContactSortIndices];
  501. }
  502. return _workContactsDataSource;
  503. }
  504. - (id<ContactGroupDataSource>)currentDataSource {
  505. if (_currentDataSource == nil) {
  506. if (_mode == ModeContacts) {
  507. _contactsDataSource = [self contactsDataSource];
  508. _currentDataSource = _contactsDataSource;
  509. }
  510. else if (_mode == ModeWorkContacts) {
  511. _workContactsDataSource = [self workContactsDataSource];
  512. _currentDataSource = _workContactsDataSource;
  513. }
  514. else {
  515. _groupsDataSource = [self groupsDataSource];
  516. _currentDataSource = _groupsDataSource;
  517. }
  518. }
  519. return _currentDataSource;
  520. }
  521. - (void)setFooterView:(BOOL)show {
  522. if (_mode == ModeGroups) {
  523. self.tableView.tableFooterView = nil;
  524. } else {
  525. if (show) {
  526. NSUInteger contactsCount;
  527. if (_mode == ModeContacts) {
  528. contactsCount = [((ContactTableDataSource *)self.currentDataSource) countOfContacts];
  529. }
  530. else {
  531. contactsCount = [((WorkContactTableDataSource *)self.currentDataSource) countOfWorkContacts];
  532. }
  533. _countContactsFooterLabel.text = [NSString stringWithFormat:@"%lu %@", (unsigned long)contactsCount, [BundleUtil localizedStringForKey:@"contacts"]];
  534. self.tableView.tableFooterView = _countContactsFooterView;
  535. } else {
  536. self.tableView.tableFooterView = nil;
  537. }
  538. }
  539. }
  540. - (void)setRefreshControlTitle:(BOOL)active {
  541. NSString *refreshText = nil;
  542. UIFont *font = [UIFont preferredFontForTextStyle:UIFontTextStyleFootnote];
  543. if (active) {
  544. refreshText = NSLocalizedString(@"synchronizing", nil);
  545. } else {
  546. refreshText = NSLocalizedString(@"pull_to_sync", nil);
  547. }
  548. NSMutableAttributedString *attributedRefreshText = [[NSMutableAttributedString alloc] initWithString:refreshText attributes:@{ NSFontAttributeName: font, NSForegroundColorAttributeName: [Colors fontLight], NSBackgroundColorAttributeName: [UIColor clearColor]}];
  549. _rfControl.attributedTitle = attributedRefreshText;
  550. }
  551. #pragma mark - Table view
  552. - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(nonnull UIView *)view forSection:(NSInteger)section {
  553. UITableViewHeaderFooterView *headerView = (UITableViewHeaderFooterView*)view;
  554. [headerView.textLabel setTextColor:[Colors fontNormal]];
  555. headerView.backgroundView = [[UIView alloc] initWithFrame:headerView.bounds];
  556. headerView.backgroundView.backgroundColor = [Colors backgroundDark];
  557. }
  558. - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
  559. if ([cell isKindOfClass:[ContactCell class]]) {
  560. ContactCell *contactCell = (ContactCell *)cell;
  561. [contactCell.identityLabel setTextColor:[Colors fontLight]];
  562. [contactCell.nicknameLabel setTextColor:[Colors fontLight]];
  563. } else if ([cell isKindOfClass:[GroupCell class]]) {
  564. GroupCell *groupCell = (GroupCell *)cell;
  565. [groupCell.groupNameLabel setTextColor:[Colors fontNormal]];
  566. [groupCell.countMemberLabel setTextColor:[Colors fontLight]];
  567. [groupCell.creatorLabel setTextColor:[Colors fontLight]];
  568. } else {
  569. [Colors updateTableViewCell:cell];
  570. }
  571. [Colors updateTableViewCellBackground:cell];
  572. }
  573. - (void)tableView:(UITableView *)tableView willBeginEditingRowAtIndexPath:(NSIndexPath *)indexPath {
  574. [super setEditing:YES animated:YES];
  575. }
  576. - (void)tableView:(UITableView *)tableView didEndEditingRowAtIndexPath:(NSIndexPath *)indexPath {
  577. [super setEditing:NO animated:YES];
  578. }
  579. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  580. if (@available(iOS 11.0, *)) {
  581. return [self.currentDataSource numberOfSectionsInTableView:tableView];
  582. } else {
  583. NSInteger section = [self.currentDataSource numberOfSectionsInTableView:tableView];
  584. if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) {
  585. section++;
  586. }
  587. return section;
  588. }
  589. }
  590. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  591. if (@available(iOS 11.0, *)) {
  592. return [self.currentDataSource tableView:tableView numberOfRowsInSection:section];
  593. } else {
  594. if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true && section == 0) {
  595. return 1;
  596. }
  597. if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) {
  598. return [self.currentDataSource tableView:tableView numberOfRowsInSection:section - 1];
  599. }
  600. return [self.currentDataSource tableView:tableView numberOfRowsInSection:section];
  601. }
  602. }
  603. - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
  604. return [self.currentDataSource sectionIndexTitlesForTableView:tableView];
  605. }
  606. - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
  607. return [self.currentDataSource tableView:tableView sectionForSectionIndexTitle:title atIndex:index];
  608. }
  609. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
  610. if (@available(iOS 11.0, *)) {
  611. return [self.currentDataSource tableView:tableView titleForHeaderInSection:section];
  612. } else {
  613. if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true && section == 0) {
  614. return nil;
  615. } else if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) {
  616. return [self.currentDataSource tableView:tableView titleForHeaderInSection:section - 1];
  617. } else {
  618. return [self.currentDataSource tableView:tableView titleForHeaderInSection:section];
  619. }
  620. }
  621. }
  622. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath {
  623. UITableViewCell *cell;
  624. if (_mode == ModeContacts) {
  625. cell = [self tableView:tableView contactCellForIndexPath:indexPath];
  626. }
  627. else if (_mode == ModeWorkContacts) {
  628. cell = [self tableView:tableView workContactCellForIndexPath:indexPath];
  629. }
  630. else {
  631. cell = [self tableView:tableView groupCellForIndexPath:indexPath];
  632. }
  633. if (_searchController.isActive) {
  634. cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  635. } else {
  636. cell.accessoryType = UITableViewCellAccessoryNone;
  637. }
  638. if (@available(iOS 11.0, *)) {
  639. // do nothing
  640. } else {
  641. if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true && indexPath.section == 0) {
  642. cell.accessoryType = UITableViewCellAccessoryDisclosureIndicator;
  643. }
  644. }
  645. return cell;
  646. }
  647. - (UITableViewCell *)tableView:(UITableView *)tableView contactCellForIndexPath:(NSIndexPath *)indexPath {
  648. ContactCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactCell"];
  649. Contact *contact = [self.contactsDataSource contactAtIndexPath:indexPath];
  650. cell.contact = contact;
  651. return cell;
  652. }
  653. - (UITableViewCell *)tableView:(UITableView *)tableView groupCellForIndexPath:(NSIndexPath *)indexPath {
  654. GroupCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"GroupCell"];
  655. GroupProxy *group = [self.groupsDataSource groupAtIndexPath:indexPath];
  656. cell.group = group;
  657. return cell;
  658. }
  659. - (UITableViewCell *)tableView:(UITableView *)tableView workContactCellForIndexPath:(NSIndexPath *)indexPath {
  660. ContactCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactCell"];
  661. Contact *contact = nil;
  662. if (@available(iOS 11.0, *)) {
  663. contact = [self.workContactsDataSource workContactAtIndexPath:indexPath];
  664. } else {
  665. if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true && indexPath.section == 0) {
  666. return _companyDirectoryCell;
  667. }
  668. else if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) {
  669. NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1];
  670. contact = [self.workContactsDataSource workContactAtIndexPath:newIndexPath];
  671. } else {
  672. contact = [self.workContactsDataSource workContactAtIndexPath:indexPath];
  673. }
  674. }
  675. cell.contact = contact;
  676. return cell;
  677. }
  678. - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath
  679. {
  680. return YES;
  681. }
  682. - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath
  683. {
  684. if (editingStyle == UITableViewCellEditingStyleDelete) {
  685. _deletionIndexPath = indexPath;
  686. if (_mode == ModeGroups) {
  687. GroupProxy *group = [self.groupsDataSource groupAtIndexPath:indexPath];
  688. [self deleteGroup:group];
  689. [self updateNoContactsView];
  690. }
  691. else if (_mode == ModeWorkContacts) {
  692. Contact *contact = nil;
  693. if (@available(iOS 11.0, *)) {
  694. contact = [self.workContactsDataSource workContactAtIndexPath:indexPath];
  695. } else {
  696. if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) {
  697. NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1];
  698. contact = [self.workContactsDataSource workContactAtIndexPath:newIndexPath];
  699. } else {
  700. contact = [self.workContactsDataSource workContactAtIndexPath:indexPath];
  701. }
  702. }
  703. [self deleteContact:contact];
  704. [self updateNoContactsView];
  705. }
  706. else {
  707. Contact *contact = [self.contactsDataSource contactAtIndexPath:indexPath];
  708. [self deleteContact:contact];
  709. [self updateNoContactsView];
  710. }
  711. }
  712. }
  713. - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0)){
  714. UITableViewCell *contextCell = [tableView cellForRowAtIndexPath:indexPath];
  715. if ([contextCell isKindOfClass:[ContactCell class]]) {
  716. ContactCell *contactCell = (ContactCell *)contextCell;
  717. ContactDetailsViewController *contactVc = [self contactDetailsViewControllerForContact:contactCell.contact];
  718. contactVc.hideActionButtons = YES;
  719. contactVc.delegate = self;
  720. UIContextMenuConfiguration *conf = [UIContextMenuConfiguration configurationWithIdentifier:indexPath previewProvider:^UIViewController * _Nullable{
  721. return contactVc;
  722. } actionProvider:^UIMenu * _Nullable(NSArray<UIMenuElement *> * _Nonnull suggestedActions) {
  723. NSMutableArray *actionArray = [NSMutableArray new];
  724. NSString *sendMessageActionTitle = [BundleUtil localizedStringForKey:@"send_message"];
  725. UIImage *sendMessageImage = [[BundleUtil imageNamed:@"SendMessage"] imageWithTintColor:[Colors fontNormal]];
  726. UIAction *sendMessageAction = [UIAction actionWithTitle:sendMessageActionTitle image:sendMessageImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  727. contactForDetails = contactCell.contact;
  728. [contactVc sendMessageAction];
  729. }];
  730. [actionArray addObject:sendMessageAction];
  731. if ([ScanIdentityController canScan]) {
  732. NSString *scanQrCodeActionTitle = [BundleUtil localizedStringForKey:@"scan_qr"];
  733. UIImage *scanQrCodeActionImage = [[BundleUtil imageNamed:@"QRScan"] imageWithTintColor:[Colors fontNormal]];
  734. UIAction *scanQrCodeAction = [UIAction actionWithTitle:scanQrCodeActionTitle image:scanQrCodeActionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  735. contactForDetails = contactCell.contact;
  736. [self presentWithContactDetailsViewController:contactVc onCompletion:^(ContactDetailsViewController * contactDetailsVc) {
  737. [contactDetailsVc scanIdentityAction];
  738. }];
  739. }];
  740. [actionArray addObject:scanQrCodeAction];
  741. }
  742. if ([[UserSettings sharedUserSettings] enableThreemaCall] && is64Bit == 1) {
  743. NSString *threemaCallActionTitle = [BundleUtil localizedStringForKey:@"call_threema_title"];
  744. UIImage *threemaCallActionImage = [[BundleUtil imageNamed:@"ThreemaPhone"] imageWithTintColor:[Colors fontNormal]];
  745. UIAction *threemaCallAction = [UIAction actionWithTitle:threemaCallActionTitle image:threemaCallActionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  746. contactForDetails = contactCell.contact;
  747. [self presentWithContactDetailsViewController:contactVc onCompletion:^(ContactDetailsViewController * contactDetailsVc) {
  748. [contactDetailsVc startThreemaCallAction:false];
  749. }];
  750. }];
  751. [actionArray addObject:threemaCallAction];
  752. }
  753. return [UIMenu menuWithTitle:contactVc.contact.displayName image:nil identifier:nil options:UIMenuOptionsDisplayInline children:actionArray];
  754. }];
  755. return conf;
  756. }
  757. else if ([contextCell isKindOfClass:[GroupCell class]]) {
  758. GroupCell *groupCell = (GroupCell *)contextCell;
  759. GroupDetailsViewController *groupVc = [self groupDetailsViewControllerForGroup:groupCell.group];
  760. groupVc.hideActionButtons = YES;
  761. groupVc.delegate = self;
  762. UIContextMenuConfiguration *conf = [UIContextMenuConfiguration configurationWithIdentifier:indexPath previewProvider:^UIViewController * _Nullable{
  763. return groupVc;
  764. } actionProvider:^UIMenu * _Nullable(NSArray<UIMenuElement *> * _Nonnull suggestedActions) {
  765. NSMutableArray *actionArray = [NSMutableArray new];
  766. NSString *sendMessageActionTitle = [BundleUtil localizedStringForKey:@"send_message"];
  767. UIImage *sendMessageImage = [[BundleUtil imageNamed:@"TabBar-Chats"] imageWithTintColor:[Colors fontNormal]];
  768. UIAction *sendMessageAction = [UIAction actionWithTitle:sendMessageActionTitle image:sendMessageImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  769. groupForDetails = groupCell.group;
  770. [groupVc sendMessageAction];
  771. }];
  772. [actionArray addObject:sendMessageAction];
  773. if ([groupVc.group isOwnGroup]) {
  774. NSString *syncActionTitle = [BundleUtil localizedStringForKey:@"sync_group"];
  775. UIImage *syncActionImage = [[BundleUtil imageNamed:@"Sync"] imageWithTintColor:[Colors fontNormal]];
  776. UIAction *syncAction = [UIAction actionWithTitle:syncActionTitle image:syncActionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  777. groupForDetails = groupCell.group;
  778. [self presentGroupDetails:groupVc onCompletion:^(GroupDetailsViewController *groupDetailsViewController) {
  779. [groupDetailsViewController syncMembers];
  780. }];
  781. }];
  782. [actionArray addObject:syncAction];
  783. }
  784. if (![groupVc.group didLeaveGroup]) {
  785. NSString *leaveActionTitle = [BundleUtil localizedStringForKey:@"leave_group"];
  786. UIImage *leaveActionImage = [UIImage systemImageNamed:@"minus.circle.fill" compatibleWithTraitCollection:self.traitCollection];
  787. UIAction *leaveAction = [UIAction actionWithTitle:leaveActionTitle image:leaveActionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  788. groupForDetails = groupCell.group;
  789. [self presentGroupDetails:groupVc onCompletion:^(GroupDetailsViewController *groupDetailsViewController) {
  790. [groupDetailsViewController leaveGroup];
  791. }];
  792. }];
  793. leaveAction.attributes = UIMenuElementAttributesDestructive;
  794. [actionArray addObject:leaveAction];
  795. }
  796. return [UIMenu menuWithTitle:groupVc.group.name image:nil identifier:nil options:UIMenuOptionsDisplayInline children:actionArray];
  797. }];
  798. return conf;
  799. }
  800. return nil;
  801. }
  802. - (void)tableView:(UITableView *)tableView willPerformPreviewActionForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration animator:(id<UIContextMenuInteractionCommitAnimating>)animator API_AVAILABLE(ios(13.0)){
  803. UIViewController *previewVc = animator.previewViewController;
  804. if ([previewVc isKindOfClass:[ContactDetailsViewController class]]) {
  805. contactForDetails = ((ContactDetailsViewController *)previewVc).contact;
  806. [animator addCompletion:^{
  807. [self displayContact];
  808. }];
  809. } else if ([previewVc isKindOfClass:[GroupDetailsViewController class]]) {
  810. groupForDetails = ((GroupDetailsViewController *)previewVc).group;
  811. [animator addCompletion:^{
  812. [self displayGroup];
  813. }];
  814. }
  815. }
  816. - (void)updateTableViewAfterDeletion:(BOOL)succeeded {
  817. if (succeeded) {
  818. if ([_currentDataSource isFiltered]) {
  819. [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]];
  820. [self.tableView deleteRowsAtIndexPaths:[NSArray arrayWithObject:_deletionIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
  821. } // else FRC kicks in
  822. } else {
  823. if ([_currentDataSource isFiltered]) {
  824. [self.tableView setEditing:NO animated:YES];
  825. [super setEditing:NO animated:YES];
  826. } else {
  827. [self.tableView setEditing:NO animated:YES];
  828. [super setEditing:NO animated:YES];
  829. }
  830. }
  831. if (!_isMultipleEditing) {
  832. [super setEditing:NO animated:YES];
  833. }
  834. }
  835. - (void)deleteGroup:(GroupProxy *)group {
  836. Conversation *conversation = group.conversation;
  837. DeleteConversationAction *deleteAction = [DeleteConversationAction deleteActionForConversation:conversation];
  838. deleteAction.presentingViewController = self;
  839. _deleteAction = deleteAction;
  840. [deleteAction executeOnCompletion:^(BOOL succeeded) {
  841. [self updateTableViewAfterDeletion:succeeded];
  842. }];
  843. }
  844. - (void)deleteContact:(Contact *) contact {
  845. DeleteContactAction *deleteAction = [DeleteContactAction deleteActionForContact:contact];
  846. deleteAction.presentingViewController = self;
  847. _deleteAction = deleteAction;
  848. [deleteAction executeOnCompletion:^(BOOL succeeded) {
  849. [self updateTableViewAfterDeletion:succeeded];
  850. }];
  851. }
  852. - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath
  853. {
  854. return NO;
  855. }
  856. - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender
  857. {
  858. if ([[segue identifier] isEqualToString:@"ShowContact"]) {
  859. [[segue destinationViewController] setContact:contactForDetails];
  860. }
  861. }
  862. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  863. if (_mode == ModeGroups) {
  864. groupForDetails = [self.groupsDataSource groupAtIndexPath:indexPath];
  865. [self displayGroup];
  866. }
  867. else if (_mode == ModeWorkContacts) {
  868. if (@available(iOS 11.0, *)) {
  869. contactForDetails = [self.workContactsDataSource workContactAtIndexPath:indexPath];
  870. } else {
  871. if (_mode == ModeWorkContacts && [UserSettings sharedUserSettings].companyDirectory == true) {
  872. NSIndexPath *newIndexPath = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1];
  873. contactForDetails = [self.workContactsDataSource workContactAtIndexPath:newIndexPath];
  874. } else {
  875. contactForDetails = [self.workContactsDataSource workContactAtIndexPath:indexPath];
  876. }
  877. }
  878. [self displayContact];
  879. }
  880. else {
  881. contactForDetails = [self.contactsDataSource contactAtIndexPath:indexPath];
  882. [self displayContact];
  883. }
  884. }
  885. #pragma mark - Fetched results controller
  886. - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller
  887. {
  888. //nop
  889. }
  890. - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo
  891. atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type
  892. {
  893. //nop
  894. }
  895. - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject
  896. atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type
  897. newIndexPath:(NSIndexPath *)newIndexPath
  898. {
  899. //nop
  900. }
  901. - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller
  902. {
  903. [self updateNoContactsView];
  904. [self.tableView reloadData];
  905. }
  906. #pragma mark - Search controller delegate
  907. - (void)willPresentSearchController:(UISearchController *)searchController {
  908. if (@available(iOS 11.0, *)) {
  909. self.searchController.searchBar.searchFieldBackgroundPositionAdjustment = UIOffsetMake(0.0, 0.0);
  910. } else {
  911. [self.searchController.view addSubview:_statusBarView];
  912. }
  913. [self setFooterView:NO];
  914. _rfControl = self.refreshControl;
  915. self.refreshControl = nil;
  916. }
  917. - (void)willDismissSearchController:(UISearchController *)searchController {
  918. if (@available(iOS 11.0, *)) {
  919. self.searchController.searchBar.searchFieldBackgroundPositionAdjustment = UIOffsetMake(0.0, 7.0);
  920. } else {
  921. [_statusBarView removeFromSuperview];
  922. }
  923. self.refreshControl = _rfControl;
  924. [self updateNoContactsView];
  925. }
  926. -(void)updateSearchResultsForSearchController:(UISearchController *)searchController{
  927. [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]];
  928. [self.tableView reloadData];
  929. }
  930. - (NSArray *)searchWordsForText:(NSString *)text {
  931. NSArray *searchWords = nil;
  932. if (text && [text length] > 0) {
  933. searchWords = [text componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
  934. }
  935. return searchWords;
  936. }
  937. #pragma mark - Actions
  938. - (IBAction)segmentedControlChanged:(id)sender {
  939. _mode = self.segmentedControl.selectedSegmentIndex;
  940. switch (_mode) {
  941. case ModeContacts:
  942. self.navigationItem.title = NSLocalizedString(@"segmentcontrol_contacts", nil);
  943. self.refreshControl = _rfControl;
  944. _currentDataSource = [self contactsDataSource];
  945. [_contactsDataSource setIgnoreFRCUpdates:NO];
  946. [_workContactsDataSource setIgnoreFRCUpdates:YES];
  947. [_groupsDataSource setIgnoreFRCUpdates:YES];
  948. [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]];
  949. [self updateNoContactsView];
  950. if (@available(iOS 11.0, *)) {
  951. self.tableView.tableHeaderView = nil;
  952. }
  953. break;
  954. case ModeWorkContacts:
  955. self.navigationItem.title = NSLocalizedString(@"segmentcontrol_work_contacts", nil);
  956. self.refreshControl = _rfControl;
  957. _currentDataSource = [self workContactsDataSource];
  958. [_contactsDataSource setIgnoreFRCUpdates:YES];
  959. [_workContactsDataSource setIgnoreFRCUpdates:NO];
  960. [_groupsDataSource setIgnoreFRCUpdates:YES];
  961. [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]];
  962. if (@available(iOS 11.0, *)) {
  963. self.tableView.tableHeaderView = [UserSettings sharedUserSettings].companyDirectory == true ? _companyDirectoryCell : nil;
  964. }
  965. [self updateNoContactsView];
  966. break;
  967. case ModeGroups:
  968. self.navigationItem.title = NSLocalizedString(@"segmentcontrol_groups", nil);
  969. _rfControl = self.refreshControl;
  970. self.refreshControl = nil;
  971. _currentDataSource = [self groupsDataSource];
  972. [_contactsDataSource setIgnoreFRCUpdates:YES];
  973. [_workContactsDataSource setIgnoreFRCUpdates:YES];
  974. [_groupsDataSource setIgnoreFRCUpdates:NO];
  975. [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]];
  976. if (@available(iOS 11.0, *)) {
  977. self.tableView.tableHeaderView = nil;
  978. }
  979. self.tableView.tableFooterView = nil;
  980. break;
  981. default:
  982. break;
  983. }
  984. [self.tableView reloadData];
  985. if ([self hasData]) {
  986. NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
  987. [self.tableView scrollToRowAtIndexPath:indexPath atScrollPosition:UITableViewScrollPositionTop animated:YES];
  988. }
  989. }
  990. - (IBAction)addAction:(id)sender {
  991. MDMSetup *mdmSetup = [[MDMSetup alloc] initWithSetup:NO];
  992. if (_mode == ModeContacts || _mode == ModeWorkContacts) {
  993. if ([mdmSetup disableAddContact]) {
  994. [UIAlertTemplate showAlertWithOwner:self title:@"" message:NSLocalizedString(@"disabled_by_device_policy", nil) actionOk:nil];
  995. return;
  996. }
  997. UIStoryboard *storyboard = self.storyboard;
  998. UINavigationController *navVC = [storyboard instantiateViewControllerWithIdentifier:@"AddContactNavigationController"];
  999. [self presentViewController:navVC animated:YES completion:nil];
  1000. } else {
  1001. if ([mdmSetup disableCreateGroup]) {
  1002. [UIAlertTemplate showAlertWithOwner:self title:@"" message:NSLocalizedString(@"disabled_by_device_policy", nil) actionOk:nil];
  1003. return;
  1004. }
  1005. UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"CreateGroup" bundle:nil];
  1006. UINavigationController *navVC = [storyboard instantiateInitialViewController];
  1007. [self presentViewController:navVC animated:YES completion:nil];
  1008. }
  1009. }
  1010. - (IBAction)pulledForRefresh:(UIRefreshControl *)sender {
  1011. _mode = self.segmentedControl.selectedSegmentIndex;
  1012. if ((_mode == ModeContacts || _mode == ModeWorkContacts) && !self.searchController.active) {
  1013. [self setRefreshControlTitle:YES];
  1014. _segmentedControl.userInteractionEnabled = NO;
  1015. [[GatewayAvatarMaker gatewayAvatarMaker] refreshForced];
  1016. if ([UserSettings sharedUserSettings].syncContacts) {
  1017. [[ContactStore sharedContactStore] synchronizeAddressBookForceFullSync:YES ignoreMinimumInterval:YES onCompletion:^(BOOL addressBookAccessGranted) {
  1018. [self updateWorkDataAndEndRefreshing:sender];
  1019. if (!addressBookAccessGranted) {
  1020. [UIAlertTemplate showAlertWithOwner:self title:NSLocalizedString(@"no_contacts_permission_title", nil) message:NSLocalizedString(@"no_contacts_permission_message", nil) actionOk:nil];
  1021. }
  1022. } onError:^(NSError *error) {
  1023. [UIAlertTemplate showAlertWithOwner:self title:error.localizedDescription message:error.localizedFailureReason actionOk:^(UIAlertAction * _Nonnull okAction) {
  1024. [self updateWorkDataAndEndRefreshing:sender];
  1025. }];
  1026. }];
  1027. } else {
  1028. [[ContactStore sharedContactStore] synchronizeAddressBookForceFullSync:YES ignoreMinimumInterval:YES onCompletion:^(BOOL addressBookAccessGranted) {
  1029. [self updateWorkDataAndEndRefreshing:sender];
  1030. } onError:^(NSError *error) {
  1031. [self updateWorkDataAndEndRefreshing:sender];
  1032. }];
  1033. }
  1034. } else {
  1035. self.segmentedControl.userInteractionEnabled = YES;
  1036. [sender endRefreshing];
  1037. [self.rfControl endRefreshing];
  1038. }
  1039. }
  1040. - (void)updateWorkDataAndEndRefreshing:(UIRefreshControl*)rfSender {
  1041. [WorkDataFetcher checkUpdateWorkDataForce:YES onCompletion:^{
  1042. [self endRefreshingAndScrollUp:rfSender];
  1043. } onError:^(NSError *error) {
  1044. [self endRefreshingAndScrollUp:rfSender];
  1045. [UIAlertTemplate showAlertWithOwner:self title:error.localizedDescription message:error.localizedFailureReason actionOk:nil];
  1046. }];
  1047. }
  1048. - (void)endRefreshingAndScrollUp:(UIRefreshControl*)rfSender {
  1049. [self setRefreshControlTitle:NO];
  1050. self.segmentedControl.userInteractionEnabled = YES;
  1051. [rfSender endRefreshing];
  1052. [self.rfControl endRefreshing];
  1053. if ([self.tableView numberOfSections] > 0 && [self.tableView numberOfRowsInSection:0] > 0)
  1054. [self.tableView scrollToRowAtIndexPath:[NSIndexPath indexPathForRow:0 inSection:0] atScrollPosition:UITableViewScrollPositionTop animated:YES];
  1055. }
  1056. - (void)companyDirectoryTapped:(UITapGestureRecognizer *)recognizer {
  1057. ThemedTableViewController *companyDirectoryViewController = (ThemedTableViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"CompanyDirectoryViewController"];
  1058. ModalNavigationController *nav = [[ModalNavigationController alloc] initWithRootViewController:companyDirectoryViewController];
  1059. nav.showDoneButton = true;
  1060. nav.showFullScreenOnIPad = false;
  1061. [self presentViewController:nav animated:YES completion:nil];
  1062. }
  1063. #pragma mark - UIViewControllerPreviewingDelegate
  1064. - (void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
  1065. if ([viewControllerToCommit isKindOfClass:[ContactDetailsViewController class]]) {
  1066. contactForDetails = ((ContactDetailsViewController *)viewControllerToCommit).contact;
  1067. [self displayContact];
  1068. } else if ([viewControllerToCommit isKindOfClass:[GroupDetailsViewController class]]) {
  1069. groupForDetails = ((GroupDetailsViewController *)viewControllerToCommit).group;
  1070. [self displayGroup];
  1071. }
  1072. }
  1073. - (UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
  1074. UIView *view = [self.view hitTest:location withEvent:nil];
  1075. if ([view.superview isKindOfClass:[ContactCell class]]) {
  1076. ContactCell *contactCell = (ContactCell *)view.superview;
  1077. ContactDetailsViewController *contactVc = [self contactDetailsViewControllerForContact:contactCell.contact];
  1078. contactVc.hideActionButtons = YES;
  1079. contactVc.delegate = self;
  1080. return contactVc;
  1081. } else if ([view.superview isKindOfClass:[GroupCell class]]) {
  1082. GroupCell *groupCell = (GroupCell *)view.superview;
  1083. GroupDetailsViewController *groupVc = [self groupDetailsViewControllerForGroup:groupCell.group];
  1084. groupVc.hideActionButtons = YES;
  1085. groupVc.delegate = self;
  1086. return groupVc;
  1087. }
  1088. return nil;
  1089. }
  1090. #pragma mark - GroupDetailsViewControllerDelegate
  1091. - (void)presentGroupDetails:(GroupDetailsViewController *)groupDetailsViewController onCompletion:(void (^)(GroupDetailsViewController *))onCompletion {
  1092. groupForDetails = groupDetailsViewController.group;
  1093. _prevNavigationControllerDelegate = self.navigationController.delegate;
  1094. self.navigationController.delegate = self;
  1095. [self displayGroup];
  1096. _groupCompletionBlock = onCompletion;
  1097. }
  1098. #pragma mark - ContactDetailsViewControllerDelegate
  1099. - (void)presentWithContactDetailsViewController:(ContactDetailsViewController *)contactDetailsViewController onCompletion:(void (^)(ContactDetailsViewController * _Nonnull))onCompletion {
  1100. contactForDetails = contactDetailsViewController.contact;
  1101. _prevNavigationControllerDelegate = self.navigationController.delegate;
  1102. self.navigationController.delegate = self;
  1103. [self displayContact];
  1104. _contactCompletionBlock = onCompletion;
  1105. }
  1106. #pragma mark - UINavigationControllerDelegate
  1107. - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
  1108. if (_groupCompletionBlock) {
  1109. if ([viewController isKindOfClass:[GroupDetailsViewController class]]) {
  1110. _groupCompletionBlock((GroupDetailsViewController *)viewController);
  1111. }
  1112. _groupCompletionBlock = nil;
  1113. }
  1114. if (_contactCompletionBlock) {
  1115. if ([viewController isKindOfClass:[ContactDetailsViewController class]]) {
  1116. _contactCompletionBlock((ContactDetailsViewController *)viewController);
  1117. }
  1118. _contactCompletionBlock = nil;
  1119. }
  1120. self.navigationController.delegate = _prevNavigationControllerDelegate;
  1121. }
  1122. #pragma mark - Notifications
  1123. - (void)showProfilePictureChanged:(NSNotification *)notification {
  1124. [self refresh];
  1125. }
  1126. - (void)refreshWorkContactTableView:(NSNotification *)notification {
  1127. if (@available(iOS 11.0, *)) {
  1128. [self refresh];
  1129. self.tableView.tableHeaderView = [UserSettings sharedUserSettings].companyDirectory == true ? _companyDirectoryCell : nil;
  1130. } else {
  1131. [self refresh];
  1132. }
  1133. }
  1134. - (void)refreshContactSortIndices:(NSNotification *)notification {
  1135. if (_contactsDataSource != nil) {
  1136. [_contactsDataSource refreshContactSortIndices];
  1137. }
  1138. if (_workContactsDataSource != nil) {
  1139. [_workContactsDataSource refreshWorkContactSortIndices];
  1140. }
  1141. }
  1142. @end