mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
fixes #1231 Motivation ---------- Previously when messages failed to send, there was no reason given. Furthermore, when media messages failed to send there was no indication that any attempt to send the message even occurred, nor a retry dialog. UX Changes ---------- - Show "uploading" status for media - Show specific error message in retry-send dialog - Only scroll to bottom when new message is inserted - Show specific errors when group creation fails Code Changes ----------- - Updated incorrect references to TSMessageAdapters which were actually references to OWSMessageData - MessageSender was extracted from SSK MessagesManager - access MessagesManager as property - idiomatic init/properties for Env - log contact intersections - Move scroll-to-bottom animation to main thread. // FREEBIE
390 lines
16 KiB
Objective-C
390 lines
16 KiB
Objective-C
#import "AppDelegate.h"
|
|
#import "AppStoreRating.h"
|
|
#import "CategorizingLogger.h"
|
|
#import "CodeVerificationViewController.h"
|
|
#import "DebugLogger.h"
|
|
#import "Environment.h"
|
|
#import "NotificationsManager.h"
|
|
#import "OWSContactsManager.h"
|
|
#import "OWSStaleNotificationObserver.h"
|
|
#import "PreferencesUtil.h"
|
|
#import "PushManager.h"
|
|
#import "Release.h"
|
|
#import "TSAccountManager.h"
|
|
#import "TSMessagesManager.h"
|
|
#import "TSPreKeyManager.h"
|
|
#import "TSSocketManager.h"
|
|
#import "TextSecureKitEnv.h"
|
|
#import "VersionMigrations.h"
|
|
#import <SignalServiceKit/OWSDisappearingMessagesJob.h>
|
|
#import <SignalServiceKit/OWSIncomingMessageReadObserver.h>
|
|
#import <SignalServiceKit/OWSMessageSender.h>
|
|
|
|
static NSString *const kStoryboardName = @"Storyboard";
|
|
static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewController";
|
|
static NSString *const kURLSchemeSGNLKey = @"sgnl";
|
|
static NSString *const kURLHostVerifyPrefix = @"verify";
|
|
|
|
@interface AppDelegate ()
|
|
|
|
@property (nonatomic, retain) UIWindow *screenProtectionWindow;
|
|
@property (nonatomic) OWSIncomingMessageReadObserver *incomingMessageReadObserver;
|
|
@property (nonatomic) OWSStaleNotificationObserver *staleNotificationObserver;
|
|
|
|
@end
|
|
|
|
@implementation AppDelegate
|
|
|
|
#pragma mark Detect updates - perform migrations
|
|
|
|
- (void)applicationWillEnterForeground:(UIApplication *)application {
|
|
[[UIApplication sharedApplication] setApplicationIconBadgeNumber:0];
|
|
}
|
|
|
|
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions {
|
|
// Initializing logger
|
|
CategorizingLogger *logger = [CategorizingLogger categorizingLogger];
|
|
[logger addLoggingCallback:^(NSString *category, id details, NSUInteger index){
|
|
}];
|
|
|
|
// Setting up environment
|
|
[Environment setCurrent:[Release releaseEnvironmentWithLogging:logger]];
|
|
|
|
[self setupAppearance];
|
|
[[PushManager sharedManager] registerPushKitNotificationFuture];
|
|
|
|
if (getenv("runningTests_dontStartApp")) {
|
|
return YES;
|
|
}
|
|
|
|
if ([TSAccountManager isRegistered]) {
|
|
[Environment.getCurrent.contactsManager doAfterEnvironmentInitSetup];
|
|
}
|
|
[Environment.getCurrent initCallListener];
|
|
|
|
BOOL loggingIsEnabled;
|
|
|
|
#ifdef DEBUG
|
|
// Specified at Product -> Scheme -> Edit Scheme -> Test -> Arguments -> Environment to avoid things like
|
|
// the phone directory being looked up during tests.
|
|
loggingIsEnabled = TRUE;
|
|
[DebugLogger.sharedLogger enableTTYLogging];
|
|
#elif RELEASE
|
|
loggingIsEnabled = Environment.preferences.loggingIsEnabled;
|
|
#endif
|
|
[self verifyBackgroundBeforeKeysAvailableLaunch];
|
|
|
|
if (loggingIsEnabled) {
|
|
[DebugLogger.sharedLogger enableFileLogging];
|
|
}
|
|
|
|
[self setupTSKitEnv];
|
|
|
|
UIStoryboard *storyboard = [UIStoryboard storyboardWithName:kStoryboardName bundle:[NSBundle mainBundle]];
|
|
UIViewController *viewController =
|
|
[storyboard instantiateViewControllerWithIdentifier:kInitialViewControllerIdentifier];
|
|
|
|
self.window = [[UIWindow alloc] initWithFrame:[[UIScreen mainScreen] bounds]];
|
|
self.window.rootViewController = viewController;
|
|
|
|
[self.window makeKeyAndVisible];
|
|
|
|
[VersionMigrations performUpdateCheck]; // this call must be made after environment has been initialized because in
|
|
// general upgrade may depend on environment
|
|
|
|
// Accept push notification when app is not open
|
|
NSDictionary *remoteNotif = launchOptions[UIApplicationLaunchOptionsRemoteNotificationKey];
|
|
if (remoteNotif) {
|
|
DDLogInfo(@"Application was launched by tapping a push notification.");
|
|
[self application:application didReceiveRemoteNotification:remoteNotif];
|
|
}
|
|
|
|
[self prepareScreenProtection];
|
|
|
|
// Avoid blocking app launch by putting all possible DB access in async thread.
|
|
UIApplicationState launchState = application.applicationState;
|
|
[TSAccountManager runIfRegistered:^{
|
|
if (launchState == UIApplicationStateInactive) {
|
|
DDLogWarn(@"The app was launched from inactive");
|
|
[TSSocketManager becomeActiveFromForeground];
|
|
} else if (launchState == UIApplicationStateBackground) {
|
|
DDLogWarn(@"The app was launched from being backgrounded");
|
|
[TSSocketManager becomeActiveFromBackgroundExpectMessage:NO];
|
|
} else {
|
|
DDLogWarn(@"The app was launched in an unknown way");
|
|
}
|
|
|
|
[[PushManager sharedManager] validateUserNotificationSettings];
|
|
[TSPreKeyManager refreshPreKeys];
|
|
|
|
// Clean up any messages that expired since last launch.
|
|
[[[OWSDisappearingMessagesJob alloc] initWithStorageManager:[TSStorageManager sharedManager]] run];
|
|
}];
|
|
|
|
[AppStoreRating setupRatingLibrary];
|
|
return YES;
|
|
}
|
|
|
|
- (void)setupTSKitEnv {
|
|
[TextSecureKitEnv sharedEnv].contactsManager = [Environment getCurrent].contactsManager;
|
|
[[TSStorageManager sharedManager] setupDatabase];
|
|
[TextSecureKitEnv sharedEnv].notificationsManager = [[NotificationsManager alloc] init];
|
|
|
|
OWSMessageSender *messageSender =
|
|
[[OWSMessageSender alloc] initWithNetworkManager:[Environment getCurrent].networkManager
|
|
storageManager:[TSStorageManager sharedManager]
|
|
contactsManager:[Environment getCurrent].contactsManager
|
|
contactsUpdater:[Environment getCurrent].contactsUpdater];
|
|
|
|
self.incomingMessageReadObserver =
|
|
[[OWSIncomingMessageReadObserver alloc] initWithStorageManager:[TSStorageManager sharedManager]
|
|
messageSender:messageSender];
|
|
[self.incomingMessageReadObserver startObserving];
|
|
|
|
self.staleNotificationObserver = [OWSStaleNotificationObserver new];
|
|
[self.staleNotificationObserver startObserving];
|
|
}
|
|
|
|
- (void)application:(UIApplication *)application didRegisterForRemoteNotificationsWithDeviceToken:(NSData *)deviceToken
|
|
{
|
|
DDLogDebug(@"%@ Successfully registered for remote notifications with token: %@", self.tag, deviceToken);
|
|
[PushManager.sharedManager.pushNotificationFutureSource trySetResult:deviceToken];
|
|
}
|
|
|
|
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
|
|
{
|
|
DDLogError(@"%@ Failed to register for remote notifications with error %@", self.tag, error);
|
|
#ifdef DEBUG
|
|
DDLogWarn(@"%@ We're in debug mode. Faking success for remote registration with a fake push identifier", self.tag);
|
|
[PushManager.sharedManager.pushNotificationFutureSource trySetResult:[NSData dataWithLength:32]];
|
|
#else
|
|
[PushManager.sharedManager.pushNotificationFutureSource trySetFailure:error];
|
|
#endif
|
|
}
|
|
|
|
- (void)application:(UIApplication *)application
|
|
didRegisterUserNotificationSettings:(UIUserNotificationSettings *)notificationSettings {
|
|
[PushManager.sharedManager.userNotificationFutureSource trySetResult:notificationSettings];
|
|
}
|
|
|
|
- (BOOL)application:(UIApplication *)application
|
|
openURL:(NSURL *)url
|
|
sourceApplication:(NSString *)sourceApplication
|
|
annotation:(id)annotation {
|
|
if ([url.scheme isEqualToString:kURLSchemeSGNLKey]) {
|
|
if ([url.host hasPrefix:kURLHostVerifyPrefix] && ![TSAccountManager isRegistered]) {
|
|
id signupController = [Environment getCurrent].signUpFlowNavigationController;
|
|
if ([signupController isKindOfClass:[UINavigationController class]]) {
|
|
UINavigationController *navController = (UINavigationController *)signupController;
|
|
UIViewController *controller = [navController.childViewControllers lastObject];
|
|
if ([controller isKindOfClass:[CodeVerificationViewController class]]) {
|
|
CodeVerificationViewController *cvvc = (CodeVerificationViewController *)controller;
|
|
NSString *verificationCode = [url.path substringFromIndex:1];
|
|
|
|
cvvc.challengeTextField.text = verificationCode;
|
|
[cvvc verifyChallengeAction:nil];
|
|
} else {
|
|
DDLogWarn(@"Not the verification view controller we expected. Got %@ instead",
|
|
NSStringFromClass(controller.class));
|
|
}
|
|
}
|
|
} else {
|
|
DDLogWarn(@"Application opened with an unknown URL action: %@", url.host);
|
|
}
|
|
} else {
|
|
DDLogWarn(@"Application opened with an unknown URL scheme: %@", url.scheme);
|
|
}
|
|
return NO;
|
|
}
|
|
|
|
- (void)applicationDidBecomeActive:(UIApplication *)application {
|
|
if (getenv("runningTests_dontStartApp")) {
|
|
return;
|
|
}
|
|
|
|
[TSAccountManager runIfRegistered:^{
|
|
// We're double checking that the app is active, to be sure since we can't verify in production env due to code
|
|
// signing.
|
|
[TSSocketManager becomeActiveFromForeground];
|
|
[[Environment getCurrent].contactsManager verifyABPermission];
|
|
}];
|
|
|
|
[self removeScreenProtection];
|
|
}
|
|
|
|
- (void)applicationWillResignActive:(UIApplication *)application {
|
|
UIBackgroundTaskIdentifier __block bgTask = UIBackgroundTaskInvalid;
|
|
bgTask = [application beginBackgroundTaskWithExpirationHandler:^{
|
|
|
|
}];
|
|
|
|
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
|
if ([TSAccountManager isRegistered]) {
|
|
dispatch_sync(dispatch_get_main_queue(), ^{
|
|
[self protectScreen];
|
|
[[[Environment getCurrent] signalsViewController] updateInboxCountLabel];
|
|
});
|
|
[TSSocketManager resignActivity];
|
|
}
|
|
|
|
[application endBackgroundTask:bgTask];
|
|
bgTask = UIBackgroundTaskInvalid;
|
|
});
|
|
}
|
|
|
|
- (void)application:(UIApplication *)application
|
|
performActionForShortcutItem:(UIApplicationShortcutItem *)shortcutItem
|
|
completionHandler:(void (^)(BOOL succeeded))completionHandler {
|
|
if ([TSAccountManager isRegistered]) {
|
|
[[Environment getCurrent].signalsViewController composeNew];
|
|
completionHandler(YES);
|
|
} else {
|
|
UIAlertController *controller =
|
|
[UIAlertController alertControllerWithTitle:NSLocalizedString(@"REGISTER_CONTACTS_WELCOME", nil)
|
|
message:NSLocalizedString(@"REGISTRATION_RESTRICTED_MESSAGE", nil)
|
|
preferredStyle:UIAlertControllerStyleAlert];
|
|
|
|
[controller addAction:[UIAlertAction actionWithTitle:NSLocalizedString(@"OK", nil)
|
|
style:UIAlertActionStyleDefault
|
|
handler:^(UIAlertAction *_Nonnull action){
|
|
|
|
}]];
|
|
[[Environment getCurrent]
|
|
.signalsViewController.presentedViewController presentViewController:controller
|
|
animated:YES
|
|
completion:^{
|
|
completionHandler(NO);
|
|
}];
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Screen protection obscures the app screen shown in the app switcher.
|
|
*/
|
|
- (void)prepareScreenProtection
|
|
{
|
|
UIWindow *window = [[UIWindow alloc] initWithFrame:self.window.bounds];
|
|
window.hidden = YES;
|
|
window.opaque = YES;
|
|
window.userInteractionEnabled = NO;
|
|
window.windowLevel = CGFLOAT_MAX;
|
|
window.backgroundColor = UIColor.ows_materialBlueColor;
|
|
window.rootViewController =
|
|
[[UIStoryboard storyboardWithName:@"Launch Screen" bundle:nil] instantiateInitialViewController];
|
|
|
|
self.screenProtectionWindow = window;
|
|
}
|
|
|
|
- (void)protectScreen {
|
|
if (Environment.preferences.screenSecurityIsEnabled) {
|
|
self.screenProtectionWindow.hidden = NO;
|
|
}
|
|
}
|
|
|
|
- (void)removeScreenProtection {
|
|
if (Environment.preferences.screenSecurityIsEnabled) {
|
|
self.screenProtectionWindow.hidden = YES;
|
|
}
|
|
}
|
|
|
|
- (void)setupAppearance {
|
|
[[UIApplication sharedApplication] setStatusBarStyle:UIStatusBarStyleLightContent];
|
|
[[UINavigationBar appearance] setBarTintColor:[UIColor ows_materialBlueColor]];
|
|
[[UINavigationBar appearance] setTintColor:[UIColor whiteColor]];
|
|
|
|
[[UIBarButtonItem appearanceWhenContainedIn:[UISearchBar class], nil] setTintColor:[UIColor ows_materialBlueColor]];
|
|
|
|
|
|
[[UIToolbar appearance] setTintColor:[UIColor ows_materialBlueColor]];
|
|
[[UIBarButtonItem appearance] setTintColor:[UIColor whiteColor]];
|
|
|
|
NSShadow *shadow = [NSShadow new];
|
|
[shadow setShadowColor:[UIColor clearColor]];
|
|
|
|
NSDictionary *navbarTitleTextAttributes = @{
|
|
NSForegroundColorAttributeName : [UIColor whiteColor],
|
|
NSShadowAttributeName : shadow,
|
|
};
|
|
|
|
[[UISwitch appearance] setOnTintColor:[UIColor ows_materialBlueColor]];
|
|
|
|
[[UINavigationBar appearance] setTitleTextAttributes:navbarTitleTextAttributes];
|
|
}
|
|
|
|
#pragma mark Push Notifications Delegate Methods
|
|
|
|
- (void)application:(UIApplication *)application didReceiveRemoteNotification:(NSDictionary *)userInfo {
|
|
[[PushManager sharedManager] application:application didReceiveRemoteNotification:userInfo];
|
|
}
|
|
- (void)application:(UIApplication *)application
|
|
didReceiveRemoteNotification:(NSDictionary *)userInfo
|
|
fetchCompletionHandler:(void (^)(UIBackgroundFetchResult))completionHandler {
|
|
[[PushManager sharedManager] application:application
|
|
didReceiveRemoteNotification:userInfo
|
|
fetchCompletionHandler:completionHandler];
|
|
}
|
|
|
|
- (void)application:(UIApplication *)application didReceiveLocalNotification:(UILocalNotification *)notification {
|
|
[[PushManager sharedManager] application:application didReceiveLocalNotification:notification];
|
|
}
|
|
|
|
- (void)application:(UIApplication *)application
|
|
handleActionWithIdentifier:(NSString *)identifier
|
|
forLocalNotification:(UILocalNotification *)notification
|
|
completionHandler:(void (^)())completionHandler {
|
|
[[PushManager sharedManager] application:application
|
|
handleActionWithIdentifier:identifier
|
|
forLocalNotification:notification
|
|
completionHandler:completionHandler];
|
|
}
|
|
|
|
- (void)application:(UIApplication *)application
|
|
handleActionWithIdentifier:(NSString *)identifier
|
|
forLocalNotification:(UILocalNotification *)notification
|
|
withResponseInfo:(NSDictionary *)responseInfo
|
|
completionHandler:(void (^)())completionHandler {
|
|
[[PushManager sharedManager] application:application
|
|
handleActionWithIdentifier:identifier
|
|
forLocalNotification:notification
|
|
withResponseInfo:responseInfo
|
|
completionHandler:completionHandler];
|
|
}
|
|
|
|
/**
|
|
* Signal requires an iPhone to be unlocked after reboot to be able to access keying material.
|
|
*/
|
|
- (void)verifyBackgroundBeforeKeysAvailableLaunch {
|
|
if ([self applicationIsActive]) {
|
|
return;
|
|
}
|
|
|
|
if (![[TSStorageManager sharedManager] databasePasswordAccessible]) {
|
|
UILocalNotification *notification = [[UILocalNotification alloc] init];
|
|
notification.alertBody = NSLocalizedString(@"PHONE_NEEDS_UNLOCK", nil);
|
|
[[UIApplication sharedApplication] presentLocalNotificationNow:notification];
|
|
exit(0);
|
|
}
|
|
}
|
|
|
|
- (BOOL)applicationIsActive {
|
|
UIApplication *app = [UIApplication sharedApplication];
|
|
|
|
if (app.applicationState == UIApplicationStateActive) {
|
|
return YES;
|
|
}
|
|
|
|
return NO;
|
|
}
|
|
|
|
+ (NSString *)tag
|
|
{
|
|
return [NSString stringWithFormat:@"[%@]", self.class];
|
|
}
|
|
|
|
- (NSString *)tag
|
|
{
|
|
return self.class.tag;
|
|
}
|
|
|
|
@end
|