Statistics
| Branch: | Tag: | Revision:

chatsecureios / ChatSecure / Classes / OTRAppDelegate.m @ fa6e5004

History | View | Annotate | Download (22.7 KB)

1
//
2
//  OTRAppDelegate.m
3
//  Off the Record
4
//
5
//  Created by Chris Ballinger on 8/11/11.
6
//  Copyright (c) 2011 Chris Ballinger. All rights reserved.
7
//
8
//  This file is part of ChatSecure.
9
//
10
//  ChatSecure is free software: you can redistribute it and/or modify
11
//  it under the terms of the GNU General Public License as published by
12
//  the Free Software Foundation, either version 3 of the License, or
13
//  (at your option) any later version.
14
//
15
//  ChatSecure is distributed in the hope that it will be useful,
16
//  but WITHOUT ANY WARRANTY; without even the implied warranty of
17
//  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
18
//  GNU General Public License for more details.
19
//
20
//  You should have received a copy of the GNU General Public License
21
//  along with ChatSecure.  If not, see <http://www.gnu.org/licenses/>.
22

    
23
#import "OTRAppDelegate.h"
24

    
25
#import "OTRConversationViewController.h"
26

    
27
#import "OTRMessagesHoldTalkViewController.h"
28
#import "OTRSettingsViewController.h"
29
#import "OTRSettingsManager.h"
30

    
31
@import Appirater;
32
#import "OTRConstants.h"
33

    
34
#import "OTRUtilities.h"
35
#import "OTRAccountsManager.h"
36
#import "OTRSettingsManager.h"
37
@import OTRAssets;
38
#import "OTRDatabaseManager.h"
39
@import SAMKeychain;
40

    
41
#import "OTRLog.h"
42
@import CocoaLumberjack;
43
#import "OTRAccount.h"
44
#import "OTRXMPPAccount.h"
45
#import "OTRBuddy.h"
46
@import YapDatabase;
47

    
48
#import "OTRCertificatePinning.h"
49
#import "NSURL+ChatSecure.h"
50
#import "OTRDatabaseUnlockViewController.h"
51
#import "OTRIncomingMessage.h"
52
#import "OTROutgoingMessage.h"
53
#import "OTRPasswordGenerator.h"
54
#import "UIViewController+ChatSecure.h"
55
#import "OTRNotificationController.h"
56
@import XMPPFramework;
57
#import "OTRProtocolManager.h"
58
#import "OTRInviteViewController.h"
59
#import "OTRTheme.h"
60
#import <ChatSecureCore/ChatSecureCore-Swift.h>
61
#import "OTRMessagesViewController.h"
62
#import "OTRXMPPTorAccount.h"
63
@import OTRAssets;
64
@import OTRKit;
65
#import "OTRPushTLVHandlerProtocols.h"
66
#import <KSCrash/KSCrash.h>
67
#import <KSCrash/KSCrashInstallationQuincyHockey.h>
68
#import <KSCrash/KSCrashInstallation+Alert.h>
69
@import UserNotifications;
70

    
71
#import "OTRChatDemo.h"
72

    
73
@interface OTRAppDelegate () <UNUserNotificationCenterDelegate>
74

    
75
@property (nonatomic, strong) OTRSplitViewCoordinator *splitViewCoordinator;
76
@property (nonatomic, strong) OTRSplitViewControllerDelegateObject *splitViewControllerDelegate;
77

    
78
@property (nonatomic, strong) NSTimer *fetchTimer;
79
@property (nonatomic, strong) NSTimer *backgroundTimer;
80
@property (nonatomic) UIBackgroundTaskIdentifier backgroundTask;
81

    
82
@end
83

    
84
@implementation OTRAppDelegate
85
@synthesize window = _window;
86

    
87
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
88
{
89
#if DEBUG
90
    [DDLog addLogger:[DDTTYLogger sharedInstance]];
91
    
92
    DDFileLogger *fileLogger = [[DDFileLogger alloc] init];
93
    fileLogger.rollingFrequency = 0;
94
    fileLogger.maximumFileSize = 0;
95
    [DDLog addLogger:fileLogger withLevel:DDLogLevelAll];
96
#endif
97
    
98
    [self setupCrashReporting];
99
    
100
    _theme = [[[self themeClass] alloc] init];
101
    [self.theme setupGlobalTheme];
102
    
103
    [SAMKeychain setAccessibilityType:kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly];
104
    
105
    UIViewController *rootViewController = nil;
106
    
107
    // Create 3 primary view controllers, settings, conversation list and messages
108
    _conversationViewController = [self.theme conversationViewController];
109
    _messagesViewController = [self.theme messagesViewController];
110
    
111
    
112
    if ([OTRDatabaseManager existsYapDatabase] && ![[OTRDatabaseManager sharedInstance] hasPassphrase]) {
113
        // user needs to enter password for current database
114
        rootViewController = [[OTRDatabaseUnlockViewController alloc] init];
115
    } else {
116
        ////// Normal launch to conversationViewController //////
117
        if (![OTRDatabaseManager existsYapDatabase]) {
118
            /**
119
             First Launch
120
             Create password and save to keychain
121
             **/
122
            NSString *newPassword = [OTRPasswordGenerator passwordWithLength:OTRDefaultPasswordLength];
123
            NSError *error = nil;
124
            [[OTRDatabaseManager sharedInstance] setDatabasePassphrase:newPassword remember:YES error:&error];
125
            if (error) {
126
                DDLogError(@"Password Error: %@",error);
127
            }
128
        }
129

    
130
        [[OTRDatabaseManager sharedInstance] setupDatabaseWithName:OTRYapDatabaseName];
131
        rootViewController = [self setupDefaultSplitViewControllerWithLeadingViewController:[[UINavigationController alloc] initWithRootViewController:self.conversationViewController]];
132
        if ([[[NSProcessInfo processInfo] environment][@"OTRLaunchMode"] isEqualToString:@"ChatSecureUITestsDemoData"]) {
133
            [OTRChatDemo loadDemoChatInDatabase];
134
        } else if ([[[NSProcessInfo processInfo] environment][@"OTRLaunchMode"] isEqualToString:@"ChatSecureUITests"]) {
135
            [[OTRDatabaseManager sharedInstance].readWriteDatabaseConnection readWriteWithBlock:^(YapDatabaseReadWriteTransaction * _Nonnull transaction) {
136
                [transaction removeAllObjectsInAllCollections];
137
            }];
138
        }
139
    }
140
    self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
141
    self.window.rootViewController = rootViewController;
142
    
143
    /////////// testing VCs
144
//    OTRXMPPAccount *account = [[OTRXMPPAccount alloc] init];
145
//    account.username = @"test@example.com";
146
//    OTRInviteViewController *vc = [[OTRInviteViewController alloc] initWithAccount:account];
147
//    UINavigationController *nav = [[UINavigationController alloc] initWithRootViewController:vc];
148
//    self.window.rootViewController = nav;
149
    ////////////
150
    
151
    [self.window makeKeyAndVisible];
152
    [TransactionObserver.shared startObserving];
153
    
154
    OTRNotificationController *notificationController = [OTRNotificationController sharedInstance];
155
    [notificationController start];
156
    
157
    if ([PushController getPushPreference] == PushPreferenceEnabled) {
158
        [PushController registerForPushNotifications];
159
    }
160
  
161
    [Appirater setAppId:@"464200063"];
162
    [Appirater setOpenInAppStore:NO];
163
    [Appirater appLaunched:YES];
164
    
165
    [self autoLoginFromBackground:NO];
166
    [application setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
167
        
168
    // For disabling screen dimming while plugged in
169
    [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(batteryStateDidChange:) name:UIDeviceBatteryStateDidChangeNotification object:nil];
170
    [UIDevice currentDevice].batteryMonitoringEnabled = YES;
171
    [self batteryStateDidChange:nil];
172
    
173
    // Setup iOS 10+ in-app notifications
174
    NSOperatingSystemVersion ios10version = {.majorVersion = 10, .minorVersion = 0, .patchVersion = 0};
175
    if ([[NSProcessInfo processInfo] isOperatingSystemAtLeastVersion:ios10version]) {
176
        [UNUserNotificationCenter currentNotificationCenter].delegate = self;
177
    }
178

    
179
    
180
    
181
    return YES;
182
}
183

    
184
- (void) setupCrashReporting {
185
    KSCrash *crash = [KSCrash sharedInstance];
186
    crash.monitoring = KSCrashMonitorTypeProductionSafeMinimal;
187
    
188
//#warning Change this to KSCrashMonitorTypeProductionSafeMinimal before App Store release!
189
//#warning Otherwise it may crash for pauses longer than the deadlockWatchdogInterval!
190
    
191
    // People are reporting deadlocks again...
192
    // Let's turn this back on for a little while.
193
#if DEBUG
194
    crash.monitoring = KSCrashMonitorTypeDebuggerSafe;
195
#else
196
    //crash.monitoring = KSCrashMonitorTypeAll;
197
    //crash.deadlockWatchdogInterval = 20;
198
#endif
199
    
200
    // Setup Crash Reporting
201
    KSCrashInstallationHockey* installation = [KSCrashInstallationHockey sharedInstance];
202
    [installation addConditionalAlertWithTitle:Crash_Detected_Title()
203
                                       message:Crash_Detected_Message()
204
                                     yesAnswer:OK_STRING()
205
                                      noAnswer:CANCEL_STRING()];
206

    
207
    installation.appIdentifier = [OTRSecrets hockeyLiveIdentifier];
208
    
209
    [installation install];
210
    [installation sendAllReportsWithCompletion:^(NSArray *filteredReports, BOOL completed, NSError *error)
211
    {
212
        if (error) {
213
            NSLog(@"Error sending KSCrashInstallationHockey reports: %@", error);
214
        } else {
215
            NSLog(@"Sending %d KSCrashInstallationHockey reports.", (int)filteredReports.count);
216
        }
217
    }];
218
}
219

    
220
/**
221
 * This creates a UISplitViewController using a leading view controller (the left view controller). It uses a navigation controller with
222
 * self.messagesViewController as teh right view controller;
223
 * This also creates and sets up teh OTRSplitViewCoordinator
224
 *
225
 * @param leadingViewController The leading or left most view controller in a UISplitViewController. Should most likely be some sort of UINavigationViewController
226
 * @return The base default UISplitViewController
227
 *
228
 */
229
- (UIViewController *)setupDefaultSplitViewControllerWithLeadingViewController:(nonnull UIViewController *)leadingViewController
230
{
231
    
232
    YapDatabaseConnection *connection = [OTRDatabaseManager sharedInstance].readWriteDatabaseConnection;
233
    self.splitViewCoordinator = [[OTRSplitViewCoordinator alloc] initWithDatabaseConnection:connection];
234
    self.splitViewControllerDelegate = [[OTRSplitViewControllerDelegateObject alloc] init];
235
    self.conversationViewController.delegate = self.splitViewCoordinator;
236
    
237
    //MessagesViewController Nav
238
    UINavigationController *messagesNavigationController = [[UINavigationController alloc ]initWithRootViewController:self.messagesViewController];
239
    
240
    //SplitViewController
241
    UISplitViewController *splitViewController = [[UISplitViewController alloc] init];
242
    splitViewController.viewControllers = @[leadingViewController,messagesNavigationController];
243
    splitViewController.delegate = self.splitViewControllerDelegate;
244
    splitViewController.title = CHAT_STRING();
245
    
246
    //setup 'back' button in nav bar
247
    messagesNavigationController.topViewController.navigationItem.leftBarButtonItem = splitViewController.displayModeButtonItem;
248
    messagesNavigationController.topViewController.navigationItem.leftItemsSupplementBackButton = YES;
249
    
250
    self.splitViewCoordinator.splitViewController = splitViewController;
251
    
252
    return splitViewController;
253
}
254

    
255
- (void)showConversationViewController
256
{
257
    self.window.rootViewController = [self setupDefaultSplitViewControllerWithLeadingViewController:[[UINavigationController alloc] initWithRootViewController:self.conversationViewController]];
258
}
259

    
260
- (void)applicationWillResignActive:(UIApplication *)application
261
{
262
    [OTRProtocolManager sharedInstance].lastInteractionDate = [NSDate date];
263
    /*
264
     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.
265
     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.
266
     */
267
}
268

    
269
- (void)applicationDidEnterBackground:(UIApplication *)application
270
{
271
    [[OTRProtocolManager sharedInstance] goAwayForAllAccounts];
272
    DDLogInfo(@"Application entered background state.");
273
    NSAssert(self.backgroundTask == UIBackgroundTaskInvalid, nil);
274
    
275
    __block NSUInteger unread = 0;
276
    [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection asyncReadWithBlock:^(YapDatabaseReadTransaction *transaction) {
277
        unread = [transaction numberOfUnreadMessages];
278
    } completionBlock:^{
279
        application.applicationIconBadgeNumber = unread;
280
    }];
281
    
282
    self.backgroundTask = [application beginBackgroundTaskWithExpirationHandler: ^{
283
        DDLogInfo(@"Background task expired, disconnecting all accounts. Remaining: %f", application.backgroundTimeRemaining);
284
        if (self.backgroundTimer)
285
        {
286
            [self.backgroundTimer invalidate];
287
            self.backgroundTimer = nil;
288
        }
289
        [[OTRProtocolManager sharedInstance] disconnectAllAccountsSocketOnly:YES timeout:application.backgroundTimeRemaining - .5 completionBlock:^{
290
            [application endBackgroundTask:self.backgroundTask];
291
            self.backgroundTask = UIBackgroundTaskInvalid;
292
        }];
293
    }];
294
    
295
    dispatch_async(dispatch_get_main_queue(), ^{
296
        self.backgroundTimer = [NSTimer scheduledTimerWithTimeInterval:1 target:self selector:@selector(timerUpdate:) userInfo:nil repeats:YES];
297
    });
298
}
299
                                
300
- (void) timerUpdate:(NSTimer*)timer {
301
    UIApplication *application = [UIApplication sharedApplication];
302
    NSTimeInterval timeRemaining = application.backgroundTimeRemaining;
303
    DDLogVerbose(@"Timer update, background time left: %f", timeRemaining);
304
}
305

    
306
/** Doesn't stop autoLogin if previous crash when it's a background launch */
307
- (void)autoLoginFromBackground:(BOOL)fromBackground
308
{
309
    [[OTRProtocolManager sharedInstance] loginAccounts:[OTRAccountsManager allAutoLoginAccounts]];
310
}
311

    
312
- (void)applicationWillEnterForeground:(UIApplication *)application
313
{
314
    [Appirater appEnteredForeground:YES];
315
}
316

    
317
- (void)applicationDidBecomeActive:(UIApplication *)application
318
{
319
    [OTRProtocolManager sharedInstance].lastInteractionDate = [NSDate date];
320
    [self autoLoginFromBackground:NO];
321
    /*
322
     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.
323
     */
324
    [self batteryStateDidChange:nil];
325
    
326
    DDLogInfo(@"Application became active");
327
    
328
    if (self.backgroundTimer) 
329
    {
330
        [self.backgroundTimer invalidate];
331
        self.backgroundTimer = nil;
332
    }
333
    if (self.backgroundTask != UIBackgroundTaskInvalid) 
334
    {
335
        [application endBackgroundTask:self.backgroundTask];
336
        self.backgroundTask = UIBackgroundTaskInvalid;
337
    }
338
    
339
    [UIApplication.sharedApplication removeExtraForegroundNotifications];
340
    
341
    if (self.fetchTimer) {
342
        if (self.fetchTimer.isValid) {
343
            NSDictionary *userInfo = self.fetchTimer.userInfo;
344
            void (^completion)(UIBackgroundFetchResult) = [userInfo objectForKey:@"completion"];
345
            // We should probbaly return accurate fetch results
346
            if (completion) {
347
                completion(UIBackgroundFetchResultNewData);
348
            }
349
            [self.fetchTimer invalidate];
350
        }
351
        self.fetchTimer = nil;
352
    }
353
}
354

    
355
- (void)applicationWillTerminate:(UIApplication *)application
356
{
357
    /*
358
     Called when the application is about to terminate.
359
     Save data if appropriate.
360
     See also applicationDidEnterBackground:.
361
     */
362
    
363
    
364
    [[OTRProtocolManager sharedInstance] disconnectAllAccounts];
365
    
366
    //FIXME? [OTRManagedAccount resetAccountsConnectionStatus];
367
    //[OTRUtilities deleteAllBuddiesAndMessages];
368
}
369

    
370
-(void)application:(UIApplication *)application performFetchWithCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
371
    [self autoLoginFromBackground:YES];
372
    
373
    self.fetchTimer = [NSTimer scheduledTimerWithTimeInterval:28.5 target:self selector:@selector(fetchTimerUpdate:) userInfo:@{@"completion": completionHandler} repeats:NO];
374
}
375

    
376
- (void) fetchTimerUpdate:(NSTimer*)timer {
377
    void (^completion)(UIBackgroundFetchResult) = timer.userInfo[@"completion"];
378
    NSTimeInterval timeout = [[UIApplication sharedApplication] backgroundTimeRemaining] - .5;
379

    
380
    [[OTRProtocolManager sharedInstance] disconnectAllAccountsSocketOnly:YES timeout:timeout completionBlock:^{
381
        dispatch_async(dispatch_get_main_queue(), ^{
382
            [UIApplication.sharedApplication removeExtraForegroundNotifications];
383
            // We should probably return accurate fetch results
384
            if (completion) {
385
                completion(UIBackgroundFetchResultNewData);
386
            }
387
        });
388
    }];
389
    self.fetchTimer = nil;
390
}
391

    
392
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler
393
{
394
    [self application:application performFetchWithCompletionHandler:completionHandler];
395
    
396
    [[OTRProtocolManager sharedInstance].pushController receiveRemoteNotification:userInfo completion:^(OTRBuddy * _Nullable buddy, NSError * _Nullable error) {
397
        // Only show notification if buddy lookup succeeds
398
        if (buddy) {
399
            [application showLocalNotificationForKnockFrom:buddy];
400
        }
401
    }];
402
}
403

    
404
- (BOOL)application:(UIApplication *)application continueUserActivity:(NSUserActivity *)userActivity restorationHandler:(void(^)(NSArray * __nullable restorableObjects))restorationHandler {
405
    if ([userActivity.activityType isEqualToString:NSUserActivityTypeBrowsingWeb]) {
406
        NSURL *url = userActivity.webpageURL;
407
        if ([url otr_isInviteLink]) {
408
            __block XMPPJID *jid = nil;
409
            __block NSString *fingerprint = nil;
410
            NSString *otr = [OTRAccount fingerprintStringTypeForFingerprintType:OTRFingerprintTypeOTR];
411
            [url otr_decodeShareLink:^(XMPPJID * _Nullable inJid, NSArray<NSURLQueryItem*> * _Nullable queryItems) {
412
                jid = inJid;
413
                [queryItems enumerateObjectsUsingBlock:^(NSURLQueryItem * _Nonnull obj, NSUInteger idx, BOOL * _Nonnull stop) {
414
                    if ([obj.name isEqualToString:otr]) {
415
                        fingerprint = obj.value;
416
                        *stop = YES;
417
                    }
418
                }];
419
            }];
420
            if (jid) {
421
                [OTRProtocolManager handleInviteForJID:jid otrFingerprint:fingerprint buddyAddedCallback:nil];
422
            }
423
            return YES;
424
        }
425
    }
426
    return NO;
427
}
428

    
429
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
430
    
431
    
432
}
433

    
434
- (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation
435
{
436
    if ([url.scheme isEqualToString:@"xmpp"]) {
437
        XMPPURI *xmppURI = [[XMPPURI alloc] initWithURL:url];
438
        XMPPJID *jid = xmppURI.jid;
439
        NSString *otrFingerprint = xmppURI.queryParameters[@"otr-fingerprint"];
440
        // NSString *action = xmppURI.queryAction; //  && [action isEqualToString:@"subscribe"]
441
        if (jid) {
442
            [OTRProtocolManager handleInviteForJID:jid otrFingerprint:otrFingerprint buddyAddedCallback:^ (OTRBuddy *buddy) {
443
                OTRXMPPBuddy *xmppBuddy = (OTRXMPPBuddy *)buddy;
444
                if (xmppBuddy != nil) {
445
                    NSDictionary *userInfo = [[NSDictionary alloc] initWithObjectsAndKeys:xmppBuddy.threadIdentifier, kOTRNotificationThreadKey, xmppBuddy.threadCollection, kOTRNotificationThreadCollection, nil];
446
                    [self enterThreadWithUserInfo:userInfo];
447
                }
448
            }];
449
            return YES;
450
        }
451
    }
452
    return NO;
453
}
454

    
455

    
456

    
457
- (void) showSubscriptionRequestForBuddy:(NSDictionary*)userInfo {
458
    // This is probably in response to a user requesting subscriptions from us
459
    [self.splitViewCoordinator showConversationsViewController];
460
}
461

    
462
- (void) enterThreadWithUserInfo:(NSDictionary*)userInfo {
463
    NSString *threadKey = userInfo[kOTRNotificationThreadKey];
464
    NSString *threadCollection = userInfo[kOTRNotificationThreadCollection];
465
    NSParameterAssert(threadKey);
466
    NSParameterAssert(threadCollection);
467
    if (!threadKey || !threadCollection) { return; }
468
    __block id <OTRThreadOwner> thread = nil;
469
    [[OTRDatabaseManager sharedInstance].readOnlyDatabaseConnection readWithBlock:^(YapDatabaseReadTransaction * _Nonnull transaction) {
470
        thread = [transaction objectForKey:threadKey inCollection:threadCollection];
471
    }];
472
    if (thread) {
473
        [self.splitViewCoordinator enterConversationWithThread:thread sender:self];
474
    }
475
}
476

    
477
- (void)application:(UIApplication *)application didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings
478
{
479
    [[NSNotificationCenter defaultCenter] postNotificationName:OTRUserNotificationsChanged object:self userInfo:@{@"settings": notificationSettings}];
480
    if (notificationSettings.types == UIUserNotificationTypeNone) {
481
        NSLog(@"Push notifications disabled by user.");
482
    } else {
483
        [application registerForRemoteNotifications];
484
    }
485
}
486

    
487
- (void)application:(UIApplication *)app didRegisterForRemoteNotificationsWithDeviceToken:(nonnull NSData *)deviceToken
488
{
489
    [[OTRProtocolManager sharedInstance].pushController didRegisterForRemoteNotificationsWithDeviceToken:deviceToken];
490
}
491

    
492
- (void)application:(UIApplication *)app didFailToRegisterForRemoteNotificationsWithError:(NSError *)err {
493
    [[NSNotificationCenter defaultCenter] postNotificationName:OTRFailedRemoteNotificationRegistration object:self userInfo:@{kOTRNotificationErrorKey:err}];
494
    DDLogError(@"Error in registration. Error: %@%@", [err localizedDescription], [err userInfo]);
495
}
496

    
497
// To improve usability, keep the app open when you're plugged in
498
- (void) batteryStateDidChange:(NSNotification*)notification {
499
    UIDeviceBatteryState currentState = [[UIDevice currentDevice] batteryState];
500
    if (currentState == UIDeviceBatteryStateCharging || currentState == UIDeviceBatteryStateFull) {
501
        [[UIApplication sharedApplication] setIdleTimerDisabled:YES];
502
    } else {
503
        [[UIApplication sharedApplication] setIdleTimerDisabled:NO];
504
    }
505
}
506

    
507
#pragma mark UNUserNotificationCenterDelegate methods (iOS 10+)
508

    
509
- (void) userNotificationCenter:(UNUserNotificationCenter *)center willPresentNotification:(UNNotification *)notification withCompletionHandler:(void (^)(UNNotificationPresentationOptions))completionHandler {
510
    [OTRAppDelegate visibleThread:^(YapCollectionKey * _Nullable ck) {
511
        if ([ck.key isEqualToString:notification.request.content.threadIdentifier]) {
512
            completionHandler(UNNotificationPresentationOptionNone);
513
        } else {
514
            completionHandler(UNNotificationPresentationOptionBadge | UNNotificationPresentationOptionSound | UNNotificationPresentationOptionAlert);
515
        }
516
    } completionQueue:nil];
517
}
518

    
519
- (void) userNotificationCenter:(UNUserNotificationCenter *)center didReceiveNotificationResponse:(UNNotificationResponse *)response withCompletionHandler:(void (^)(void))completionHandler {
520
    NSDictionary *userInfo = response.notification.request.content.userInfo;
521
    if ([userInfo[kOTRNotificationType] isEqualToString:kOTRNotificationTypeNone]) {
522
        // Nothing
523
    } else if ([userInfo[kOTRNotificationType] isEqualToString:kOTRNotificationTypeSubscriptionRequest]) {
524
        // This is a subscription request
525
        [self showSubscriptionRequestForBuddy:userInfo];
526
    } else {
527
        [self enterThreadWithUserInfo:userInfo];
528
    }
529
    completionHandler();
530
}
531

    
532
#pragma - mark Class Methods
533
+ (instancetype)appDelegate
534
{
535
    return (OTRAppDelegate*)[[UIApplication sharedApplication] delegate];
536
}
537

    
538
#pragma mark - Theming
539

    
540
- (Class) themeClass {
541
    return [OTRTheme class];
542
}
543

    
544
@end