MainTabBarController.m 21 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553
  1. // _____ _
  2. // |_ _| |_ _ _ ___ ___ _ __ __ _
  3. // | | | ' \| '_/ -_) -_) ' \/ _` |_
  4. // |_| |_||_|_| \___\___|_|_|_\__,_(_)
  5. //
  6. // Threema iOS Client
  7. // Copyright (c) 2015-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 "MainTabBarController.h"
  21. #import "AppDelegate.h"
  22. #import "UIDefines.h"
  23. #import "StatusNavigationBar.h"
  24. #import "ContactsViewController.h"
  25. #import "ContactDetailsViewController.h"
  26. #import "GroupDetailsViewController.h"
  27. #import "ConversationsViewController.h"
  28. #import "ChatViewController.h"
  29. #import "ModalNavigationController.h"
  30. #import "MyIdentityViewController.h"
  31. #import "PortraitNavigationController.h"
  32. #import "EntityManager.h"
  33. #import "ChatViewControllerCache.h"
  34. #import "MWPhotoBrowser.h"
  35. #import "AppGroup.h"
  36. #import "GroupProxy.h"
  37. #import "AvatarMaker.h"
  38. #import "JKLLockScreenViewController.h"
  39. #ifdef DEBUG
  40. static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
  41. #else
  42. static const DDLogLevel ddLogLevel = DDLogLevelWarning;
  43. #endif
  44. @interface MainTabBarController () <ModalNavigationControllerDelegate>
  45. @property ChatViewController *chatViewController;
  46. @property ContactDetailsViewController *contactDetailViewController;
  47. @property GroupDetailsViewController *groupDetailViewController;
  48. @property ContactsViewController *contactsViewController;
  49. @property ConversationsViewController *conversationsViewController;
  50. @property PortraitNavigationController *contactsNavigationController;
  51. @property PortraitNavigationController *conversationsNavigationController;
  52. @property MyIdentityViewController *myIdentityViewController;
  53. @property SettingsViewController *settingsViewController;
  54. @property NSUInteger currentIndex;
  55. @property BOOL isFirstAppearance;
  56. @end
  57. @implementation MainTabBarController
  58. - (void)viewDidLoad {
  59. [super viewDidLoad];
  60. _isFirstAppearance = YES;
  61. _currentIndex = -1;
  62. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedContact:) name:kNotificationShowContact object:nil];
  63. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedGroup:) name:kNotificationShowGroup object:nil];
  64. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedConversation:) name:kNotificationShowConversation object:nil];
  65. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deletedConversation:) name:kNotificationDeletedConversation object:nil];
  66. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deletedContact:) name:kNotificationDeletedContact object:nil];
  67. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showSafeSetup:) name:kSafeSetupUI object:nil];
  68. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wallpaperChanged:) name:kNotificationWallpaperChanged object:nil];
  69. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(chatFontSizeChanged:) name:kNotificationFontSizeChanged object:nil];
  70. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(chatFontSizeChanged:) name:UIContentSizeCategoryDidChangeNotification object:nil];
  71. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(timestampSettingsChanged:) name:kNotificationShowTimestampSettingsChanged object:nil];
  72. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(colorThemeChanged:) name:kNotificationColorThemeChanged object:nil];
  73. [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
  74. [Colors updateTabBar:self.tabBar];
  75. }
  76. - (void)viewWillAppear:(BOOL)animated {
  77. [super viewWillAppear:animated];
  78. if (_isFirstAppearance) {
  79. self.selectedIndex = kDefaultInitialTabIndex;
  80. _isFirstAppearance = NO;
  81. }
  82. }
  83. - (void)dealloc {
  84. [[NSNotificationCenter defaultCenter] removeObserver:self];
  85. }
  86. - (BOOL)shouldAutorotate {
  87. if ([self.presentedViewController isKindOfClass:[PortraitNavigationController class]]) {
  88. return NO;
  89. }
  90. return YES;
  91. }
  92. -(UIInterfaceOrientationMask)supportedInterfaceOrientations {
  93. if ([self.presentedViewController isKindOfClass:[PortraitNavigationController class]]) {
  94. return NO;
  95. }
  96. if (SYSTEM_IS_IPAD) {
  97. return UIInterfaceOrientationMaskAll;
  98. }
  99. return UIInterfaceOrientationMaskAllButUpsideDown;
  100. }
  101. - (void)setSelectedViewController:(UIViewController *)selectedViewController {
  102. NSUInteger index = [self.viewControllers indexOfObject:selectedViewController];
  103. [self setSelectedIndex:index];
  104. }
  105. - (void)setSelectedIndex:(NSUInteger)selectedIndex {
  106. // fallback for when setting share extension inactive fails for some reason (crash etc.)
  107. [AppGroup setActive:NO forType:AppGroupTypeShareExtension];
  108. if (SYSTEM_IS_IPAD == NO) {
  109. [super setSelectedIndex:selectedIndex];
  110. return;
  111. }
  112. NSUInteger previousIndex = _currentIndex;
  113. _currentIndex = selectedIndex;
  114. switch (selectedIndex) {
  115. case kChatTabBarIndex:
  116. if (selectedIndex != previousIndex) {
  117. [self switchToChats];
  118. }
  119. break;
  120. case kContactsTabBarIndex:
  121. if (selectedIndex != previousIndex) {
  122. [self switchToContacts];
  123. }
  124. break;
  125. case kMyIdentityTabBarIndex:
  126. [self showMyIdentity];
  127. break;
  128. case kSettingsTabBarIndex:
  129. [self showSettings];
  130. break;
  131. default:
  132. // ignore
  133. break;
  134. }
  135. _currentIndex = selectedIndex;
  136. }
  137. - (void)switchToChats {
  138. if (_conversationsViewController == nil) {
  139. _conversationsViewController = (ConversationsViewController *)[self loadViewControllerNamed:@"conversationsViewController"];
  140. _conversationsNavigationController = [[PortraitNavigationController alloc] initWithNavigationBarClass:[StatusNavigationBar class] toolbarClass:nil];
  141. [_conversationsNavigationController setViewControllers:@[_conversationsViewController]];
  142. }
  143. self.splitViewController.viewControllers = @[
  144. _conversationsNavigationController,
  145. self
  146. ];
  147. [self switchConversation];
  148. }
  149. - (void)switchToContacts {
  150. if (_contactsViewController == nil) {
  151. _contactsViewController = (ContactsViewController *)[self loadViewControllerNamed:@"contactsViewController"];
  152. _contactsNavigationController = [[PortraitNavigationController alloc] initWithNavigationBarClass:[StatusNavigationBar class] toolbarClass:nil];
  153. [_contactsNavigationController setViewControllers:@[_contactsViewController]];
  154. }
  155. self.splitViewController.viewControllers = @[
  156. _contactsNavigationController,
  157. self
  158. ];
  159. [self switchContact];
  160. }
  161. - (void)switchConversation {
  162. if (_chatViewController == nil) {
  163. Conversation *conversation = [_conversationsViewController getFirstConversation];
  164. if (conversation) {
  165. _chatViewController = [ChatViewControllerCache controllerForConversation:conversation];
  166. }
  167. }
  168. if (_chatViewController) {
  169. UINavigationController *navigationController = self.viewControllers[kChatTabBarIndex];
  170. _chatViewController.delegate = _conversationsViewController;
  171. [navigationController setViewControllers:@[_chatViewController]];
  172. [_conversationsViewController setSelectionForConversation:_chatViewController.conversation];
  173. } else {
  174. [self clearNavigationControllerAt:kChatTabBarIndex];
  175. }
  176. [super setSelectedIndex:kChatTabBarIndex];
  177. }
  178. - (void)switchContact {
  179. if (_contactDetailViewController) {
  180. UINavigationController *navigationController = self.viewControllers[kContactsTabBarIndex];
  181. [navigationController setViewControllers:@[_contactDetailViewController]];
  182. if ([_contactsViewController isWorkActive]) {
  183. [_contactsViewController setSelectionForWorkContact:_contactDetailViewController.contact];
  184. } else {
  185. [_contactsViewController setSelectionForContact:_contactDetailViewController.contact];
  186. }
  187. } else if (_groupDetailViewController) {
  188. UINavigationController *navigationController = self.viewControllers[kContactsTabBarIndex];
  189. [navigationController setViewControllers:@[_groupDetailViewController]];
  190. [_contactsViewController setSelectionForGroup:_groupDetailViewController.group];
  191. } else {
  192. BOOL gotDetailsView = [_contactsViewController showFirstEntryForCurrentMode];
  193. if (gotDetailsView == NO) {
  194. [self clearNavigationControllerAt:kContactsTabBarIndex];
  195. }
  196. }
  197. [super setSelectedIndex:kContactsTabBarIndex];
  198. }
  199. - (void)showMyIdentity {
  200. if (_myIdentityViewController == nil) {
  201. _myIdentityViewController = (MyIdentityViewController *)[self loadMyIdentityControllerNamed:@"myIdentityViewController"];
  202. }
  203. [self showModal:_myIdentityViewController];
  204. }
  205. - (void)showSettings {
  206. if (_settingsViewController == nil) {
  207. _settingsViewController = (SettingsViewController *)[self loadSettingsControllerNamed:@"settingsViewController"];
  208. }
  209. [self showModal:_settingsViewController];
  210. }
  211. - (void)clearNavigationControllerAt:(NSUInteger)index {
  212. UINavigationController *navigationController = self.viewControllers[index];
  213. UIViewController *dummyViewController = [[UIViewController alloc] init];
  214. [navigationController setViewControllers:@[dummyViewController]];
  215. }
  216. - (UIViewController *)loadViewControllerNamed:(NSString *)viewControllerName {
  217. UIStoryboard *storyboard = [AppDelegate getMainStoryboard];
  218. return [storyboard instantiateViewControllerWithIdentifier:viewControllerName];
  219. }
  220. - (UIViewController *)loadSettingsControllerNamed:(NSString *)viewControllerName {
  221. UIStoryboard *storyboard = [AppDelegate getSettingsStoryboard];
  222. return [storyboard instantiateViewControllerWithIdentifier:viewControllerName];
  223. }
  224. - (UIViewController *)loadMyIdentityControllerNamed:(NSString *)viewControllerName {
  225. UIStoryboard *storyboard = [AppDelegate getMyIdentityStoryboard];
  226. return [storyboard instantiateViewControllerWithIdentifier:viewControllerName];
  227. }
  228. - (void)showModal:(UIViewController *)viewController {
  229. ModalNavigationController *navigationController = [[ModalNavigationController alloc] init];
  230. navigationController.showDoneButton = YES;
  231. navigationController.dismissOnTapOutside = YES;
  232. navigationController.modalDelegate = self;
  233. [navigationController pushViewController:viewController animated:NO];
  234. [self presentViewController:navigationController animated:YES completion:nil];
  235. }
  236. - (void)applicationDidReceiveMemoryWarning:(NSNotification*)notification {
  237. _chatViewController = nil;
  238. _conversationsViewController = nil;
  239. _conversationsNavigationController = nil;
  240. }
  241. #pragma mark - notifications
  242. - (void)selectedGroup:(NSNotification*)notification {
  243. GroupProxy *group = [notification.userInfo objectForKey:kKeyGroup];
  244. _contactDetailViewController = nil;
  245. if (SYSTEM_IS_IPAD) {
  246. _groupDetailViewController = (GroupDetailsViewController *)[self loadViewControllerNamed:@"groupDetailsViewController"];
  247. _groupDetailViewController.group = group;
  248. if (self.selectedIndex == kContactsTabBarIndex) {
  249. [self switchContact];
  250. } else {
  251. [self setSelectedIndex:kContactsTabBarIndex];
  252. }
  253. } else {
  254. if (_contactsViewController == nil) {
  255. _contactsNavigationController = self.viewControllers[kContactsTabBarIndex];
  256. _contactsViewController = [[_contactsNavigationController viewControllers] objectAtIndex:0];
  257. }
  258. [self setSelectedViewController:_contactsNavigationController];
  259. [_contactsNavigationController popToViewController:_contactsViewController animated:NO];
  260. [_contactsViewController showDetailsForGroup:group];
  261. }
  262. }
  263. - (void)selectedContact:(NSNotification*)notification {
  264. Contact *contact = [notification.userInfo objectForKey:kKeyContact];
  265. _groupDetailViewController = nil;
  266. if (SYSTEM_IS_IPAD) {
  267. _contactDetailViewController = (ContactDetailsViewController *)[self loadViewControllerNamed:@"contactDetailsViewController"];
  268. _contactDetailViewController.contact = contact;
  269. if (self.selectedIndex == kContactsTabBarIndex) {
  270. [self switchContact];
  271. } else {
  272. [self setSelectedIndex:kContactsTabBarIndex];
  273. }
  274. } else {
  275. if (_contactsViewController == nil) {
  276. _contactsNavigationController = self.viewControllers[kContactsTabBarIndex];
  277. _contactsViewController = [[_contactsNavigationController viewControllers] objectAtIndex:0];
  278. }
  279. [self setSelectedViewController:_contactsNavigationController];
  280. [_contactsNavigationController popToViewController:_contactsViewController animated:NO];
  281. [_contactsViewController showDetailsForContact:contact];
  282. }
  283. }
  284. - (void)selectedConversation:(NSNotification*)notification {
  285. [self hideModal];
  286. _chatViewController = [ChatViewControllerCache controllerForNotificationInfo:notification.userInfo];
  287. if (SYSTEM_IS_IPAD) {
  288. if (self.selectedIndex == kChatTabBarIndex) {
  289. [self switchConversation];
  290. } else {
  291. [self setSelectedIndex:kChatTabBarIndex];
  292. }
  293. } else {
  294. if (_conversationsViewController == nil) {
  295. _conversationsNavigationController = self.viewControllers[kChatTabBarIndex];
  296. if ([_conversationsNavigationController.topViewController isKindOfClass:[ConversationsViewController class]]) {
  297. _conversationsViewController = (ConversationsViewController *)_conversationsNavigationController.topViewController;
  298. }
  299. }
  300. [self setSelectedViewController:_conversationsNavigationController];
  301. [_conversationsViewController displayChat:_chatViewController animated:YES];
  302. }
  303. }
  304. - (void)deletedConversation:(NSNotification*)notification {
  305. if (SYSTEM_IS_IPAD) {
  306. Conversation *deletedConversation = [ChatViewControllerCache getConversationForNotificationInfo:notification.userInfo];
  307. if (_chatViewController.conversation == deletedConversation) {
  308. _chatViewController = nil;
  309. if (self.selectedIndex == kChatTabBarIndex) {
  310. [self switchConversation];
  311. }
  312. }
  313. if (_groupDetailViewController.group.conversation == deletedConversation) {
  314. _groupDetailViewController = nil;
  315. if (self.selectedIndex == kContactsTabBarIndex) {
  316. [self switchContact];
  317. }
  318. }
  319. }
  320. }
  321. - (void)deletedContact:(NSNotification*)notification {
  322. if (SYSTEM_IS_IPAD) {
  323. Contact *deletedContact = [notification.userInfo objectForKey:kKeyContact];
  324. if (_contactDetailViewController.contact == deletedContact) {
  325. _contactDetailViewController = nil;
  326. if (self.selectedIndex == kContactsTabBarIndex) {
  327. [self switchContact];
  328. }
  329. }
  330. }
  331. }
  332. - (void)showSafeSetup:(NSNotification*)notification {
  333. // switch to My Identity tab and show Threema Safe settings/setup
  334. [self setSelectedIndex:kMyIdentityTabBarIndex];
  335. UINavigationController *myIdentityNavigation = [[self viewControllers] objectAtIndex:kMyIdentityTabBarIndex];
  336. MyIdentityViewController *myIdentity = [[myIdentityNavigation viewControllers] objectAtIndex:0];
  337. if (myIdentity != nil) {
  338. [myIdentity showSafeSetup];
  339. }
  340. }
  341. - (void)hideModal {
  342. if (self.presentedViewController == nil) {
  343. return;
  344. }
  345. if (_currentIndex == kSettingsTabBarIndex || _currentIndex == kMyIdentityTabBarIndex) {
  346. [self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
  347. return;
  348. }
  349. UIViewController *controller = self.presentedViewController;
  350. if ([controller isKindOfClass:[UINavigationController class]]) {
  351. UINavigationController *navigationController = (UINavigationController*)controller;
  352. if ([navigationController.topViewController isKindOfClass:[MWPhotoBrowser class]]) {
  353. [navigationController dismissViewControllerAnimated:YES completion:nil];
  354. }
  355. else if ([navigationController.topViewController isKindOfClass:[PreviewImageViewController class]]) {
  356. [navigationController dismissViewControllerAnimated:YES completion:nil];
  357. }
  358. }
  359. }
  360. - (void)wallpaperChanged:(NSNotification*)notification {
  361. DDLogInfo(@"Wallpaper changed, removing cached chat view controllers");
  362. [self resetChats];
  363. }
  364. - (void)colorThemeChanged:(NSNotification*)notification {
  365. DDLogInfo(@"Color theme changed, removing cached chat view controllers");
  366. [AvatarMaker clearCache];
  367. [Colors updateWindow:[[AppDelegate sharedAppDelegate] window]];
  368. [Colors updateNavigationBar:self.selectedViewController.navigationController.navigationBar];
  369. [Colors updateTabBar:self.tabBar];
  370. [ChatViewControllerCache refresh];
  371. [self setNeedsStatusBarAppearanceUpdate];
  372. if (SYSTEM_IS_IPAD) {
  373. [_settingsViewController refresh];
  374. [Colors updateNavigationBar:_settingsViewController.navigationController.navigationBar];
  375. [_myIdentityViewController refresh];
  376. [Colors updateNavigationBar:_settingsViewController.navigationController.navigationBar];
  377. [_contactDetailViewController refresh];
  378. [Colors updateNavigationBar:_contactDetailViewController.navigationController.navigationBar];
  379. [_contactsViewController refresh];
  380. [Colors updateNavigationBar:_contactsViewController.navigationController.navigationBar];
  381. [_conversationsViewController refresh];
  382. [Colors updateNavigationBar:_conversationsViewController.navigationController.navigationBar];
  383. [Colors updateNavigationBar:_chatViewController.navigationController.navigationBar];
  384. [_chatViewController refresh];
  385. for (UIViewController *vc in _conversationsNavigationController.viewControllers) {
  386. [Colors updateNavigationBar:vc.navigationController.navigationBar];
  387. }
  388. [Colors updateNavigationBar:_conversationsNavigationController.navigationBar];
  389. }
  390. }
  391. - (void)chatFontSizeChanged:(NSNotification*)notification {
  392. DDLogInfo(@"Chat font size changed, removing cached chat view controllers");
  393. [self resetChats];
  394. }
  395. - (void)timestampSettingsChanged:(NSNotification*)notification {
  396. DDLogInfo(@"Timestamp settings changed, removing cached chat view controllers");
  397. [self resetChats];
  398. }
  399. - (void)resetChats {
  400. [ChatViewControllerCache clearCache];
  401. [self resetDisplayedChat];
  402. }
  403. - (void)resetDisplayedChat {
  404. if (SYSTEM_IS_IPAD) {
  405. if (_chatViewController) {
  406. Conversation *conversation = _chatViewController.conversation;
  407. _chatViewController = [ChatViewControllerCache controllerForConversation:conversation];
  408. [self switchConversation];
  409. }
  410. } else {
  411. if (self.selectedIndex == kChatTabBarIndex) {
  412. [_conversationsNavigationController popToRootViewControllerAnimated:true];
  413. }
  414. }
  415. }
  416. #pragma mark - ModalNavigationControllerDelegate
  417. - (void)willDismissModalNavigationController {
  418. //fool the tab bar to switch the selected tab
  419. NSUInteger index = [self.viewControllers indexOfObject:self.selectedViewController];
  420. _currentIndex = -1;
  421. [super setSelectedIndex:kSettingsTabBarIndex];
  422. [self setSelectedIndex:index];
  423. }
  424. - (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
  425. [super traitCollectionDidChange:previousTraitCollection];
  426. if (@available(iOS 13.0, *)) {
  427. if ([[UserSettings sharedUserSettings] useSystemTheme] && [[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
  428. if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
  429. if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
  430. if ([Colors getTheme] != ColorThemeDark && [Colors getTheme] != ColorThemeDarkWork) {
  431. [Colors setTheme:[LicenseStore requiresLicenseKey] ? ColorThemeDarkWork : ColorThemeDark];
  432. }
  433. } else {
  434. if ([Colors getTheme] != ColorThemeLight && [Colors getTheme] != ColorThemeLightWork) {
  435. [Colors setTheme:[LicenseStore requiresLicenseKey] ? ColorThemeLightWork : ColorThemeLight];
  436. }
  437. }
  438. }
  439. }
  440. }
  441. if (previousTraitCollection.preferredContentSizeCategory != self.traitCollection.preferredContentSizeCategory) {
  442. [self resetChats];
  443. }
  444. }
  445. @end