/* Copyright 2017 Vector Creations Ltd Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except in compliance with the License. You may obtain a copy of the License at http://www.apache.org/licenses/LICENSE-2.0 Unless required by applicable law or agreed to in writing, software distributed under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the License for the specific language governing permissions and limitations under the License. */ #import "MasterTabBarController.h" #import "UnifiedSearchViewController.h" #import "RecentsDataSource.h" #import "AppDelegate.h" @interface MasterTabBarController () { // Array of `MXSession` instances. NSMutableArray *mxSessionArray; // Tell whether the authentication screen is preparing. BOOL isAuthViewControllerPreparing; // Observer that checks when the Authentification view controller has gone. id authViewControllerObserver; // The parameters to pass to the Authentification view controller. NSDictionary *authViewControllerRegistrationParameters; // The recents data source shared between all the view controllers of the tab bar. RecentsDataSource *recentsDataSource; // The current unified search screen if any UnifiedSearchViewController *unifiedSearchViewController; // Current alert (if any). MXKAlert *currentAlert; } @end @implementation MasterTabBarController - (void)viewDidLoad { [super viewDidLoad]; // Do any additional setup after loading the view, typically from a nib. // Retrieve the all view controllers _homeViewController = [self.viewControllers objectAtIndex:TABBAR_HOME_INDEX]; _favouritesViewController = [self.viewControllers objectAtIndex:TABBAR_FAVOURITES_INDEX]; _peopleViewController = [self.viewControllers objectAtIndex:TABBAR_PEOPLE_INDEX]; _roomsViewController = [self.viewControllers objectAtIndex:TABBAR_ROOMS_INDEX]; // Sanity check NSAssert(_homeViewController && _favouritesViewController && _peopleViewController && _roomsViewController, @"Something wrong in Main.storyboard"); self.tabBar.tintColor = kRiotColorGreen; // Initialize here the data sources if a matrix session has been already set. [self initializeDataSources]; } - (void)viewDidAppear:(BOOL)animated { [super viewDidAppear:animated]; // Check whether we're not logged in if (![MXKAccountManager sharedManager].accounts.count) { [self showAuthenticationScreen]; } else { // Check whether the user has been already prompted to send crash reports. // (Check whether 'enableCrashReport' flag has been set once) if (![[NSUserDefaults standardUserDefaults] objectForKey:@"enableCrashReport"]) { [self promptUserBeforeUsingGoogleAnalytics]; } } if (unifiedSearchViewController) { [unifiedSearchViewController destroy]; unifiedSearchViewController = nil; } } - (void)dealloc { mxSessionArray = nil; _homeViewController = nil; _favouritesViewController = nil; _peopleViewController = nil; _roomsViewController = nil; if (currentAlert) { [currentAlert dismiss:NO]; currentAlert = nil; } if (authViewControllerObserver) { [[NSNotificationCenter defaultCenter] removeObserver:authViewControllerObserver]; authViewControllerObserver = nil; } } #pragma mark - - (NSArray*)mxSessions { return [NSArray arrayWithArray:mxSessionArray]; } - (void)initializeDataSources { MXSession *mainSession = mxSessionArray.firstObject; if (mainSession) { // Init the recents data source recentsDataSource = [[RecentsDataSource alloc] initWithMatrixSession:mainSession]; [_homeViewController displayList:recentsDataSource]; [_favouritesViewController displayList:recentsDataSource]; [_peopleViewController displayDirectRooms:recentsDataSource]; [_roomsViewController displayList:recentsDataSource]; // Check whether there are others sessions NSArray* mxSessions = self.mxSessions; if (mxSessions.count > 1) { for (MXSession *mxSession in mxSessions) { if (mxSession != mainSession) { // Add the session to the recents data source [recentsDataSource addMatrixSession:mxSession]; } } } } } - (void)addMatrixSession:(MXSession *)mxSession { // Check whether the controller'€™s view is loaded into memory. if (_homeViewController) { // Check whether the data sources have been initialized. if (!recentsDataSource) { // Add first the session. The updated sessions list will be used during data sources initialization. mxSessionArray = [NSMutableArray array]; [mxSessionArray addObject:mxSession]; // Prepare data sources and return [self initializeDataSources]; return; } else { // Add the session to the existing data sources [recentsDataSource addMatrixSession:mxSession]; } } if (!mxSessionArray) { mxSessionArray = [NSMutableArray array]; } [mxSessionArray addObject:mxSession]; } - (void)removeMatrixSession:(MXSession *)mxSession { [recentsDataSource removeMatrixSession:mxSession]; // Check whether there are others sessions if (!recentsDataSource.mxSessions.count) { [_homeViewController displayList:nil]; [_favouritesViewController displayList:nil]; [_peopleViewController displayDirectRooms:nil]; [_roomsViewController displayList:nil]; [recentsDataSource destroy]; recentsDataSource = nil; } [mxSessionArray removeObject:mxSession]; } - (void)showAuthenticationScreen { // Check whether an authentication screen is not already shown or preparing if (!self.authViewController && !isAuthViewControllerPreparing) { isAuthViewControllerPreparing = YES; [[AppDelegate theDelegate] restoreInitialDisplay:^{ [self performSegueWithIdentifier:@"showAuth" sender:self]; }]; } } - (void)showAuthenticationScreenWithRegistrationParameters:(NSDictionary *)parameters { if (self.authViewController) { NSLog(@"[MasterTabBarController] Universal link: Forward registration parameter to the existing AuthViewController"); self.authViewController.externalRegistrationParameters = parameters; } else { NSLog(@"[MasterTabBarController] Universal link: Logout current sessions and open AuthViewController to complete the registration"); // Keep a ref on the params authViewControllerRegistrationParameters = parameters; // And do a logout out. It will then display AuthViewController [[AppDelegate theDelegate] logout]; } } - (void)selectRoomWithId:(NSString*)roomId andEventId:(NSString*)eventId inMatrixSession:(MXSession*)matrixSession { if (_selectedRoomId && [_selectedRoomId isEqualToString:roomId] && _selectedEventId && [_selectedEventId isEqualToString:eventId] && _selectedRoomSession && _selectedRoomSession == matrixSession) { // Nothing to do return; } _selectedRoomId = roomId; _selectedEventId = eventId; _selectedRoomSession = matrixSession; if (roomId && matrixSession) { [self performSegueWithIdentifier:@"showRoomDetails" sender:self]; } else { [self releaseSelectedItem]; } } - (void)showRoomPreview:(RoomPreviewData *)roomPreviewData { _selectedRoomPreviewData = roomPreviewData; _selectedRoomId = roomPreviewData.roomId; _selectedRoomSession = roomPreviewData.mxSession; [self performSegueWithIdentifier:@"showRoomDetails" sender:self]; } - (void)selectContact:(MXKContact*)contact { _selectedContact = contact; [self performSegueWithIdentifier:@"showContactDetails" sender:self]; } - (void)releaseSelectedItem { _selectedRoomId = nil; _selectedEventId = nil; _selectedRoomSession = nil; if (_currentRoomViewController) { // If the displayed data is not a preview, let the manager release the room data source // (except if the view controller has the room data source ownership). if (!_currentRoomViewController.roomPreviewData && _currentRoomViewController.roomDataSource && !_currentRoomViewController.hasRoomDataSourceOwnership) { MXSession *mxSession = _currentRoomViewController.roomDataSource.mxSession; MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:mxSession]; // Let the manager release live room data sources where the user is in [roomDataSourceManager closeRoomDataSource:_currentRoomViewController.roomDataSource forceClose:NO]; } [_currentRoomViewController destroy]; _currentRoomViewController = nil; } _selectedContact = nil; if (_currentContactDetailViewController) { [_currentContactDetailViewController destroy]; _currentContactDetailViewController = nil; } } - (void)dismissUnifiedSearch:(BOOL)animated completion:(void (^)(void))completion { if (unifiedSearchViewController) { [self.navigationController dismissViewControllerAnimated:animated completion:completion]; } else if (completion) { completion(); } } #pragma mark - - (void)prepareForSegue:(UIStoryboardSegue *)segue sender:(id)sender { if ([[segue identifier] isEqualToString:@"showRoomDetails"] || [[segue identifier] isEqualToString:@"showContactDetails"]) { UINavigationController *navigationController = [segue destinationViewController]; // Release existing Room view controller (if any) if (_currentRoomViewController) { // If the displayed data is not a preview, let the manager release the room data source // (except if the view controller has the room data source ownership). if (!_currentRoomViewController.roomPreviewData && _currentRoomViewController.roomDataSource && !_currentRoomViewController.hasRoomDataSourceOwnership) { MXSession *mxSession = _currentRoomViewController.roomDataSource.mxSession; MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:mxSession]; [roomDataSourceManager closeRoomDataSource:_currentRoomViewController.roomDataSource forceClose:NO]; } [_currentRoomViewController destroy]; _currentRoomViewController = nil; } else if (_currentContactDetailViewController) { [_currentContactDetailViewController destroy]; _currentContactDetailViewController = nil; } if ([[segue identifier] isEqualToString:@"showRoomDetails"]) { // Replace the rootviewcontroller with a room view controller // Get the RoomViewController from the storyboard UIStoryboard *storyboard = [UIStoryboard storyboardWithName:@"Main" bundle:[NSBundle mainBundle]]; _currentRoomViewController = [storyboard instantiateViewControllerWithIdentifier:@"RoomViewControllerStoryboardId"]; navigationController.viewControllers = @[_currentRoomViewController]; if (!_selectedRoomPreviewData) { MXKRoomDataSource *roomDataSource; // Check whether an event has been selected from messages or files search tab. MXEvent *selectedSearchEvent = unifiedSearchViewController.selectedSearchEvent; MXSession *selectedSearchEventSession = unifiedSearchViewController.selectedSearchEventSession; if (!selectedSearchEvent) { if (!_selectedEventId) { // LIVE: Show the room live timeline managed by MXKRoomDataSourceManager MXKRoomDataSourceManager *roomDataSourceManager = [MXKRoomDataSourceManager sharedManagerForMatrixSession:_selectedRoomSession]; roomDataSource = [roomDataSourceManager roomDataSourceForRoom:_selectedRoomId create:YES]; } else { // Open the room on the requested event roomDataSource = [[RoomDataSource alloc] initWithRoomId:_selectedRoomId initialEventId:_selectedEventId andMatrixSession:_selectedRoomSession]; [roomDataSource finalizeInitialization]; // Give the data source ownership to the room view controller. _currentRoomViewController.hasRoomDataSourceOwnership = YES; } } else { // Search result: Create a temp timeline from the selected event roomDataSource = [[RoomDataSource alloc] initWithRoomId:selectedSearchEvent.roomId initialEventId:selectedSearchEvent.eventId andMatrixSession:selectedSearchEventSession]; [roomDataSource finalizeInitialization]; // Give the data source ownership to the room view controller. _currentRoomViewController.hasRoomDataSourceOwnership = YES; } [_currentRoomViewController displayRoom:roomDataSource]; } else { [_currentRoomViewController displayRoomPreview:_selectedRoomPreviewData]; _selectedRoomPreviewData = nil; } } else { // Replace the rootviewcontroller with a contact details view controller _currentContactDetailViewController = [ContactDetailsViewController contactDetailsViewController]; _currentContactDetailViewController.enableVoipCall = NO; _currentContactDetailViewController.contact = _selectedContact; navigationController.viewControllers = @[_currentContactDetailViewController]; } if (self.splitViewController) { // Refresh selected cell without scrolling the selected cell (We suppose it's visible here) [self refreshCurrentSelectedCell:NO]; if (_currentRoomViewController) { _currentRoomViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; _currentRoomViewController.navigationItem.leftItemsSupplementBackButton = YES; } else { _currentContactDetailViewController.navigationItem.leftBarButtonItem = self.splitViewController.displayModeButtonItem; _currentContactDetailViewController.navigationItem.leftItemsSupplementBackButton = YES; } } } else { // Keep ref on destinationViewController [super prepareForSegue:segue sender:sender]; if ([[segue identifier] isEqualToString:@"showAuth"]) { // Keep ref on the authentification view controller while it is displayed // ie until we get the notification about a new account _authViewController = segue.destinationViewController; isAuthViewControllerPreparing = NO; authViewControllerObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidAddAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) { _authViewController = nil; [[NSNotificationCenter defaultCenter] removeObserver:authViewControllerObserver]; authViewControllerObserver = nil; }]; // Forward parameters if any if (authViewControllerRegistrationParameters) { _authViewController.externalRegistrationParameters = authViewControllerRegistrationParameters; authViewControllerRegistrationParameters = nil; } } else if ([[segue identifier] isEqualToString:@"showUnifiedSearch"]) { unifiedSearchViewController= segue.destinationViewController; for (MXSession *session in mxSessionArray) { [unifiedSearchViewController addMatrixSession:session]; } } } // Hide back button title self.navigationController.topViewController.navigationItem.backBarButtonItem = [[UIBarButtonItem alloc] initWithTitle:@"" style:UIBarButtonItemStylePlain target:nil action:nil]; } // Made the actual selected view controller update its selected cell. - (void)refreshCurrentSelectedCell:(BOOL)forceVisible { UIViewController *selectedViewController = self.selectedViewController; if ([selectedViewController respondsToSelector:@selector(refreshCurrentSelectedCell:)]) { [(id)selectedViewController refreshCurrentSelectedCell:forceVisible]; }} #pragma mark - - (void)promptUserBeforeUsingGoogleAnalytics { NSLog(@"[MasterTabBarController]: Invite the user to send crash reports"); __weak typeof(self) weakSelf = self; [currentAlert dismiss:NO]; NSString *appDisplayName = [[[NSBundle mainBundle] infoDictionary] objectForKey:@"CFBundleDisplayName"]; currentAlert = [[MXKAlert alloc] initWithTitle:[NSString stringWithFormat:NSLocalizedStringFromTable(@"google_analytics_use_prompt", @"Vector", nil), appDisplayName] message:nil style:MXKAlertStyleAlert]; currentAlert.cancelButtonIndex = [currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"no"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) { [[NSUserDefaults standardUserDefaults] setBool:NO forKey:@"enableCrashReport"]; [[NSUserDefaults standardUserDefaults] synchronize]; if (weakSelf) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf->currentAlert = nil; } }]; [currentAlert addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"yes"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert) { [[NSUserDefaults standardUserDefaults] setBool:YES forKey:@"enableCrashReport"]; [[NSUserDefaults standardUserDefaults] synchronize]; if (weakSelf) { __strong __typeof(weakSelf)strongSelf = weakSelf; strongSelf->currentAlert = nil; } [[AppDelegate theDelegate] startGoogleAnalytics]; }]; currentAlert.mxkAccessibilityIdentifier = @"HomeVCUseGoogleAnalyticsAlert"; [currentAlert showInViewController:self]; } @end