2014-10-02 15:02:47 +00:00
/ *
Copyright 2014 OpenMarket Ltd
2017-03-08 15:14:41 +00:00
Copyright 2017 Vector Creations Ltd
2014-10-02 15:02:47 +00:00
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
2015-11-20 17:14:15 +00:00
# import "RecentsDataSource.h"
2015-08-21 18:00:39 +00:00
# import "RoomDataSource.h"
2015-11-17 23:15:52 +00:00
2015-08-21 18:00:39 +00:00
# import "EventFormatter.h"
2015-11-17 23:15:52 +00:00
# import "RoomViewController.h"
2016-06-21 19:47:20 +00:00
# import "DirectoryViewController.h"
# import "SettingsViewController.h"
# import "ContactDetailsViewController.h"
2015-08-07 08:42:47 +00:00
# import "NSBundle+MatrixKit.h"
2016-04-22 15:54:57 +00:00
# import "MatrixSDK/MatrixSDK.h"
2015-08-07 08:42:47 +00:00
2016-04-29 14:19:13 +00:00
# import "Tools.h"
2015-02-12 10:16:28 +00:00
# import "AFNetworkReachabilityManager.h"
2015-04-30 14:19:12 +00:00
# import < AudioToolbox / AudioToolbox . h >
2016-08-19 15:43:29 +00:00
# import "CallViewController.h"
2015-11-16 09:30:57 +00:00
// # define MX_CALL _STACK _OPENWEBRTC
2015-08-07 14:20:46 +00:00
# ifdef MX_CALL _STACK _OPENWEBRTC
2015-07-21 09:30:37 +00:00
# import < MatrixOpenWebRTCWrapper / MatrixOpenWebRTCWrapper . h >
2015-08-07 14:20:46 +00:00
# endif
# ifdef MX_CALL _STACK _ENDPOINT
# import < MatrixEndpointWrapper / MatrixEndpointWrapper . h >
# endif
2015-07-21 09:30:37 +00:00
2016-07-22 16:26:55 +00:00
# include < MatrixSDK / MXJingleCallStack . h >
2016-09-02 20:21:02 +00:00
# define CALL_STATUS _BAR _HEIGHT 44
2015-02-04 23:16:11 +00:00
# define MAKE_STRING ( x ) # x
# define MAKE_NS _STRING ( x ) @ MAKE_STRING ( x )
2015-02-04 17:18:38 +00:00
2016-05-19 15:22:29 +00:00
NSString * const kAppDelegateDidTapStatusBarNotification = @ "kAppDelegateDidTapStatusBarNotification" ;
2016-08-11 08:38:10 +00:00
NSString * const kAppDelegateNetworkStatusDidChangeNotification = @ "kAppDelegateNetworkStatusDidChangeNotification" ;
2016-05-19 15:22:29 +00:00
2015-11-17 23:15:52 +00:00
@ interface AppDelegate ( )
2015-06-18 09:19:42 +00:00
{
2015-05-11 21:11:23 +00:00
/ * *
Reachability observer
* /
2015-02-18 16:40:55 +00:00
id reachabilityObserver ;
2015-04-17 13:45:32 +00:00
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 .
* /
2015-04-17 13:45:32 +00:00
id matrixSessionStateObserver ;
2015-04-30 14:19:12 +00:00
2015-05-11 21:11:23 +00:00
/ * *
2015-06-18 13:20:33 +00:00
matrix account observers .
2015-05-11 21:11:23 +00:00
* /
2015-06-18 13:20:33 +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 ) .
* /
2016-08-19 15:43:29 +00:00
CallViewController * currentCallViewController ;
2015-05-11 21:11:23 +00:00
2015-05-28 16:31:08 +00:00
/ * *
Account picker used in case of multiple account .
* /
MXKAlert * accountPicker ;
2015-11-17 23:15:52 +00:00
/ * *
Array of ` MXSession` instances .
* /
NSMutableArray * mxSessionArray ;
2016-04-06 09:28:12 +00:00
/ * *
The room id of the current handled remote notification ( if any )
* /
NSString * remoteNotificationRoomId ;
2016-04-12 15:44:06 +00:00
/ * *
The fragment of the universal link being processing .
Only one fragment is handled at a time .
* /
NSString * universalLinkFragmentPending ;
2017-02-13 16:58:29 +00:00
/ * *
The potential room alias related to the fragment of the universal link being processing .
Only one alias is handled at a time , the key is the room id and the value is the alias .
* /
NSDictionary * universalLinkFragmentPendingRoomAlias ;
2016-04-12 15:44:06 +00:00
/ * *
An universal link may need to wait for an account to be logged in or for a
session to be running . Hence , this observer .
* /
id universalLinkWaitingObserver ;
2016-04-13 15:08:02 +00:00
2016-06-16 14:50:41 +00:00
/ * *
Suspend the error notifications when the navigation stack of the root view controller is updating .
* /
BOOL isErrorNotificationSuspended ;
2016-04-13 15:08:02 +00:00
/ * *
Completion block called when [ self popToHomeViewControllerAnimated : ] has been
completed .
* /
void ( ^ popToHomeViewControllerCompletion ) ( ) ;
2016-05-20 12:31:23 +00:00
/ * *
The listeners to call events .
There is one listener per MXSession .
2016-05-20 13:45:29 +00:00
The key is an identifier of the MXSession . The value , the listener .
2016-05-20 12:31:23 +00:00
* /
NSMutableDictionary * callEventsListeners ;
/ * *
Currently displayed "Call not supported" alert .
* /
MXKAlert * noCallSupportAlert ;
2016-12-16 12:43:08 +00:00
/ * *
Prompt to ask the user to log in again .
* /
MXKAlert * cryptoDataCorruptedAlert ;
2017-01-31 23:18:01 +00:00
/ * *
The launch animation container view
* /
UIView * launchAnimationContainerView ;
NSDate * launchAnimationStart ;
2015-02-18 16:40:55 +00:00
}
2014-10-02 15:02:47 +00:00
2015-04-30 14:19:12 +00:00
@ property ( strong , nonatomic ) MXKAlert * mxInAppNotification ;
2016-08-19 15:43:29 +00:00
@ property ( strong , nonatomic ) MXKAlert * incomingCallNotification ;
2015-04-30 14:19:12 +00:00
2014-10-02 15:02:47 +00:00
@ end
@ implementation AppDelegate
2014-10-08 12:14:12 +00:00
# pragma mark -
2015-06-18 09:19:42 +00:00
+ ( AppDelegate * ) theDelegate
{
2014-10-08 12:14:12 +00:00
return ( AppDelegate * ) [ [ UIApplication sharedApplication ] delegate ] ;
}
2015-02-04 17:18:38 +00:00
# pragma mark -
2015-06-18 09:19:42 +00:00
- ( NSString * ) appVersion
{
if ( ! _appVersion )
{
2015-02-04 17:18:38 +00:00
_appVersion = [ [ NSBundle mainBundle ] objectForInfoDictionaryKey : @ "CFBundleShortVersionString" ] ;
}
return _appVersion ;
}
2015-06-18 09:19:42 +00:00
- ( NSString * ) build
{
if ( ! _build )
{
2015-02-04 17:18:38 +00:00
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 ) ;
2015-02-04 17:18:38 +00:00
# endif
2015-02-04 23:16:11 +00:00
# ifdef BUILD_NUMBER
buildNumber = [ NSString stringWithFormat : @ "#%d" , BUILD_NUMBER ] ;
2015-02-04 17:18:38 +00:00
# endif
2015-06-18 09:19:42 +00:00
if ( buildBranch && buildNumber )
{
2015-02-04 17:18:38 +00:00
_build = [ NSString stringWithFormat : @ "%@ %@" , buildBranch , buildNumber ] ;
} else if ( buildNumber ) {
_build = buildNumber ;
2015-06-18 09:19:42 +00:00
} else
{
2015-08-12 07:32:45 +00:00
_build = buildBranch ? buildBranch : NSLocalizedStringFromTable ( @ "settings_config_no_build_info" , @ "Vector" , nil ) ;
2015-02-04 17:18:38 +00:00
}
}
return _build ;
}
2016-02-11 15:09:35 +00:00
- ( void ) setIsOffline : ( BOOL ) isOffline
{
if ( ! reachabilityObserver )
{
// Define reachability observer when isOffline property is set for the first time
reachabilityObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : AFNetworkingReachabilityDidChangeNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * note ) {
NSNumber * statusItem = note . userInfo [ AFNetworkingReachabilityNotificationStatusItem ] ;
if ( statusItem )
{
AFNetworkReachabilityStatus reachabilityStatus = statusItem . integerValue ;
if ( reachabilityStatus = = AFNetworkReachabilityStatusNotReachable )
{
[ AppDelegate theDelegate ] . isOffline = YES ;
}
else
{
[ AppDelegate theDelegate ] . isOffline = NO ;
}
}
} ] ;
}
2016-08-11 08:38:10 +00:00
if ( _isOffline ! = isOffline )
{
_isOffline = isOffline ;
[ [ NSNotificationCenter defaultCenter ] postNotificationName : kAppDelegateNetworkStatusDidChangeNotification object : nil ] ;
}
2016-02-11 15:09:35 +00:00
}
2017-03-14 16:25:46 +00:00
- ( UINavigationController * ) secondaryNavigationController
{
UIViewController * rootViewController = self . window . rootViewController ;
if ( [ rootViewController isKindOfClass : [ UISplitViewController class ] ] )
{
UISplitViewController * splitViewController = ( UISplitViewController * ) rootViewController ;
if ( splitViewController . viewControllers . count = = 2 )
{
UIViewController * secondViewController = [ splitViewController . viewControllers lastObject ] ;
if ( [ secondViewController isKindOfClass : [ UINavigationController class ] ] )
{
return ( UINavigationController * ) secondViewController ;
}
}
}
return nil ;
}
2015-02-12 10:16:28 +00:00
# pragma mark - UIApplicationDelegate
2014-10-02 15:02:47 +00:00
2015-06-18 09:19:42 +00:00
- ( BOOL ) application : ( UIApplication * ) application didFinishLaunchingWithOptions : ( NSDictionary * ) launchOptions
{
2016-03-23 13:45:16 +00:00
# ifdef DEBUG
// log the full launchOptions only in DEBUG
2016-02-03 11:21:45 +00:00
NSLog ( @ "[AppDelegate] didFinishLaunchingWithOptions: %@" , launchOptions ) ;
2016-03-23 13:45:16 +00:00
# else
NSLog ( @ "[AppDelegate] didFinishLaunchingWithOptions" ) ;
# endif
2017-01-24 14:46:46 +00:00
// Log app information
NSString * appDisplayName = [ [ [ NSBundle mainBundle ] infoDictionary ] objectForKey : @ "CFBundleDisplayName" ] ;
NSString * appVersion = [ AppDelegate theDelegate ] . appVersion ;
NSString * build = [ AppDelegate theDelegate ] . build ;
NSLog ( @ "------------------------------" ) ;
NSLog ( @ "Application info:" ) ;
NSLog ( @ "%@ version: %@" , appDisplayName , appVersion ) ;
NSLog ( @ "MatrixKit version: %@" , MatrixKitVersion ) ;
NSLog ( @ "MatrixSDK version: %@" , MatrixSDKVersion ) ;
NSLog ( @ "Build: %@\n" , build ) ;
NSLog ( @ "------------------------------\n" ) ;
2016-02-11 14:19:49 +00:00
// Define the navigation bar text color
2017-03-08 15:14:41 +00:00
[ [ UINavigationBar appearance ] setTintColor : kRiotColorGreen ] ;
2015-11-26 17:23:04 +00:00
2015-12-21 09:35:48 +00:00
// Customize the localized string table
[ NSBundle mxk_customizeLocalizedStringTableName : @ "Vector" ] ;
2015-11-17 23:15:52 +00:00
mxSessionArray = [ NSMutableArray array ] ;
2016-05-20 12:31:23 +00:00
callEventsListeners = [ NSMutableDictionary dictionary ] ;
2015-11-17 23:15:52 +00:00
2017-03-28 16:17:14 +00:00
// To simplify navigation into the app , we retrieve here the main navigation controller and the tab bar controller .
UISplitViewController * splitViewController = ( UISplitViewController * ) self . window . rootViewController ;
splitViewController . delegate = self ;
2015-11-17 23:15:52 +00:00
2017-03-28 16:17:14 +00:00
_masterNavigationController = [ splitViewController . viewControllers objectAtIndex : 0 ] ;
_masterTabBarController = _masterNavigationController . viewControllers . firstObject ;
// 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
2017-04-21 14:28:57 +00:00
if ( [ ( NSString * ) [ UIDevice currentDevice ] . model hasPrefix : @ "iPad" ] )
2015-11-17 23:15:52 +00:00
{
2017-03-28 16:17:14 +00:00
splitViewController . preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible ;
2014-10-02 15:02:47 +00:00
}
2015-11-17 23:15:52 +00:00
// Sanity check
2017-03-23 16:48:05 +00:00
NSAssert ( _masterTabBarController , @ "Something wrong in Main.storyboard" ) ;
2015-11-17 23:15:52 +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 ] ;
2016-06-03 14:07:34 +00:00
// Configure Google Analytics here if the option is enabled
2016-06-04 15:37:23 +00:00
[ self startGoogleAnalytics ] ;
2017-01-23 17:40:30 +00:00
2016-03-23 13:45:16 +00:00
// Add matrix observers , and initialize matrix sessions if the app is not launched in background .
2015-11-17 23:15:52 +00:00
[ self initMatrixSessions ] ;
2015-01-30 08:05:15 +00:00
2014-10-02 15:02:47 +00:00
return YES ;
}
2015-06-18 09:19:42 +00:00
- ( void ) applicationWillResignActive : ( UIApplication * ) application
{
2016-02-03 11:21:45 +00:00
NSLog ( @ "[AppDelegate] applicationWillResignActive" ) ;
2014-10-02 15:02:47 +00:00
// 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 )
{
2014-10-31 17:54:32 +00:00
[ self . errorNotification dismiss : NO ] ;
self . errorNotification = nil ;
}
2015-05-28 16:31:08 +00:00
2015-06-18 09:19:42 +00:00
if ( accountPicker )
{
2015-05-28 16:31:08 +00:00
[ accountPicker dismiss : NO ] ;
accountPicker = nil ;
}
2016-05-20 12:31:23 +00:00
if ( noCallSupportAlert )
{
[ noCallSupportAlert dismiss : NO ] ;
noCallSupportAlert = nil ;
}
2016-12-16 12:43:08 +00:00
if ( cryptoDataCorruptedAlert )
{
[ cryptoDataCorruptedAlert dismiss : NO ] ;
cryptoDataCorruptedAlert = nil ;
}
2014-12-09 17:21:39 +00:00
}
2015-06-18 09:19:42 +00:00
- ( void ) applicationDidEnterBackground : ( UIApplication * ) application
{
2016-02-03 11:21:45 +00:00
NSLog ( @ "[AppDelegate] applicationDidEnterBackground" ) ;
2014-12-09 17:21:39 +00:00
// 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 .
2015-01-12 13:19:23 +00:00
2015-02-12 10:16:28 +00:00
// Stop reachability monitoring
2016-02-11 14:05:24 +00:00
if ( reachabilityObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : reachabilityObserver ] ;
reachabilityObserver = nil ;
}
2016-02-11 15:09:35 +00:00
[ [ AFNetworkReachabilityManager sharedManager ] setReachabilityStatusChangeBlock : nil ] ;
2015-02-12 10:16:28 +00:00
[ [ AFNetworkReachabilityManager sharedManager ] stopMonitoring ] ;
2015-04-30 14:19:12 +00:00
// check if some media must be released to reduce the cache size
2016-12-01 13:55:29 +00:00
[ MXMediaManager reduceCacheSizeToInsert : 0 ] ;
2015-04-30 14:19:12 +00:00
// Hide potential notification
2015-06-18 09:19:42 +00:00
if ( self . mxInAppNotification )
{
2015-04-30 14:19:12 +00:00
[ self . mxInAppNotification dismiss : NO ] ;
self . mxInAppNotification = nil ;
}
2016-04-12 15:44:06 +00:00
// Discard any process on pending universal link
[ self resetPendingUniversalLink ] ;
2015-04-30 14:19:12 +00:00
// Suspend all running matrix sessions
2015-06-19 08:31:27 +00:00
NSArray * mxAccounts = [ MXKAccountManager sharedManager ] . activeAccounts ;
2015-06-18 09:19:42 +00:00
for ( MXKAccount * account in mxAccounts )
{
2015-04-30 14:19:12 +00:00
[ account pauseInBackgroundTask ] ;
}
2015-01-30 08:05:15 +00:00
2016-03-03 08:15:44 +00:00
// Refresh the notifications counter
[ self refreshApplicationIconBadgeNumber ] ;
2015-10-20 08:19:21 +00:00
2015-02-02 15:43:47 +00:00
_isAppForeground = NO ;
2016-06-03 14:07:34 +00:00
// GA : End a session while the app is in background
[ [ [ GAI sharedInstance ] defaultTracker ] set : kGAISessionControl value : @ "end" ] ;
2014-10-02 15:02:47 +00:00
}
2015-06-18 09:19:42 +00:00
- ( void ) applicationWillEnterForeground : ( UIApplication * ) application
{
2016-02-03 11:21:45 +00:00
NSLog ( @ "[AppDelegate] applicationWillEnterForeground" ) ;
2014-10-02 15:02:47 +00:00
// 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 .
2016-02-26 11:27:22 +00:00
2016-03-03 08:15:44 +00:00
// cancel any background sync before resuming
// i . e . warn IOS that there is no new data with any received push .
[ self cancelBackgroundSync ] ;
2015-02-02 15:43:47 +00:00
2016-03-23 13:45:16 +00:00
// Open account session ( s ) if this is not already done ( see [ initMatrixSessions ] in case of background launch ) .
[ [ MXKAccountManager sharedManager ] prepareSessionForActiveAccounts ] ;
2015-02-02 15:43:47 +00:00
_isAppForeground = YES ;
2016-06-03 14:07:34 +00:00
// GA : Start a new session . The next hit from this tracker will be the first in a new session .
[ [ [ GAI sharedInstance ] defaultTracker ] set : kGAISessionControl value : @ "start" ] ;
2014-10-02 15:02:47 +00:00
}
2015-06-18 09:19:42 +00:00
- ( void ) applicationDidBecomeActive : ( UIApplication * ) application
{
2016-02-03 11:21:45 +00:00
NSLog ( @ "[AppDelegate] applicationDidBecomeActive" ) ;
2016-04-06 09:28:12 +00:00
remoteNotificationRoomId = nil ;
2015-11-30 16:22:39 +00:00
2016-08-01 12:21:58 +00:00
// Check if there is crash log to send
if ( [ [ NSUserDefaults standardUserDefaults ] boolForKey : @ "enableCrashReport" ] )
{
[ self checkExceptionToReport ] ;
}
2014-10-02 15:02:47 +00:00
// 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 .
2015-02-12 10:16:28 +00:00
// Start monitoring reachability
2016-02-11 15:09:35 +00:00
[ [ AFNetworkReachabilityManager sharedManager ] setReachabilityStatusChangeBlock : ^ ( AFNetworkReachabilityStatus status ) {
2016-02-11 14:05:24 +00:00
2016-02-11 15:09:35 +00:00
// Check whether monitoring is ready
if ( status ! = AFNetworkReachabilityStatusUnknown )
2016-02-11 14:05:24 +00:00
{
2016-02-11 15:09:35 +00:00
if ( status = = AFNetworkReachabilityStatusNotReachable )
2016-02-11 14:05:24 +00:00
{
2016-02-11 15:09:35 +00:00
// Prompt user
2016-02-11 14:05:24 +00:00
[ [ AppDelegate theDelegate ] showErrorAsAlert : [ NSError errorWithDomain : NSURLErrorDomain code : NSURLErrorNotConnectedToInternet userInfo : @ { NSLocalizedDescriptionKey : NSLocalizedStringFromTable ( @ "network_offline_prompt" , @ "Vector" , nil ) } ] ] ;
}
else
{
self . isOffline = NO ;
}
2016-08-01 15:07:39 +00:00
// Use a dispatch to avoid to kill ourselves
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ [ AFNetworkReachabilityManager sharedManager ] setReachabilityStatusChangeBlock : nil ] ;
} ) ;
2016-02-11 14:05:24 +00:00
}
} ] ;
2015-02-12 10:16:28 +00:00
[ [ 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 ] ;
} ] ;
2016-12-16 12:43:08 +00:00
// Observe crypto data storage corruption
[ [ NSNotificationCenter defaultCenter ] addObserver : self selector : @ selector ( onSessionCryptoDidCorruptData : ) name : kMXSessionCryptoDidCorruptDataNotification object : nil ] ;
2015-08-04 16:37:48 +00:00
2016-04-05 13:31:52 +00:00
// Resume all existing matrix sessions
NSArray * mxAccounts = [ MXKAccountManager sharedManager ] . activeAccounts ;
for ( MXKAccount * account in mxAccounts )
2015-06-18 09:19:42 +00:00
{
2016-04-05 13:31:52 +00:00
[ account resume ] ;
2015-03-10 13:15:02 +00:00
}
2016-04-05 13:31:52 +00:00
2017-01-17 22:13:30 +00:00
// Refresh local contact from the contact book .
[ self refreshLocalContacts ] ;
2016-04-05 13:31:52 +00:00
_isAppForeground = YES ;
2017-01-31 23:18:01 +00:00
[ self handleLaunchAnimation ] ;
2014-10-02 15:02:47 +00:00
}
2015-06-18 09:19:42 +00:00
- ( void ) applicationWillTerminate : ( UIApplication * ) application
{
2016-02-03 11:21:45 +00:00
NSLog ( @ "[AppDelegate] applicationWillTerminate" ) ;
2014-10-02 15:02:47 +00:00
// Called when the application is about to terminate . Save data if appropriate . See also applicationDidEnterBackground : .
2016-06-03 14:07:34 +00:00
2016-06-04 15:37:23 +00:00
[ self stopGoogleAnalytics ] ;
2014-10-02 15:02:47 +00:00
}
2016-04-06 16:05:26 +00:00
- ( BOOL ) application : ( UIApplication * ) application continueUserActivity : ( NSUserActivity * ) userActivity restorationHandler : ( void ( ^ ) ( NSArray * _Nullable ) ) restorationHandler
{
2016-08-04 09:56:06 +00:00
BOOL continueUserActivity = NO ;
2016-04-12 08:23:48 +00:00
2016-04-06 16:05:26 +00:00
if ( [ userActivity . activityType isEqualToString : NSUserActivityTypeBrowsingWeb ] )
{
2016-04-12 08:23:48 +00:00
continueUserActivity = [ self handleUniversalLink : userActivity ] ;
2016-04-06 16:05:26 +00:00
}
2016-04-07 15:38:19 +00:00
return continueUserActivity ;
2016-04-06 16:05:26 +00:00
}
2015-11-17 23:15:52 +00:00
# pragma mark - Application layout handling
2016-04-05 16:05:50 +00:00
- ( void ) restoreInitialDisplay : ( void ( ^ ) ( ) ) completion
2015-11-17 23:15:52 +00:00
{
2016-06-16 14:50:41 +00:00
// Suspend error notifications during navigation stack change .
isErrorNotificationSuspended = YES ;
2016-05-23 14:01:11 +00:00
// Dismiss potential view controllers that were presented modally ( like the media picker ) .
2016-04-05 16:05:50 +00:00
if ( self . window . rootViewController . presentedViewController )
2015-11-17 23:15:52 +00:00
{
2016-04-05 16:05:50 +00:00
// Do it asynchronously to avoid hasardous dispatch_async after calling restoreInitialDisplay
[ self . window . rootViewController dismissViewControllerAnimated : NO completion : ^ {
2016-05-23 14:01:11 +00:00
[ self popToHomeViewControllerAnimated : NO completion : ^ {
if ( completion )
{
completion ( ) ;
}
2016-09-06 15:20:16 +00:00
// Enable error notifications
isErrorNotificationSuspended = NO ;
// Restore call alert if any
if ( _incomingCallNotification )
{
NSLog ( @ "[AppDelegate] restoreInitialDisplay: keep visible incoming call alert" ) ;
[ self showNotificationAlert : _incomingCallNotification ] ;
}
else if ( noCallSupportAlert )
2016-05-23 14:01:11 +00:00
{
NSLog ( @ "[AppDelegate] restoreInitialDisplay: keep visible noCall support alert" ) ;
2016-09-06 15:20:16 +00:00
[ self showNotificationAlert : noCallSupportAlert ] ;
2016-05-23 14:01:11 +00:00
}
2016-12-16 12:43:08 +00:00
else if ( cryptoDataCorruptedAlert )
{
NSLog ( @ "[AppDelegate] restoreInitialDisplay: keep visible log in again" ) ;
[ self showNotificationAlert : cryptoDataCorruptedAlert ] ;
}
2016-09-06 15:20:16 +00:00
// Check whether an error notification is pending
else if ( _errorNotification )
2016-06-16 14:50:41 +00:00
{
2016-09-06 15:20:16 +00:00
[ self showNotificationAlert : _errorNotification ] ;
2016-06-16 14:50:41 +00:00
}
2016-05-23 14:01:11 +00:00
} ] ;
2016-05-23 13:39:48 +00:00
2016-04-05 16:05:50 +00:00
} ] ;
}
else
{
2016-06-16 14:50:41 +00:00
[ self popToHomeViewControllerAnimated : NO completion : ^ {
if ( completion )
{
completion ( ) ;
}
// Enable error notification ( Check whether a notification is pending )
isErrorNotificationSuspended = NO ;
2016-09-06 15:20:16 +00:00
if ( _errorNotification )
2016-06-16 14:50:41 +00:00
{
2016-09-06 15:20:16 +00:00
[ self showNotificationAlert : _errorNotification ] ;
2016-06-16 14:50:41 +00:00
}
} ] ;
2015-11-17 23:15:52 +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
if ( self . isOffline && [ error . domain isEqualToString : NSURLErrorDomain ] && error . code = = NSURLErrorNotConnectedToInternet )
{
return nil ;
}
2016-09-06 15:20:16 +00:00
[ _errorNotification dismiss : NO ] ;
2015-11-17 23:15:52 +00:00
NSString * title = [ error . userInfo valueForKey : NSLocalizedFailureReasonErrorKey ] ;
2016-02-11 13:06:48 +00:00
NSString * msg = [ error . userInfo valueForKey : NSLocalizedDescriptionKey ] ;
2015-11-17 23:15:52 +00:00
if ( ! title )
{
2016-02-11 13:06:48 +00:00
if ( msg )
{
title = msg ;
msg = nil ;
}
else
{
title = [ NSBundle mxk_localizedStringForKey : @ "error" ] ;
}
2015-11-17 23:15:52 +00:00
}
2016-09-06 15:20:16 +00:00
_errorNotification = [ [ MXKAlert alloc ] initWithTitle : title message : msg style : MXKAlertStyleAlert ] ;
_errorNotification . cancelButtonIndex = [ self . errorNotification addActionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "ok" ] style : MXKAlertActionStyleDefault handler : ^ ( MXKAlert * alert ) {
[ AppDelegate theDelegate ] . errorNotification = nil ;
} ] ;
2015-11-17 23:15:52 +00:00
2016-06-16 14:50:41 +00:00
// Display the error notification
2016-09-06 15:20:16 +00:00
if ( ! isErrorNotificationSuspended )
{
2017-02-08 16:12:43 +00:00
_errorNotification . mxkAccessibilityIdentifier = @ "AppDelegateErrorAlert" ;
2016-09-06 15:20:16 +00:00
[ self showNotificationAlert : _errorNotification ] ;
}
2015-11-17 23:15:52 +00:00
// Switch in offline mode in case of network reachability error
if ( [ error . domain isEqualToString : NSURLErrorDomain ] && error . code = = NSURLErrorNotConnectedToInternet )
{
self . isOffline = YES ;
}
return self . errorNotification ;
}
2016-09-06 15:20:16 +00:00
- ( void ) showNotificationAlert : ( MXKAlert * ) alert
2016-06-16 14:50:41 +00:00
{
2016-09-06 15:20:16 +00:00
if ( self . window . rootViewController . presentedViewController )
2016-06-16 14:50:41 +00:00
{
2016-09-06 15:20:16 +00:00
alert . sourceView = self . window . rootViewController . presentedViewController . view ;
[ alert showInViewController : self . window . rootViewController . presentedViewController ] ;
}
else
{
alert . sourceView = self . window . rootViewController . view ;
[ alert showInViewController : self . window . rootViewController ] ;
2016-06-16 14:50:41 +00:00
}
}
2016-12-16 12:43:08 +00:00
- ( void ) onSessionCryptoDidCorruptData : ( NSNotification * ) notification
{
NSString * userId = notification . object ;
MXKAccount * account = [ [ MXKAccountManager sharedManager ] accountForUserId : userId ] ;
if ( account )
{
if ( cryptoDataCorruptedAlert )
{
[ cryptoDataCorruptedAlert dismiss : NO ] ;
}
cryptoDataCorruptedAlert = [ [ MXKAlert alloc ] initWithTitle : nil
message : NSLocalizedStringFromTable ( @ "e2e_need_log_in_again" , @ "Vector" , nil )
style : MXKAlertStyleAlert ] ;
__weak typeof ( self ) weakSelf = self ;
cryptoDataCorruptedAlert . cancelButtonIndex = [ cryptoDataCorruptedAlert addActionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "later" ] style : MXKAlertActionStyleDefault handler : ^ ( MXKAlert * alert ) {
if ( weakSelf )
{
2017-03-23 16:48:05 +00:00
typeof ( self ) self = weakSelf ;
self -> cryptoDataCorruptedAlert = nil ;
2016-12-16 12:43:08 +00:00
}
} ] ;
[ cryptoDataCorruptedAlert addActionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "settings_sign_out" ] style : MXKAlertActionStyleDefault handler : ^ ( MXKAlert * alert ) {
2017-03-23 16:48:05 +00:00
typeof ( self ) self = weakSelf ;
self -> cryptoDataCorruptedAlert = nil ;
2016-12-16 12:43:08 +00:00
[ [ MXKAccountManager sharedManager ] removeAccount : account completion : nil ] ;
} ] ;
[ self showNotificationAlert : cryptoDataCorruptedAlert ] ;
}
}
2016-04-05 16:05:50 +00:00
# pragma mark
2016-04-13 15:08:02 +00:00
- ( void ) popToHomeViewControllerAnimated : ( BOOL ) animated completion : ( void ( ^ ) ( ) ) completion
2016-04-05 16:05:50 +00:00
{
2017-03-14 16:25:46 +00:00
UINavigationController * secondNavController = self . secondaryNavigationController ;
if ( secondNavController )
{
[ secondNavController popToRootViewControllerAnimated : animated ] ;
}
2017-03-23 16:48:05 +00:00
// Force back to the main screen if this is not the one that is displayed
if ( _masterTabBarController && _masterTabBarController ! = _masterNavigationController . visibleViewController )
2016-04-05 16:05:50 +00:00
{
2017-03-23 16:48:05 +00:00
// Listen to the masterNavigationController changes
// We need to be sure that masterTabBarController is back to the screen
2016-04-13 15:08:02 +00:00
popToHomeViewControllerCompletion = completion ;
2017-03-23 16:48:05 +00:00
_masterNavigationController . delegate = self ;
2016-04-13 15:08:02 +00:00
2017-03-23 16:48:05 +00:00
[ _masterNavigationController popToViewController : _masterTabBarController animated : animated ] ;
2016-04-05 16:05:50 +00:00
}
2016-04-13 15:08:02 +00:00
else
{
2017-03-23 16:48:05 +00:00
// Select the Home tab
_masterTabBarController . selectedIndex = TABBAR_HOME _INDEX ;
2016-06-16 14:50:41 +00:00
if ( completion )
{
completion ( ) ;
}
2016-04-13 15:08:02 +00:00
}
}
# pragma mark - UINavigationController delegate
- ( void ) navigationController : ( UINavigationController * ) navigationController didShowViewController : ( UIViewController * ) viewController animated : ( BOOL ) animated
{
2017-03-23 16:48:05 +00:00
if ( viewController = = _masterTabBarController )
2016-04-13 15:08:02 +00:00
{
2017-03-23 16:48:05 +00:00
_masterNavigationController . delegate = nil ;
2016-06-10 14:11:09 +00:00
// For unknown reason , the navigation bar is not restored correctly by [ popToViewController : animated : ]
// when a ViewController has hidden it ( see MXKAttachmentsViewController ) .
// Patch : restore navigation bar by default here .
2017-03-23 16:48:05 +00:00
_masterNavigationController . navigationBarHidden = NO ;
2016-06-10 14:11:09 +00:00
2017-03-28 15:26:50 +00:00
// Release the current selected item ( room / contact /. . . ) .
[ _masterTabBarController releaseSelectedItem ] ;
2017-03-23 16:48:05 +00:00
// Select the Home tab
_masterTabBarController . selectedIndex = TABBAR_HOME _INDEX ;
2016-06-10 14:11:09 +00:00
2016-04-13 15:08:02 +00:00
if ( popToHomeViewControllerCompletion )
{
2016-04-19 07:14:32 +00:00
void ( ^ popToHomeViewControllerCompletion2 ) ( ) = popToHomeViewControllerCompletion ;
popToHomeViewControllerCompletion = nil ;
// Dispatch the completion in order to let navigation stack refresh itself .
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
popToHomeViewControllerCompletion2 ( ) ;
} ) ;
2016-04-13 15:08:02 +00:00
}
}
2016-04-05 16:05:50 +00:00
}
2016-06-03 14:07:34 +00:00
# pragma mark - Crash report handling
2016-06-04 15:37:23 +00:00
- ( void ) startGoogleAnalytics
2016-06-03 14:07:34 +00:00
{
// Check whether the user has enabled the sending of crash reports .
if ( [ [ NSUserDefaults standardUserDefaults ] boolForKey : @ "enableCrashReport" ] )
{
2016-06-03 14:38:02 +00:00
// Retrieve trackerId from GoogleService - Info . plist .
NSString * googleServiceInfoPath = [ [ NSBundle mainBundle ] pathForResource : @ "GoogleService-Info" ofType : @ "plist" ] ;
NSDictionary * googleServiceInfo = [ NSDictionary dictionaryWithContentsOfFile : googleServiceInfoPath ] ;
NSString * gaTrackingID = [ googleServiceInfo objectForKey : @ "TRACKING_ID" ] ;
if ( gaTrackingID )
{
// Catch and log crashes
[ MXLogger logCrashes : YES ] ;
[ MXLogger setBuildVersion : [ AppDelegate theDelegate ] . build ] ;
// Configure GAI options .
GAI * gai = [ GAI sharedInstance ] ;
// Disable GA UncaughtException : their crash reports are quite limited ( 100 first chars of the stack trace )
// Let ' s MXLogger manage them
gai . trackUncaughtExceptions = NO ;
// Initialize it with the app tracker ID
[ gai trackerWithTrackingId : gaTrackingID ] ;
// Set Google Analytics dispatch interval to e . g . 20 seconds .
gai . dispatchInterval = 20 ;
}
else
{
NSLog ( @ "[AppDelegate] Unable to find tracker id for Google Analytics" ) ;
}
2016-06-03 14:07:34 +00:00
}
else if ( [ [ NSUserDefaults standardUserDefaults ] objectForKey : @ "enableCrashReport" ] )
{
NSLog ( @ "[AppDelegate] The user decides to do not use Google Analytics" ) ;
}
}
2016-06-04 15:37:23 +00:00
- ( void ) stopGoogleAnalytics
{
GAI * gai = [ GAI sharedInstance ] ;
// End a session . The next hit from this tracker will be the last in the current session .
[ [ gai defaultTracker ] set : kGAISessionControl value : @ "end" ] ;
// Flush pending GA messages
[ gai dispatch ] ;
[ gai removeTrackerByName : [ gai defaultTracker ] . name ] ;
[ MXLogger logCrashes : NO ] ;
}
2016-06-03 14:07:34 +00:00
// Check if there is a crash log to send to server
- ( void ) checkExceptionToReport
{
// Check if the app crashed last time
NSString * filePath = [ MXLogger crashLog ] ;
if ( filePath )
{
NSString * description = [ [ NSString alloc ] initWithContentsOfFile : filePath
usedEncoding : nil
error : nil ] ;
NSLog ( @ "[AppDelegate] Send crash log to Google Analytics:\n%@" , description ) ;
// Send it via Google Analytics
// The doc says the exception description must not exceeed 100 chars but it seems
// to accept much more .
// https : // developers . google . com / analytics / devguides / collection / ios / v3 / exceptions # overview
2016-06-03 15:29:34 +00:00
id < GAITracker > tracker = [ [ GAI sharedInstance ] defaultTracker ] ;
2016-06-03 14:07:34 +00:00
[ tracker send : [ [ GAIDictionaryBuilder
createExceptionWithDescription : description
withFatal : [ NSNumber numberWithBool : YES ] ] build ] ] ;
[ [ GAI sharedInstance ] dispatch ] ;
2016-08-01 12:21:58 +00:00
// Ask the user to send a crash report by email too
// The email will provide logs and thus more context to the crash
[ [ RageShakeManager sharedManager ] promptCrashReportInViewController : self . window . rootViewController ] ;
2016-06-03 14:07:34 +00:00
}
}
2014-12-12 09:49:15 +00:00
# pragma mark - APNS methods
2015-06-18 09:19:42 +00:00
- ( void ) registerUserNotificationSettings
{
if ( ! isAPNSRegistered )
{
2015-12-21 09:35:48 +00:00
// Registration on iOS 8 and later
UIUserNotificationSettings * settings = [ UIUserNotificationSettings settingsForTypes : ( UIUserNotificationTypeBadge | UIUserNotificationTypeSound | UIUserNotificationTypeAlert ) categories : nil ] ;
[ [ UIApplication sharedApplication ] registerUserNotificationSettings : settings ] ;
2014-12-18 15:29:34 +00:00
}
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-10-28 14:33:07 +00:00
NSUInteger len = ( ( deviceToken . length > 8 ) ? 8 : deviceToken . length / 2 ) ;
NSLog ( @ "[AppDelegate] Got APNS token! (%@ ...)" , [ deviceToken subdataWithRange : NSMakeRange ( 0 , len ) ] ) ;
2014-12-12 09:49:15 +00:00
2015-06-18 13:20:33 +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
}
2015-10-20 08:19:21 +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
2016-03-23 13:45:16 +00:00
NSLog ( @ "[AppDelegate] didReceiveRemoteNotification: %@" , userInfo ) ;
# else
NSLog ( @ "[AppDelegate] didReceiveRemoteNotification" ) ;
2014-12-12 09:49:15 +00:00
# endif
2015-10-20 08:19:21 +00:00
// Look for the room id
NSString * roomId = [ userInfo objectForKey : @ "room_id" ] ;
if ( roomId . length )
2015-06-18 09:19:42 +00:00
{
2015-10-20 08:19:21 +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
{
2015-10-20 08:19:21 +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 ;
2015-05-28 17:20:18 +00:00
2015-10-20 08:19:21 +00:00
// 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
{
2016-04-06 09:28:12 +00:00
// Check whether another remote notification is not already processed
if ( ! remoteNotificationRoomId )
{
remoteNotificationRoomId = roomId ;
NSLog ( @ "[AppDelegate] didReceiveRemoteNotification: open the roomViewController %@" , roomId ) ;
2016-04-07 15:38:19 +00:00
[ self showRoom : roomId andEventId : nil withMatrixSession : dedicatedAccount . mxSession ] ;
2016-04-06 09:28:12 +00:00
}
else
{
NSLog ( @ "[AppDelegate] didReceiveRemoteNotification: busy" ) ;
}
2015-10-20 08:19:21 +00:00
}
else if ( ! _completionHandler && ( state = = UIApplicationStateBackground ) )
2015-06-18 09:19:42 +00:00
{
2015-10-20 08:19:21 +00:00
_completionHandler = completionHandler ;
2015-11-30 16:22:39 +00:00
2016-05-23 13:39:48 +00:00
NSLog ( @ "[AppDelegate] didReceiveRemoteNotification: starts a background sync" ) ;
2015-10-20 08:19:21 +00:00
2016-01-07 13:00:37 +00:00
[ dedicatedAccount backgroundSync : 20000 success : ^ {
2016-05-23 13:39:48 +00:00
NSLog ( @ "[AppDelegate] didReceiveRemoteNotification: the background sync succeeds" ) ;
2015-10-20 08:19:21 +00:00
if ( _completionHandler )
2015-06-18 09:19:42 +00:00
{
2015-10-20 08:19:21 +00:00
_completionHandler ( UIBackgroundFetchResultNewData ) ;
_completionHandler = nil ;
2015-05-28 17:20:18 +00:00
}
2015-10-20 08:19:21 +00:00
} failure : ^ ( NSError * error ) {
2016-05-23 13:39:48 +00:00
NSLog ( @ "[AppDelegate] didReceiveRemoteNotification: the background sync fails" ) ;
2015-10-20 08:19:21 +00:00
if ( _completionHandler )
{
_completionHandler ( UIBackgroundFetchResultNoData ) ;
_completionHandler = nil ;
}
} ] ;
// wait that the background sync is done
return ;
2015-05-28 17:20:18 +00:00
}
2015-10-20 08:19:21 +00:00
}
else
{
2016-05-23 13:39:48 +00:00
NSLog ( @ "[AppDelegate] didReceiveRemoteNotification : no linked session / account has been found." ) ;
2015-02-19 17:23:24 +00:00
}
2015-02-13 15:16:27 +00:00
}
2015-10-20 08:19:21 +00:00
completionHandler ( UIBackgroundFetchResultNoData ) ;
2014-12-12 09:49:15 +00:00
}
2016-09-12 08:33:10 +00:00
- ( void ) application : ( UIApplication * ) application didReceiveRemoteNotification : ( NSDictionary * ) userInfo
{
// iOS 10 ( at least up to GM beta release ) does not call application : didReceiveRemoteNotification : fetchCompletionHandler :
// when the user clicks on a notification but it calls this deprecated version
// of didReceiveRemoteNotification .
// Use this method as a workaround as adviced at http : // stackoverflow . com / a / 39419245
NSLog ( @ "[AppDelegate] didReceiveRemoteNotification (deprecated version)" ) ;
[ self application : application didReceiveRemoteNotification : userInfo fetchCompletionHandler : ^ ( UIBackgroundFetchResult result ) {
} ] ;
}
2016-03-03 08:15:44 +00:00
- ( void ) refreshApplicationIconBadgeNumber
{
2017-04-18 13:55:51 +00:00
// Consider the total number of missed discussions including the invites .
NSUInteger count = [ self . masterTabBarController missedDiscussionsCount ] ;
2016-04-26 12:45:19 +00:00
NSLog ( @ "[AppDelegate] refreshApplicationIconBadgeNumber: %tu" , count ) ;
[ UIApplication sharedApplication ] . applicationIconBadgeNumber = count ;
2016-03-03 08:15:44 +00:00
}
2015-11-17 23:15:52 +00:00
2016-04-12 08:23:48 +00:00
# pragma mark - Universal link
- ( BOOL ) handleUniversalLink : ( NSUserActivity * ) userActivity
{
NSURL * webURL = userActivity . webpageURL ;
NSLog ( @ "[AppDelegate] handleUniversalLink: %@" , webURL . absoluteString ) ;
2016-04-20 07:21:46 +00:00
// iOS Patch : fix vector . im urls before using it
2016-04-29 14:19:13 +00:00
webURL = [ Tools fixURLWithSeveralHashKeys : webURL ] ;
2016-04-20 07:21:46 +00:00
2016-04-22 15:54:57 +00:00
// Manage email validation link
if ( [ webURL . path isEqualToString : @ "/_matrix/identity/api/v1/validate/email/submitToken" ] )
{
// Validate the email on the passed identity server
NSString * identityServer = [ NSString stringWithFormat : @ "%@://%@" , webURL . scheme , webURL . host ] ;
MXRestClient * identityRestClient = [ [ MXRestClient alloc ] initWithHomeServer : identityServer andOnUnrecognizedCertificateBlock : nil ] ;
// Extract required parameters from the link
NSArray < NSString * > * pathParams ;
NSMutableDictionary * queryParams ;
[ self parseUniversalLinkFragment : webURL . absoluteString outPathParams : & pathParams outQueryParams : & queryParams ] ;
2017-02-20 17:34:41 +00:00
[ identityRestClient submit3PIDValidationToken : queryParams [ @ "token" ] medium : kMX3PIDMediumEmail clientSecret : queryParams [ @ "client_secret" ] sid : queryParams [ @ "sid" ] success : ^ {
2016-04-22 15:54:57 +00:00
NSLog ( @ "[AppDelegate] handleUniversalLink. Email successfully validated." ) ;
if ( queryParams [ @ "nextLink" ] )
{
// Continue the registration with the passed nextLink
NSLog ( @ "[AppDelegate] handleUniversalLink. Complete registration with nextLink" ) ;
NSURL * nextLink = [ NSURL URLWithString : queryParams [ @ "nextLink" ] ] ;
[ self handleUniversalLinkFragment : nextLink . fragment ] ;
}
else
{
// No nextLink in Vector world means validation for binding a new email
NSLog ( @ "[AppDelegate] handleUniversalLink. TODO: Complete email binding" ) ;
}
} failure : ^ ( NSError * error ) {
2016-10-21 15:10:02 +00:00
NSLog ( @ "[AppDelegate] handleUniversalLink. Error: submitToken failed" ) ;
2016-04-22 15:54:57 +00:00
[ self showErrorAsAlert : error ] ;
} ] ;
return YES ;
}
2016-04-12 12:19:38 +00:00
return [ self handleUniversalLinkFragment : webURL . fragment ] ;
}
- ( BOOL ) handleUniversalLinkFragment : ( NSString * ) fragment
{
BOOL continueUserActivity = NO ;
2016-04-12 13:30:02 +00:00
MXKAccountManager * accountManager = [ MXKAccountManager sharedManager ] ;
2016-04-12 12:19:38 +00:00
2016-04-12 15:44:06 +00:00
NSLog ( @ "[AppDelegate] Universal link: handleUniversalLinkFragment: %@" , fragment ) ;
// The app manages only one universal link at a time
// Discard any pending one
[ self resetPendingUniversalLink ] ;
2016-04-12 12:19:38 +00:00
2016-04-12 09:19:50 +00:00
// Extract params
2016-04-12 08:23:48 +00:00
NSArray < NSString * > * pathParams ;
NSMutableDictionary * queryParams ;
2016-04-12 12:19:38 +00:00
[ self parseUniversalLinkFragment : fragment outPathParams : & pathParams outQueryParams : & queryParams ] ;
2016-04-12 08:23:48 +00:00
2016-04-20 07:21:46 +00:00
// Sanity check
if ( ! pathParams . count )
{
NSLog ( @ "[AppDelegate] Universal link: Error: No path parameters" ) ;
return NO ;
}
2016-08-29 09:57:01 +00:00
NSString * roomIdOrAlias ;
NSString * eventId ;
// Check permalink to room or event
2016-04-12 08:23:48 +00:00
if ( [ pathParams [ 0 ] isEqualToString : @ "room" ] && pathParams . count >= 2 )
{
2016-08-29 09:57:01 +00:00
// The link is the form of "/room/[roomIdOrAlias]" or "/room/[roomIdOrAlias]/[eventId]"
roomIdOrAlias = pathParams [ 1 ] ;
// Is it a link to an event of a room ?
eventId = ( pathParams . count >= 3 ) ? pathParams [ 2 ] : nil ;
}
else if ( ( [ pathParams [ 0 ] hasPrefix : @ "#" ] || [ pathParams [ 0 ] hasPrefix : @ "!" ] ) && pathParams . count >= 1 )
{
// The link is the form of "/#/[roomIdOrAlias]" or "/#/[roomIdOrAlias]/[eventId]"
// Such links come from matrix . to permalinks
roomIdOrAlias = pathParams [ 0 ] ;
eventId = ( pathParams . count >= 2 ) ? pathParams [ 1 ] : nil ;
}
2017-02-13 16:58:29 +00:00
// Check the conditions to keep the room alias information of a pending fragment .
if ( universalLinkFragmentPendingRoomAlias )
{
if ( ! roomIdOrAlias || ! universalLinkFragmentPendingRoomAlias [ roomIdOrAlias ] )
{
universalLinkFragmentPendingRoomAlias = nil ;
}
}
2016-08-29 09:57:01 +00:00
if ( roomIdOrAlias )
2017-02-13 16:58:29 +00:00
{
2016-04-12 13:30:02 +00:00
if ( accountManager . activeAccounts . count )
2016-04-12 08:23:48 +00:00
{
2016-04-12 13:30:02 +00:00
// Check there is an account that knows this room
MXKAccount * account = [ accountManager accountKnowingRoomWithRoomIdOrAlias : roomIdOrAlias ] ;
if ( account )
2016-04-12 08:23:48 +00:00
{
2016-04-12 13:30:02 +00:00
NSString * roomId = roomIdOrAlias ;
// Translate the alias into the room id
if ( [ roomIdOrAlias hasPrefix : @ "#" ] )
2016-04-12 08:23:48 +00:00
{
2016-04-12 13:30:02 +00:00
MXRoom * room = [ account . mxSession roomWithAlias : roomIdOrAlias ] ;
if ( room )
{
roomId = room . roomId ;
}
2016-04-12 08:23:48 +00:00
}
2016-04-12 13:30:02 +00:00
// Open the room page
[ self showRoom : roomId andEventId : eventId withMatrixSession : account . mxSession ] ;
continueUserActivity = YES ;
2016-04-12 08:23:48 +00:00
}
2016-04-19 13:18:24 +00:00
else
2016-04-12 13:30:02 +00:00
{
2016-04-19 13:18:24 +00:00
// We will display something but we need to do some requests before .
// So , come back to the home VC and show its loading wheel while processing
[ self restoreInitialDisplay : ^ {
2017-03-23 16:48:05 +00:00
if ( [ _masterTabBarController . selectedViewController isKindOfClass : MXKViewController . class ] )
2016-04-19 13:18:24 +00:00
{
2017-03-23 16:48:05 +00:00
MXKViewController * homeViewController = ( MXKViewController * ) _masterTabBarController . selectedViewController ;
[ homeViewController startActivityIndicator ] ;
if ( [ roomIdOrAlias hasPrefix : @ "#" ] )
{
// The alias may be not part of user ' s rooms states
// Ask the HS to resolve the room alias into a room id and then retry
universalLinkFragmentPending = fragment ;
MXKAccount * account = accountManager . activeAccounts . firstObject ;
[ account . mxSession . matrixRestClient roomIDForRoomAlias : roomIdOrAlias success : ^ ( NSString * roomId ) {
2017-02-13 16:58:29 +00:00
2017-03-23 16:48:05 +00:00
// Note : the activity indicator will not disappear if the session is not ready
[ homeViewController stopActivityIndicator ] ;
2017-02-13 16:58:29 +00:00
2017-03-23 16:48:05 +00:00
// Check that ' fragment ' has not been cancelled
if ( [ universalLinkFragmentPending isEqualToString : fragment ] )
2016-04-19 13:18:24 +00:00
{
2017-03-23 16:48:05 +00:00
// Retry opening the link but with the returned room id
NSString * newUniversalLinkFragment =
[ fragment stringByReplacingOccurrencesOfString : [ roomIdOrAlias stringByAddingPercentEscapesUsingEncoding : NSUTF8StringEncoding ]
withString : [ roomId stringByAddingPercentEscapesUsingEncoding : NSUTF8StringEncoding ] ] ;
universalLinkFragmentPendingRoomAlias = @ { roomId : roomIdOrAlias } ;
[ self handleUniversalLinkFragment : newUniversalLinkFragment ] ;
2016-04-19 13:18:24 +00:00
}
2017-03-23 16:48:05 +00:00
} failure : ^ ( NSError * error ) {
NSLog ( @ "[AppDelegate] Universal link: Error: The home server failed to resolve the room alias (%@)" , roomIdOrAlias ) ;
} ] ;
}
else if ( [ roomIdOrAlias hasPrefix : @ "!" ] && ( ( MXKAccount * ) accountManager . activeAccounts . firstObject ) . mxSession . state ! = MXSessionStateRunning )
2016-04-19 13:18:24 +00:00
{
2017-03-23 16:48:05 +00:00
// The user does not know the room id but this may be because their session is not yet sync ' ed
// So , wait for the completion of the sync and then retry
// FIXME : Manange all user ' s accounts not only the first one
MXKAccount * account = accountManager . activeAccounts . firstObject ;
NSLog ( @ "[AppDelegate] Universal link: Need to wait for the session to be sync'ed and running" ) ;
universalLinkFragmentPending = fragment ;
universalLinkWaitingObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXSessionStateDidChangeNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * _Nonnull notif ) {
// Check that ' fragment ' has not been cancelled
if ( [ universalLinkFragmentPending isEqualToString : fragment ] )
{
// Check whether the concerned session is the associated one
if ( notif . object = = account . mxSession && account . mxSession . state = = MXSessionStateRunning )
{
NSLog ( @ "[AppDelegate] Universal link: The session is running. Retry the link" ) ;
[ self handleUniversalLinkFragment : fragment ] ;
}
}
} ] ;
2016-04-19 13:18:24 +00:00
}
else
{
2017-03-23 16:48:05 +00:00
NSLog ( @ "[AppDelegate] Universal link: The room (%@) is not known by any account (email invitation: %@). Display its preview to try to join it" , roomIdOrAlias , queryParams ? @ "YES" : @ "NO" ) ;
// FIXME : In case of multi - account , ask the user which one to use
MXKAccount * account = accountManager . activeAccounts . firstObject ;
RoomPreviewData * roomPreviewData ;
if ( queryParams )
{
2016-04-19 13:18:24 +00:00
// Note : the activity indicator will not disappear if the session is not ready
2017-03-23 16:48:05 +00:00
[ homeViewController stopActivityIndicator ] ;
2017-02-13 16:58:29 +00:00
2017-03-23 16:48:05 +00:00
roomPreviewData = [ [ RoomPreviewData alloc ] initWithRoomId : roomIdOrAlias emailInvitationParams : queryParams andSession : account . mxSession ] ;
2016-04-19 13:18:24 +00:00
[ self showRoomPreview : roomPreviewData ] ;
2017-03-23 16:48:05 +00:00
}
else
{
roomPreviewData = [ [ RoomPreviewData alloc ] initWithRoomId : roomIdOrAlias andSession : account . mxSession ] ;
// Is it a link to an event of a room ?
// If yes , the event will be displayed once the room is joined
roomPreviewData . eventId = ( pathParams . count >= 3 ) ? pathParams [ 2 ] : nil ;
// Try to get more information about the room before opening its preview
[ roomPreviewData peekInRoom : ^ ( BOOL succeeded ) {
// Note : the activity indicator will not disappear if the session is not ready
[ homeViewController stopActivityIndicator ] ;
// If no data is available for this room , we name it with the known room alias ( if any ) .
if ( ! succeeded && universalLinkFragmentPendingRoomAlias [ roomIdOrAlias ] )
{
roomPreviewData . roomName = universalLinkFragmentPendingRoomAlias [ roomIdOrAlias ] ;
}
universalLinkFragmentPendingRoomAlias = nil ;
[ self showRoomPreview : roomPreviewData ] ;
} ] ;
}
2016-04-12 15:44:06 +00:00
}
2017-03-23 16:48:05 +00:00
2016-04-12 13:30:02 +00:00
}
} ] ;
2016-04-12 13:56:17 +00:00
// Let ' s say we are handling the case
continueUserActivity = YES ;
2016-04-12 13:30:02 +00:00
}
2016-04-12 12:19:38 +00:00
}
2016-04-12 08:23:48 +00:00
else
{
2016-04-12 13:56:17 +00:00
// There is no account . The app will display the AuthenticationVC .
// Wait for a successful login
NSLog ( @ "[AppDelegate] Universal link: The user is not logged in. Wait for a successful login" ) ;
2016-04-12 15:44:06 +00:00
universalLinkFragmentPending = fragment ;
2016-04-12 13:56:17 +00:00
// Register an observer in order to handle new account
2016-04-12 15:44:06 +00:00
universalLinkWaitingObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXKAccountManagerDidAddAccountNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
2016-04-12 13:56:17 +00:00
2016-04-12 15:44:06 +00:00
// Check that ' fragment ' has not been cancelled
if ( [ universalLinkFragmentPending isEqualToString : fragment ] )
{
NSLog ( @ "[AppDelegate] Universal link: The user is now logged in. Retry the link" ) ;
[ self handleUniversalLinkFragment : fragment ] ;
}
2016-04-12 13:56:17 +00:00
} ] ;
2016-04-12 08:23:48 +00:00
}
}
2016-04-13 15:56:46 +00:00
else if ( [ pathParams [ 0 ] isEqualToString : @ "register" ] )
{
2016-04-22 16:42:14 +00:00
NSLog ( @ "[AppDelegate] Universal link with registration parameters" ) ;
2016-04-21 14:19:16 +00:00
continueUserActivity = YES ;
2016-04-22 16:42:14 +00:00
2017-03-23 16:48:05 +00:00
[ _masterTabBarController showAuthenticationScreenWithRegistrationParameters : queryParams ] ;
2016-04-13 15:56:46 +00:00
}
2016-04-12 08:23:48 +00:00
else
{
2016-04-13 15:56:46 +00:00
// Unknown command : Do nothing except coming back to the main screen
2016-04-12 13:30:02 +00:00
NSLog ( @ "[AppDelegate] Universal link: TODO: Do not know what to do with the link arguments: %@" , pathParams ) ;
2016-04-13 15:56:46 +00:00
[ self popToHomeViewControllerAnimated : NO completion : nil ] ;
2016-04-12 08:23:48 +00:00
}
return continueUserActivity ;
}
2016-04-12 15:44:06 +00:00
- ( void ) resetPendingUniversalLink
{
universalLinkFragmentPending = nil ;
if ( universalLinkWaitingObserver )
{
[ [ NSNotificationCenter defaultCenter ] removeObserver : universalLinkWaitingObserver ] ;
universalLinkWaitingObserver = nil ;
}
}
2016-04-12 09:19:50 +00:00
/ * *
2016-04-12 13:30:02 +00:00
Extract params from the URL fragment part ( after ' # ' ) of a vector . im Universal link :
2016-04-12 15:44:06 +00:00
2016-04-12 09:19:50 +00:00
The fragment can contain a ' ? ' . So there are two kinds of parameters : path params and query params .
It is in the form of / [ pathParam1 ] / [ pathParam2 ] ? [ queryParam1Key ] = [ queryParam1Value ] & [ queryParam2Key ] = [ queryParam2Value ]
@ param fragment the fragment to parse .
@ param outPathParams the decoded path params .
@ param outQueryParams the decoded query params . If there is no query params , it will be nil .
* /
- ( void ) parseUniversalLinkFragment : ( NSString * ) fragment outPathParams : ( NSArray < NSString * > * * ) outPathParams outQueryParams : ( NSMutableDictionary * * ) outQueryParams
{
NSParameterAssert ( outPathParams && outQueryParams ) ;
NSArray < NSString * > * pathParams ;
NSMutableDictionary * queryParams ;
NSArray < NSString * > * fragments = [ fragment componentsSeparatedByString : @ "?" ] ;
// Extract path params
pathParams = [ fragments [ 0 ] componentsSeparatedByString : @ "/" ] ;
// Remove the first empty path param string
pathParams = [ pathParams filteredArrayUsingPredicate : [ NSPredicate predicateWithFormat : @ "length > 0" ] ] ;
// URL decode each path param
NSMutableArray < NSString * > * pathParams2 = [ NSMutableArray arrayWithArray : pathParams ] ;
for ( NSInteger i = 0 ; i < pathParams . count ; i + + )
{
pathParams2 [ i ] = [ pathParams2 [ i ] stringByReplacingPercentEscapesUsingEncoding : NSUTF8StringEncoding ] ;
}
pathParams = pathParams2 ;
// Extract query params if any
2016-05-16 14:03:56 +00:00
// Query params are in the form [ queryParam1Key ] = [ queryParam1Value ] , so the
// presence of at least one ' = ' character is mandatory
2016-05-16 14:01:28 +00:00
if ( fragments . count = = 2 && ( NSNotFound ! = [ fragments [ 1 ] rangeOfString : @ "=" ] . location ) )
2016-04-12 09:19:50 +00:00
{
queryParams = [ [ NSMutableDictionary alloc ] init ] ;
for ( NSString * keyValue in [ fragments [ 1 ] componentsSeparatedByString : @ "&" ] )
{
// Get the parameter name
NSString * key = [ [ keyValue componentsSeparatedByString : @ "=" ] objectAtIndex : 0 ] ;
// Get the parameter value
NSString * value = [ [ keyValue componentsSeparatedByString : @ "=" ] objectAtIndex : 1 ] ;
2016-04-13 15:10:35 +00:00
if ( value . length )
{
value = [ value stringByReplacingOccurrencesOfString : @ "+" withString : @ " " ] ;
value = [ value stringByReplacingPercentEscapesUsingEncoding : NSUTF8StringEncoding ] ;
2016-04-12 09:19:50 +00:00
2016-04-13 15:10:35 +00:00
queryParams [ key ] = value ;
}
2016-04-12 09:19:50 +00:00
}
}
* outPathParams = pathParams ;
* outQueryParams = queryParams ;
}
2015-04-30 14:19:12 +00:00
# pragma mark - Matrix sessions handling
2015-06-18 09:19:42 +00:00
- ( void ) initMatrixSessions
{
2017-02-20 17:34:41 +00:00
NSLog ( @ "[AppDelegate] initMatrixSessions" ) ;
2017-03-23 10:49:58 +00:00
// Define the media cache version
[ MXSDKOptions sharedInstance ] . mediaCacheAppVersion = 0 ;
2016-11-16 12:40:04 +00:00
// Enable e2e encryption for newly created MXSession
[ MXSDKOptions sharedInstance ] . enableCryptoWhenStartingMXSession = YES ;
2015-12-11 12:51:48 +00:00
// Disable identicon use
[ MXSDKOptions sharedInstance ] . disableIdenticonUseForUserAvatar = YES ;
2017-01-16 16:26:53 +00:00
// Enable SDK stats upload to GA
[ MXSDKOptions sharedInstance ] . enableGoogleAnalytics = YES ;
2015-12-11 12:51:48 +00:00
2016-02-11 14:19:49 +00:00
// Disable long press on event in bubble cells
[ MXKRoomBubbleTableViewCell disableLongPressGestureOnEvent : YES ] ;
2015-08-21 18:00:39 +00:00
// Set first RoomDataSource class used in Vector
[ MXKRoomDataSourceManager registerRoomDataSourceClass : RoomDataSource . class ] ;
2015-05-21 16:15:45 +00:00
// 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 )
{
2015-04-30 14:19:12 +00:00
MXSession * mxSession = ( MXSession * ) notif . object ;
2015-05-11 22:26:00 +00:00
// Remove by default potential call observer on matrix session state change
2015-06-18 09:19:42 +00:00
if ( matrixCallObserver )
{
2015-05-11 22:26:00 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : matrixCallObserver ] ;
matrixCallObserver = nil ;
}
2015-04-30 14:19:12 +00:00
// Check whether the concerned session is a new one
2015-06-18 09:19:42 +00:00
if ( mxSession . state = = MXSessionStateInitialised )
{
2015-11-20 13:47:38 +00:00
// Store this new session
2015-11-17 23:15:52 +00:00
[ self addMatrixSession : mxSession ] ;
2015-12-18 10:55:35 +00:00
2016-03-23 13:45:16 +00:00
// Set the VoIP call stack ( if supported ) .
id < MXCallStack > callStack ;
# ifdef MX_CALL _STACK _OPENWEBRTC
callStack = [ [ MXOpenWebRTCCallStack alloc ] init ] ;
# endif
# ifdef MX_CALL _STACK _ENDPOINT
callStack = [ [ MXEndpointCallStack alloc ] initWithMatrixId : mxSession . myUser . userId ] ;
2016-07-22 16:26:55 +00:00
# endif
# ifdef MX_CALL _STACK _JINGLE
callStack = [ [ MXJingleCallStack alloc ] init ] ;
2016-03-23 13:45:16 +00:00
# endif
if ( callStack )
{
[ mxSession enableVoIPWithCallStack : callStack ] ;
}
2016-05-20 13:30:10 +00:00
else
{
// When there is no call stack , display alerts on call invites
[ self enableNoVoIPOnMatrixSession : mxSession ] ;
}
2017-03-21 08:06:18 +00:00
2015-12-22 07:43:55 +00:00
// Each room member will be considered as a potential contact .
[ MXKContactManager sharedManager ] . contactManagerMXRoomSource = MXKContactManagerMXRoomSourceAll ;
2015-06-18 13:20:33 +00:00
}
else if ( mxSession . state = = MXSessionStateStoreDataReady )
2015-06-18 09:19:42 +00:00
{
2015-06-18 13:20:33 +00:00
// Check whether the app user wants inApp notifications on new events for this session
2015-06-19 08:31:27 +00:00
NSArray * mxAccounts = [ MXKAccountManager sharedManager ] . activeAccounts ;
2015-06-18 13:20:33 +00:00
for ( MXKAccount * account in mxAccounts )
2015-06-18 09:19:42 +00:00
{
2015-06-18 13:20:33 +00:00
if ( account . mxSession = = mxSession )
{
[ self enableInAppNotificationsForAccount : account ] ;
break ;
}
2015-04-30 14:19:12 +00:00
}
2015-06-18 13:20:33 +00:00
}
else if ( mxSession . state = = MXSessionStateClosed )
2015-06-18 09:19:42 +00:00
{
2015-11-17 23:15:52 +00:00
[ self removeMatrixSession : mxSession ] ;
2015-05-21 16:15:45 +00:00
}
// Restore call observer only if all session are running
2015-11-17 23:15:52 +00:00
NSArray * mxSessions = self . mxSessions ;
2015-05-21 16:15:45 +00:00
BOOL shouldAddMatrixCallObserver = ( mxSessions . count ) ;
2015-06-18 09:19:42 +00:00
for ( mxSession in mxSessions )
{
if ( mxSession . state ! = MXSessionStateRunning )
{
2015-05-21 16:15:45 +00:00
shouldAddMatrixCallObserver = NO ;
break ;
}
}
2015-06-18 13:20:33 +00:00
2015-06-18 09:19:42 +00:00
if ( shouldAddMatrixCallObserver )
{
2015-05-11 22:26:00 +00:00
// A new call observer may be added here
[ self addMatrixCallObserver ] ;
2015-04-30 14:19:12 +00:00
}
2017-01-31 23:18:01 +00:00
[ self handleLaunchAnimation ] ;
2015-04-30 14:19:12 +00:00
} ] ;
// Register an observer in order to handle new account
2015-06-18 13:20:33 +00:00
addedAccountObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXKAccountManagerDidAddAccountNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif ) {
2015-04-30 14:19:12 +00:00
2016-03-23 13:45:16 +00:00
// Finalize the initialization of this new account
2015-06-18 13:20:33 +00:00
MXKAccount * account = notif . object ;
2015-06-18 09:19:42 +00:00
if ( account )
{
2017-03-17 15:46:44 +00:00
// Replace default room summary updater
EventFormatter * eventFormatter = [ [ EventFormatter alloc ] initWithMatrixSession : account . mxSession ] ;
eventFormatter . isForSubtitle = YES ;
account . mxSession . roomSummaryUpdateDelegate = eventFormatter ;
2016-03-21 13:49:15 +00:00
// Set the push gateway URL .
account . pushGatewayURL = [ [ NSUserDefaults standardUserDefaults ] objectForKey : @ "pushGatewayURL" ] ;
2015-06-19 08:31:27 +00:00
if ( isAPNSRegistered )
{
// Enable push notifications by default on new added account
account . enablePushNotifications = YES ;
}
else
{
// Set up push notifications
[ self registerUserNotificationSettings ] ;
}
2015-04-30 14:19:12 +00:00
2015-06-18 13:20:33 +00:00
// Observe inApp notifications toggle change
[ account addObserver : self forKeyPath : @ "enableInAppNotifications" options : 0 context : nil ] ;
2015-04-30 14:19:12 +00:00
}
2017-01-17 22:13:30 +00:00
// Load the local contacts on first account creation .
if ( [ MXKAccountManager sharedManager ] . accounts . count = = 1 )
{
dispatch_async ( dispatch_get _main _queue ( ) , ^ {
[ self refreshLocalContacts ] ;
} ) ;
}
2015-05-11 21:11:23 +00:00
} ] ;
2015-06-18 13:20:33 +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 ] ;
}
} ] ;
2016-05-04 16:35:59 +00:00
[ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXSessionIgnoredUsersDidChangeNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * _Nonnull notif ) {
NSLog ( @ "[AppDelegate] kMXSessionIgnoredUsersDidChangeNotification received. Reload the app" ) ;
// Reload entirely the app when a user has been ignored or unignored
[ [ AppDelegate theDelegate ] reloadMatrixSessions : YES ] ;
} ] ;
2015-06-18 13:20:33 +00:00
2016-08-12 14:50:27 +00:00
[ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXSessionDidCorruptDataNotification object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * _Nonnull notif ) {
NSLog ( @ "[AppDelegate] kMXSessionDidCorruptDataNotification received. Reload the app" ) ;
// Reload entirely the app when a session has corrupted its data
[ [ AppDelegate theDelegate ] reloadMatrixSessions : YES ] ;
} ] ;
2015-04-30 14:19:12 +00:00
// Observe settings changes
[ [ MXKAppSettings standardAppSettings ] addObserver : self forKeyPath : @ "showAllEventsInRoomHistory" options : 0 context : nil ] ;
2015-07-21 09:30:37 +00:00
// Prepare account manager
MXKAccountManager * accountManager = [ MXKAccountManager sharedManager ] ;
// Use MXFileStore as MXStore to permanently store events .
accountManager . storeClass = [ MXFileStore class ] ;
2016-03-23 13:45:16 +00:00
// Observers have been defined , we can start a matrix session for each enabled accounts .
// except if the app is still in background .
if ( [ [ UIApplication sharedApplication ] applicationState ] ! = UIApplicationStateBackground )
2015-08-07 14:20:46 +00:00
{
2017-02-20 17:34:41 +00:00
NSLog ( @ "[AppDelegate] initMatrixSessions: prepareSessionForActiveAccounts" ) ;
2016-03-23 13:45:16 +00:00
[ accountManager prepareSessionForActiveAccounts ] ;
}
else
{
// The app is launched in background as a result of a remote notification .
// Presently we are not able to initialize the matrix session ( s ) in background . ( FIXME : initialize matrix session ( s ) in case of a background launch ) .
// Patch : the account session ( s ) will be opened when the app will enter foreground .
NSLog ( @ "[AppDelegate] initMatrixSessions: The application has been launched in background" ) ;
2015-08-07 14:20:46 +00:00
}
2015-06-19 08:31:27 +00:00
// Check whether we ' re already logged in
2015-07-21 09:30:37 +00:00
NSArray * mxAccounts = accountManager . accounts ;
2015-06-18 09:19:42 +00:00
if ( mxAccounts . count )
{
2016-03-21 13:49:15 +00:00
for ( MXKAccount * account in mxAccounts )
{
2017-03-17 15:46:44 +00:00
// Replace default room summary updater
EventFormatter * eventFormatter = [ [ EventFormatter alloc ] initWithMatrixSession : account . mxSession ] ;
eventFormatter . isForSubtitle = YES ;
account . mxSession . roomSummaryUpdateDelegate = eventFormatter ;
// The push gateway url is now configurable .
// Set this url in the existing accounts when it is undefined .
2016-03-21 13:49:15 +00:00
if ( ! account . pushGatewayURL )
{
account . pushGatewayURL = [ [ NSUserDefaults standardUserDefaults ] objectForKey : @ "pushGatewayURL" ] ;
}
}
2015-06-19 08:31:27 +00:00
// Set up push notifications
2015-04-30 14:19:12 +00:00
[ self registerUserNotificationSettings ] ;
2015-06-19 08:31:27 +00:00
// Observe inApp notifications toggle change for each account
2015-06-18 09:19:42 +00:00
for ( MXKAccount * account in mxAccounts )
{
2015-06-18 13:20:33 +00:00
[ account addObserver : self forKeyPath : @ "enableInAppNotifications" options : 0 context : nil ] ;
2015-05-21 16:15:45 +00:00
}
2015-04-30 14:19:12 +00:00
}
}
2015-11-17 23:15:52 +00:00
- ( NSArray * ) mxSessions
{
return [ NSArray arrayWithArray : mxSessionArray ] ;
}
- ( void ) addMatrixSession : ( MXSession * ) mxSession
{
if ( mxSession )
{
2015-11-20 13:47:38 +00:00
// Report this session to contact manager
[ [ MXKContactManager sharedManager ] addMatrixSession : mxSession ] ;
2016-06-14 09:10:37 +00:00
// Update home data sources
2017-03-23 16:48:05 +00:00
[ _masterTabBarController addMatrixSession : mxSession ] ;
2015-11-17 23:15:52 +00:00
[ mxSessionArray addObject : mxSession ] ;
2017-03-23 16:48:05 +00:00
// Do the one time check on device id
[ self checkDeviceId : mxSession ] ;
2015-11-17 23:15:52 +00:00
}
}
- ( void ) removeMatrixSession : ( MXSession * ) mxSession
{
2015-11-20 13:47:38 +00:00
[ [ MXKContactManager sharedManager ] removeMatrixSession : mxSession ] ;
2015-12-09 17:12:42 +00:00
// Update home data sources
2017-03-23 16:48:05 +00:00
[ _masterTabBarController removeMatrixSession : mxSession ] ;
2016-05-20 12:31:23 +00:00
2016-05-20 13:30:10 +00:00
// If any , disable the no VoIP support workaround
2016-05-20 12:31:23 +00:00
[ self disableNoVoIPOnMatrixSession : mxSession ] ;
2015-11-17 23:15:52 +00:00
[ mxSessionArray removeObject : mxSession ] ;
}
2016-08-26 14:12:46 +00:00
- ( void ) markAllMessagesAsRead
{
2017-04-18 13:55:51 +00:00
for ( MXSession * session in mxSessionArray )
{
[ session markAllMessagesAsRead ] ;
}
2016-08-26 14:12:46 +00:00
}
2015-06-18 09:19:42 +00:00
- ( void ) reloadMatrixSessions : ( BOOL ) clearCache
{
2015-04-30 14:19:12 +00:00
// Reload all running matrix sessions
2015-06-19 08:31:27 +00:00
NSArray * mxAccounts = [ MXKAccountManager sharedManager ] . activeAccounts ;
2015-06-18 09:19:42 +00:00
for ( MXKAccount * account in mxAccounts )
{
2015-06-19 08:31:27 +00:00
[ account reload : clearCache ] ;
2017-03-27 08:02:26 +00:00
// Replace default room summary updater
EventFormatter * eventFormatter = [ [ EventFormatter alloc ] initWithMatrixSession : account . mxSession ] ;
eventFormatter . isForSubtitle = YES ;
account . mxSession . roomSummaryUpdateDelegate = eventFormatter ;
2015-04-30 14:19:12 +00:00
}
// Force back to Recents list if room details is displayed ( Room details are not available until the end of initial sync )
2016-04-13 15:08:02 +00:00
[ self popToHomeViewControllerAnimated : NO completion : nil ] ;
2015-04-30 14:19:12 +00:00
2015-06-18 09:19:42 +00:00
if ( clearCache )
{
2015-04-30 14:19:12 +00:00
// clear the media cache
2016-12-01 13:55:29 +00:00
[ MXMediaManager clearCache ] ;
2015-04-30 14:19:12 +00:00
}
}
2014-10-08 16:55:26 +00:00
2015-06-18 09:19:42 +00:00
- ( void ) logout
{
2014-12-12 09:49:15 +00:00
[ [ UIApplication sharedApplication ] unregisterForRemoteNotifications ] ;
isAPNSRegistered = NO ;
2015-04-17 13:45:32 +00:00
2014-10-17 23:04:05 +00:00
// Clear cache
2016-12-01 13:55:29 +00:00
[ MXMediaManager clearCache ] ;
2015-08-07 16:30:33 +00:00
# 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
2015-04-17 13:45:32 +00:00
2015-04-30 14:19:12 +00:00
// Logout all matrix account
[ [ MXKAccountManager sharedManager ] logout ] ;
2015-04-17 13:45:32 +00:00
// Return to authentication screen
2017-03-23 16:48:05 +00:00
[ _masterTabBarController showAuthenticationScreen ] ;
2015-04-17 13:45:32 +00:00
2017-01-17 22:13:30 +00:00
// Note : Keep App settings
2015-04-17 13:45:32 +00:00
2015-04-17 12:24:08 +00:00
// Reset the contact manager
2015-06-08 16:53:57 +00:00
[ [ MXKContactManager sharedManager ] reset ] ;
2014-10-08 16:55:26 +00:00
}
2014-10-03 17:26:39 +00:00
2015-11-17 23:15:52 +00:00
- ( void ) observeValueForKeyPath : ( NSString * ) keyPath ofObject : ( id ) object change : ( NSDictionary * ) change context : ( void * ) context
2015-06-18 09:19:42 +00:00
{
2015-11-17 23:15:52 +00:00
if ( [ @ "showAllEventsInRoomHistory" isEqualToString : keyPath ] )
2015-06-18 09:19:42 +00:00
{
2015-11-17 23:15:52 +00:00
// Flush and restore Matrix data
[ self reloadMatrixSessions : NO ] ;
2015-02-18 16:40:55 +00:00
}
2015-11-17 23:15:52 +00:00
else if ( [ @ "enableInAppNotifications" isEqualToString : keyPath ] && [ object isKindOfClass : [ MXKAccount class ] ] )
2015-06-18 09:19:42 +00:00
{
2015-11-17 23:15:52 +00:00
[ self enableInAppNotificationsForAccount : ( MXKAccount * ) object ] ;
2014-10-31 17:54:32 +00:00
}
2015-11-17 23:15:52 +00:00
}
- ( void ) addMatrixCallObserver
{
if ( matrixCallObserver )
2014-10-08 12:14:12 +00:00
{
2015-11-17 23:15:52 +00:00
[ [ NSNotificationCenter defaultCenter ] removeObserver : matrixCallObserver ] ;
2014-10-03 17:26:39 +00:00
}
2014-10-08 12:14:12 +00:00
2015-11-17 23:15:52 +00:00
// Register call observer in order to handle new opened session
matrixCallObserver = [ [ NSNotificationCenter defaultCenter ] addObserverForName : kMXCallManagerNewCall object : nil queue : [ NSOperationQueue mainQueue ] usingBlock : ^ ( NSNotification * notif )
2015-06-18 09:19:42 +00:00
{
2015-11-17 23:15:52 +00:00
// Ignore the call if a call is already in progress
if ( ! currentCallViewController )
{
MXCall * mxCall = ( MXCall * ) notif . object ;
2016-08-19 15:43:29 +00:00
// Prepare the call view controller
currentCallViewController = [ CallViewController callViewController : mxCall ] ;
2015-11-17 23:15:52 +00:00
currentCallViewController . delegate = self ;
2016-08-19 15:43:29 +00:00
if ( mxCall . isIncoming )
{
// Prompt user before presenting the call view controller
NSString * callPromptFormat = mxCall . isVideoCall ? NSLocalizedStringFromTable ( @ "call_incoming_video_prompt" , @ "Vector" , nil ) : NSLocalizedStringFromTable ( @ "call_incoming_voice_prompt" , @ "Vector" , nil ) ;
NSString * callerName = currentCallViewController . peer . displayname ;
if ( ! callerName . length )
{
callerName = currentCallViewController . peer . userId ;
}
NSString * callPrompt = [ NSString stringWithFormat : callPromptFormat , callerName ] ;
__weak typeof ( self ) weakSelf = self ;
// Removing existing notification ( if any )
[ _incomingCallNotification dismiss : NO ] ;
_incomingCallNotification = [ [ MXKAlert alloc ] initWithTitle : callPrompt
message : nil
style : MXKAlertStyleAlert ] ;
_incomingCallNotification . cancelButtonIndex = [ _incomingCallNotification addActionWithTitle : NSLocalizedStringFromTable ( @ "decline" , @ "Vector" , nil )
style : MXKAlertActionStyleDefault
handler : ^ ( MXKAlert * alert ) {
if ( weakSelf )
{
2017-03-23 16:48:05 +00:00
typeof ( self ) self = weakSelf ;
2016-09-06 12:21:11 +00:00
2016-11-24 14:09:04 +00:00
// Reject the call .
// Note : Do not reset the incoming call notification before this operation , because it is used to release properly the dismissed call view controller .
2017-03-23 16:48:05 +00:00
if ( self -> currentCallViewController )
2016-09-06 12:21:11 +00:00
{
2017-03-23 16:48:05 +00:00
[ self -> currentCallViewController onButtonPressed : self -> currentCallViewController . rejectCallButton ] ;
2016-09-06 12:21:11 +00:00
currentCallViewController = nil ;
}
2016-08-19 15:43:29 +00:00
2017-03-23 16:48:05 +00:00
self . incomingCallNotification = nil ;
2016-11-24 14:09:04 +00:00
2016-08-19 15:43:29 +00:00
mxCall . delegate = nil ;
}
} ] ;
[ _incomingCallNotification addActionWithTitle : NSLocalizedStringFromTable ( @ "accept" , @ "Vector" , nil )
style : MXKAlertActionStyleDefault
handler : ^ ( MXKAlert * alert ) {
if ( weakSelf )
{
2017-03-23 16:48:05 +00:00
typeof ( self ) self = weakSelf ;
2016-08-19 15:43:29 +00:00
2017-03-23 16:48:05 +00:00
self . incomingCallNotification = nil ;
2016-09-06 12:21:11 +00:00
2017-03-23 16:48:05 +00:00
if ( self -> currentCallViewController )
2016-09-06 12:21:11 +00:00
{
2017-03-23 16:48:05 +00:00
[ self -> currentCallViewController onButtonPressed : self -> currentCallViewController . answerCallButton ] ;
2016-09-06 12:21:11 +00:00
2017-03-23 16:48:05 +00:00
[ self presentCallViewController : nil ] ;
2016-09-06 12:21:11 +00:00
}
2016-08-19 15:43:29 +00:00
}
} ] ;
2017-02-08 16:12:43 +00:00
_incomingCallNotification . mxkAccessibilityIdentifier = @ "AppDelegateIncomingCallAlert" ;
2016-09-06 15:20:16 +00:00
[ self showNotificationAlert : _incomingCallNotification ] ;
2016-08-19 15:43:29 +00:00
}
else
{
2016-11-24 14:09:04 +00:00
[ self presentCallViewController : nil ] ;
2016-08-19 15:43:29 +00:00
}
2015-11-17 23:15:52 +00:00
}
2016-08-19 15:43:29 +00:00
2014-11-04 16:26:33 +00:00
} ] ;
2014-10-03 17:26:39 +00:00
}
2017-01-31 23:18:01 +00:00
- ( void ) handleLaunchAnimation
{
MXSession * mainSession = self . mxSessions . firstObject ;
if ( mainSession )
{
BOOL isLaunching = NO ;
switch ( mainSession . state )
{
case MXSessionStateClosed :
case MXSessionStateInitialised :
isLaunching = YES ;
break ;
case MXSessionStateStoreDataReady :
case MXSessionStateSyncInProgress :
isLaunching = ( mainSession . rooms . count = = 0 ) ;
default :
break ;
}
if ( isLaunching )
{
UIWindow * window = [ [ UIApplication sharedApplication ] keyWindow ] ;
if ( ! launchAnimationContainerView && window )
{
launchAnimationContainerView = [ [ UIView alloc ] initWithFrame : window . bounds ] ;
launchAnimationContainerView . backgroundColor = [ UIColor whiteColor ] ;
launchAnimationContainerView . autoresizingMask = UIViewAutoresizingFlexibleWidth | UIViewAutoresizingFlexibleHeight ;
[ window addSubview : launchAnimationContainerView ] ;
// Add animation view
UIImageView * animationView = [ [ UIImageView alloc ] initWithFrame : CGRectMake ( 0 , 0 , 170 , 170 ) ] ;
animationView . image = [ UIImage animatedImageNamed : @ "animatedLogo-" duration : 2 ] ;
animationView . center = CGPointMake ( launchAnimationContainerView . center . x , 3 * launchAnimationContainerView . center . y / 4 ) ;
animationView . translatesAutoresizingMaskIntoConstraints = NO ;
[ launchAnimationContainerView addSubview : animationView ] ;
NSLayoutConstraint * widthConstraint = [ NSLayoutConstraint constraintWithItem : animationView
attribute : NSLayoutAttributeWidth
relatedBy : NSLayoutRelationEqual
toItem : nil
attribute : NSLayoutAttributeNotAnAttribute
multiplier : 1
constant : 170 ] ;
NSLayoutConstraint * heightConstraint = [ NSLayoutConstraint constraintWithItem : animationView
attribute : NSLayoutAttributeHeight
relatedBy : NSLayoutRelationEqual
toItem : nil
attribute : NSLayoutAttributeNotAnAttribute
multiplier : 1
constant : 170 ] ;
NSLayoutConstraint * centerXConstraint = [ NSLayoutConstraint constraintWithItem : animationView
attribute : NSLayoutAttributeCenterX
relatedBy : NSLayoutRelationEqual
toItem : launchAnimationContainerView
attribute : NSLayoutAttributeCenterX
multiplier : 1
constant : 0 ] ;
NSLayoutConstraint * centerYConstraint = [ NSLayoutConstraint constraintWithItem : animationView
attribute : NSLayoutAttributeCenterY
relatedBy : NSLayoutRelationEqual
toItem : launchAnimationContainerView
attribute : NSLayoutAttributeCenterY
multiplier : 3.0 / 4.0
constant : 0 ] ;
[ NSLayoutConstraint activateConstraints : @ [ widthConstraint , heightConstraint , centerXConstraint , centerYConstraint ] ] ;
// In addition , show a spinner under this giffy animation
UIActivityIndicatorView * activityIndicator = [ [ UIActivityIndicatorView alloc ] initWithActivityIndicatorStyle : UIActivityIndicatorViewStyleWhite ] ;
activityIndicator . backgroundColor = [ UIColor colorWithRed : 0.8 green : 0.8 blue : 0.8 alpha : 1.0 ] ;
activityIndicator . autoresizingMask = UIViewAutoresizingFlexibleTopMargin | UIViewAutoresizingFlexibleBottomMargin | UIViewAutoresizingFlexibleLeftMargin | UIViewAutoresizingFlexibleRightMargin ;
activityIndicator . hidesWhenStopped = YES ;
CGRect frame = activityIndicator . frame ;
frame . size . width + = 30 ;
frame . size . height + = 30 ;
activityIndicator . bounds = frame ;
[ activityIndicator . layer setCornerRadius : 5 ] ;
activityIndicator . center = CGPointMake ( launchAnimationContainerView . center . x , 6 * launchAnimationContainerView . center . y / 4 ) ;
[ launchAnimationContainerView addSubview : activityIndicator ] ;
activityIndicator . translatesAutoresizingMaskIntoConstraints = NO ;
NSLayoutConstraint * widthConstraint2 = [ NSLayoutConstraint constraintWithItem : activityIndicator
attribute : NSLayoutAttributeWidth
relatedBy : NSLayoutRelationEqual
toItem : nil
attribute : NSLayoutAttributeNotAnAttribute
multiplier : 1
constant : frame . size . width ] ;
NSLayoutConstraint * heightConstraint2 = [ NSLayoutConstraint constraintWithItem : activityIndicator
attribute : NSLayoutAttributeHeight
relatedBy : NSLayoutRelationEqual
toItem : nil
attribute : NSLayoutAttributeNotAnAttribute
multiplier : 1
constant : frame . size . height ] ;
NSLayoutConstraint * centerXConstraint2 = [ NSLayoutConstraint constraintWithItem : activityIndicator
attribute : NSLayoutAttributeCenterX
relatedBy : NSLayoutRelationEqual
toItem : launchAnimationContainerView
attribute : NSLayoutAttributeCenterX
multiplier : 1
constant : 0 ] ;
NSLayoutConstraint * centerYConstraint2 = [ NSLayoutConstraint constraintWithItem : activityIndicator
attribute : NSLayoutAttributeCenterY
relatedBy : NSLayoutRelationEqual
toItem : launchAnimationContainerView
attribute : NSLayoutAttributeCenterY
multiplier : 6.0 / 4.0
constant : 0 ] ;
[ NSLayoutConstraint activateConstraints : @ [ widthConstraint2 , heightConstraint2 , centerXConstraint2 , centerYConstraint2 ] ] ;
[ activityIndicator startAnimating ] ;
launchAnimationStart = [ NSDate date ] ;
}
2017-02-21 15:08:10 +00:00
return ;
2017-01-31 23:18:01 +00:00
}
2017-02-21 15:08:10 +00:00
}
if ( launchAnimationContainerView )
{
NSTimeInterval durationMs = [ [ NSDate date ] timeIntervalSinceDate : launchAnimationStart ] * 1000 ;
NSLog ( @ "[AppDelegate] LaunchAnimation was shown for %.3fms" , durationMs ) ;
if ( [ MXSDKOptions sharedInstance ] . enableGoogleAnalytics )
2017-01-31 23:18:01 +00:00
{
2017-02-21 15:08:10 +00:00
id < GAITracker > tracker = [ [ GAI sharedInstance ] defaultTracker ] ;
[ tracker send : [ [ GAIDictionaryBuilder createTimingWithCategory : kMXGoogleAnalyticsStartupCategory
interval : @ ( ( int ) durationMs )
name : kMXGoogleAnalyticsStartupLaunchScreen
label : nil ] build ] ] ;
2017-01-31 23:18:01 +00:00
}
2017-02-21 15:08:10 +00:00
[ launchAnimationContainerView removeFromSuperview ] ;
launchAnimationContainerView = nil ;
2017-01-31 23:18:01 +00:00
}
}
2017-03-23 16:48:05 +00:00
# pragma mark -
/ * *
Check the existence of device id .
* /
- ( void ) checkDeviceId : ( MXSession * ) mxSession
{
// In case of the app update for the e2e encryption , the app starts with
// no device id provided by the homeserver .
// Ask the user to login again in order to enable e2e . Ask it once
if ( ! isErrorNotificationSuspended && ! [ [ NSUserDefaults standardUserDefaults ] boolForKey : @ "deviceIdAtStartupChecked" ] )
{
[ [ NSUserDefaults standardUserDefaults ] setBool : YES forKey : @ "deviceIdAtStartupChecked" ] ;
[ [ NSUserDefaults standardUserDefaults ] synchronize ] ;
// Check if there is a device id
if ( ! mxSession . matrixRestClient . credentials . deviceId )
{
NSLog ( @ "WARNING: The user has no device. Prompt for login again" ) ;
NSString * msg = NSLocalizedStringFromTable ( @ "e2e_enabling_on_app_update" , @ "Vector" , nil ) ;
__weak typeof ( self ) weakSelf = self ;
[ _errorNotification dismiss : NO ] ;
_errorNotification = [ [ MXKAlert alloc ] initWithTitle : nil message : msg style : MXKAlertStyleAlert ] ;
_errorNotification . cancelButtonIndex = [ _errorNotification addActionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "later" ] style : MXKAlertActionStyleDefault handler : ^ ( MXKAlert * alert ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> _errorNotification = nil ;
}
} ] ;
[ _errorNotification addActionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "ok" ] style : MXKAlertActionStyleDefault handler : ^ ( MXKAlert * alert ) {
if ( weakSelf )
{
typeof ( self ) self = weakSelf ;
self -> _errorNotification = nil ;
[ self logout ] ;
}
} ] ;
// Prompt the user
_errorNotification . mxkAccessibilityIdentifier = @ "AppDelegateErrorAlert" ;
[ self showNotificationAlert : _errorNotification ] ;
}
}
}
2015-11-17 23:15:52 +00:00
# pragma mark - Matrix Accounts handling
2015-01-30 08:05:15 +00:00
2015-06-18 13:20:33 +00:00
- ( void ) enableInAppNotificationsForAccount : ( MXKAccount * ) account
2015-06-18 09:19:42 +00:00
{
2015-06-18 13:20:33 +00:00
if ( account . mxSession )
2015-06-18 09:19:42 +00:00
{
2015-06-18 13:20:33 +00:00
if ( account . enableInAppNotifications )
2015-06-18 09:19:42 +00:00
{
2015-06-18 13:20:33 +00:00
// Build MXEvent -> NSString formatter
2015-08-21 18:00:39 +00:00
EventFormatter * eventFormatter = [ [ EventFormatter alloc ] initWithMatrixSession : account . mxSession ] ;
2015-06-18 13:20:33 +00:00
eventFormatter . isForSubtitle = YES ;
2015-06-19 08:31:27 +00:00
[ account listenToNotifications : ^ ( MXEvent * event , MXRoomState * roomState , MXPushRule * rule ) {
// Check conditions to display this notification
2015-11-17 23:15:52 +00:00
if ( ! [ self . visibleRoomId isEqualToString : event . roomId ]
&& ! self . window . rootViewController . presentedViewController )
2015-06-19 08:31:27 +00:00
{
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 ] ;
2015-08-07 08:42:47 +00:00
self . mxInAppNotification . cancelButtonIndex = [ self . mxInAppNotification addActionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "cancel" ]
2015-06-19 08:31:27 +00:00
style : MXKAlertActionStyleDefault
handler : ^ ( MXKAlert * alert )
{
weakSelf . mxInAppNotification = nil ;
[ account updateNotificationListenerForRoomId : event . roomId ignore : YES ] ;
} ] ;
2015-08-12 07:32:45 +00:00
[ self . mxInAppNotification addActionWithTitle : NSLocalizedStringFromTable ( @ "view" , @ "Vector" , nil )
2015-06-19 08:31:27 +00:00
style : MXKAlertActionStyleDefault
handler : ^ ( MXKAlert * alert )
2015-06-18 13:20:33 +00:00
{
2015-06-19 08:31:27 +00:00
weakSelf . mxInAppNotification = nil ;
// Show the room
2016-04-07 15:38:19 +00:00
[ weakSelf showRoom : event . roomId andEventId : nil withMatrixSession : account . mxSession ] ;
2015-06-19 08:31:27 +00:00
} ] ;
2015-11-17 23:15:52 +00:00
[ self . mxInAppNotification showInViewController : self . window . rootViewController ] ;
2015-06-19 08:31:27 +00:00
}
}
} ] ;
2015-06-18 13:20:33 +00:00
}
else
{
[ account removeNotificationListener ] ;
2015-04-30 14:19:12 +00:00
}
}
2015-06-18 09:19:42 +00:00
if ( self . mxInAppNotification )
{
2015-04-30 14:19:12 +00:00
[ self . mxInAppNotification dismiss : NO ] ;
self . mxInAppNotification = nil ;
}
}
2015-06-18 09:19:42 +00:00
- ( void ) selectMatrixAccount : ( void ( ^ ) ( MXKAccount * selectedAccount ) ) onSelection
{
2015-06-19 08:31:27 +00:00
NSArray * mxAccounts = [ MXKAccountManager sharedManager ] . activeAccounts ;
2015-05-28 16:31:08 +00:00
2015-06-18 09:19:42 +00:00
if ( mxAccounts . count = = 1 )
{
if ( onSelection )
{
2015-05-28 16:31:08 +00:00
onSelection ( mxAccounts . firstObject ) ;
}
2015-06-18 09:19:42 +00:00
}
else if ( mxAccounts . count > 1 )
{
2016-09-06 15:20:16 +00:00
[ accountPicker dismiss : NO ] ;
2015-05-28 16:31:08 +00:00
2015-08-07 08:42:47 +00:00
accountPicker = [ [ MXKAlert alloc ] initWithTitle : [ NSBundle mxk_localizedStringForKey : @ "select_account" ] message : nil style : MXKAlertStyleActionSheet ] ;
2015-05-28 16:31:08 +00:00
__weak typeof ( self ) weakSelf = self ;
2015-06-18 09:19:42 +00:00
for ( MXKAccount * account in mxAccounts )
{
2016-03-21 17:38:44 +00:00
[ accountPicker addActionWithTitle : account . mxCredentials . userId style : MXKAlertActionStyleDefault handler : ^ ( MXKAlert * alert ) {
2017-03-23 16:48:05 +00:00
typeof ( self ) self = weakSelf ;
self -> accountPicker = nil ;
2015-05-28 16:31:08 +00:00
2015-06-18 09:19:42 +00:00
if ( onSelection )
{
2015-05-28 16:31:08 +00:00
onSelection ( account ) ;
}
} ] ;
}
2016-03-21 17:38:44 +00:00
accountPicker . cancelButtonIndex = [ accountPicker addActionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "cancel" ] style : MXKAlertActionStyleDefault handler : ^ ( MXKAlert * alert ) {
2017-03-23 16:48:05 +00:00
typeof ( self ) self = weakSelf ;
self -> accountPicker = nil ;
2016-03-21 17:38:44 +00:00
if ( onSelection )
{
onSelection ( nil ) ;
}
2015-05-28 16:31:08 +00:00
} ] ;
2016-09-06 15:20:16 +00:00
[ self showNotificationAlert : accountPicker ] ;
2015-05-28 16:31:08 +00:00
}
}
2015-05-04 12:49:30 +00:00
# pragma mark - Matrix Rooms handling
2016-04-07 15:38:19 +00:00
- ( void ) showRoom : ( NSString * ) roomId andEventId : ( NSString * ) eventId withMatrixSession : ( MXSession * ) mxSession
2015-08-26 17:04:30 +00:00
{
2015-12-09 17:12:42 +00:00
[ self restoreInitialDisplay : ^ {
2016-03-23 13:45:16 +00:00
2015-12-09 17:12:42 +00:00
// Select room to display its details ( dispatch this action in order to let TabBarController end its refresh )
2017-03-23 16:48:05 +00:00
[ _masterTabBarController selectRoomWithId : roomId andEventId : eventId inMatrixSession : mxSession ] ;
2016-03-23 13:45:16 +00:00
2015-12-09 17:12:42 +00:00
} ] ;
2015-08-26 17:04:30 +00:00
}
2016-04-15 07:43:56 +00:00
- ( void ) showRoomPreview : ( RoomPreviewData * ) roomPreviewData
{
[ self restoreInitialDisplay : ^ {
2017-03-23 16:48:05 +00:00
[ _masterTabBarController showRoomPreview : roomPreviewData ] ;
2016-04-15 07:43:56 +00:00
} ] ;
}
2015-11-17 23:15:52 +00:00
- ( void ) setVisibleRoomId : ( NSString * ) roomId
2015-06-18 09:19:42 +00:00
{
2015-11-17 23:15:52 +00:00
if ( roomId )
2015-06-18 09:19:42 +00:00
{
2015-11-17 23:15:52 +00:00
// Enable inApp notification for this room in all existing accounts .
NSArray * mxAccounts = [ MXKAccountManager sharedManager ] . accounts ;
for ( MXKAccount * account in mxAccounts )
{
[ account updateNotificationListenerForRoomId : roomId ignore : NO ] ;
}
2014-10-02 15:02:47 +00:00
}
2015-11-17 23:15:52 +00:00
_visibleRoomId = roomId ;
2014-10-02 15:02:47 +00:00
}
2016-11-02 13:32:02 +00:00
- ( void ) createDirectChatWithUserId : ( NSString * ) userId completion : ( void ( ^ ) ( void ) ) completion
2015-06-18 09:19:42 +00:00
{
2015-11-17 23:15:52 +00:00
// Handle here potential multiple accounts
2016-03-21 17:38:44 +00:00
[ self selectMatrixAccount : ^ ( MXKAccount * selectedAccount ) {
MXSession * mxSession = selectedAccount . mxSession ;
if ( mxSession )
{
2016-11-02 13:32:02 +00:00
// Create a new room by inviting the other user only if it is defined and not oneself
NSArray * invite = ( ( userId && ! [ mxSession . myUser . userId isEqualToString : userId ] ) ? @ [ userId ] : nil ) ;
2016-03-21 17:38:44 +00:00
2016-11-02 13:32:02 +00:00
[ mxSession createRoom : nil
visibility : kMXRoomDirectoryVisibilityPrivate
roomAlias : nil
topic : nil
invite : invite
invite3PID : nil
isDirect : ( invite . count ! = 0 )
2016-11-02 15:35:45 +00:00
preset : kMXRoomPresetTrustedPrivateChat
2016-11-02 13:32:02 +00:00
success : ^ ( MXRoom * room ) {
// Open created room
[ self showRoom : room . state . roomId andEventId : nil withMatrixSession : mxSession ] ;
if ( completion )
{
completion ( ) ;
2016-03-21 17:38:44 +00:00
}
2016-11-02 13:32:02 +00:00
}
failure : ^ ( NSError * error ) {
NSLog ( @ "[AppDelegate] Create direct chat failed" ) ;
// Alert user
[ self showErrorAsAlert : error ] ;
if ( completion )
{
completion ( ) ;
}
} ] ;
2016-03-21 17:38:44 +00:00
}
else if ( completion )
{
completion ( ) ;
}
} ] ;
2015-01-05 15:53:41 +00:00
}
2016-11-02 16:54:28 +00:00
- ( void ) startDirectChatWithUserId : ( NSString * ) userId completion : ( void ( ^ ) ( void ) ) completion
{
// Handle here potential multiple accounts
[ self selectMatrixAccount : ^ ( MXKAccount * selectedAccount ) {
MXSession * mxSession = selectedAccount . mxSession ;
if ( mxSession )
{
2016-11-03 17:12:08 +00:00
MXRoom * directRoom = [ mxSession directJoinedRoomWithUserId : userId ] ;
2016-11-02 16:54:28 +00:00
// if the room exists
2016-11-03 17:12:08 +00:00
if ( directRoom )
2016-11-02 16:54:28 +00:00
{
// open it
2016-11-03 17:12:08 +00:00
[ self showRoom : directRoom . roomId andEventId : nil withMatrixSession : mxSession ] ;
2016-11-02 16:54:28 +00:00
if ( completion )
{
completion ( ) ;
}
}
else
{
[ self createDirectChatWithUserId : userId completion : completion ] ;
}
}
else if ( completion )
{
completion ( ) ;
}
} ] ;
}
2017-01-17 22:13:30 +00:00
# pragma mark - Contacts handling
- ( void ) refreshLocalContacts
{
// Check whether the application is allowed to access the local contacts .
if ( ABAddressBookGetAuthorizationStatus ( ) = = kABAuthorizationStatusAuthorized )
{
// Check the user permission for syncing local contacts . This permission was handled independently on previous application version .
if ( ! [ MXKAppSettings standardAppSettings ] . syncLocalContacts )
{
// Check whether it was not requested yet .
if ( ! [ MXKAppSettings standardAppSettings ] . syncLocalContactsPermissionRequested )
{
[ MXKAppSettings standardAppSettings ] . syncLocalContactsPermissionRequested = YES ;
UIViewController * viewController = self . window . rootViewController . presentedViewController ;
if ( ! viewController )
{
viewController = self . window . rootViewController ;
}
[ MXKContactManager requestUserConfirmationForLocalContactsSyncInViewController : viewController completionHandler : ^ ( BOOL granted ) {
if ( granted )
{
// Allow local contacts sync in order to discover matrix users .
[ MXKAppSettings standardAppSettings ] . syncLocalContacts = YES ;
}
} ] ;
}
}
2017-01-23 17:40:30 +00:00
// Refresh the local contacts list .
[ [ MXKContactManager sharedManager ] refreshLocalContacts ] ;
2017-01-17 22:13:30 +00:00
}
}
2015-05-11 21:11:23 +00:00
# pragma mark - MXKCallViewControllerDelegate
2016-08-19 15:43:29 +00:00
- ( void ) dismissCallViewController : ( MXKCallViewController * ) callViewController completion : ( void ( ^ ) ( ) ) completion
2015-06-18 09:19:42 +00:00
{
2016-11-24 14:09:04 +00:00
if ( currentCallViewController && callViewController = = currentCallViewController )
2015-06-18 09:19:42 +00:00
{
2016-08-19 15:43:29 +00:00
if ( _incomingCallNotification )
{
// The user was prompted for an incoming call which ended
// The call view controller was not presented yet .
[ _incomingCallNotification dismiss : NO ] ;
_incomingCallNotification = nil ;
// Release properly
currentCallViewController . mxCall . delegate = nil ;
currentCallViewController . delegate = nil ;
currentCallViewController = nil ;
if ( completion )
{
completion ( ) ;
}
}
2016-11-24 14:09:04 +00:00
else if ( callViewController . isBeingPresented )
{
// 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 completion : completion ] ;
} ) ;
}
// Check whether the call view controller is actually presented
else if ( callViewController . presentingViewController )
2015-06-18 09:19:42 +00:00
{
2015-05-12 08:53:47 +00:00
BOOL callIsEnded = ( callViewController . mxCall . state = = MXCallStateEnded ) ;
NSLog ( @ "Call view controller is dismissed (%d)" , callIsEnded ) ;
2016-09-14 13:36:42 +00:00
if ( callIsEnded )
{
// Restore system status bar
[ UIApplication sharedApplication ] . statusBarHidden = NO ;
}
2015-05-12 08:53:47 +00:00
[ callViewController dismissViewControllerAnimated : YES completion : ^ {
2015-06-18 09:19:42 +00:00
if ( ! callIsEnded )
{
2016-09-02 20:21:02 +00:00
NSString * btnTitle = [ NSString stringWithFormat : NSLocalizedStringFromTable ( @ "active_call_details" , @ "Vector" , nil ) , callViewController . callerNameLabel . text ] ;
[ self addCallStatusBar : btnTitle ] ;
2015-05-12 08:53:47 +00:00
}
2016-08-19 15:43:29 +00:00
if ( completion )
{
completion ( ) ;
}
2016-11-24 14:09:04 +00:00
2015-05-12 08:53:47 +00:00
} ] ;
2015-06-18 09:19:42 +00:00
if ( callIsEnded )
{
2015-05-12 08:53:47 +00:00
[ self removeCallStatusBar ] ;
// Release properly
currentCallViewController . mxCall . delegate = nil ;
currentCallViewController . delegate = nil ;
currentCallViewController = nil ;
}
2016-08-19 15:43:29 +00:00
}
else
2015-06-18 09:19:42 +00:00
{
2016-11-24 14:09:04 +00:00
// Here the call view controller was not presented .
NSLog ( @ "Call view controller was not presented" ) ;
// Workaround to manage the "back to call" banner : present temporarily the call screen .
// This will correctly manage the navigation bar layout .
[ self presentCallViewController : ^ {
[ self dismissCallViewController : currentCallViewController completion : completion ] ;
} ] ;
2015-05-12 08:53:47 +00:00
}
2015-05-11 21:11:23 +00:00
}
}
# pragma mark - Call status handling
2016-09-02 20:21:02 +00:00
- ( void ) addCallStatusBar : ( NSString * ) buttonTitle
2015-06-18 09:19:42 +00:00
{
2015-05-11 21:11:23 +00:00
// Add a call status bar
2016-09-02 20:21:02 +00:00
CGSize topBarSize = CGSizeMake ( [ [ UIScreen mainScreen ] bounds ] . size . width , CALL_STATUS _BAR _HEIGHT ) ;
2015-05-11 21:11:23 +00:00
2016-09-02 20:21:02 +00:00
_callStatusBarWindow = [ [ UIWindow alloc ] initWithFrame : CGRectMake ( 0 , 0 , topBarSize . width , topBarSize . height ) ] ;
_callStatusBarWindow . windowLevel = UIWindowLevelStatusBar ;
2015-05-11 21:11:23 +00:00
// Create statusBarButton
2016-09-02 20:21:02 +00:00
_callStatusBarButton = [ UIButton buttonWithType : UIButtonTypeCustom ] ;
_callStatusBarButton . frame = CGRectMake ( 0 , 0 , topBarSize . width , topBarSize . height ) ;
2015-05-11 21:11:23 +00:00
2016-09-02 20:21:02 +00:00
[ _callStatusBarButton setTitle : buttonTitle forState : UIControlStateNormal ] ;
[ _callStatusBarButton setTitle : buttonTitle forState : UIControlStateHighlighted ] ;
_callStatusBarButton . titleLabel . textColor = [ UIColor whiteColor ] ;
if ( [ UIFont respondsToSelector : @ selector ( systemFontOfSize : weight : ) ] )
{
_callStatusBarButton . titleLabel . font = [ UIFont systemFontOfSize : 17 weight : UIFontWeightMedium ] ;
}
else
{
_callStatusBarButton . titleLabel . font = [ UIFont boldSystemFontOfSize : 17 ] ;
}
2015-05-11 21:11:23 +00:00
2017-03-08 15:14:41 +00:00
[ _callStatusBarButton setBackgroundColor : kRiotColorGreen ] ;
2016-09-06 15:20:16 +00:00
[ _callStatusBarButton addTarget : self action : @ selector ( presentCallViewController ) forControlEvents : UIControlEventTouchUpInside ] ;
2015-05-11 21:11:23 +00:00
// Place button into the new window
2016-09-02 20:21:02 +00:00
[ _callStatusBarButton setTranslatesAutoresizingMaskIntoConstraints : NO ] ;
[ _callStatusBarWindow addSubview : _callStatusBarButton ] ;
// Force callStatusBarButton to fill the window ( to handle auto - layout in case of screen rotation )
NSLayoutConstraint * widthConstraint = [ NSLayoutConstraint constraintWithItem : _callStatusBarButton
attribute : NSLayoutAttributeWidth
relatedBy : NSLayoutRelationEqual
toItem : _callStatusBarWindow
attribute : NSLayoutAttributeWidth
multiplier : 1.0
constant : 0 ] ;
NSLayoutConstraint * heightConstraint = [ NSLayoutConstraint constraintWithItem : _callStatusBarButton
attribute : NSLayoutAttributeHeight
relatedBy : NSLayoutRelationEqual
toItem : _callStatusBarWindow
attribute : NSLayoutAttributeHeight
multiplier : 1.0
constant : 0 ] ;
[ NSLayoutConstraint activateConstraints : @ [ widthConstraint , heightConstraint ] ] ;
_callStatusBarWindow . hidden = NO ;
2015-05-11 21:11:23 +00:00
[ 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
{
2016-09-02 20:21:02 +00:00
if ( _callStatusBarWindow )
2015-06-18 09:19:42 +00:00
{
2016-09-02 20:21:02 +00:00
// No more need to listen to system status bar changes
[ [ NSNotificationCenter defaultCenter ] removeObserver : self name : UIApplicationDidChangeStatusBarFrameNotification object : nil ] ;
2015-05-11 21:11:23 +00:00
// Hide & destroy it
2016-09-02 20:21:02 +00:00
_callStatusBarWindow . hidden = YES ;
[ _callStatusBarButton removeFromSuperview ] ;
_callStatusBarButton = nil ;
_callStatusBarWindow = nil ;
2015-05-11 21:11:23 +00:00
2016-09-02 20:21:02 +00:00
[ self statusBarDidChangeFrame ] ;
2015-05-11 21:11:23 +00:00
}
}
2016-09-06 15:20:16 +00:00
- ( void ) presentCallViewController
2016-11-24 14:09:04 +00:00
{
[ self presentCallViewController : nil ] ;
}
- ( void ) presentCallViewController : ( void ( ^ ) ( ) ) completion
2015-06-18 09:19:42 +00:00
{
2015-05-11 21:11:23 +00:00
[ self removeCallStatusBar ] ;
2016-09-06 12:21:11 +00:00
if ( currentCallViewController )
{
2016-09-06 15:20:16 +00:00
if ( self . window . rootViewController . presentedViewController )
{
[ self . window . rootViewController . presentedViewController presentViewController : currentCallViewController animated : YES completion : ^ {
2016-09-14 13:36:42 +00:00
// Hide system status bar
[ UIApplication sharedApplication ] . statusBarHidden = YES ;
2016-11-24 14:09:04 +00:00
if ( completion )
{
completion ( ) ;
}
2016-09-06 15:20:16 +00:00
} ] ;
}
else
{
[ self . window . rootViewController presentViewController : currentCallViewController animated : YES completion : ^ {
2016-09-14 13:36:42 +00:00
// Hide system status bar
[ UIApplication sharedApplication ] . statusBarHidden = YES ;
2016-11-24 14:09:04 +00:00
if ( completion )
{
completion ( ) ;
}
2016-09-06 15:20:16 +00:00
} ] ;
}
2016-09-06 12:21:11 +00:00
}
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
2016-09-02 20:21:02 +00:00
CGRect rootControllerFrame = [ [ UIScreen mainScreen ] bounds ] ;
if ( _callStatusBarWindow )
2015-06-18 09:19:42 +00:00
{
2016-09-02 20:21:02 +00:00
UIInterfaceOrientation statusBarOrientation = [ UIApplication sharedApplication ] . statusBarOrientation ;
2015-05-11 21:11:23 +00:00
2016-09-02 20:21:02 +00:00
switch ( statusBarOrientation )
{
case UIInterfaceOrientationLandscapeLeft :
{
_callStatusBarWindow . frame = CGRectMake ( - rootControllerFrame . size . width / 2 , - CALL_STATUS _BAR _HEIGHT / 2 , rootControllerFrame . size . width , CALL_STATUS _BAR _HEIGHT ) ;
_callStatusBarWindow . transform = CGAffineTransformMake ( 0 , -1 , 1 , 0 , CALL_STATUS _BAR _HEIGHT / 2 , rootControllerFrame . size . width / 2 ) ;
break ;
}
case UIInterfaceOrientationLandscapeRight :
{
_callStatusBarWindow . frame = CGRectMake ( - rootControllerFrame . size . width / 2 , - CALL_STATUS _BAR _HEIGHT / 2 , rootControllerFrame . size . width , CALL_STATUS _BAR _HEIGHT ) ;
_callStatusBarWindow . transform = CGAffineTransformMake ( 0 , 1 , -1 , 0 , rootControllerFrame . size . height - CALL_STATUS _BAR _HEIGHT / 2 , rootControllerFrame . size . width / 2 ) ;
break ;
}
default :
{
_callStatusBarWindow . transform = CGAffineTransformIdentity ;
_callStatusBarWindow . frame = CGRectMake ( 0 , 0 , rootControllerFrame . size . width , CALL_STATUS _BAR _HEIGHT ) ;
break ;
}
}
// Apply the vertical offset due to call status bar
rootControllerFrame . origin . y = CALL_STATUS _BAR _HEIGHT ;
rootControllerFrame . size . height - = CALL_STATUS _BAR _HEIGHT ;
2015-05-11 21:11:23 +00:00
}
2016-09-02 20:21:02 +00:00
rootController . view . frame = rootControllerFrame ;
2016-09-06 15:20:16 +00:00
if ( rootController . presentedViewController )
{
rootController . presentedViewController . view . frame = rootControllerFrame ;
}
2015-05-11 21:11:23 +00:00
[ rootController . view setNeedsLayout ] ;
}
2015-11-17 23:15:52 +00:00
# pragma mark - SplitViewController delegate
2016-06-21 19:47:20 +00:00
- ( nullable UIViewController * ) splitViewController : ( UISplitViewController * ) splitViewController separateSecondaryViewControllerFromPrimaryViewController : ( UIViewController * ) primaryViewController
{
2017-03-28 15:26:50 +00:00
// Return the top view controller of the master navigation controller , if it is a navigation controller itself .
2017-03-23 16:48:05 +00:00
UIViewController * topViewController = _masterNavigationController . topViewController ;
2017-03-28 15:26:50 +00:00
if ( [ topViewController isKindOfClass : UINavigationController . class ] )
2016-06-21 19:47:20 +00:00
{
2017-03-28 15:26:50 +00:00
return topViewController ;
2016-06-21 19:47:20 +00:00
}
2017-03-28 15:26:50 +00:00
2017-04-21 14:28:57 +00:00
// Else return the default empty details view controller from the storyboard .
// Be sure that the primary is then visible too .
if ( splitViewController . displayMode = = UISplitViewControllerDisplayModePrimaryHidden )
{
splitViewController . preferredDisplayMode = UISplitViewControllerDisplayModeAllVisible ;
}
2017-03-28 15:26:50 +00:00
UIStoryboard * storyboard = [ UIStoryboard storyboardWithName : @ "Main" bundle : [ NSBundle mainBundle ] ] ;
return [ storyboard instantiateViewControllerWithIdentifier : @ "EmptyDetailsViewControllerStoryboardId" ] ;
2016-06-21 19:47:20 +00:00
}
2015-11-17 23:15:52 +00:00
- ( BOOL ) splitViewController : ( UISplitViewController * ) splitViewController collapseSecondaryViewController : ( UIViewController * ) secondaryViewController ontoPrimaryViewController : ( UIViewController * ) primaryViewController
{
2017-03-28 15:26:50 +00:00
if ( ! self . masterTabBarController . currentRoomViewController && ! self . masterTabBarController . currentContactDetailViewController )
2015-11-17 23:15:52 +00:00
{
// Return YES to indicate that we have handled the collapse by doing nothing ; the secondary controller will be discarded .
return YES ;
}
else
{
return NO ;
}
}
- ( 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 ;
}
2016-05-19 15:22:29 +00:00
# pragma mark - Status Bar Tap handling
- ( void ) touchesBegan : ( NSSet * ) touches withEvent : ( UIEvent * ) event
{
[ super touchesBegan : touches withEvent : event ] ;
UITouch * touch = [ touches anyObject ] ;
CGPoint point = [ touch locationInView : self . window ] ;
CGRect statusBarFrame = [ UIApplication sharedApplication ] . statusBarFrame ;
if ( CGRectContainsPoint ( statusBarFrame , point ) )
{
[ [ NSNotificationCenter defaultCenter ] postNotificationName : kAppDelegateDidTapStatusBarNotification object : nil ] ;
}
}
2016-05-20 12:31:23 +00:00
# pragma mark - No call support
/ * *
Display a "Call not supported" alert when the session receives a call invitation .
@ param mxSession the session to spy
* /
- ( void ) enableNoVoIPOnMatrixSession : ( MXSession * ) mxSession
{
// Listen to call events
2016-05-20 13:45:29 +00:00
callEventsListeners [ @ ( mxSession . hash ) ] =
2016-05-20 12:31:23 +00:00
[ mxSession listenToEventsOfTypes : @ [
kMXEventTypeStringCallInvite ,
kMXEventTypeStringCallCandidates ,
kMXEventTypeStringCallAnswer ,
kMXEventTypeStringCallHangup
]
onEvent : ^ ( MXEvent * event , MXTimelineDirection direction , id customObject )
{
if ( MXTimelineDirectionForwards = = direction )
{
switch ( event . eventType )
{
case MXEventTypeCallInvite :
{
if ( noCallSupportAlert )
{
[ noCallSupportAlert dismiss : NO ] ;
}
2016-05-20 12:44:18 +00:00
MXCallInviteEventContent * callInviteEventContent = [ MXCallInviteEventContent modelFromJSON : event . content ] ;
// Sanity and invite expiration checks
if ( ! callInviteEventContent || event . age >= callInviteEventContent . lifetime )
{
return ;
}
2016-05-20 12:31:23 +00:00
MXUser * caller = [ mxSession userWithUserId : event . sender ] ;
NSString * callerDisplayname = caller . displayname ;
if ( ! callerDisplayname . length )
{
callerDisplayname = event . sender ;
}
2016-09-13 14:35:12 +00:00
NSString * appDisplayName = [ [ [ NSBundle mainBundle ] infoDictionary ] objectForKey : @ "CFBundleDisplayName" ] ;
NSString * message = [ NSString stringWithFormat : NSLocalizedStringFromTable ( @ "no_voip" , @ "Vector" , nil ) , callerDisplayname , appDisplayName ] ;
2016-05-20 12:31:23 +00:00
noCallSupportAlert = [ [ MXKAlert alloc ] initWithTitle : NSLocalizedStringFromTable ( @ "no_voip_title" , @ "Vector" , nil )
message : message
style : MXKAlertStyleAlert ] ;
__weak typeof ( self ) weakSelf = self ;
noCallSupportAlert . cancelButtonIndex = [ noCallSupportAlert addActionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "ignore" ] style : MXKAlertActionStyleDefault handler : ^ ( MXKAlert * alert ) {
2017-03-23 16:48:05 +00:00
typeof ( self ) self = weakSelf ;
self -> noCallSupportAlert = nil ;
2016-05-20 12:31:23 +00:00
} ] ;
[ noCallSupportAlert addActionWithTitle : [ NSBundle mxk_localizedStringForKey : @ "reject_call" ] style : MXKAlertActionStyleDefault handler : ^ ( MXKAlert * alert ) {
// Reject the call by sending the hangup event
2016-05-20 12:44:18 +00:00
NSDictionary * content = @ {
@ "call_id" : callInviteEventContent . callId ,
@ "version" : @ ( 0 )
} ;
2016-05-20 12:31:23 +00:00
2016-05-20 12:44:18 +00:00
[ mxSession . matrixRestClient sendEventToRoom : event . roomId eventType : kMXEventTypeStringCallHangup content : content success : nil failure : ^ ( NSError * error ) {
2016-10-21 15:10:02 +00:00
NSLog ( @ "[AppDelegate] enableNoVoIPOnMatrixSession: ERROR: Cannot send m.call.hangup event." ) ;
2016-05-20 12:44:18 +00:00
} ] ;
2016-05-20 12:31:23 +00:00
2017-03-23 16:48:05 +00:00
typeof ( self ) self = weakSelf ;
self -> noCallSupportAlert = nil ;
2016-05-20 12:31:23 +00:00
} ] ;
2016-09-06 15:20:16 +00:00
[ self showNotificationAlert : noCallSupportAlert ] ;
2016-05-20 12:31:23 +00:00
break ;
}
case MXEventTypeCallAnswer :
case MXEventTypeCallHangup :
// The call has ended . The alert is no more needed .
if ( noCallSupportAlert )
{
[ noCallSupportAlert dismiss : YES ] ;
noCallSupportAlert = nil ;
}
break ;
default :
break ;
}
}
} ] ;
}
- ( void ) disableNoVoIPOnMatrixSession : ( MXSession * ) mxSession
{
// Stop listening to the call events of this session
2016-05-20 13:45:29 +00:00
[ mxSession removeListener : callEventsListeners [ @ ( mxSession . hash ) ] ] ;
[ callEventsListeners removeObjectForKey : @ ( mxSession . hash ) ] ;
2016-05-20 12:31:23 +00:00
}
2014-10-02 15:02:47 +00:00
@ end