ContactPickerViewController.m 19 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507
  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 "ContactPickerViewController.h"
  21. #import "ContactCell.h"
  22. #import "GroupCell.h"
  23. #import "CreateGroupCell.h"
  24. #import "Contact.h"
  25. #import "ProtocolDefines.h"
  26. #import "AppDelegate.h"
  27. #import "ServerAPIConnector.h"
  28. #import "ErrorHandler.h"
  29. #import "EntityManager.h"
  30. #import "BundleUtil.h"
  31. #import "ContactTableDataSource.h"
  32. #import "GroupTableDataSource.h"
  33. #import "WorkContactTableDataSource.h"
  34. #import "ModalPresenter.h"
  35. #import "UserSettings.h"
  36. #import "LicenseStore.h"
  37. #import "ThemedTableViewController.h"
  38. #import "ModalNavigationController.h"
  39. #import "Threema-Swift.h"
  40. typedef enum : NSUInteger {
  41. ModeContacts,
  42. ModeGroups,
  43. ModeWorkContacts
  44. } Mode;
  45. @interface ContactPickerViewController ()
  46. @property Mode mode;
  47. @property id<ContactGroupDataSource> currentDataSource;
  48. @property (nonatomic) ContactTableDataSource *contactsDataSource;
  49. @property (nonatomic) GroupTableDataSource *groupsDataSource;
  50. @property (nonatomic) WorkContactTableDataSource *workContactsDataSource;
  51. @property (strong, nonatomic) UIView *statusBarView;
  52. @end
  53. @implementation ContactPickerViewController {
  54. NSMutableSet *groupContacts;
  55. BOOL groupMode;
  56. NSArray *filteredContacts;
  57. }
  58. - (id)initWithCoder:(NSCoder *)aDecoder {
  59. self = [super initWithCoder:aDecoder];
  60. if (self) {
  61. groupContacts = [NSMutableSet set];
  62. }
  63. return self;
  64. }
  65. - (void)viewDidLoad
  66. {
  67. [super viewDidLoad];
  68. _mode = ModeContacts;
  69. _currentDataSource = [self contactsDataSource];
  70. [self.segmentedControl setTitle:@"contacts" forSegmentAtIndex:ModeContacts];
  71. [self.segmentedControl setTitle:@"groups" forSegmentAtIndex:ModeGroups];
  72. if ([LicenseStore requiresLicenseKey]) {
  73. [self.segmentedControl insertSegmentWithTitle:@"work" atIndex:ModeWorkContacts animated:NO];
  74. if ([[self workContactsDataSource] numberOfSectionsInTableView:self.tableView] > 0) {
  75. // No regular contacts, so show Work contacts by default
  76. _mode = ModeWorkContacts;
  77. [self.segmentedControl setSelectedSegmentIndex:ModeWorkContacts];
  78. _currentDataSource = [self workContactsDataSource];
  79. }
  80. }
  81. for (int i = 0; i < self.segmentedControl.numberOfSegments; i++) {
  82. UIView *segment = self.segmentedControl.subviews[i];
  83. for (id subview in segment.subviews) {
  84. if ([subview isKindOfClass:[UILabel class]]) {
  85. UILabel *label = (UILabel *)subview;
  86. if ([label.text isEqualToString:@"contacts"]) {
  87. segment.accessibilityLabel = NSLocalizedString(@"segmentcontrol_contacts", @"");
  88. }
  89. else if ([label.text isEqualToString:@"groups"]) {
  90. segment.accessibilityLabel = NSLocalizedString(@"segmentcontrol_groups", @"");
  91. }
  92. else if ([label.text isEqualToString:@"work"]) {
  93. segment.accessibilityLabel = NSLocalizedString(@"segmentcontrol_work_contacts", @"");
  94. }
  95. }
  96. }
  97. }
  98. [self.segmentedControl setTitle:nil forSegmentAtIndex:ModeContacts];
  99. [self.segmentedControl setTitle:nil forSegmentAtIndex:ModeGroups];
  100. [self.segmentedControl setImage:[BundleUtil imageNamed:@"Contact"] forSegmentAtIndex:ModeContacts];
  101. [self.segmentedControl setImage:[BundleUtil imageNamed:@"Group"] forSegmentAtIndex:ModeGroups];
  102. if ([LicenseStore requiresLicenseKey]) {
  103. [self.segmentedControl setTitle:nil forSegmentAtIndex:ModeWorkContacts];
  104. [self.segmentedControl setImage:[BundleUtil imageNamed:@"Case"] forSegmentAtIndex:ModeWorkContacts];
  105. }
  106. self.searchController = [[UISearchController alloc]initWithSearchResultsController:nil];
  107. self.searchController.delegate = self;
  108. self.searchController.searchBar.showsScopeBar = NO;
  109. self.searchController.searchBar.scopeButtonTitles = nil;
  110. self.tableView.keyboardDismissMode = UIScrollViewKeyboardDismissModeOnDrag;
  111. self.searchController.searchBar.delegate = self;
  112. self.searchController.searchResultsUpdater = self;
  113. self.searchController.searchBar.autoresizingMask = UIViewAutoresizingFlexibleWidth;
  114. [self.searchController.searchBar sizeToFit];
  115. self.searchController.searchBar.barStyle = UISearchBarStyleMinimal;
  116. self.searchController.dimsBackgroundDuringPresentation = NO;
  117. self.definesPresentationContext = YES;
  118. self.searchController.hidesNavigationBarDuringPresentation = YES;
  119. if (@available(iOS 11.0, *)) {
  120. self.navigationItem.searchController = _searchController;
  121. } else {
  122. [self.view addSubview:self.searchController.searchBar];
  123. self.tableView.contentInset = UIEdgeInsetsMake(self.searchController.searchBar.frame.size.height, 0, 0, 0);
  124. self.statusBarView = [[UIView alloc] initWithFrame:[[UIApplication sharedApplication] statusBarFrame]];
  125. }
  126. [self setupColors];
  127. self.tableView.rowHeight = UITableViewAutomaticDimension;
  128. if (@available(iOS 11.0, *)) {
  129. self.tableView.estimatedRowHeight = UITableViewAutomaticDimension;
  130. } else {
  131. self.tableView.estimatedRowHeight = 44.0;
  132. }
  133. }
  134. - (void)setupColors {
  135. [self.view setBackgroundColor:[Colors backgroundLight]];
  136. [self.navigationController.view setBackgroundColor:[Colors background]];
  137. [Colors updateTableView:self.tableView];
  138. if (@available(iOS 11.0, *)) {
  139. self.searchController.searchBar.barTintColor = [UIColor clearColor];
  140. self.searchController.searchBar.backgroundColor = [UIColor clearColor];
  141. UINavigationBar *navigationBar = self.navigationController.navigationBar;
  142. if (navigationBar) {
  143. navigationBar.barTintColor = [Colors backgroundBaseColor];
  144. }
  145. } else {
  146. self.searchController.searchBar.backgroundColor = [Colors backgroundBaseColor];
  147. UINavigationBar *navigationBar = self.navigationController.navigationBar;
  148. if (navigationBar) {
  149. navigationBar.barTintColor = [Colors backgroundBaseColor];
  150. }
  151. }
  152. [Colors updateSearchBar:_searchController.searchBar];
  153. if (@available(iOS 11.0, *)) {
  154. self.searchController.searchBar.searchFieldBackgroundPositionAdjustment = UIOffsetMake(0.0, 7.0);
  155. self.navigationItem.largeTitleDisplayMode = [UserSettings sharedUserSettings].largeTitleDisplayMode;
  156. } else {
  157. [_statusBarView setBackgroundColor:[Colors searchBarStatusBar]];
  158. }
  159. }
  160. - (BOOL)shouldAutorotate {
  161. return YES;
  162. }
  163. -(UIInterfaceOrientationMask)supportedInterfaceOrientations {
  164. if (SYSTEM_IS_IPAD) {
  165. return UIInterfaceOrientationMaskAll;
  166. }
  167. return UIInterfaceOrientationMaskAllButUpsideDown;
  168. }
  169. #pragma mark - Table view
  170. - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
  171. [Colors updateTableViewCell:cell];
  172. }
  173. - (void)tableView:(UITableView *)tableView willDisplayHeaderView:(nonnull UIView *)view forSection:(NSInteger)section {
  174. UITableViewHeaderFooterView *headerView = (UITableViewHeaderFooterView*)view;
  175. [headerView.contentView setBackgroundColor:[Colors backgroundDark]];
  176. [headerView.textLabel setTextColor:[Colors fontNormal]];
  177. }
  178. - (CGFloat)tableView:(UITableView *)tableView heightForRowAtIndexPath:(NSIndexPath *)indexPath {
  179. return UITableViewAutomaticDimension;
  180. }
  181. -(CGFloat)tableView:(UITableView *)tableView estimatedHeightForRowAtIndexPath:(NSIndexPath *)indexPath {
  182. return UITableViewAutomaticDimension;
  183. }
  184. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView {
  185. if (_mode == ModeGroups) {
  186. return [self.currentDataSource numberOfSectionsInTableView:tableView] + 1;
  187. }
  188. if (_mode == ModeWorkContacts && [[UserSettings sharedUserSettings] companyDirectory] == true) {
  189. return [self.currentDataSource numberOfSectionsInTableView:tableView] + 1;
  190. }
  191. return [self.currentDataSource numberOfSectionsInTableView:tableView];
  192. }
  193. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section {
  194. if (_mode == ModeGroups) {
  195. if (section == 0) {
  196. if (_searchController.searchBar.isFirstResponder) {
  197. return 0;
  198. }
  199. return 1;
  200. }
  201. return [self.currentDataSource tableView:tableView numberOfRowsInSection:section - 1];
  202. }
  203. if (_mode == ModeWorkContacts && [[UserSettings sharedUserSettings] companyDirectory] == true) {
  204. if (section == 0) {
  205. return 1;
  206. }
  207. return [self.currentDataSource tableView:tableView numberOfRowsInSection:section - 1];
  208. }
  209. return [self.currentDataSource tableView:tableView numberOfRowsInSection:section];
  210. }
  211. - (NSArray *)sectionIndexTitlesForTableView:(UITableView *)tableView {
  212. return [self.currentDataSource sectionIndexTitlesForTableView:tableView];
  213. }
  214. - (NSInteger)tableView:(UITableView *)tableView sectionForSectionIndexTitle:(NSString *)title atIndex:(NSInteger)index {
  215. return [self.currentDataSource tableView:tableView sectionForSectionIndexTitle:title atIndex:index];
  216. }
  217. - (NSString *)tableView:(UITableView *)tableView titleForHeaderInSection:(NSInteger)section {
  218. if (_mode == ModeGroups) {
  219. if (section == 1 && _searchController.searchBar.isFirstResponder == NO) {
  220. return NSLocalizedString(@"existing groups", nil);
  221. }
  222. return nil;
  223. }
  224. if (_mode == ModeWorkContacts && [[UserSettings sharedUserSettings] companyDirectory] == true) {
  225. return nil;
  226. }
  227. return [self.currentDataSource tableView:tableView titleForHeaderInSection:section];
  228. }
  229. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  230. {
  231. UITableViewCell *cell;
  232. if (_mode == ModeGroups) {
  233. if (indexPath.section == 0) {
  234. cell = [self.tableView dequeueReusableCellWithIdentifier:@"CreateGroupCell"];
  235. } else {
  236. NSIndexPath *convertedIndex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1];
  237. cell = [self tableView:tableView groupCellForIndexPath:convertedIndex];
  238. }
  239. }
  240. else if (_mode == ModeWorkContacts) {
  241. if ([[UserSettings sharedUserSettings] companyDirectory] == true) {
  242. if (indexPath.section == 0) {
  243. cell = [self.tableView dequeueReusableCellWithIdentifier:@"CompanyDirectoryCell"];
  244. [((CompanyDirectoryCell *)cell) setupColors];
  245. } else {
  246. NSIndexPath *convertedIndex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1];
  247. cell = [self tableView:tableView workContactCellForIndexPath:convertedIndex];
  248. }
  249. } else {
  250. NSIndexPath *convertedIndex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section];
  251. cell = [self tableView:tableView workContactCellForIndexPath:convertedIndex];
  252. }
  253. }
  254. else {
  255. cell = [self tableView:tableView contactCellForIndexPath:indexPath];
  256. }
  257. return cell;
  258. }
  259. - (UITableViewCell *)tableView:(UITableView *)tableView contactCellForIndexPath:(NSIndexPath *)indexPath {
  260. ContactCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactCell"];
  261. Contact *contact = [self.contactsDataSource contactAtIndexPath:indexPath];
  262. cell.contact = contact;
  263. return cell;
  264. }
  265. - (UITableViewCell *)tableView:(UITableView *)tableView groupCellForIndexPath:(NSIndexPath *)indexPath {
  266. GroupCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"GroupCell"];
  267. GroupProxy *group = [self.groupsDataSource groupAtIndexPath:indexPath];
  268. cell.group = group;
  269. return cell;
  270. }
  271. - (UITableViewCell *)tableView:(UITableView *)tableView workContactCellForIndexPath:(NSIndexPath *)indexPath {
  272. ContactCell *cell = [self.tableView dequeueReusableCellWithIdentifier:@"ContactCell"];
  273. Contact *contact = [self.workContactsDataSource workContactAtIndexPath:indexPath];
  274. cell.contact = contact;
  275. return cell;
  276. }
  277. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  278. UIViewController *presentingVC = self.presentingViewController;
  279. GroupProxy *group = nil;
  280. Contact *contact = nil;
  281. if (_mode == ModeGroups) {
  282. if (indexPath.section != 0) {
  283. NSIndexPath *convertedIndex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1];
  284. group = [((GroupTableDataSource *)_currentDataSource) groupAtIndexPath:convertedIndex];
  285. }
  286. }
  287. else if (_mode == ModeWorkContacts) {
  288. if ([[UserSettings sharedUserSettings] companyDirectory] == true) {
  289. if (indexPath.section != 0) {
  290. NSIndexPath *convertedIndex = [NSIndexPath indexPathForRow:indexPath.row inSection:indexPath.section - 1];
  291. contact = [((WorkContactTableDataSource *)_currentDataSource) workContactAtIndexPath:convertedIndex];
  292. }
  293. } else {
  294. contact = [((WorkContactTableDataSource *)_currentDataSource) workContactAtIndexPath:indexPath];
  295. }
  296. }
  297. else {
  298. contact = [((ContactTableDataSource *)_currentDataSource) contactAtIndexPath:indexPath];
  299. }
  300. [_searchController setActive:NO];
  301. [self dismissViewControllerAnimated:YES completion:^{
  302. if (_mode == ModeGroups) {
  303. if (indexPath.section == 0) {
  304. MDMSetup *mdmSetup = [[MDMSetup alloc] initWithSetup:NO];
  305. if ([mdmSetup disableCreateGroup]) {
  306. [UIAlertTemplate showAlertWithOwner:presentingVC title:@"" message:NSLocalizedString(@"disabled_by_device_policy", nil) actionOk:nil];
  307. } else {
  308. UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"CreateGroup" bundle:nil];
  309. UINavigationController *navVC = [storyboard instantiateInitialViewController];
  310. [presentingVC presentViewController:navVC animated:YES completion:nil];
  311. }
  312. } else {
  313. [self showConversationForGroup:group];
  314. }
  315. }
  316. else if (_mode == ModeWorkContacts && [[UserSettings sharedUserSettings] companyDirectory] == true) {
  317. if (indexPath.section == 0) {
  318. CompanyDirectoryViewController *companyDirectoryViewController = (CompanyDirectoryViewController *)[self.storyboard instantiateViewControllerWithIdentifier:@"CompanyDirectoryViewController"];
  319. companyDirectoryViewController.addContactActive = false;
  320. ModalNavigationController *nav = [[ModalNavigationController alloc] initWithRootViewController:companyDirectoryViewController];
  321. nav.showDoneButton = true;
  322. nav.showFullScreenOnIPad = false;
  323. [presentingVC presentViewController:nav animated:YES completion:nil];
  324. } else {
  325. [self showConversationForContact:contact];
  326. }
  327. }
  328. else {
  329. [self showConversationForContact:contact];
  330. }
  331. }];
  332. }
  333. - (void)showConversationForGroup:(GroupProxy *)group {
  334. Conversation *conversation = group.conversation;
  335. NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
  336. conversation, kKeyConversation,
  337. [NSNumber numberWithBool:YES], kKeyForceCompose,
  338. nil];
  339. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowConversation object:nil
  340. userInfo:info];
  341. }
  342. - (void)showConversationForContact:(Contact *)contact {
  343. NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
  344. contact, kKeyContact,
  345. [NSNumber numberWithBool:YES], kKeyForceCompose,
  346. nil];
  347. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowConversation object:nil
  348. userInfo:info];
  349. }
  350. - (IBAction)cancelAction:(id)sender {
  351. [self dismissViewControllerAnimated:YES completion:nil];
  352. }
  353. - (ContactTableDataSource *)contactsDataSource {
  354. if (_contactsDataSource == nil) {
  355. _contactsDataSource = [ContactTableDataSource contactTableDataSource];
  356. }
  357. return _contactsDataSource;
  358. }
  359. - (GroupTableDataSource *)groupsDataSource {
  360. if (_groupsDataSource == nil) {
  361. _groupsDataSource = [GroupTableDataSource groupTableDataSource];
  362. }
  363. return _groupsDataSource;
  364. }
  365. - (WorkContactTableDataSource *)workContactsDataSource {
  366. if (_workContactsDataSource == nil) {
  367. _workContactsDataSource = [WorkContactTableDataSource workContactTableDataSource];
  368. }
  369. return _workContactsDataSource;
  370. }
  371. #pragma mark - Search controller delegate
  372. - (void)willPresentSearchController:(UISearchController *)searchController {
  373. if (@available(iOS 11.0, *)) {
  374. self.searchController.searchBar.searchFieldBackgroundPositionAdjustment = UIOffsetMake(0.0, 0.0);
  375. } else {
  376. [self.searchController.view addSubview:_statusBarView];
  377. }
  378. }
  379. - (void)willDismissSearchController:(UISearchController *)searchController {
  380. if (@available(iOS 11.0, *)) {
  381. self.searchController.searchBar.searchFieldBackgroundPositionAdjustment = UIOffsetMake(0.0, 7.0);
  382. } else {
  383. [_statusBarView removeFromSuperview];
  384. }
  385. }
  386. -(void)updateSearchResultsForSearchController:(UISearchController *)searchController {
  387. [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]];
  388. [self.tableView reloadData];
  389. }
  390. - (NSArray *)searchWordsForText:(NSString *)text {
  391. NSArray *searchWords = nil;
  392. if (text && [text length] > 0) {
  393. searchWords = [text componentsSeparatedByCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
  394. }
  395. return searchWords;
  396. }
  397. #pragma mark - Actions
  398. - (IBAction)segmentedControlChanged:(id)sender {
  399. _mode = self.segmentedControl.selectedSegmentIndex;
  400. switch (_mode) {
  401. case ModeContacts:
  402. _currentDataSource = [self contactsDataSource];
  403. [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]];
  404. break;
  405. case ModeGroups:
  406. _currentDataSource = [self groupsDataSource];
  407. [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]];
  408. break;
  409. case ModeWorkContacts:
  410. _currentDataSource = [self workContactsDataSource];
  411. [_currentDataSource filterByWords: [self searchWordsForText:_searchController.searchBar.text]];
  412. break;
  413. default:
  414. break;
  415. }
  416. [self.tableView reloadData];
  417. }
  418. @end