ConversationsViewController.m 43 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845846847848849850851852853854855856857858859860861862863864865866867868869870871872873874875876877878879880881882883884885886887888889890891892893894895896897898899900901902903904905906907908909910911912913914915916917918919920921922923924925926927928929930931932933934935936937938939940941942943944945946947948949950951952953954955956957958959960961962963964965966967968
  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 "ConversationsViewController.h"
  21. #import "AppDelegate.h"
  22. #import "Conversation.h"
  23. #import "Contact.h"
  24. #import "ConversationCell.h"
  25. #import "ChatViewController.h"
  26. #import "ContactPickerViewController.h"
  27. #import "Utils.h"
  28. #import "NaClCrypto.h"
  29. #import "ProtocolDefines.h"
  30. #import "MyIdentityStore.h"
  31. #import "PortraitNavigationController.h"
  32. #import "EntityManager.h"
  33. #import "ErrorHandler.h"
  34. #import "GroupProxy.h"
  35. #import "ChatViewControllerCache.h"
  36. #import "DatabaseManager.h"
  37. #import "BrandingUtils.h"
  38. #import "MessageDraftStore.h"
  39. #import "MGSwipeTableCell.h"
  40. #import "ChatDefines.h"
  41. #import "MessageSender.h"
  42. #import "UIImage+ColoredImage.h"
  43. #import "ConversationUtils.h"
  44. #import "UserSettings.h"
  45. #import "NotificationManager.h"
  46. #import "LicenseStore.h"
  47. #import "BundleUtil.h"
  48. #import "SendMediaAction.h"
  49. #import "SendLocationAction.h"
  50. #ifdef DEBUG
  51. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  52. #else
  53. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  54. #endif
  55. @interface ConversationsViewController () <NSFetchedResultsControllerDelegate, UIViewControllerPreviewingDelegate, UINavigationControllerDelegate, ChatViewControllerDelegate, MGSwipeTableCellDelegate, ConversationCellDelegate, UIScrollViewDelegate>
  56. @property (strong, nonatomic) NSFetchedResultsController *fetchedResultsController;
  57. @property id<UINavigationControllerDelegate> prevNavigationControllerDelegate;
  58. @property (copy) ChatViewControllerCompletionBlock chatViewCompletionBlock;
  59. @end
  60. @implementation ConversationsViewController {
  61. UIBarButtonItem *composeButtonItem;
  62. NSDate *lastAppearDate;
  63. Conversation *conversationToDelete;
  64. BOOL isEditing;
  65. BOOL viewLoadedInBackground;
  66. NSIndexPath *lastSelectedCell;
  67. BOOL canTransitionToLarge;
  68. BOOL canTransitionToSmall;
  69. }
  70. - (id)initWithCoder:(NSCoder *)aDecoder {
  71. self = [super initWithCoder:aDecoder];
  72. if (self) {
  73. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(addressbookSyncronized:) name:kNotificationAddressbookSyncronized object:nil];
  74. }
  75. return self;
  76. }
  77. - (void)viewDidLoad {
  78. [super viewDidLoad];
  79. self.navigationItem.leftBarButtonItem = self.editButtonItem;
  80. // Listen for unread messages count changes so we can update our title
  81. // and back button
  82. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(unreadMessagesCountChanged:) name:@"ThreemaUnreadMessagesCountChanged" object:nil];
  83. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification object:nil];
  84. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(refreshDirtyObjects:) name:kNotificationDBRefreshedDirtyObject object:nil];
  85. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(colorThemeChanged:) name:kNotificationColorThemeChanged object:nil];
  86. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(updateDraftForCell:) name:kNotificationUpdateDraftForCell object:nil];
  87. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showProfilePictureChanged:) name:kNotificationShowProfilePictureChanged object:nil];
  88. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(reloadTableView:) name:kNotificationBlockedContact object:nil];
  89. self.clearsSelectionOnViewWillAppear = NO;
  90. [self registerForPreviewingWithDelegate:self sourceView:self.view];
  91. [BrandingUtils updateTitleLogoOfNavigationItem:self.navigationItem navigationController:self.navigationController];
  92. if (@available(iOS 11.0, *)) {
  93. self.navigationItem.largeTitleDisplayMode = [UserSettings sharedUserSettings].largeTitleDisplayMode;
  94. }
  95. canTransitionToLarge = false;
  96. canTransitionToSmall = true;
  97. }
  98. - (void)viewWillAppear:(BOOL)animated {
  99. [super viewWillAppear:animated];
  100. self.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle: NSLocalizedString(@"messages", nil) style: UIBarButtonItemStylePlain target: nil action: nil];
  101. _createMessageBarButtonItem.accessibilityLabel = [BundleUtil localizedStringForKey:@"new_message_accessibility"];
  102. if (SYSTEM_IS_IPAD == NO) {
  103. [self.tableView deselectRowAtIndexPath:[self.tableView indexPathForSelectedRow] animated:YES];
  104. }
  105. if (@available(iOS 11.0, *)) {
  106. self.tableView.estimatedSectionHeaderHeight = 0;
  107. self.tableView.estimatedSectionFooterHeight = 0;
  108. }
  109. self.tableView.estimatedRowHeight = 0.0;
  110. self.tableView.rowHeight = UITableViewAutomaticDimension;
  111. // iOS fix where the logo is moved to the right sometimes
  112. if (self.navigationController.navigationBar.frame.size.height == 44.0 && [LicenseStore requiresLicenseKey]) {
  113. [BrandingUtils updateTitleLogoOfNavigationItem:self.navigationItem navigationController:self.navigationController];
  114. }
  115. else if (self.navigationController.navigationBar.frame.size.height == 44.0 && ![LicenseStore requiresLicenseKey] && self.navigationItem.titleView != nil) {
  116. [BrandingUtils updateTitleLogoOfNavigationItem:self.navigationItem navigationController:self.navigationController];
  117. }
  118. // make sure conversations was updated
  119. if (![[AppDelegate sharedAppDelegate] isAppInBackground]) {
  120. viewLoadedInBackground = false;
  121. } else {
  122. viewLoadedInBackground = true;
  123. }
  124. // remove top space on tableview
  125. CGRect frame = CGRectZero;
  126. frame.size.height = CGFLOAT_MIN;
  127. [self.tableView setTableHeaderView:[[UIView alloc] initWithFrame:frame]];
  128. [self refreshData];
  129. }
  130. - (void)viewWillDisappear:(BOOL)animated {
  131. [super viewWillDisappear:animated];
  132. [self setEditing:NO animated:NO];
  133. }
  134. - (void)refreshData {
  135. if (viewLoadedInBackground == false) {
  136. [_fetchedResultsController performFetch:nil];
  137. [self.tableView reloadData];
  138. }
  139. }
  140. - (void)colorThemeChanged:(NSNotification*)notification {
  141. [BrandingUtils updateTitleLogoOfNavigationItem:self.navigationItem navigationController:self.navigationController];
  142. }
  143. - (void)checkDateAndUpdateTimestamps {
  144. NSDate *now = [NSDate date];
  145. if (lastAppearDate != nil && ![Utils isSameDayWithDate1:lastAppearDate date2:now]) {
  146. DDLogInfo(@"Last appeared on a different date; updating timestamps");
  147. if (viewLoadedInBackground == false) {
  148. [self.tableView reloadData];
  149. }
  150. }
  151. lastAppearDate = now;
  152. }
  153. - (void)dealloc {
  154. [[NSNotificationCenter defaultCenter] removeObserver:self];
  155. self.fetchedResultsController = nil;
  156. }
  157. - (void)applicationWillEnterForeground:(NSNotification*)notification {
  158. [self checkDateAndUpdateTimestamps];
  159. if (viewLoadedInBackground == true) {
  160. viewLoadedInBackground = false;
  161. [self refreshData];
  162. }
  163. }
  164. - (void)unreadMessagesCountChanged:(NSNotification*)notification {
  165. int unread = ((NSNumber*)[notification.userInfo objectForKey:@"unread"]).intValue;
  166. NSString *backButtonTitle;
  167. if (unread > 0) {
  168. backButtonTitle = [NSString stringWithFormat:NSLocalizedString(@"bar_messages_x", nil), unread];
  169. } else {
  170. backButtonTitle = NSLocalizedString(@"messages", nil);
  171. }
  172. UIBarButtonItem *backButton = [[UIBarButtonItem alloc] initWithTitle:backButtonTitle style:UIBarButtonItemStylePlain target:nil action:nil];
  173. dispatch_async(dispatch_get_main_queue(), ^{
  174. self.navigationItem.title = NSLocalizedString(@"messages", nil);
  175. [[self navigationItem] setBackBarButtonItem:backButton];
  176. });
  177. }
  178. - (void)didReceiveMemoryWarning {
  179. DDLogWarn(@"Memory warning, removing cached chat view controllers");
  180. [ChatViewControllerCache clearCache];
  181. [super didReceiveMemoryWarning];
  182. }
  183. - (void)addressbookSyncronized:(NSNotification*)notification {
  184. dispatch_async(dispatch_get_main_queue(), ^{
  185. [ChatViewControllerCache clearCache];
  186. [self.tableView reloadData];
  187. });
  188. }
  189. - (BOOL)shouldAutorotate {
  190. return YES;
  191. }
  192. -(UIInterfaceOrientationMask)supportedInterfaceOrientations {
  193. if (SYSTEM_IS_IPAD) {
  194. return UIInterfaceOrientationMaskAll;
  195. }
  196. return UIInterfaceOrientationMaskAllButUpsideDown;
  197. }
  198. - (IBAction)newMessage:(id)sender {
  199. /* have user pick a Contact for the new message/conversation first */
  200. UINavigationController *contactPickerNavVc = [[self storyboard] instantiateViewControllerWithIdentifier:@"ContactPickerNav"];
  201. [self presentViewController:contactPickerNavVc animated:YES completion:nil];
  202. }
  203. - (void)setSelectionForConversation:(Conversation *)conversation {
  204. /* fix highlighted cell in our view */
  205. NSIndexPath *selectedRow = [self.tableView indexPathForSelectedRow];
  206. NSIndexPath *newRow = [self.fetchedResultsController indexPathForObject:conversation];
  207. DDLogInfo(@"selectedRow: %@, newRow: %@", selectedRow, newRow);
  208. if (![selectedRow isEqual:newRow]) {
  209. if (selectedRow != nil)
  210. [self.tableView deselectRowAtIndexPath:selectedRow animated:NO];
  211. if (newRow != nil)
  212. [self.tableView selectRowAtIndexPath:newRow animated:NO scrollPosition:UITableViewScrollPositionNone];
  213. }
  214. }
  215. - (void)displayChat:(ChatViewController *)chatViewController animated:(BOOL)animated {
  216. [self setSelectionForConversation:chatViewController.conversation];
  217. if (self.navigationController.topViewController == chatViewController) {
  218. return;
  219. }
  220. if ([self.navigationController.viewControllers containsObject:chatViewController]) {
  221. if (self.navigationController.topViewController.presentedViewController) {
  222. return;
  223. }
  224. [self.navigationController popToViewController:chatViewController animated:animated];
  225. return;
  226. }
  227. [self.navigationController popToViewController:self animated:NO];
  228. [self.navigationController pushViewController:chatViewController animated:animated];
  229. }
  230. - (void)showConversationFor:(Contact*)contact overrideCompose:(BOOL)overrideCompose compose:(BOOL)compose defaultText:(NSString*)defaultText defaultImage:(UIImage *)defaultImage {
  231. UIViewController *topVc = [self.navigationController.viewControllers lastObject];
  232. if (topVc.presentedViewController != nil && ![topVc.presentedViewController isKindOfClass:[PortraitNavigationController class]])
  233. return; // modal view present and not passcode lock
  234. }
  235. - (Conversation *)getFirstConversation {
  236. if ([self hasData]) {
  237. NSIndexPath *indexPath = [NSIndexPath indexPathForRow:0 inSection:0];
  238. return [[self fetchedResultsController] objectAtIndexPath:indexPath];
  239. }
  240. return nil;
  241. }
  242. - (BOOL)hasData {
  243. if ([self.fetchedResultsController.sections count] > 0) {
  244. id<NSFetchedResultsSectionInfo> info = self.fetchedResultsController.sections[0];
  245. if ([info numberOfObjects] > 0) {
  246. return YES;
  247. }
  248. }
  249. return NO;
  250. }
  251. - (void)setEditing:(BOOL)editing animated:(BOOL)animated {
  252. if (editing) {
  253. UIBarButtonItem *deleteButton = [[UIBarButtonItem alloc] initWithTitle:NSLocalizedString(@"delete_all", nil) style:UIBarButtonItemStylePlain target:self action:@selector(deleteAllAction:)];
  254. deleteButton.tintColor = [Colors red];
  255. composeButtonItem = self.navigationItem.rightBarButtonItem;
  256. self.navigationItem.rightBarButtonItem = deleteButton;
  257. } else {
  258. if (composeButtonItem) {
  259. self.navigationItem.rightBarButtonItem = composeButtonItem;
  260. }
  261. }
  262. isEditing = editing;
  263. [super setEditing:editing animated:animated];
  264. }
  265. - (void)deleteAllAction:(id)sender {
  266. UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"conversations_delete_all_confirm", nil) message:nil preferredStyle:UIAlertControllerStyleActionSheet];
  267. [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"delete", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) {
  268. EntityManager *entityManager = [[EntityManager alloc] init];
  269. [entityManager performSyncBlockAndSafe:^{
  270. NSArray *conversations = [entityManager.entityFetcher allConversations];
  271. for (Conversation* conversation in conversations) {
  272. /* do not delete group conversations */
  273. if (conversation.groupId == nil)
  274. [[entityManager entityDestroyer] deleteObjectWithObject:conversation];
  275. }
  276. }];
  277. self.editing = NO;
  278. }]];
  279. [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {
  280. }]];
  281. if (!self.tabBarController) {
  282. actionSheet.popoverPresentationController.barButtonItem = self.navigationItem.rightBarButtonItem;
  283. actionSheet.popoverPresentationController.sourceView = self.view;
  284. }
  285. [self presentViewController:actionSheet animated:YES completion:nil];
  286. }
  287. - (void)deleteConversation:(Conversation*)conversation {
  288. if (conversation == nil)
  289. return;
  290. if ([conversation isGroup]) {
  291. GroupProxy *group = [GroupProxy groupProxyForConversation:conversation];
  292. [group adminDeleteGroup];
  293. }
  294. [MessageDraftStore deleteDraftForConversation:conversation];
  295. // Remove cached chat view controller for this conversation to avoid observer overload
  296. ChatViewController *oldController = [ChatViewControllerCache controllerForConversation:conversation];
  297. [oldController removeConversationObservers];
  298. [ChatViewControllerCache clearConversation:conversation];
  299. /* Remove from Core Data */
  300. EntityManager *entityManager = [[EntityManager alloc] init];
  301. [entityManager performSyncBlockAndSafe:^{
  302. [[entityManager entityDestroyer] deleteObjectWithObject:conversation];
  303. }];
  304. [[NotificationManager sharedInstance] updateUnreadMessagesCount:NO];
  305. NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
  306. conversation, kKeyConversation,
  307. nil];
  308. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationDeletedConversation object:nil userInfo:info];
  309. }
  310. - (void)showAlertToDeleteConversation:(Conversation *)conversation cellRect:(CGRect)cellRect {
  311. /* If this was a group conversation with members, ask for confirmation */
  312. GroupProxy *groupProxy = nil;
  313. BOOL isGroup = false;
  314. if (conversation.groupId != nil && conversation.members.count > 0) {
  315. groupProxy = [GroupProxy groupProxyForConversation:conversation];
  316. isGroup = true;
  317. }
  318. if (isGroup && [groupProxy didLeaveGroup] == false) {
  319. conversationToDelete = conversation;
  320. NSString *message;
  321. if (conversation.contact == nil) {
  322. message = NSLocalizedString(@"group_admin_delete_confirm", nil);
  323. } else {
  324. message = NSLocalizedString(@"group_delete_confirm", nil);
  325. }
  326. UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:message message:nil preferredStyle:UIAlertControllerStyleActionSheet];
  327. [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"delete", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) {
  328. [self deleteConversation:conversationToDelete];
  329. }]];
  330. [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {
  331. }]];
  332. if (!self.tabBarController) {
  333. actionSheet.popoverPresentationController.sourceRect = cellRect;
  334. actionSheet.popoverPresentationController.sourceView = self.view;
  335. }
  336. [self presentViewController:actionSheet animated:YES completion:nil];
  337. } else {
  338. conversationToDelete = conversation;
  339. UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:NSLocalizedString(@"conversation_delete_confirm", nil) message:nil preferredStyle:UIAlertControllerStyleActionSheet];
  340. [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"delete", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) {
  341. [self deleteConversation:conversationToDelete];
  342. }]];
  343. [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {
  344. }]];
  345. if (!self.tabBarController) {
  346. actionSheet.popoverPresentationController.sourceRect = cellRect;
  347. actionSheet.popoverPresentationController.sourceView = self.view;
  348. }
  349. [self presentViewController:actionSheet animated:YES completion:nil];
  350. }
  351. }
  352. - (void)showAlertToLeaveGroup:(Conversation *)conversation cellRect:(CGRect)cellRect {
  353. /* If this was a group conversation with members, ask for confirmation */
  354. GroupProxy *groupProxy = nil;
  355. BOOL isGroup = false;
  356. if (conversation.groupId != nil) {
  357. groupProxy = [GroupProxy groupProxyForConversation:conversation];
  358. isGroup = true;
  359. }
  360. if (isGroup && [groupProxy isSelfMember] == true) {
  361. conversationToDelete = conversation;
  362. NSString *messageTitle = NSLocalizedString(@"leave_group", nil);
  363. NSString *message;
  364. if ([groupProxy isOwnGroup]) {
  365. message = [BundleUtil localizedStringForKey:@"group_admin_delete_confirm"];
  366. } else {
  367. message = [BundleUtil localizedStringForKey:@"group_delete_confirm"];
  368. }
  369. UIAlertController *actionSheet = [UIAlertController alertControllerWithTitle:messageTitle message:message preferredStyle:UIAlertControllerStyleActionSheet];
  370. [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"leave", nil) style:UIAlertActionStyleDestructive handler:^(UIAlertAction * action) {
  371. [groupProxy leaveGroup];
  372. }]];
  373. [actionSheet addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"cancel", nil) style:UIAlertActionStyleCancel handler:^(UIAlertAction * action) {
  374. }]];
  375. if (!self.tabBarController) {
  376. actionSheet.popoverPresentationController.sourceRect = cellRect;
  377. actionSheet.popoverPresentationController.sourceView = self.view;
  378. }
  379. [self presentViewController:actionSheet animated:YES completion:nil];
  380. }
  381. }
  382. #pragma mark - notification observer
  383. - (void)refreshDirtyObjects:(NSNotification*)notification {
  384. NSManagedObjectID *objectID = [notification.userInfo objectForKey:kKeyObjectID];
  385. if (objectID && [objectID.entity.managedObjectClassName isEqualToString:@"Conversation"]) {
  386. dispatch_async(dispatch_get_main_queue(), ^{
  387. [self refreshData];
  388. });
  389. }
  390. }
  391. - (void)updateDraftForCell:(NSNotification*)notification {
  392. ConversationCell *cell = [self.tableView cellForRowAtIndexPath:lastSelectedCell];
  393. if (cell)
  394. [cell updateLastMessagePreview];
  395. lastSelectedCell = self.tableView.indexPathForSelectedRow;
  396. }
  397. - (void)showProfilePictureChanged:(NSNotification*)notification {
  398. [self refresh];
  399. }
  400. - (void)reloadTableView:(NSNotification *)notification {
  401. if (viewLoadedInBackground == false) {
  402. [self.tableView reloadData];
  403. }
  404. }
  405. #pragma mark - Table view
  406. - (void)tableView:(UITableView *)tableView willDisplayCell:(UITableViewCell *)cell forRowAtIndexPath:(NSIndexPath *)indexPath {
  407. //overwrite since conversation cells have custom UI
  408. }
  409. - (CGFloat)tableView:(UITableView *)tableView heightForHeaderInSection:(NSInteger)section {
  410. return CGFLOAT_MIN;
  411. }
  412. - (NSInteger)numberOfSectionsInTableView:(UITableView *)tableView
  413. {
  414. return [[self.fetchedResultsController sections] count];
  415. }
  416. - (NSInteger)tableView:(UITableView *)tableView numberOfRowsInSection:(NSInteger)section
  417. {
  418. id <NSFetchedResultsSectionInfo> sectionInfo = [self.fetchedResultsController sections][section];
  419. return [sectionInfo numberOfObjects];
  420. }
  421. - (UITableViewCell *)tableView:(UITableView *)tableView cellForRowAtIndexPath:(NSIndexPath *)indexPath
  422. {
  423. static NSString *CellIdentifier = @"ConversationCell";
  424. ConversationCell *cell = [tableView dequeueReusableCellWithIdentifier:CellIdentifier];
  425. cell.conversation = [self.fetchedResultsController objectAtIndexPath:indexPath];
  426. cell.delegate = self;
  427. cell.conversationCellDelegate = self;
  428. return cell;
  429. }
  430. - (BOOL)tableView:(UITableView *)tableView canEditRowAtIndexPath:(NSIndexPath *)indexPath {
  431. // MGSwipeTableCell already handles "swipe left" deletion of single conversation cells,
  432. // so we need to avoid triggering edit mode by swiping two cells left in succession.
  433. return isEditing;
  434. }
  435. - (void)tableView:(UITableView *)tableView commitEditingStyle:(UITableViewCellEditingStyle)editingStyle forRowAtIndexPath:(NSIndexPath *)indexPath {
  436. if (editingStyle == UITableViewCellEditingStyleDelete) {
  437. Conversation *conversation = [self.fetchedResultsController objectAtIndexPath:indexPath];
  438. CGRect cellRect = [tableView rectForRowAtIndexPath:indexPath];
  439. [self showAlertToDeleteConversation:conversation cellRect:cellRect];
  440. }
  441. }
  442. - (BOOL)tableView:(UITableView *)tableView canMoveRowAtIndexPath:(NSIndexPath *)indexPath {
  443. return NO;
  444. }
  445. - (void)tableView:(UITableView *)tableView didSelectRowAtIndexPath:(NSIndexPath *)indexPath {
  446. if (!lastSelectedCell)
  447. lastSelectedCell = indexPath;
  448. Conversation *conversation = [self.fetchedResultsController objectAtIndexPath:indexPath];
  449. NSDictionary *info = [NSDictionary dictionaryWithObjectsAndKeys:
  450. conversation, kKeyConversation,
  451. nil];
  452. [[NSNotificationCenter defaultCenter] postNotificationName:kNotificationShowConversation object:nil
  453. userInfo:info];
  454. }
  455. - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForHeaderInSection:(NSInteger)section {
  456. return 0.0;
  457. }
  458. - (CGFloat)tableView:(UITableView *)tableView estimatedHeightForFooterInSection:(NSInteger)section {
  459. return 0.0;
  460. }
  461. - (UIContextMenuConfiguration *)tableView:(UITableView *)tableView contextMenuConfigurationForRowAtIndexPath:(NSIndexPath *)indexPath point:(CGPoint)point API_AVAILABLE(ios(13.0)){
  462. UITableViewCell *contextCell = [tableView cellForRowAtIndexPath:indexPath];
  463. if ([contextCell isKindOfClass:[ConversationCell class]]) {
  464. ConversationCell *cell = (ConversationCell *)contextCell;
  465. ChatViewController *chatVc = [ChatViewControllerCache newControllerForConversation:cell.conversation forceTouch:YES];
  466. UIContextMenuConfiguration *conf = [UIContextMenuConfiguration configurationWithIdentifier:indexPath previewProvider:^UIViewController * _Nullable{
  467. return chatVc;
  468. } actionProvider:^UIMenu * _Nullable(NSArray<UIMenuElement *> * _Nonnull suggestedActions) {
  469. NSMutableArray *actionArray = [NSMutableArray new];
  470. if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypeCamera]) {
  471. NSString *actionTitle = [BundleUtil localizedStringForKey:@"take_photo_or_video"];
  472. UIImage *actionImage = [[BundleUtil imageNamed:@"ActionCamera"] imageWithTintColor:[Colors fontNormal]];
  473. UIAction *action = [UIAction actionWithTitle:actionTitle image:actionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  474. ChatViewController *chatViewController = [ChatViewControllerCache controllerForConversation:chatVc.conversation];
  475. [chatViewController showContentAfterForceTouch];
  476. [self displayChat:chatViewController animated:YES];
  477. SendMediaAction *sendMediaAction = [SendMediaAction actionForChatViewController:chatViewController];
  478. sendMediaAction.mediaPickerType = MediaPickerTakePhoto;
  479. [chatViewController setCurrentAction:sendMediaAction];
  480. [sendMediaAction executeAction];
  481. }];
  482. [actionArray addObject:action];
  483. }
  484. if ([UIImagePickerController isSourceTypeAvailable:UIImagePickerControllerSourceTypePhotoLibrary]) {
  485. NSString *actionTitle = [BundleUtil localizedStringForKey:@"choose_existing"];
  486. UIImage *actionImage = [[BundleUtil imageNamed:@"ActionPhoto"] imageWithTintColor:[Colors fontNormal]];
  487. UIAction *action = [UIAction actionWithTitle:actionTitle image:actionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  488. ChatViewController *chatViewController = [ChatViewControllerCache controllerForConversation:chatVc.conversation];
  489. [chatViewController showContentAfterForceTouch];
  490. [self displayChat:chatViewController animated:YES];
  491. SendMediaAction *sendMediaAction = [SendMediaAction actionForChatViewController:chatViewController];
  492. sendMediaAction.mediaPickerType = MediaPickerChooseExisting;
  493. [chatViewController setCurrentAction:sendMediaAction];
  494. [sendMediaAction executeAction];
  495. }];
  496. [actionArray addObject:action];
  497. }
  498. if ([CLLocationManager locationServicesEnabled]) {
  499. NSString *actionTitle = [BundleUtil localizedStringForKey:@"share_location"];
  500. UIImage *actionImage = [[BundleUtil imageNamed:@"ActionLocation"] imageWithTintColor:[Colors fontNormal]];
  501. UIAction *action = [UIAction actionWithTitle:actionTitle image:actionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  502. ChatViewController *chatViewController = [ChatViewControllerCache controllerForConversation:chatVc.conversation];
  503. [chatViewController showContentAfterForceTouch];
  504. [self displayChat:chatViewController animated:YES];
  505. SendLocationAction *sendLocationAction = [SendLocationAction actionForChatViewController:chatViewController];
  506. [chatViewController setCurrentAction:sendLocationAction];
  507. [sendLocationAction executeAction];
  508. }];
  509. [actionArray addObject:action];
  510. }
  511. if ([PlayRecordAudioViewController canRecordAudio]) {
  512. NSString *actionTitle = [BundleUtil localizedStringForKey:@"record_audio"];
  513. UIImage *actionImage = [[BundleUtil imageNamed:@"ActionMicrophone"] imageWithTintColor:[Colors fontNormal]];
  514. UIAction *action = [UIAction actionWithTitle:actionTitle image:actionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  515. ChatViewController *chatViewController = [ChatViewControllerCache controllerForConversation:chatVc.conversation];
  516. [chatViewController showContentAfterForceTouch];
  517. [self displayChat:chatViewController animated:YES];
  518. dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(1 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
  519. [chatViewController startRecordingAudio];
  520. });
  521. }];
  522. [actionArray addObject:action];
  523. }
  524. NSString *ballotActionTitle = NSLocalizedStringFromTable(@"ballot_create", @"Ballot", nil);
  525. UIImage *ballotActionImage = [[BundleUtil imageNamed:@"ActionBallot"] imageWithTintColor:[Colors fontNormal]];
  526. UIAction *ballotAction = [UIAction actionWithTitle:ballotActionTitle image:ballotActionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  527. ChatViewController *chatViewController = [ChatViewControllerCache controllerForConversation:chatVc.conversation];
  528. [chatViewController showContentAfterForceTouch];
  529. [self displayChat:chatViewController animated:YES];
  530. [chatViewController createBallot];
  531. }];
  532. [actionArray addObject:ballotAction];
  533. NSString *shareActionTitle = [BundleUtil localizedStringForKey:@"share_file"];
  534. UIImage *shareActionImage = [[BundleUtil imageNamed:@"ActionFile"] imageWithTintColor:[Colors fontNormal]];
  535. UIAction *shareAction = [UIAction actionWithTitle:shareActionTitle image:shareActionImage identifier:nil handler:^(__kindof UIAction * _Nonnull action) {
  536. ChatViewController *chatViewController = [ChatViewControllerCache controllerForConversation:chatVc.conversation];
  537. [chatViewController showContentAfterForceTouch];
  538. [self displayChat:chatViewController animated:YES];
  539. [chatViewController sendFile];
  540. }];
  541. [actionArray addObject:shareAction];
  542. return [UIMenu menuWithTitle:chatVc.conversation.displayName image:nil identifier:nil options:UIMenuOptionsDisplayInline children:actionArray];
  543. }];
  544. return conf;
  545. } else {
  546. return nil;
  547. }
  548. }
  549. - (void)tableView:(UITableView *)tableView willPerformPreviewActionForMenuWithConfiguration:(UIContextMenuConfiguration *)configuration animator:(id<UIContextMenuInteractionCommitAnimating>)animator API_AVAILABLE(ios(13.0)){
  550. ChatViewController *previewVc = (ChatViewController *)animator.previewViewController;
  551. ChatViewController *chatVc = [ChatViewControllerCache controllerForConversation:previewVc.conversation];
  552. [chatVc showContentAfterForceTouch];
  553. [animator addCompletion:^{
  554. [self displayChat:chatVc animated:YES];
  555. }];
  556. }
  557. #pragma mark - Fetched results controller
  558. - (NSFetchedResultsController *)fetchedResultsController {
  559. if (_fetchedResultsController != nil) {
  560. return _fetchedResultsController;
  561. }
  562. EntityManager *entityManager = [[EntityManager alloc] init];
  563. _fetchedResultsController = [entityManager.entityFetcher fetchedResultsControllerForConversations];
  564. _fetchedResultsController.delegate = self;
  565. NSError *error = nil;
  566. if (![_fetchedResultsController performFetch:&error]) {
  567. DDLogError(@"Unresolved error %@, %@", error, [error userInfo]);
  568. [ErrorHandler abortWithError: error];
  569. }
  570. return _fetchedResultsController;
  571. }
  572. - (void)controllerWillChangeContent:(NSFetchedResultsController *)controller {
  573. [self.tableView beginUpdates];
  574. }
  575. - (void)controller:(NSFetchedResultsController *)controller didChangeSection:(id <NSFetchedResultsSectionInfo>)sectionInfo atIndex:(NSUInteger)sectionIndex forChangeType:(NSFetchedResultsChangeType)type {
  576. if (viewLoadedInBackground == false) {
  577. switch(type) {
  578. case NSFetchedResultsChangeInsert:
  579. [self.tableView insertSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
  580. break;
  581. case NSFetchedResultsChangeDelete:
  582. [self.tableView deleteSections:[NSIndexSet indexSetWithIndex:sectionIndex] withRowAnimation:UITableViewRowAnimationAutomatic];
  583. break;
  584. default:
  585. break;
  586. }
  587. }
  588. }
  589. - (void)controller:(NSFetchedResultsController *)controller didChangeObject:(id)anObject atIndexPath:(NSIndexPath *)indexPath forChangeType:(NSFetchedResultsChangeType)type newIndexPath:(NSIndexPath *)newIndexPath {
  590. if (viewLoadedInBackground == false) {
  591. UITableView *tableView = self.tableView;
  592. switch(type) {
  593. case NSFetchedResultsChangeInsert:
  594. [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
  595. break;
  596. case NSFetchedResultsChangeDelete: {
  597. Conversation *conversation = anObject;
  598. [ChatViewControllerCache clearConversation:conversation];
  599. [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
  600. break;
  601. }
  602. case NSFetchedResultsChangeUpdate: {
  603. ConversationCell *cell = (ConversationCell*)[tableView cellForRowAtIndexPath:indexPath];
  604. Conversation *conversation = anObject;
  605. if ([anObject changedValues].count != 0) {
  606. cell.conversation = conversation;
  607. }
  608. [cell refreshButtons:YES];
  609. break;
  610. }
  611. case NSFetchedResultsChangeMove:
  612. if ([indexPath isEqual:newIndexPath] == NO) {
  613. [tableView deleteRowsAtIndexPaths:@[indexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
  614. [tableView insertRowsAtIndexPaths:@[newIndexPath] withRowAnimation:UITableViewRowAnimationAutomatic];
  615. }
  616. break;
  617. default:
  618. break;
  619. }
  620. }
  621. }
  622. - (void)controllerDidChangeContent:(NSFetchedResultsController *)controller {
  623. [self.tableView endUpdates];
  624. }
  625. #pragma mark - UIViewControllerPreviewingDelegate
  626. - (void)previewingContext:(id<UIViewControllerPreviewing>)previewingContext commitViewController:(UIViewController *)viewControllerToCommit {
  627. if ([viewControllerToCommit isKindOfClass:[ChatViewController class]]) {
  628. ChatViewController *previewVc = (ChatViewController *)viewControllerToCommit;
  629. ChatViewController *chatVc = [ChatViewControllerCache controllerForConversation:previewVc.conversation];
  630. [chatVc showContentAfterForceTouch];
  631. [self displayChat:chatVc animated:YES];
  632. }
  633. }
  634. - (UIViewController *)previewingContext:(id<UIViewControllerPreviewing>)previewingContext viewControllerForLocation:(CGPoint)location {
  635. UIView *view = [self.view hitTest:location withEvent:nil];
  636. if ([view.superview isKindOfClass:[ConversationCell class]]) {
  637. ConversationCell *cell = (ConversationCell *)view.superview;
  638. ChatViewController *chatVc = [ChatViewControllerCache newControllerForConversation:cell.conversation forceTouch:YES];
  639. chatVc.delegate = self;
  640. return chatVc;
  641. }
  642. return nil;
  643. }
  644. #pragma mark - GroupDetailsViewControllerDelegate
  645. - (void)presentChatViewController:(ChatViewController *)chatViewController onCompletion:(ChatViewControllerCompletionBlock)onCompletion {
  646. _prevNavigationControllerDelegate = self.navigationController.delegate;
  647. self.navigationController.delegate = self;
  648. [chatViewController showContentAfterForceTouch];
  649. _chatViewCompletionBlock = onCompletion;
  650. [self displayChat:chatViewController animated:NO];
  651. }
  652. - (void)cancelSwipeGestureFromConversations {
  653. for (MGSwipeTableCell *cell in self.tableView.visibleCells) {
  654. if (cell.swipeState != MGSwipeStateNone) {
  655. [cell hideSwipeAnimated:true];
  656. }
  657. }
  658. }
  659. - (void)pushSettingChanged:(Conversation *)conversation {
  660. NSIndexPath *indexPath = [self.fetchedResultsController indexPathForObject:conversation];
  661. if (indexPath != nil) {
  662. ConversationCell *cell = (ConversationCell*)[self.tableView cellForRowAtIndexPath:indexPath];
  663. cell.conversation = conversation;
  664. [cell refreshButtons:YES];
  665. }
  666. }
  667. #pragma mark - UINavigationControllerDelegate
  668. - (void)navigationController:(UINavigationController *)navigationController didShowViewController:(UIViewController *)viewController animated:(BOOL)animated {
  669. if (_chatViewCompletionBlock) {
  670. if ([viewController isKindOfClass:[ChatViewController class]]) {
  671. [((ChatViewController *)viewController) showContentAfterForceTouch];
  672. _chatViewCompletionBlock((ChatViewController *)viewController);
  673. }
  674. _chatViewCompletionBlock = nil;
  675. }
  676. self.navigationController.delegate = _prevNavigationControllerDelegate;
  677. }
  678. #pragma mark Swipe Delegate
  679. -(BOOL)swipeTableCell:(MGSwipeTableCell*)cell canSwipe:(MGSwipeDirection)direction fromPoint:(CGPoint) point {
  680. if (UIAccessibilityIsVoiceOverRunning()) {
  681. return NO;
  682. }
  683. return YES;
  684. }
  685. -(NSArray*) swipeTableCell:(MGSwipeTableCell*) cell swipeButtonsForDirection:(MGSwipeDirection)direction swipeSettings:(MGSwipeSettings*) swipeSettings expansionSettings:(MGSwipeExpansionSettings*) expansionSettings {
  686. swipeSettings.transition = MGSwipeTransitionBorder;
  687. expansionSettings.buttonIndex = 0;
  688. NSIndexPath *indexPath = [self.tableView indexPathForCell:cell];
  689. if (indexPath != nil) {
  690. Conversation *conversation = [self.fetchedResultsController objectAtIndexPath:indexPath];
  691. if (direction == MGSwipeDirectionLeftToRight) {
  692. return [self swipeLeftToRightForTableCell:cell conversation:conversation swipeSettings:swipeSettings expansionSettings:expansionSettings];
  693. }
  694. else {
  695. return [self swipeRightToLeftForTableCell:cell conversation:conversation swipeSettings:swipeSettings];
  696. }
  697. }
  698. return nil;
  699. }
  700. - (NSArray *)swipeLeftToRightForTableCell:(MGSwipeTableCell *)cell conversation:(Conversation *)conversation swipeSettings:(MGSwipeSettings *)swipeSettings expansionSettings:(MGSwipeExpansionSettings *)expansionSettings {
  701. expansionSettings.fillOnTrigger = NO;
  702. expansionSettings.threshold = 1.5;
  703. swipeSettings.enableSwipeBounces = YES;
  704. __block NSString *buttonTitle;
  705. __block UIImage *buttonIcon;
  706. NSMutableArray *buttonArray = [NSMutableArray new];
  707. if (conversation.unreadMessageCount.intValue > 0) {
  708. NSString *readTitle = NSLocalizedString(@"read", @"");
  709. buttonTitle = NSLocalizedString(@"read", @"");
  710. buttonIcon = [UIImage imageNamed:@"MessageStatus_read" inColor:[UIColor whiteColor]];
  711. __block MGSwipeButton *read = [MGSwipeButton buttonWithTitle:buttonTitle icon:buttonIcon backgroundColor:[Colors workBlue] padding:10 callback:^BOOL(MGSwipeTableCell *sender) {
  712. [ConversationUtils unreadConversation:conversation];
  713. [cell refreshButtons:YES];
  714. [cell refreshContentView];
  715. return YES;
  716. }];
  717. CGRect readFrame = [readTitle boundingRectWithSize:CGSizeMake(self.view.frame.size.width/2, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{ NSFontAttributeName:read.titleLabel.font } context:nil];
  718. [read setButtonWidth:readFrame.size.width + 30.0];
  719. [read centerIconOverText];
  720. [buttonArray addObject:read];
  721. }
  722. NSString *markTitle = [conversation.marked isEqualToNumber:[NSNumber numberWithBool:YES]] ? NSLocalizedString(@"unpin", nil) : NSLocalizedString(@"pin", nil);
  723. UIImage *markImage = [conversation.marked isEqualToNumber:[NSNumber numberWithBool:YES]] ? [UIImage imageNamed:@"Unpin" inColor:[UIColor whiteColor]] : [UIImage imageNamed:@"Pin" inColor:[UIColor whiteColor]];
  724. __block MGSwipeButton *mark = [MGSwipeButton buttonWithTitle:markTitle icon:markImage backgroundColor:[Colors markTag] padding:10 callback:^BOOL(MGSwipeTableCell *sender) {
  725. if ([conversation.marked isEqualToNumber:[NSNumber numberWithBool:YES]]) {
  726. [ConversationUtils unmarkConversation:conversation];
  727. } else {
  728. [ConversationUtils markConversation:conversation];
  729. }
  730. [cell refreshButtons:YES];
  731. [cell refreshContentView];
  732. return YES;
  733. }];
  734. CGRect markFrame = [markTitle boundingRectWithSize:CGSizeMake(self.view.frame.size.width/2, CGFLOAT_MAX) options:NSStringDrawingUsesLineFragmentOrigin attributes:@{ NSFontAttributeName:mark.titleLabel.font } context:nil];
  735. [mark setButtonWidth:markFrame.size.width + 30.0];
  736. [mark centerIconOverText];
  737. [buttonArray addObject:mark];
  738. return buttonArray;
  739. }
  740. - (NSArray *)swipeRightToLeftForTableCell:(MGSwipeTableCell *)cell conversation:(Conversation *)conversation swipeSettings:(MGSwipeSettings *)swipeSettings {
  741. swipeSettings.enableSwipeBounces = NO;
  742. MGSwipeButton *delete = [MGSwipeButton buttonWithTitle:NSLocalizedString(@"delete", @"") backgroundColor:[Colors red] padding:20 callback:^BOOL(MGSwipeTableCell *sender) {
  743. CGRect cellRect = [self.tableView convertRect:cell.frame toView:self.view];
  744. [self showAlertToDeleteConversation:conversation cellRect:cellRect];
  745. return NO;
  746. }];
  747. [delete centerIconOverText];
  748. MGSwipeButton *leaveGroup;
  749. if (conversation.isGroup) {
  750. GroupProxy *group = [GroupProxy groupProxyForConversation:conversation];
  751. if ([group isSelfMember]) {
  752. NSString *leaveGroupString = [BundleUtil localizedStringForKey:@"leave_group"];
  753. NSString *strSpace = @" ";
  754. NSRange range = [leaveGroupString rangeOfString:strSpace];
  755. if (NSNotFound != range.location) {
  756. leaveGroupString = [leaveGroupString stringByReplacingCharactersInRange:range withString:@"\n"];
  757. }
  758. leaveGroup = [MGSwipeButton buttonWithTitle:leaveGroupString backgroundColor:[Colors orange] padding:20 callback:^BOOL(MGSwipeTableCell *sender) {
  759. CGRect cellRect = [self.tableView convertRect:cell.frame toView:self.view];
  760. [self showAlertToLeaveGroup:conversation cellRect:cellRect];
  761. return NO;
  762. }];
  763. [leaveGroup centerIconOverText];
  764. }
  765. }
  766. if (leaveGroup != nil) {
  767. return @[leaveGroup, delete];
  768. } else {
  769. return @[delete];
  770. }
  771. }
  772. #pragma mark - ConversationCellDelegate
  773. - (void)voiceOverDeleteConversation:(ConversationCell *)cell {
  774. CGRect cellRect = [self.tableView convertRect:cell.frame toView:self.view];
  775. [self showAlertToDeleteConversation:cell.conversation cellRect:cellRect];
  776. }
  777. - (void)voiceOverLeaveGroup:(ConversationCell *)cell {
  778. CGRect cellRect = [self.tableView convertRect:cell.frame toView:self.view];
  779. [self showAlertToLeaveGroup:cell.conversation cellRect:cellRect];
  780. }
  781. #pragma mark - UIScrollViewDelegate
  782. - (void)scrollViewDidScroll:(UIScrollView *)scrollView {
  783. if (![LicenseStore requiresLicenseKey]) {
  784. if ([[self.navigationController navigationBar] frame].size.height < 60.0 && self.navigationItem.titleView != nil) {
  785. self.navigationItem.titleView = nil;
  786. self.navigationItem.title = NSLocalizedString(@"messages", nil);
  787. }
  788. else if ([[self.navigationController navigationBar] frame].size.height >= 59.5 && self.navigationItem.titleView == nil) {
  789. [BrandingUtils updateTitleLogoOfNavigationItem:self.navigationItem navigationController:self.navigationController];
  790. }
  791. }
  792. }
  793. @end