element-ios/Vector/AppDelegate.m

1193 lines
44 KiB
Mathematica
Raw Normal View History

/*
Copyright 2014 OpenMarket 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 "AppDelegate.h"
2015-08-21 18:00:39 +00:00
#import "RoomDataSource.h"
#import "EventFormatter.h"
#import "RoomViewController.h"
#import "SettingsViewController.h"
#import "RageShakeManager.h"
#import "NSBundle+MatrixKit.h"
#import "AFNetworkReachabilityManager.h"
#import <AudioToolbox/AudioToolbox.h>
2015-08-11 12:42:09 +00:00
#define MX_CALL_STACK_OPENWEBRTC
#ifdef MX_CALL_STACK_OPENWEBRTC
#import <MatrixOpenWebRTCWrapper/MatrixOpenWebRTCWrapper.h>
#endif
#ifdef MX_CALL_STACK_ENDPOINT
#import <MatrixEndpointWrapper/MatrixEndpointWrapper.h>
#endif
2015-02-04 23:16:11 +00:00
#define MAKE_STRING(x) #x
#define MAKE_NS_STRING(x) @MAKE_STRING(x)
2015-06-18 09:19:42 +00:00
@interface AppDelegate () <UISplitViewControllerDelegate>
{
2015-05-11 21:11:23 +00:00
/**
Reachability observer
*/
id reachabilityObserver;
2015-08-04 16:37:48 +00:00
/**
MatrixKit error observer
*/
id matrixKitErrorObserver;
2015-05-11 21:11:23 +00:00
/**
matrix session observer used to detect new opened sessions.
*/
id matrixSessionStateObserver;
2015-05-11 21:11:23 +00:00
/**
matrix account observers.
2015-05-11 21:11:23 +00:00
*/
id addedAccountObserver;
id removedAccountObserver;
2015-05-11 21:11:23 +00:00
/**
matrix call observer used to handle incoming/outgoing call.
*/
id matrixCallObserver;
/**
The current call view controller (if any).
*/
MXKCallViewController *currentCallViewController;
2015-05-11 21:11:23 +00:00
/**
Call status window displayed when user goes back to app during a call.
*/
UIWindow* callStatusBarWindow;
UIButton* callStatusBarButton;
/**
Account picker used in case of multiple account.
*/
MXKAlert *accountPicker;
}
@property (strong, nonatomic) MXKAlert *mxInAppNotification;
@end
@implementation AppDelegate
#pragma mark -
2015-06-18 09:19:42 +00:00
+ (AppDelegate*)theDelegate
{
return (AppDelegate*)[[UIApplication sharedApplication] delegate];
}
#pragma mark -
2015-06-18 09:19:42 +00:00
- (NSString*)appVersion
{
if (!_appVersion)
{
_appVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleShortVersionString"];
}
return _appVersion;
}
2015-06-18 09:19:42 +00:00
- (NSString*)build
{
if (!_build)
{
NSString *buildBranch = nil;
NSString *buildNumber = nil;
// Check whether GIT_BRANCH and BUILD_NUMBER were provided during compilation in command line argument.
2015-02-04 23:16:11 +00:00
#ifdef GIT_BRANCH
buildBranch = MAKE_NS_STRING(GIT_BRANCH);
#endif
2015-02-04 23:16:11 +00:00
#ifdef BUILD_NUMBER
buildNumber = [NSString stringWithFormat:@"#%d", BUILD_NUMBER];
#endif
2015-06-18 09:19:42 +00:00
if (buildBranch && buildNumber)
{
_build = [NSString stringWithFormat:@"%@ %@", buildBranch, buildNumber];
} else if (buildNumber){
_build = buildNumber;
2015-06-18 09:19:42 +00:00
} else
{
_build = buildBranch ? buildBranch : NSLocalizedStringFromTable(@"settings_config_no_build_info", @"Vector", nil);
}
}
return _build;
}
2015-06-18 09:19:42 +00:00
- (void)setIsOffline:(BOOL)isOffline
{
if (isOffline)
{
// Add observer to leave this state automatically.
2015-08-04 16:37:48 +00:00
reachabilityObserver = [[NSNotificationCenter defaultCenter] addObserverForName:AFNetworkingReachabilityDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
NSNumber *statusItem = note.userInfo[AFNetworkingReachabilityNotificationStatusItem];
2015-06-18 09:19:42 +00:00
if (statusItem)
{
AFNetworkReachabilityStatus reachabilityStatus = statusItem.integerValue;
2015-06-18 09:19:42 +00:00
if (reachabilityStatus == AFNetworkReachabilityStatusReachableViaWiFi || reachabilityStatus == AFNetworkReachabilityStatusReachableViaWWAN)
{
self.isOffline = NO;
}
}
2015-08-04 16:37:48 +00:00
}];
2015-06-18 09:19:42 +00:00
}
else
{
// Release potential observer
2015-06-18 09:19:42 +00:00
if (reachabilityObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:reachabilityObserver];
reachabilityObserver = nil;
}
}
_isOffline = isOffline;
}
#pragma mark - UIApplicationDelegate
2015-06-18 09:19:42 +00:00
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
if ([self.window.rootViewController isKindOfClass:[MasterTabBarController class]])
{
self.masterTabBarController = (MasterTabBarController*)self.window.rootViewController;
self.masterTabBarController.delegate = self;
// By default the "Recents" tab is focused
[self.masterTabBarController setSelectedIndex:TABBAR_RECENTS_INDEX];
UIViewController* recents = [self.masterTabBarController.viewControllers objectAtIndex:TABBAR_RECENTS_INDEX];
2015-06-18 09:19:42 +00:00
if ([recents isKindOfClass:[UISplitViewController class]])
{
UISplitViewController *splitViewController = (UISplitViewController *)recents;
2015-08-19 12:53:20 +00:00
UIViewController *detailsViewController = [splitViewController.viewControllers lastObject];
if ([detailsViewController isKindOfClass:[UINavigationController class]])
{
UINavigationController *navigationController = (UINavigationController*)detailsViewController;
detailsViewController = navigationController.topViewController;
}
2014-12-16 17:30:49 +00:00
// IOS >= 8
2015-06-18 09:19:42 +00:00
if ([splitViewController respondsToSelector:@selector(displayModeButtonItem)])
{
2015-08-19 12:53:20 +00:00
detailsViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem;
// on IOS 8 iPad devices, force to display the primary and the secondary viewcontroller
// to avoid empty room View Controller in portrait orientation
// else, the user cannot select a room
// shouldHideViewController delegate method is also implemented
2015-06-18 09:19:42 +00:00
if ([splitViewController respondsToSelector:@selector(preferredDisplayMode)] && [(NSString*)[UIDevice currentDevice].model hasPrefix:@"iPad"])
{
splitViewController.preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible;
}
2014-12-16 17:30:49 +00:00
}
splitViewController.delegate = self;
2015-06-18 09:19:42 +00:00
}
else
{
2014-10-09 09:26:12 +00:00
// Patch missing image in tabBarItem for iOS < 8.0
recents.tabBarItem.image = [[UIImage imageNamed:@"tab_recents"] imageWithRenderingMode:UIImageRenderingModeAutomatic];
}
2014-12-12 09:49:15 +00:00
_isAppForeground = NO;
// Retrieve custom configuration
NSString* userDefaults = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"UserDefaults"];
NSString *defaultsPathFromApp = [[NSBundle mainBundle] pathForResource:userDefaults ofType:@"plist"];
NSDictionary *defaults = [NSDictionary dictionaryWithContentsOfFile:defaultsPathFromApp];
[[NSUserDefaults standardUserDefaults] registerDefaults:defaults];
[[NSUserDefaults standardUserDefaults] synchronize];
// Add matrix observers and initialize matrix sessions.
[self initMatrixSessions];
}
NSDictionary *remoteNotif = [launchOptions objectForKey: UIApplicationLaunchOptionsRemoteNotificationKey];
2015-06-18 09:19:42 +00:00
// The application is launched if there is a new notification
if ((remoteNotif) && ([[UIApplication sharedApplication] applicationState] != UIApplicationStateBackground))
{
// do something when the app is launched on background
#ifdef DEBUG
NSLog(@"[AppDelegate] didFinishLaunchingWithOptions: the application is launched in background");
#endif
}
else
{
#ifdef DEBUG
NSLog(@"[AppDelegate] didFinishLaunchingWithOptions: clear the notifications");
#endif
// clear the notifications counter
[self clearNotifications];
}
return YES;
}
2015-06-18 09:19:42 +00:00
- (void)applicationWillResignActive:(UIApplication *)application
{
// Sent when the application is about to move from active to inactive state. This can occur for certain types of temporary interruptions (such as an incoming phone call or SMS message) or when the user quits the application and it begins the transition to the background state.
// Use this method to pause ongoing tasks, disable timers, and throttle down OpenGL ES frame rates. Games should use this method to pause the game.
2015-08-04 16:37:48 +00:00
// Release MatrixKit error observer
if (matrixKitErrorObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:matrixKitErrorObserver];
matrixKitErrorObserver = nil;
}
2015-06-18 09:19:42 +00:00
if (self.errorNotification)
{
[self.errorNotification dismiss:NO];
self.errorNotification = nil;
}
2015-06-18 09:19:42 +00:00
if (accountPicker)
{
[accountPicker dismiss:NO];
accountPicker = nil;
}
}
2015-06-18 09:19:42 +00:00
- (void)applicationDidEnterBackground:(UIApplication *)application
{
// Use this method to release shared resources, save user data, invalidate timers, and store enough application state information to restore your application to its current state in case it is terminated later.
// If your application supports background execution, this method is called instead of applicationWillTerminate: when the user quits.
// Stop reachability monitoring
self.isOffline = NO;
[[AFNetworkReachabilityManager sharedManager] stopMonitoring];
// check if some media must be released to reduce the cache size
[MXKMediaManager reduceCacheSizeToInsert:0];
// Hide potential notification
2015-06-18 09:19:42 +00:00
if (self.mxInAppNotification)
{
[self.mxInAppNotification dismiss:NO];
self.mxInAppNotification = nil;
}
// Suspend all running matrix sessions
NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts;
2015-06-18 09:19:42 +00:00
for (MXKAccount *account in mxAccounts)
{
[account pauseInBackgroundTask];
}
// clear the notifications counter
[self clearNotifications];
// cancel any background sync before resuming
// i.e. warn IOS that there is no new data with any received push.
[self cancelBackgroundSync];
_isAppForeground = NO;
}
2015-06-18 09:19:42 +00:00
- (void)applicationWillEnterForeground:(UIApplication *)application
{
// Called as part of the transition from the background to the inactive state; here you can undo many of the changes made on entering the background.
// clear the notifications counter
[self clearNotifications];
_isAppForeground = YES;
}
2015-06-18 09:19:42 +00:00
- (void)applicationDidBecomeActive:(UIApplication *)application
{
// Restart any tasks that were paused (or not yet started) while the application was inactive. If the application was previously in the background, optionally refresh the user interface.
// Start monitoring reachability
[[AFNetworkReachabilityManager sharedManager] startMonitoring];
2015-08-04 16:37:48 +00:00
// Observe matrixKit error to alert user on error
matrixKitErrorObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKErrorNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *note) {
[self showErrorAsAlert:note.object];
}];
// Resume all existing matrix sessions
NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts;
2015-06-18 09:19:42 +00:00
for (MXKAccount *account in mxAccounts)
{
[account resume];
}
// refresh the contacts list
2015-06-09 16:06:57 +00:00
[MXKContactManager sharedManager].enableFullMatrixIdSyncOnLocalContactsDidLoad = NO;
[[MXKContactManager sharedManager] loadLocalContacts];
_isAppForeground = YES;
// check if the app crashed last time
2015-06-18 09:19:42 +00:00
if ([MXLogger crashLog])
{
[[RageShakeManager sharedManager] promptCrashReportInViewController:self.masterTabBarController.selectedViewController];
}
}
2015-06-18 09:19:42 +00:00
- (void)applicationWillTerminate:(UIApplication *)application
{
// Called when the application is about to terminate. Save data if appropriate. See also applicationDidEnterBackground:.
}
2014-12-12 09:49:15 +00:00
#pragma mark - APNS methods
2015-06-18 09:19:42 +00:00
- (void)registerUserNotificationSettings
{
if (!isAPNSRegistered)
{
if ([[UIApplication sharedApplication] respondsToSelector:@selector(registerUserNotificationSettings:)])
{
// Registration on iOS 8 and later
UIUserNotificationSettings *settings = [UIUserNotificationSettings settingsForTypes:(UIUserNotificationTypeBadge | UIUserNotificationTypeSound |UIUserNotificationTypeAlert) categories:nil];
[[UIApplication sharedApplication] registerUserNotificationSettings:settings];
}
else
{
[[UIApplication sharedApplication] registerForRemoteNotificationTypes:(UIRemoteNotificationType)(UIRemoteNotificationTypeAlert | UIRemoteNotificationTypeSound | UIRemoteNotificationTypeBadge)];
}
}
2014-12-12 09:49:15 +00:00
}
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
{
[application registerForRemoteNotifications];
}
2015-06-18 09:19:42 +00:00
- (void)application:(UIApplication*)app didRegisterForRemoteNotificationsWithDeviceToken:(NSData*)deviceToken
{
2015-02-24 12:49:05 +00:00
NSLog(@"[AppDelegate] Got APNS token!");
2014-12-12 09:49:15 +00:00
MXKAccountManager* accountManager = [MXKAccountManager sharedManager];
[accountManager setApnsDeviceToken:deviceToken];
2014-12-12 09:49:15 +00:00
isAPNSRegistered = YES;
}
2015-06-18 09:19:42 +00:00
- (void)application:(UIApplication*)app didFailToRegisterForRemoteNotificationsWithError:(NSError*)error
{
2015-02-24 12:49:05 +00:00
NSLog(@"[AppDelegate] Failed to register for APNS: %@", error);
2014-12-12 09:49:15 +00:00
}
- (void)cancelBackgroundSync
{
if (_completionHandler)
{
_completionHandler(UIBackgroundFetchResultNoData);
_completionHandler = nil;
}
}
2015-06-18 09:19:42 +00:00
- (void)application:(UIApplication*)application didReceiveRemoteNotification:(NSDictionary*)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
{
2014-12-12 09:49:15 +00:00
#ifdef DEBUG
// log the full userInfo only in DEBUG
2015-02-24 12:49:05 +00:00
NSLog(@"[AppDelegate] APNS: %@", userInfo);
2014-12-12 09:49:15 +00:00
#endif
// Look for the room id
NSString* roomId = [userInfo objectForKey:@"room_id"];
if (roomId.length)
2015-06-18 09:19:42 +00:00
{
// TODO retrieve the right matrix session
//**************
// Patch consider the first session which knows the room id
MXKAccount *dedicatedAccount = nil;
NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts;
if (mxAccounts.count == 1)
2015-06-18 09:19:42 +00:00
{
dedicatedAccount = mxAccounts.firstObject;
}
else
{
for (MXKAccount *account in mxAccounts)
{
if ([account.mxSession roomWithRoomId:roomId])
{
dedicatedAccount = account;
break;
}
}
}
// sanity checks
if (dedicatedAccount && dedicatedAccount.mxSession)
{
UIApplicationState state = [UIApplication sharedApplication].applicationState;
// Jump to the concerned room only if the app is transitioning from the background
if (state == UIApplicationStateInactive)
2015-06-18 09:19:42 +00:00
{
#ifdef DEBUG
NSLog(@"[AppDelegate] didReceiveRemoteNotification : open the roomViewController %@", roomId);
#endif
[self.masterTabBarController showRoom:roomId withMatrixSession:dedicatedAccount.mxSession];
}
else if (!_completionHandler && (state == UIApplicationStateBackground))
2015-06-18 09:19:42 +00:00
{
_completionHandler = completionHandler;
#ifdef DEBUG
NSLog(@"[AppDelegate] : starts a catchup");
#endif
[dedicatedAccount catchup:20000 success:^{
#ifdef DEBUG
NSLog(@"[AppDelegate] : the catchup succeeds");
#endif
if (_completionHandler)
2015-06-18 09:19:42 +00:00
{
_completionHandler(UIBackgroundFetchResultNewData);
_completionHandler = nil;
}
} failure:^(NSError *error) {
#ifdef DEBUG
NSLog(@"[AppDelegate] : the catchup fails");
#endif
if (_completionHandler)
{
_completionHandler(UIBackgroundFetchResultNoData);
_completionHandler = nil;
}
}];
// wait that the background sync is done
return;
}
}
else
{
#ifdef DEBUG
NSLog(@"[AppDelegate] : didReceiveRemoteNotification : no linked session / account has been found.");
#endif
}
}
completionHandler(UIBackgroundFetchResultNoData);
2014-12-12 09:49:15 +00:00
}
#pragma mark - Matrix sessions handling
2015-06-18 09:19:42 +00:00
- (void)initMatrixSessions
{
2015-08-21 18:00:39 +00:00
// Set first RoomDataSource class used in Vector
[MXKRoomDataSourceManager registerRoomDataSourceClass:RoomDataSource.class];
// Register matrix session state observer in order to handle multi-sessions.
2015-06-18 09:19:42 +00:00
matrixSessionStateObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXSessionStateDidChangeNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif)
{
MXSession *mxSession = (MXSession*)notif.object;
// Remove by default potential call observer on matrix session state change
2015-06-18 09:19:42 +00:00
if (matrixCallObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:matrixCallObserver];
matrixCallObserver = nil;
}
// Check whether the concerned session is a new one
2015-06-18 09:19:42 +00:00
if (mxSession.state == MXSessionStateInitialised)
{
// Report this session to contact manager
[[MXKContactManager sharedManager] addMatrixSession:mxSession];
// Update all view controllers thanks to tab bar controller
[self.masterTabBarController addMatrixSession:mxSession];
}
else if (mxSession.state == MXSessionStateStoreDataReady)
2015-06-18 09:19:42 +00:00
{
// Check whether the app user wants inApp notifications on new events for this session
NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts;
for (MXKAccount *account in mxAccounts)
2015-06-18 09:19:42 +00:00
{
if (account.mxSession == mxSession)
{
[self enableInAppNotificationsForAccount:account];
break;
}
}
}
else if (mxSession.state == MXSessionStateClosed)
2015-06-18 09:19:42 +00:00
{
[[MXKContactManager sharedManager] removeMatrixSession:mxSession];
[self.masterTabBarController removeMatrixSession:mxSession];
}
// Restore call observer only if all session are running
NSArray *mxSessions = self.masterTabBarController.mxSessions;
BOOL shouldAddMatrixCallObserver = (mxSessions.count);
2015-06-18 09:19:42 +00:00
for (mxSession in mxSessions)
{
if (mxSession.state != MXSessionStateRunning)
{
shouldAddMatrixCallObserver = NO;
break;
}
}
2015-06-18 09:19:42 +00:00
if (shouldAddMatrixCallObserver)
{
// A new call observer may be added here
[self addMatrixCallObserver];
}
}];
// Register an observer in order to handle new account
addedAccountObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidAddAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
// Launch matrix session for this new account
MXKAccount *account = notif.object;
2015-06-18 09:19:42 +00:00
if (account)
{
if (isAPNSRegistered)
{
// Enable push notifications by default on new added account
account.enablePushNotifications = YES;
}
else
{
// Set up push notifications
[self registerUserNotificationSettings];
}
// Observe inApp notifications toggle change
[account addObserver:self forKeyPath:@"enableInAppNotifications" options:0 context:nil];
}
2015-05-11 21:11:23 +00:00
}];
// Add observer to handle removed accounts
removedAccountObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXKAccountManagerDidRemoveAccountNotification object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif) {
// Remove inApp notifications toggle change
MXKAccount *account = notif.object;
[account removeObserver:self forKeyPath:@"enableInAppNotifications"];
// Logout the app when there is no available account
if (![MXKAccountManager sharedManager].accounts.count)
{
[self logout];
}
}];
// Observe settings changes
[[MXKAppSettings standardAppSettings] addObserver:self forKeyPath:@"showAllEventsInRoomHistory" options:0 context:nil];
// Prepare account manager
MXKAccountManager *accountManager = [MXKAccountManager sharedManager];
// Use MXFileStore as MXStore to permanently store events.
accountManager.storeClass = [MXFileStore class];
// Observers have been defined, we start now a matrix session for each enabled accounts.
[accountManager openSessionForActiveAccounts];
// Set the VoIP call stack
for (MXKAccount *account in accountManager.accounts)
{
id<MXCallStack> callStack;
#ifdef MX_CALL_STACK_OPENWEBRTC
callStack = [[MXOpenWebRTCCallStack alloc] init];
#endif
#ifdef MX_CALL_STACK_ENDPOINT
callStack = [[MXEndpointCallStack alloc] initWithMatrixId:account.mxSession.myUser.userId];
#endif
if (callStack)
{
[account.mxSession enableVoIPWithCallStack:callStack];
}
}
// Check whether we're already logged in
NSArray *mxAccounts = accountManager.accounts;
2015-06-18 09:19:42 +00:00
if (mxAccounts.count)
{
// Set up push notifications
[self registerUserNotificationSettings];
// Observe inApp notifications toggle change for each account
2015-06-18 09:19:42 +00:00
for (MXKAccount *account in mxAccounts)
{
[account addObserver:self forKeyPath:@"enableInAppNotifications" options:0 context:nil];
}
}
}
2015-06-18 09:19:42 +00:00
- (void)reloadMatrixSessions:(BOOL)clearCache
{
// Reload all running matrix sessions
NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts;
2015-06-18 09:19:42 +00:00
for (MXKAccount *account in mxAccounts)
{
[account reload:clearCache];
}
// Force back to Recents list if room details is displayed (Room details are not available until the end of initial sync)
[self.masterTabBarController popRoomViewControllerAnimated:NO];
2015-06-18 09:19:42 +00:00
if (clearCache)
{
// clear the media cache
[MXKMediaManager clearCache];
}
}
2015-06-18 09:19:42 +00:00
- (void)logout
{
2014-12-12 09:49:15 +00:00
[[UIApplication sharedApplication] unregisterForRemoteNotifications];
isAPNSRegistered = NO;
2014-10-17 23:04:05 +00:00
// Clear cache
[MXKMediaManager clearCache];
#ifdef MX_CALL_STACK_ENDPOINT
// Erase all created certificates and private keys by MXEndpointCallStack
for (MXKAccount *account in MXKAccountManager.sharedManager.accounts)
{
if ([account.mxSession.callManager.callStack isKindOfClass:MXEndpointCallStack.class])
{
[(MXEndpointCallStack*)account.mxSession.callManager.callStack deleteData:account.mxSession.myUser.userId];
}
}
#endif
// Logout all matrix account
[[MXKAccountManager sharedManager] logout];
// Return to authentication screen
[self.masterTabBarController showAuthenticationScreen];
2014-11-28 14:06:53 +00:00
// Reset App settings
2015-04-15 12:46:54 +00:00
[[MXKAppSettings standardAppSettings] reset];
2015-04-17 12:24:08 +00:00
// Reset the contact manager
[[MXKContactManager sharedManager] reset];
// By default the "Recents" tab is focussed
[self.masterTabBarController setSelectedIndex:TABBAR_RECENTS_INDEX];
}
2015-06-18 09:19:42 +00:00
- (MXKAlert*)showErrorAsAlert:(NSError*)error
{
// Ignore fake error, or connection cancellation error
if (!error || ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorCancelled))
{
return nil;
}
// Ignore network reachability error when the app is already offline
2015-06-18 09:19:42 +00:00
if (self.isOffline && [error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorNotConnectedToInternet)
{
return nil;
}
2015-06-18 09:19:42 +00:00
if (self.errorNotification)
{
[self.errorNotification dismiss:NO];
}
NSString *title = [error.userInfo valueForKey:NSLocalizedFailureReasonErrorKey];
if (!title)
{
title = [NSBundle mxk_localizedStringForKey:@"error"];
}
NSString *msg = [error.userInfo valueForKey:NSLocalizedDescriptionKey];
self.errorNotification = [[MXKAlert alloc] initWithTitle:title message:msg style:MXKAlertStyleAlert];
self.errorNotification.cancelButtonIndex = [self.errorNotification addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"ok"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert)
2015-06-18 09:19:42 +00:00
{
[AppDelegate theDelegate].errorNotification = nil;
}];
[self.errorNotification showInViewController:[self.masterTabBarController selectedViewController]];
// Switch in offline mode in case of network reachability error
2015-06-18 09:19:42 +00:00
if ([error.domain isEqualToString:NSURLErrorDomain] && error.code == NSURLErrorNotConnectedToInternet)
{
self.isOffline = YES;
}
return self.errorNotification;
}
- (void)clearNotifications
{
// force to clear the notification center
// switching from 0 -> 1 -> 0 seems forcing the notifications center to refresh
// so resetting it does not clear the notifications center.
[UIApplication sharedApplication].applicationIconBadgeNumber = 1;
[UIApplication sharedApplication].applicationIconBadgeNumber = 0;
[[UIApplication sharedApplication] cancelAllLocalNotifications];
}
- (void)enableInAppNotificationsForAccount:(MXKAccount*)account
2015-06-18 09:19:42 +00:00
{
if (account.mxSession)
2015-06-18 09:19:42 +00:00
{
if (account.enableInAppNotifications)
2015-06-18 09:19:42 +00:00
{
// Build MXEvent -> NSString formatter
2015-08-21 18:00:39 +00:00
EventFormatter *eventFormatter = [[EventFormatter alloc] initWithMatrixSession:account.mxSession];
eventFormatter.isForSubtitle = YES;
[account listenToNotifications:^(MXEvent *event, MXRoomState *roomState, MXPushRule *rule) {
// Check conditions to display this notification
if (![self.masterTabBarController.visibleRoomId isEqualToString:event.roomId]
2015-08-17 17:15:58 +00:00
&& !self.masterTabBarController.presentedViewController)
{
MXKEventFormatterError error;
NSString* messageText = [eventFormatter stringFromEvent:event withRoomState:roomState error:&error];
if (messageText.length && (error == MXKEventFormatterErrorNone))
{
// Removing existing notification (if any)
if (self.mxInAppNotification)
{
[self.mxInAppNotification dismiss:NO];
}
// Check whether tweak is required
for (MXPushRuleAction *ruleAction in rule.actions)
{
if (ruleAction.actionType == MXPushRuleActionTypeSetTweak)
{
if ([[ruleAction.parameters valueForKey:@"set_tweak"] isEqualToString:@"sound"])
{
// Play system sound (VoicemailReceived)
AudioServicesPlaySystemSound (1002);
}
}
}
__weak typeof(self) weakSelf = self;
self.mxInAppNotification = [[MXKAlert alloc] initWithTitle:roomState.displayname
message:messageText
style:MXKAlertStyleAlert];
self.mxInAppNotification.cancelButtonIndex = [self.mxInAppNotification addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"]
style:MXKAlertActionStyleDefault
handler:^(MXKAlert *alert)
{
weakSelf.mxInAppNotification = nil;
[account updateNotificationListenerForRoomId:event.roomId ignore:YES];
}];
[self.mxInAppNotification addActionWithTitle:NSLocalizedStringFromTable(@"view", @"Vector", nil)
style:MXKAlertActionStyleDefault
handler:^(MXKAlert *alert)
{
weakSelf.mxInAppNotification = nil;
// Show the room
[weakSelf.masterTabBarController showRoom:event.roomId withMatrixSession:account.mxSession];
}];
[self.mxInAppNotification showInViewController:[self.masterTabBarController selectedViewController]];
}
}
}];
}
else
{
[account removeNotificationListener];
}
}
2015-06-18 09:19:42 +00:00
if (self.mxInAppNotification)
{
[self.mxInAppNotification dismiss:NO];
self.mxInAppNotification = nil;
}
}
2015-06-18 09:19:42 +00:00
- (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(NSDictionary *)change context:(void *)context
{
if ([@"showAllEventsInRoomHistory" isEqualToString:keyPath])
{
// Flush and restore Matrix data
[self reloadMatrixSessions:NO];
}
else if ([@"enableInAppNotifications" isEqualToString:keyPath] && [object isKindOfClass:[MXKAccount class]])
2015-06-18 09:19:42 +00:00
{
[self enableInAppNotificationsForAccount:(MXKAccount*)object];
}
}
2015-06-18 09:19:42 +00:00
- (void)addMatrixCallObserver
{
if (matrixCallObserver)
{
[[NSNotificationCenter defaultCenter] removeObserver:matrixCallObserver];
}
// Register call observer in order to handle new opened session
2015-06-18 09:19:42 +00:00
matrixCallObserver = [[NSNotificationCenter defaultCenter] addObserverForName:kMXCallManagerNewCall object:nil queue:[NSOperationQueue mainQueue] usingBlock:^(NSNotification *notif)
{
// Ignore the call if a call is already in progress
2015-06-18 09:19:42 +00:00
if (!currentCallViewController)
{
MXCall *mxCall = (MXCall*)notif.object;
currentCallViewController = [MXKCallViewController callViewController:mxCall];
currentCallViewController.delegate = self;
UIViewController *selectedViewController = [self.masterTabBarController selectedViewController];
[selectedViewController presentViewController:currentCallViewController animated:YES completion:^{
currentCallViewController.isPresented = YES;
}];
// Hide system status bar
[UIApplication sharedApplication].statusBarHidden = YES;
}
}];
}
#pragma mark - Matrix Accounts handling
2015-06-18 09:19:42 +00:00
- (void)selectMatrixAccount:(void (^)(MXKAccount *selectedAccount))onSelection
{
NSArray *mxAccounts = [MXKAccountManager sharedManager].activeAccounts;
2015-06-18 09:19:42 +00:00
if (mxAccounts.count == 1)
{
if (onSelection)
{
onSelection(mxAccounts.firstObject);
}
2015-06-18 09:19:42 +00:00
}
else if (mxAccounts.count > 1)
{
if (accountPicker)
{
[accountPicker dismiss:NO];
}
accountPicker = [[MXKAlert alloc] initWithTitle:[NSBundle mxk_localizedStringForKey:@"select_account"] message:nil style:MXKAlertStyleActionSheet];
__weak typeof(self) weakSelf = self;
2015-06-18 09:19:42 +00:00
for(MXKAccount *account in mxAccounts)
{
[accountPicker addActionWithTitle:account.mxCredentials.userId style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert)
{
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf->accountPicker = nil;
2015-06-18 09:19:42 +00:00
if (onSelection)
{
onSelection(account);
}
}];
}
accountPicker.cancelButtonIndex = [accountPicker addActionWithTitle:[NSBundle mxk_localizedStringForKey:@"cancel"] style:MXKAlertActionStyleDefault handler:^(MXKAlert *alert)
2015-06-18 09:19:42 +00:00
{
__strong __typeof(weakSelf)strongSelf = weakSelf;
strongSelf->accountPicker = nil;
}];
accountPicker.sourceView = [self.masterTabBarController selectedViewController].view;
[accountPicker showInViewController:[self.masterTabBarController selectedViewController]];
}
}
#pragma mark - Matrix Rooms handling
2015-06-18 09:19:42 +00:00
- (void)startPrivateOneToOneRoomWithUserId:(NSString*)userId
{
// Handle here potential multiple accounts
2015-06-18 09:19:42 +00:00
[self selectMatrixAccount:^(MXKAccount *selectedAccount)
{
MXSession *mxSession = selectedAccount.mxSession;
2015-06-18 09:19:42 +00:00
if (mxSession)
{
MXRoom* mxRoom = [mxSession privateOneToOneRoomWithUserId:userId];
// if the room exists
2015-06-18 09:19:42 +00:00
if (mxRoom)
{
// open it
[self.masterTabBarController showRoom:mxRoom.state.roomId withMatrixSession:mxSession];
}
else
2015-06-18 09:19:42 +00:00
{
// create a new room
[mxSession createRoom:nil
visibility:kMXRoomVisibilityPrivate
roomAlias:nil
topic:nil
2015-06-18 09:19:42 +00:00
success:^(MXRoom *room)
{
// invite the other user only if it is defined and not onself
if (userId && ![mxSession.myUser.userId isEqualToString:userId])
{
// add the user
[room inviteUser:userId success:^{
} failure:^(NSError *error)
{
NSLog(@"[AppDelegate] %@ invitation failed (roomId: %@): %@", userId, room.state.roomId, error);
//Alert user
[self showErrorAsAlert:error];
}];
}
// Open created room
[self.masterTabBarController showRoom:room.state.roomId withMatrixSession:mxSession];
} failure:^(NSError *error)
{
NSLog(@"[AppDelegate] Create room failed: %@", error);
//Alert user
[self showErrorAsAlert:error];
}];
}
}
}];
}
#pragma mark - SplitViewController delegate
- (void)splitViewController:(UISplitViewController *)splitViewController willChangeToDisplayMode:(UISplitViewControllerDisplayMode)displayMode
{
// Trick: on iOS 8 and later the tabbar is hidden manually for the secondary viewcontrollers of the splitviewcontroller.
if (displayMode == UISplitViewControllerDisplayModePrimaryHidden)
{
self.masterTabBarController.tabBar.hidden = YES;
}
else
{
self.masterTabBarController.tabBar.hidden = NO;
}
[splitViewController.view setNeedsLayout];
}
2015-06-18 09:19:42 +00:00
- (BOOL)splitViewController:(UISplitViewController *)splitViewController collapseSecondaryViewController:(UIViewController *)secondaryViewController ontoPrimaryViewController:(UIViewController *)primaryViewController
{
if ([secondaryViewController isKindOfClass:[UINavigationController class]] && [[(UINavigationController *)secondaryViewController topViewController] isKindOfClass:[RoomViewController class]] && ([(RoomViewController *)[(UINavigationController *)secondaryViewController topViewController] roomDataSource] == nil))
{
// Return YES to indicate that we have handled the collapse by doing nothing; the secondary controller will be discarded.
return YES;
}
else
2015-06-18 09:19:42 +00:00
{
return NO;
}
}
2015-06-18 09:19:42 +00:00
- (BOOL)splitViewController:(UISplitViewController *)svc shouldHideViewController:(UIViewController *)vc inOrientation:(UIInterfaceOrientation)orientation
{
// oniPad devices, force to display the primary and the secondary viewcontroller
// to avoid empty room View Controller in portrait orientation
// else, the user cannot select a room
return NO;
}
#pragma mark - UITabBarControllerDelegate delegate
2015-06-18 09:19:42 +00:00
- (BOOL)tabBarController:(UITabBarController *)tabBarController shouldSelectViewController:(UIViewController *)viewController
{
BOOL res = YES;
2015-06-18 09:19:42 +00:00
if (tabBarController.selectedIndex == TABBAR_SETTINGS_INDEX)
{
// Prompt user to save unsaved profile changes before switching to another tab
UIViewController* selectedViewController = [tabBarController selectedViewController];
2015-06-18 09:19:42 +00:00
if ([selectedViewController isKindOfClass:[UINavigationController class]])
{
UIViewController *topViewController = ((UINavigationController*)selectedViewController).topViewController;
2015-06-18 09:19:42 +00:00
if ([topViewController isKindOfClass:[MXKAccountDetailsViewController class]])
{
res = [((MXKAccountDetailsViewController *)topViewController) shouldLeave:^()
{
[topViewController.navigationController popViewControllerAnimated:NO];
// This block is called when tab change is delayed to prompt user about his profile changes
NSUInteger nextSelectedViewController = [tabBarController.viewControllers indexOfObject:viewController];
tabBarController.selectedIndex = nextSelectedViewController;
}];
}
}
2015-06-18 09:19:42 +00:00
}
return res;
}
2015-05-11 21:11:23 +00:00
#pragma mark - MXKCallViewControllerDelegate
2015-06-18 09:19:42 +00:00
- (void)dismissCallViewController:(MXKCallViewController *)callViewController
{
if (callViewController == currentCallViewController)
{
2015-05-11 21:11:23 +00:00
2015-06-18 09:19:42 +00:00
if (callViewController.isPresented)
{
BOOL callIsEnded = (callViewController.mxCall.state == MXCallStateEnded);
NSLog(@"Call view controller is dismissed (%d)", callIsEnded);
[callViewController dismissViewControllerAnimated:YES completion:^{
callViewController.isPresented = NO;
2015-06-18 09:19:42 +00:00
if (!callIsEnded)
{
[self addCallStatusBar];
}
}];
2015-06-18 09:19:42 +00:00
if (callIsEnded)
{
[self removeCallStatusBar];
// Restore system status bar
[UIApplication sharedApplication].statusBarHidden = NO;
// Release properly
currentCallViewController.mxCall.delegate = nil;
currentCallViewController.delegate = nil;
currentCallViewController = nil;
}
2015-06-18 09:19:42 +00:00
} else
{
// Here the presentation of the call view controller is in progress
// Postpone the dismiss
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0.3 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{
[self dismissCallViewController:callViewController];
});
}
2015-05-11 21:11:23 +00:00
}
}
#pragma mark - MXKContactDetailsViewControllerDelegate
- (void)contactDetailsViewController:(MXKContactDetailsViewController *)contactDetailsViewController startChatWithMatrixId:(NSString *)matrixId
{
[self startPrivateOneToOneRoomWithUserId:matrixId];
}
#pragma mark - MXKRoomMemberDetailsViewControllerDelegate
- (void)roomMemberDetailsViewController:(MXKRoomMemberDetailsViewController *)roomMemberDetailsViewController startChatWithMemberId:(NSString *)matrixId
{
[self startPrivateOneToOneRoomWithUserId:matrixId];
}
2015-05-11 21:11:23 +00:00
#pragma mark - Call status handling
2015-06-18 09:19:42 +00:00
- (void)addCallStatusBar
{
2015-05-11 21:11:23 +00:00
// Add a call status bar
CGSize topBarSize = CGSizeMake([[UIScreen mainScreen] applicationFrame].size.width, 44);
callStatusBarWindow = [[UIWindow alloc] initWithFrame:CGRectMake(0,0, topBarSize.width,topBarSize.height)];
callStatusBarWindow.windowLevel = UIWindowLevelStatusBar;
// Create statusBarButton
callStatusBarButton = [UIButton buttonWithType:UIButtonTypeCustom];
callStatusBarButton.frame = CGRectMake(0, 0, topBarSize.width,topBarSize.height);
NSString *btnTitle = NSLocalizedStringFromTable(@"return_to_call", @"Vector", nil);
2015-05-11 21:11:23 +00:00
[callStatusBarButton setTitle:btnTitle forState:UIControlStateNormal];
[callStatusBarButton setTitle:btnTitle forState:UIControlStateHighlighted];
callStatusBarButton.titleLabel.textColor = [UIColor whiteColor];
[callStatusBarButton setBackgroundColor:[UIColor blueColor]];
[callStatusBarButton addTarget:self action:@selector(returnToCallView) forControlEvents:UIControlEventTouchUpInside];
// Place button into the new window
[callStatusBarWindow addSubview:callStatusBarButton];
callStatusBarWindow.hidden = NO;
[self statusBarDidChangeFrame];
// We need to listen to the system status bar size change events to refresh the root controller frame.
// Else the navigation bar position will be wrong.
[[NSNotificationCenter defaultCenter] addObserver:self
selector:@selector(statusBarDidChangeFrame)
name:UIApplicationDidChangeStatusBarFrameNotification
object:nil];
}
2015-06-18 09:19:42 +00:00
- (void)removeCallStatusBar
{
if (callStatusBarWindow)
{
2015-05-11 21:11:23 +00:00
// Hide & destroy it
callStatusBarWindow.hidden = YES;
[self statusBarDidChangeFrame];
[callStatusBarButton removeFromSuperview];
callStatusBarButton = nil;
callStatusBarWindow = nil;
// No more need to listen to system status bar changes
[[NSNotificationCenter defaultCenter] removeObserver:self name:UIApplicationDidChangeStatusBarFrameNotification object:nil];
}
}
2015-06-18 09:19:42 +00:00
- (void)returnToCallView
{
2015-05-11 21:11:23 +00:00
[self removeCallStatusBar];
UIViewController *selectedViewController = [self.masterTabBarController selectedViewController];
[selectedViewController presentViewController:currentCallViewController animated:YES completion:^{
currentCallViewController.isPresented = YES;
}];
2015-05-11 21:11:23 +00:00
}
2015-06-18 09:19:42 +00:00
- (void)statusBarDidChangeFrame
{
2015-05-11 21:11:23 +00:00
UIApplication *app = [UIApplication sharedApplication];
UIViewController *rootController = app.keyWindow.rootViewController;
// Refresh the root view controller frame
CGRect frame = [[UIScreen mainScreen] applicationFrame];
2015-06-18 09:19:42 +00:00
if (callStatusBarWindow)
{
2015-05-11 21:11:23 +00:00
// Substract the height of call status bar from the frame.
CGFloat callBarStatusHeight = callStatusBarWindow.frame.size.height;
CGFloat delta = callBarStatusHeight - frame.origin.y;
frame.origin.y = callBarStatusHeight;
frame.size.height -= delta;
}
rootController.view.frame = frame;
[rootController.view setNeedsLayout];
}
@end