// _____ _
// |_ _| |_ _ _ ___ ___ _ __ __ _
// | | | ' \| '_/ -_) -_) ' \/ _` |_
// |_| |_||_|_| \___\___|_|_|_\__,_(_)
//
// Threema iOS Client
// Copyright (c) 2015-2020 Threema GmbH
//
// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU Affero General Public License, version 3,
// as published by the Free Software Foundation.
//
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
// GNU Affero General Public License for more details.
//
// You should have received a copy of the GNU Affero General Public License
// along with this program. If not, see .
#import "MainTabBarController.h"
#import "AppDelegate.h"
#import "UIDefines.h"
#import "StatusNavigationBar.h"
#import "ContactsViewController.h"
#import "ContactDetailsViewController.h"
#import "GroupDetailsViewController.h"
#import "ConversationsViewController.h"
#import "ChatViewController.h"
#import "ModalNavigationController.h"
#import "MyIdentityViewController.h"
#import "PortraitNavigationController.h"
#import "EntityManager.h"
#import "ChatViewControllerCache.h"
#import "MWPhotoBrowser.h"
#import "AppGroup.h"
#import "GroupProxy.h"
#import "AvatarMaker.h"
#import "JKLLockScreenViewController.h"
#ifdef DEBUG
static const DDLogLevel ddLogLevel = DDLogLevelVerbose;
#else
static const DDLogLevel ddLogLevel = DDLogLevelWarning;
#endif
@interface MainTabBarController ()
@property ChatViewController *chatViewController;
@property ContactDetailsViewController *contactDetailViewController;
@property GroupDetailsViewController *groupDetailViewController;
@property ContactsViewController *contactsViewController;
@property ConversationsViewController *conversationsViewController;
@property PortraitNavigationController *contactsNavigationController;
@property PortraitNavigationController *conversationsNavigationController;
@property MyIdentityViewController *myIdentityViewController;
@property SettingsViewController *settingsViewController;
@property NSUInteger currentIndex;
@property BOOL isFirstAppearance;
@end
@implementation MainTabBarController
- (void)viewDidLoad {
[super viewDidLoad];
_isFirstAppearance = YES;
_currentIndex = -1;
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedContact:) name:kNotificationShowContact object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedGroup:) name:kNotificationShowGroup object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(selectedConversation:) name:kNotificationShowConversation object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deletedConversation:) name:kNotificationDeletedConversation object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(deletedContact:) name:kNotificationDeletedContact object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(showSafeSetup:) name:kSafeSetupUI object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(wallpaperChanged:) name:kNotificationWallpaperChanged object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(chatFontSizeChanged:) name:kNotificationFontSizeChanged object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(chatFontSizeChanged:) name:UIContentSizeCategoryDidChangeNotification object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(timestampSettingsChanged:) name:kNotificationShowTimestampSettingsChanged object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(colorThemeChanged:) name:kNotificationColorThemeChanged object:nil];
[[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationDidReceiveMemoryWarning:) name:UIApplicationDidReceiveMemoryWarningNotification object:nil];
[Colors updateTabBar:self.tabBar];
}
- (void)viewWillAppear:(BOOL)animated {
[super viewWillAppear:animated];
if (_isFirstAppearance) {
self.selectedIndex = kDefaultInitialTabIndex;
_isFirstAppearance = NO;
}
}
- (void)dealloc {
[[NSNotificationCenter defaultCenter] removeObserver:self];
}
- (BOOL)shouldAutorotate {
if ([self.presentedViewController isKindOfClass:[PortraitNavigationController class]]) {
return NO;
}
return YES;
}
-(UIInterfaceOrientationMask)supportedInterfaceOrientations {
if ([self.presentedViewController isKindOfClass:[PortraitNavigationController class]]) {
return NO;
}
if (SYSTEM_IS_IPAD) {
return UIInterfaceOrientationMaskAll;
}
return UIInterfaceOrientationMaskAllButUpsideDown;
}
- (void)setSelectedViewController:(UIViewController *)selectedViewController {
NSUInteger index = [self.viewControllers indexOfObject:selectedViewController];
[self setSelectedIndex:index];
}
- (void)setSelectedIndex:(NSUInteger)selectedIndex {
// fallback for when setting share extension inactive fails for some reason (crash etc.)
[AppGroup setActive:NO forType:AppGroupTypeShareExtension];
if (SYSTEM_IS_IPAD == NO) {
[super setSelectedIndex:selectedIndex];
return;
}
NSUInteger previousIndex = _currentIndex;
_currentIndex = selectedIndex;
switch (selectedIndex) {
case kChatTabBarIndex:
if (selectedIndex != previousIndex) {
[self switchToChats];
}
break;
case kContactsTabBarIndex:
if (selectedIndex != previousIndex) {
[self switchToContacts];
}
break;
case kMyIdentityTabBarIndex:
[self showMyIdentity];
break;
case kSettingsTabBarIndex:
[self showSettings];
break;
default:
// ignore
break;
}
_currentIndex = selectedIndex;
}
- (void)switchToChats {
if (_conversationsViewController == nil) {
_conversationsViewController = (ConversationsViewController *)[self loadViewControllerNamed:@"conversationsViewController"];
_conversationsNavigationController = [[PortraitNavigationController alloc] initWithNavigationBarClass:[StatusNavigationBar class] toolbarClass:nil];
[_conversationsNavigationController setViewControllers:@[_conversationsViewController]];
}
self.splitViewController.viewControllers = @[
_conversationsNavigationController,
self
];
[self switchConversation];
}
- (void)switchToContacts {
if (_contactsViewController == nil) {
_contactsViewController = (ContactsViewController *)[self loadViewControllerNamed:@"contactsViewController"];
_contactsNavigationController = [[PortraitNavigationController alloc] initWithNavigationBarClass:[StatusNavigationBar class] toolbarClass:nil];
[_contactsNavigationController setViewControllers:@[_contactsViewController]];
}
self.splitViewController.viewControllers = @[
_contactsNavigationController,
self
];
[self switchContact];
}
- (void)switchConversation {
if (_chatViewController == nil) {
Conversation *conversation = [_conversationsViewController getFirstConversation];
if (conversation) {
_chatViewController = [ChatViewControllerCache controllerForConversation:conversation];
}
}
if (_chatViewController) {
UINavigationController *navigationController = self.viewControllers[kChatTabBarIndex];
_chatViewController.delegate = _conversationsViewController;
[navigationController setViewControllers:@[_chatViewController]];
[_conversationsViewController setSelectionForConversation:_chatViewController.conversation];
} else {
[self clearNavigationControllerAt:kChatTabBarIndex];
}
[super setSelectedIndex:kChatTabBarIndex];
}
- (void)switchContact {
if (_contactDetailViewController) {
UINavigationController *navigationController = self.viewControllers[kContactsTabBarIndex];
[navigationController setViewControllers:@[_contactDetailViewController]];
if ([_contactsViewController isWorkActive]) {
[_contactsViewController setSelectionForWorkContact:_contactDetailViewController.contact];
} else {
[_contactsViewController setSelectionForContact:_contactDetailViewController.contact];
}
} else if (_groupDetailViewController) {
UINavigationController *navigationController = self.viewControllers[kContactsTabBarIndex];
[navigationController setViewControllers:@[_groupDetailViewController]];
[_contactsViewController setSelectionForGroup:_groupDetailViewController.group];
} else {
BOOL gotDetailsView = [_contactsViewController showFirstEntryForCurrentMode];
if (gotDetailsView == NO) {
[self clearNavigationControllerAt:kContactsTabBarIndex];
}
}
[super setSelectedIndex:kContactsTabBarIndex];
}
- (void)showMyIdentity {
if (_myIdentityViewController == nil) {
_myIdentityViewController = (MyIdentityViewController *)[self loadMyIdentityControllerNamed:@"myIdentityViewController"];
}
[self showModal:_myIdentityViewController];
}
- (void)showSettings {
if (_settingsViewController == nil) {
_settingsViewController = (SettingsViewController *)[self loadSettingsControllerNamed:@"settingsViewController"];
}
[self showModal:_settingsViewController];
}
- (void)clearNavigationControllerAt:(NSUInteger)index {
UINavigationController *navigationController = self.viewControllers[index];
UIViewController *dummyViewController = [[UIViewController alloc] init];
[navigationController setViewControllers:@[dummyViewController]];
}
- (UIViewController *)loadViewControllerNamed:(NSString *)viewControllerName {
UIStoryboard *storyboard = [AppDelegate getMainStoryboard];
return [storyboard instantiateViewControllerWithIdentifier:viewControllerName];
}
- (UIViewController *)loadSettingsControllerNamed:(NSString *)viewControllerName {
UIStoryboard *storyboard = [AppDelegate getSettingsStoryboard];
return [storyboard instantiateViewControllerWithIdentifier:viewControllerName];
}
- (UIViewController *)loadMyIdentityControllerNamed:(NSString *)viewControllerName {
UIStoryboard *storyboard = [AppDelegate getMyIdentityStoryboard];
return [storyboard instantiateViewControllerWithIdentifier:viewControllerName];
}
- (void)showModal:(UIViewController *)viewController {
ModalNavigationController *navigationController = [[ModalNavigationController alloc] init];
navigationController.showDoneButton = YES;
navigationController.dismissOnTapOutside = YES;
navigationController.modalDelegate = self;
[navigationController pushViewController:viewController animated:NO];
[self presentViewController:navigationController animated:YES completion:nil];
}
- (void)applicationDidReceiveMemoryWarning:(NSNotification*)notification {
_chatViewController = nil;
_conversationsViewController = nil;
_conversationsNavigationController = nil;
}
#pragma mark - notifications
- (void)selectedGroup:(NSNotification*)notification {
GroupProxy *group = [notification.userInfo objectForKey:kKeyGroup];
_contactDetailViewController = nil;
if (SYSTEM_IS_IPAD) {
_groupDetailViewController = (GroupDetailsViewController *)[self loadViewControllerNamed:@"groupDetailsViewController"];
_groupDetailViewController.group = group;
if (self.selectedIndex == kContactsTabBarIndex) {
[self switchContact];
} else {
[self setSelectedIndex:kContactsTabBarIndex];
}
} else {
if (_contactsViewController == nil) {
_contactsNavigationController = self.viewControllers[kContactsTabBarIndex];
_contactsViewController = [[_contactsNavigationController viewControllers] objectAtIndex:0];
}
[self setSelectedViewController:_contactsNavigationController];
[_contactsNavigationController popToViewController:_contactsViewController animated:NO];
[_contactsViewController showDetailsForGroup:group];
}
}
- (void)selectedContact:(NSNotification*)notification {
Contact *contact = [notification.userInfo objectForKey:kKeyContact];
_groupDetailViewController = nil;
if (SYSTEM_IS_IPAD) {
_contactDetailViewController = (ContactDetailsViewController *)[self loadViewControllerNamed:@"contactDetailsViewController"];
_contactDetailViewController.contact = contact;
if (self.selectedIndex == kContactsTabBarIndex) {
[self switchContact];
} else {
[self setSelectedIndex:kContactsTabBarIndex];
}
} else {
if (_contactsViewController == nil) {
_contactsNavigationController = self.viewControllers[kContactsTabBarIndex];
_contactsViewController = [[_contactsNavigationController viewControllers] objectAtIndex:0];
}
[self setSelectedViewController:_contactsNavigationController];
[_contactsNavigationController popToViewController:_contactsViewController animated:NO];
[_contactsViewController showDetailsForContact:contact];
}
}
- (void)selectedConversation:(NSNotification*)notification {
[self hideModal];
_chatViewController = [ChatViewControllerCache controllerForNotificationInfo:notification.userInfo];
if (SYSTEM_IS_IPAD) {
if (self.selectedIndex == kChatTabBarIndex) {
[self switchConversation];
} else {
[self setSelectedIndex:kChatTabBarIndex];
}
} else {
if (_conversationsViewController == nil) {
_conversationsNavigationController = self.viewControllers[kChatTabBarIndex];
if ([_conversationsNavigationController.topViewController isKindOfClass:[ConversationsViewController class]]) {
_conversationsViewController = (ConversationsViewController *)_conversationsNavigationController.topViewController;
}
}
[self setSelectedViewController:_conversationsNavigationController];
[_conversationsViewController displayChat:_chatViewController animated:YES];
}
}
- (void)deletedConversation:(NSNotification*)notification {
if (SYSTEM_IS_IPAD) {
Conversation *deletedConversation = [ChatViewControllerCache getConversationForNotificationInfo:notification.userInfo];
if (_chatViewController.conversation == deletedConversation) {
_chatViewController = nil;
if (self.selectedIndex == kChatTabBarIndex) {
[self switchConversation];
}
}
if (_groupDetailViewController.group.conversation == deletedConversation) {
_groupDetailViewController = nil;
if (self.selectedIndex == kContactsTabBarIndex) {
[self switchContact];
}
}
}
}
- (void)deletedContact:(NSNotification*)notification {
if (SYSTEM_IS_IPAD) {
Contact *deletedContact = [notification.userInfo objectForKey:kKeyContact];
if (_contactDetailViewController.contact == deletedContact) {
_contactDetailViewController = nil;
if (self.selectedIndex == kContactsTabBarIndex) {
[self switchContact];
}
}
}
}
- (void)showSafeSetup:(NSNotification*)notification {
// switch to My Identity tab and show Threema Safe settings/setup
[self setSelectedIndex:kMyIdentityTabBarIndex];
UINavigationController *myIdentityNavigation = [[self viewControllers] objectAtIndex:kMyIdentityTabBarIndex];
MyIdentityViewController *myIdentity = [[myIdentityNavigation viewControllers] objectAtIndex:0];
if (myIdentity != nil) {
[myIdentity showSafeSetup];
}
}
- (void)hideModal {
if (self.presentedViewController == nil) {
return;
}
if (_currentIndex == kSettingsTabBarIndex || _currentIndex == kMyIdentityTabBarIndex) {
[self.presentedViewController dismissViewControllerAnimated:YES completion:nil];
return;
}
UIViewController *controller = self.presentedViewController;
if ([controller isKindOfClass:[UINavigationController class]]) {
UINavigationController *navigationController = (UINavigationController*)controller;
if ([navigationController.topViewController isKindOfClass:[MWPhotoBrowser class]]) {
[navigationController dismissViewControllerAnimated:YES completion:nil];
}
else if ([navigationController.topViewController isKindOfClass:[PreviewImageViewController class]]) {
[navigationController dismissViewControllerAnimated:YES completion:nil];
}
}
}
- (void)wallpaperChanged:(NSNotification*)notification {
DDLogInfo(@"Wallpaper changed, removing cached chat view controllers");
[self resetChats];
}
- (void)colorThemeChanged:(NSNotification*)notification {
DDLogInfo(@"Color theme changed, removing cached chat view controllers");
[AvatarMaker clearCache];
[Colors updateWindow:[[AppDelegate sharedAppDelegate] window]];
[Colors updateNavigationBar:self.selectedViewController.navigationController.navigationBar];
[Colors updateTabBar:self.tabBar];
[ChatViewControllerCache refresh];
[self setNeedsStatusBarAppearanceUpdate];
if (SYSTEM_IS_IPAD) {
[_settingsViewController refresh];
[Colors updateNavigationBar:_settingsViewController.navigationController.navigationBar];
[_myIdentityViewController refresh];
[Colors updateNavigationBar:_settingsViewController.navigationController.navigationBar];
[_contactDetailViewController refresh];
[Colors updateNavigationBar:_contactDetailViewController.navigationController.navigationBar];
[_contactsViewController refresh];
[Colors updateNavigationBar:_contactsViewController.navigationController.navigationBar];
[_conversationsViewController refresh];
[Colors updateNavigationBar:_conversationsViewController.navigationController.navigationBar];
[Colors updateNavigationBar:_chatViewController.navigationController.navigationBar];
[_chatViewController refresh];
for (UIViewController *vc in _conversationsNavigationController.viewControllers) {
[Colors updateNavigationBar:vc.navigationController.navigationBar];
}
[Colors updateNavigationBar:_conversationsNavigationController.navigationBar];
}
}
- (void)chatFontSizeChanged:(NSNotification*)notification {
DDLogInfo(@"Chat font size changed, removing cached chat view controllers");
[self resetChats];
}
- (void)timestampSettingsChanged:(NSNotification*)notification {
DDLogInfo(@"Timestamp settings changed, removing cached chat view controllers");
[self resetChats];
}
- (void)resetChats {
[ChatViewControllerCache clearCache];
[self resetDisplayedChat];
}
- (void)resetDisplayedChat {
if (SYSTEM_IS_IPAD) {
if (_chatViewController) {
Conversation *conversation = _chatViewController.conversation;
_chatViewController = [ChatViewControllerCache controllerForConversation:conversation];
[self switchConversation];
}
} else {
if (self.selectedIndex == kChatTabBarIndex) {
[_conversationsNavigationController popToRootViewControllerAnimated:true];
}
}
}
#pragma mark - ModalNavigationControllerDelegate
- (void)willDismissModalNavigationController {
//fool the tab bar to switch the selected tab
NSUInteger index = [self.viewControllers indexOfObject:self.selectedViewController];
_currentIndex = -1;
[super setSelectedIndex:kSettingsTabBarIndex];
[self setSelectedIndex:index];
}
- (void)traitCollectionDidChange:(UITraitCollection *)previousTraitCollection {
[super traitCollectionDidChange:previousTraitCollection];
if (@available(iOS 13.0, *)) {
if ([[UserSettings sharedUserSettings] useSystemTheme] && [[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground) {
if ([self.traitCollection hasDifferentColorAppearanceComparedToTraitCollection:previousTraitCollection]) {
if (self.traitCollection.userInterfaceStyle == UIUserInterfaceStyleDark) {
if ([Colors getTheme] != ColorThemeDark && [Colors getTheme] != ColorThemeDarkWork) {
[Colors setTheme:[LicenseStore requiresLicenseKey] ? ColorThemeDarkWork : ColorThemeDark];
}
} else {
if ([Colors getTheme] != ColorThemeLight && [Colors getTheme] != ColorThemeLightWork) {
[Colors setTheme:[LicenseStore requiresLicenseKey] ? ColorThemeLightWork : ColorThemeLight];
}
}
}
}
}
if (previousTraitCollection.preferredContentSizeCategory != self.traitCollection.preferredContentSizeCategory) {
[self resetChats];
}
}
@end