Merge pull request #157 from loki-project/push-notification-ui

Push Notification UI
pull/158/head
Niels Andriesse 5 years ago committed by GitHub
commit c596d5b2a3
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -624,6 +624,8 @@
B9EB5ABD1884C002007CBB57 /* MessageUI.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = B9EB5ABC1884C002007CBB57 /* MessageUI.framework */; };
BFF3FB9730634F37D25903F4 /* Pods_Signal.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = D17BB5C25D615AB49813100C /* Pods_Signal.framework */; };
C34C8F7423A7830B00D82669 /* SpaceMono-Bold.ttf in Resources */ = {isa = PBXBuildFile; fileRef = C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */; };
C3548F0624456447009433A8 /* PNModeVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3548F0524456447009433A8 /* PNModeVC.swift */; };
C3548F0824456AB6009433A8 /* UIView+Wrapping.swift in Sources */ = {isa = PBXBuildFile; fileRef = C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */; };
C354E75A23FE2A7600CE22E3 /* BaseVC.swift in Sources */ = {isa = PBXBuildFile; fileRef = C354E75923FE2A7600CE22E3 /* BaseVC.swift */; };
C36B8707243C50C60049991D /* SignalMessaging.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 453518921FC63DBF00210559 /* SignalMessaging.framework */; };
C3B781FF2411C18600C859D8 /* GoogleService-Info.plist in Resources */ = {isa = PBXBuildFile; fileRef = C3B781FE2411C18600C859D8 /* GoogleService-Info.plist */; };
@ -1499,6 +1501,8 @@
B97940261832BD2400BD66CB /* UIUtil.m */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.objc; path = UIUtil.m; sourceTree = "<group>"; };
B9EB5ABC1884C002007CBB57 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpaceMono-Bold.ttf"; sourceTree = "<group>"; };
C3548F0524456447009433A8 /* PNModeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNModeVC.swift; sourceTree = "<group>"; };
C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Wrapping.swift"; sourceTree = "<group>"; };
C354E75923FE2A7600CE22E3 /* BaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseVC.swift; sourceTree = "<group>"; };
C3B781FE2411C18600C859D8 /* GoogleService-Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; name = "GoogleService-Info.plist"; path = "Signal/GoogleService-Info.plist"; sourceTree = SOURCE_ROOT; };
C3DFFAC523E96F0D0058DAF8 /* Sheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sheet.swift; sourceTree = "<group>"; };
@ -2892,6 +2896,7 @@
B886B4A82398BA1500211ABE /* QRCode.swift */,
B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */,
B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */,
C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */,
);
path = Utilities;
sourceTree = "<group>";
@ -2917,6 +2922,7 @@
B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */,
B894D0742339EDCF00B4D94D /* NukeDataModal.swift */,
C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */,
C3548F0524456447009433A8 /* PNModeVC.swift */,
B886B4A62398B23E00211ABE /* QRCodeVC.swift */,
B82B408B239A068800A248E7 /* RegisterVC.swift */,
B82B408F239DD75000A248E7 /* RestoreVC.swift */,
@ -4043,6 +4049,7 @@
34B6A907218B5241007C4606 /* TypingIndicatorCell.swift in Sources */,
4CFD151D22415AA400F2450F /* CallVideoHintView.swift in Sources */,
34D1F0AB1F867BFC0066283D /* OWSContactOffersCell.m in Sources */,
C3548F0824456AB6009433A8 /* UIView+Wrapping.swift in Sources */,
B82B408A2399EC0600A248E7 /* FakeChatView.swift in Sources */,
B8BB82B92394911B00BA5194 /* Separator.swift in Sources */,
343A65981FC4CFE7000477A1 /* ConversationScrollButton.m in Sources */,
@ -4096,6 +4103,7 @@
349ED990221B0194008045B0 /* Onboarding2FAViewController.swift in Sources */,
45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */,
340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */,
C3548F0624456447009433A8 /* PNModeVC.swift in Sources */,
B80A579F23DFF1F300876683 /* NewClosedGroupVC.swift in Sources */,
452037D11EE84975004E4CDF /* DebugUISessionState.m in Sources */,
D221A09A169C9E5E00537ABF /* main.m in Sources */,

@ -54,14 +54,13 @@
NSString *const AppDelegateStoryboardMain = @"Main";
static NSString *const kInitialViewControllerIdentifier = @"UserInitialViewController";
static NSString *const kURLSchemeSGNLKey = @"sgnl";
static NSString *const kURLHostVerifyPrefix = @"verify";
static NSString *const kURLSchemeSGNLKey = @"sgnl";
static NSString *const kURLHostVerifyPrefix = @"verify";
static NSTimeInterval launchStartedAt;
// Debug settings
static BOOL isInternalTestVersion = NO;
static BOOL isUsingFullAPNs = YES;
@interface AppDelegate () <UNUserNotificationCenterDelegate>
@ -178,7 +177,7 @@ static BOOL isUsingFullAPNs = YES;
- (void)applicationDidEnterBackground:(UIApplication *)application
{
OWSLogInfo(@"applicationDidEnterBackground.");
OWSLogInfo(@"applicationDidEnterBackground");
[DDLog flushLog];
@ -189,17 +188,17 @@ static BOOL isUsingFullAPNs = YES;
- (void)applicationWillEnterForeground:(UIApplication *)application
{
OWSLogInfo(@"applicationWillEnterForeground.");
OWSLogInfo(@"applicationWillEnterForeground");
}
- (void)applicationDidReceiveMemoryWarning:(UIApplication *)application
{
OWSLogInfo(@"applicationDidReceiveMemoryWarning.");
OWSLogInfo(@"applicationDidReceiveMemoryWarning");
}
- (void)applicationWillTerminate:(UIApplication *)application
{
OWSLogInfo(@"applicationWillTerminate.");
OWSLogInfo(@"applicationWillTerminate");
[DDLog flushLog];
@ -230,7 +229,7 @@ static BOOL isUsingFullAPNs = YES;
[DebugLogger.sharedLogger enableFileLogging];
}
OWSLogWarn(@"application: didFinishLaunchingWithOptions.");
OWSLogWarn(@"application:didFinishLaunchingWithOptions");
[Cryptography seedRandom];
// XXX - careful when moving this. It must happen before we initialize OWSPrimaryStorage.
@ -252,7 +251,7 @@ static BOOL isUsingFullAPNs = YES;
//
// ensureIsReadyForAppExtensions will show a failure mode UI that
// lets users report this error.
OWSLogInfo(@"application: didFinishLaunchingWithOptions failed.");
OWSLogInfo(@"application:didFinishLaunchingWithOptions failed");
return YES;
}
@ -376,7 +375,7 @@ static BOOL isUsingFullAPNs = YES;
}
if (![OWSPrimaryStorage isDatabasePasswordAccessible]) {
OWSLogInfo(@"exiting because we are in the background and the database password is not accessible.");
OWSLogInfo(@"Exiting because we are in the background and the database password is not accessible.");
UILocalNotification *notification = [UILocalNotification new];
NSString *messageFormat = NSLocalizedString(@"NOTIFICATION_BODY_PHONE_LOCKED_FORMAT",
@ -452,7 +451,7 @@ static BOOL isUsingFullAPNs = YES;
}
if (error) {
OWSFailDebug(@"database conversion failed: %@", error);
OWSFailDebug(@"Database conversion failed: %@", error);
[self showLaunchFailureUI:error];
return NO;
}
@ -491,7 +490,7 @@ static BOOL isUsingFullAPNs = YES;
style:UIAlertActionStyleDefault
handler:^(UIAlertAction *_Nonnull action) {
[Pastelog submitLogsWithCompletion:^{
OWSFail(@"exiting after sharing debug logs.");
OWSFail(@"Exiting after sharing debug logs.");
}];
}]];
UIViewController *fromViewController = [[UIApplication sharedApplication] frontmostViewController];
@ -504,7 +503,7 @@ static BOOL isUsingFullAPNs = YES;
NSString *databaseFilePath = [OWSPrimaryStorage legacyDatabaseFilePath];
if (![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]) {
OWSLogVerbose(@"no legacy database file found");
OWSLogVerbose(@"No legacy database file found");
return nil;
}
@ -584,17 +583,15 @@ static BOOL isUsingFullAPNs = YES;
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
return;
}
OWSLogInfo(@"Registered for push notifications with token: %@.", deviceToken);
OWSLogInfo(@"Registering for push notifications with token: %@.", deviceToken);
BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"];
if (isUsingFullAPNs) {
[LKPushNotificationManager registerWithToken:deviceToken hexEncodedPublicKey:self.tsAccountManager.localNumber];
} else {
[LKPushNotificationManager registerWithToken:deviceToken];
}
// [self.pushRegistrationManager didReceiveVanillaPushToken:deviceToken];
}
- (void)application:(UIApplication *)application didFailToRegisterForRemoteNotificationsWithError:(NSError *)error
@ -602,13 +599,13 @@ static BOOL isUsingFullAPNs = YES;
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
return;
}
OWSLogError(@"failed to register vanilla push token with error: %@", error);
OWSLogError(@"Failed to register push token with error: %@.", error);
#ifdef DEBUG
OWSLogWarn(@"We're in debug mode. Faking success for remote registration with a fake push identifier");
OWSLogWarn(@"We're in debug mode. Faking success for remote registration with a fake push identifier.");
[self.pushRegistrationManager didReceiveVanillaPushToken:[[NSMutableData dataWithLength:32] copy]];
#else
OWSProdError([OWSAnalyticsEvents appDelegateErrorFailedToRegisterForRemoteNotifications]);
@ -622,11 +619,11 @@ static BOOL isUsingFullAPNs = YES;
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
return;
}
OWSLogInfo(@"registered legacy notification settings");
OWSLogInfo(@"Registered legacy notification settings.");
[self.notificationPresenter didRegisterLegacyNotificationSettings];
}
@ -638,7 +635,7 @@ static BOOL isUsingFullAPNs = YES;
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
return NO;
}
@ -663,7 +660,7 @@ static BOOL isUsingFullAPNs = YES;
[verificationView setVerificationCodeAndTryToVerify:verificationCode];
return YES;
} else {
OWSLogWarn(@"Not the verification view controller we expected. Got %@ instead",
OWSLogWarn(@"Not the verification view controller we expected. Got %@ instead.",
NSStringFromClass(controller.class));
}
}
@ -680,11 +677,11 @@ static BOOL isUsingFullAPNs = YES;
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
return;
}
OWSLogWarn(@"applicationDidBecomeActive.");
OWSLogWarn(@"applicationDidBecomeActive");
if (CurrentAppContext().isRunningTests) {
return;
}
@ -704,11 +701,12 @@ static BOOL isUsingFullAPNs = YES;
// On every activation, clear old temp directories.
ClearOldTemporaryDirectories();
OWSLogInfo(@"applicationDidBecomeActive completed.");
OWSLogInfo(@"applicationDidBecomeActive completed");
}
- (void)enableBackgroundRefreshIfNecessary
{
BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"];
if (isUsingFullAPNs) { return; }
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
[UIApplication.sharedApplication setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum];
@ -730,7 +728,7 @@ static BOOL isUsingFullAPNs = YES;
{
OWSAssertIsOnMainThread();
OWSLogWarn(@"handleActivation.");
OWSLogWarn(@"handleActivation");
// Always check prekeys after app launches, and sometimes check on app activation.
[TSPreKeyManager checkPreKeysIfNecessary];
@ -743,7 +741,7 @@ static BOOL isUsingFullAPNs = YES;
// At this point, potentially lengthy DB locking migrations could be running.
// Avoid blocking app launch by putting all further possible DB access in async block
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
OWSLogInfo(@"running post launch block for registered user: %@", [self.tsAccountManager localNumber]);
OWSLogInfo(@"Running post launch block for registered user: %@.", [self.tsAccountManager localNumber]);
// Clean up any messages that expired since last launch immediately
// and continue cleaning in the background.
@ -763,7 +761,7 @@ static BOOL isUsingFullAPNs = YES;
[[[OWSFailedAttachmentDownloadsJob alloc] initWithPrimaryStorage:self.primaryStorage] run];
});
} else {
OWSLogInfo(@"running post launch block for unregistered user.");
OWSLogInfo(@"Running post launch block for unregistered user.");
// Unregistered user should have no unread messages. e.g. if you delete your account.
[AppEnvironment.shared.notificationPresenter clearAllNotifications];
@ -813,7 +811,7 @@ static BOOL isUsingFullAPNs = YES;
}
if (![UIApplication sharedApplication].isRegisteredForRemoteNotifications) {
OWSLogInfo(@"Retrying to register for remote notifications since user hasn't registered yet.");
OWSLogInfo(@"Retrying remote notification registration since user hasn't registered yet.");
// Push tokens don't normally change while the app is launched, so checking once during launch is
// usually sufficient, but e.g. on iOS11, users who have disabled "Allow Notifications" and disabled
// "Background App Refresh" will not be able to obtain an APN token. Enabling those settings does not
@ -825,7 +823,7 @@ static BOOL isUsingFullAPNs = YES;
if ([OWS2FAManager sharedManager].isDueForReminder) {
if (!self.hasInitialRootViewController || self.window.rootViewController == nil) {
OWSLogDebug(@"Skipping 2FA reminder since there isn't yet an initial view controller");
OWSLogDebug(@"Skipping 2FA reminder since there isn't yet an initial view controller.");
} else {
UIViewController *rootViewController = self.window.rootViewController;
OWSNavigationController *reminderNavController =
@ -837,7 +835,7 @@ static BOOL isUsingFullAPNs = YES;
});
}
OWSLogInfo(@"handleActivation completed.");
OWSLogInfo(@"handleActivation completed");
}
- (void)applicationWillResignActive:(UIApplication *)application
@ -845,11 +843,11 @@ static BOOL isUsingFullAPNs = YES;
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
return;
}
OWSLogWarn(@"applicationWillResignActive.");
OWSLogWarn(@"applicationWillResignActive");
[self clearAllNotificationsAndRestoreBadgeCount];
@ -872,7 +870,7 @@ static BOOL isUsingFullAPNs = YES;
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
completionHandler(NO);
return;
}
@ -904,197 +902,13 @@ static BOOL isUsingFullAPNs = YES;
}];
}
/**
* Among other things, this is used by "call back" callkit dialog and calling from native contacts app.
*
* We always return YES if we are going to try to handle the user activity since
* we never want iOS to contact us again using a URL.
*
* From https://developer.apple.com/documentation/uikit/uiapplicationdelegate/1623072-application?language=objc:
*
* If you do not implement this method or if your implementation returns NO, iOS tries to
* create a document for your app to open using a URL.
*/
- (BOOL)application:(UIApplication *)application
continueUserActivity:(nonnull NSUserActivity *)userActivity
restorationHandler:(nonnull void (^)(NSArray *_Nullable))restorationHandler
{
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
return NO;
}
if ([userActivity.activityType isEqualToString:@"INStartVideoCallIntent"]) {
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(10, 0)) {
OWSLogError(@"unexpectedly received INStartVideoCallIntent pre iOS10");
return NO;
}
OWSLogInfo(@"got start video call intent");
INInteraction *interaction = [userActivity interaction];
INIntent *intent = interaction.intent;
if (![intent isKindOfClass:[INStartVideoCallIntent class]]) {
OWSLogError(@"unexpected class for start call video: %@", intent);
return NO;
}
INStartVideoCallIntent *startCallIntent = (INStartVideoCallIntent *)intent;
NSString *_Nullable handle = startCallIntent.contacts.firstObject.personHandle.value;
if (!handle) {
OWSLogWarn(@"unable to find handle in startCallIntent: %@", startCallIntent);
return NO;
}
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
if (![self.tsAccountManager isRegisteredAndReady]) {
OWSLogInfo(@"Ignoring user activity; app not ready.");
return;
}
NSString *_Nullable phoneNumber = [self phoneNumberForIntentHandle:handle];
if (phoneNumber.length < 1) {
OWSLogWarn(@"ignoring attempt to initiate video call to unknown user.");
return;
}
// This intent can be received from more than one user interaction.
//
// * It can be received if the user taps the "video" button in the CallKit UI for an
// an ongoing call. If so, the correct response is to try to activate the local
// video for that call.
// * It can be received if the user taps the "video" button for a contact in the
// contacts app. If so, the correct response is to try to initiate a new call
// to that user - unless there already is another call in progress.
// if (AppEnvironment.shared.callService.call != nil) {
// if ([phoneNumber isEqualToString:AppEnvironment.shared.callService.call.remotePhoneNumber]) {
// OWSLogWarn(@"trying to upgrade ongoing call to video.");
// [AppEnvironment.shared.callService handleCallKitStartVideo];
// return;
// } else {
// OWSLogWarn(@"ignoring INStartVideoCallIntent due to ongoing WebRTC call with another party.");
// return;
// }
// }
//
// OutboundCallInitiator *outboundCallInitiator = AppEnvironment.shared.outboundCallInitiator;
// OWSAssertDebug(outboundCallInitiator);
// [outboundCallInitiator initiateCallWithHandle:phoneNumber];
}];
return YES;
} else if ([userActivity.activityType isEqualToString:@"INStartAudioCallIntent"]) {
if (!SYSTEM_VERSION_GREATER_THAN_OR_EQUAL_TO(10, 0)) {
OWSLogError(@"unexpectedly received INStartAudioCallIntent pre iOS10");
return NO;
}
OWSLogInfo(@"got start audio call intent");
INInteraction *interaction = [userActivity interaction];
INIntent *intent = interaction.intent;
if (![intent isKindOfClass:[INStartAudioCallIntent class]]) {
OWSLogError(@"unexpected class for start call audio: %@", intent);
return NO;
}
INStartAudioCallIntent *startCallIntent = (INStartAudioCallIntent *)intent;
NSString *_Nullable handle = startCallIntent.contacts.firstObject.personHandle.value;
if (!handle) {
OWSLogWarn(@"unable to find handle in startCallIntent: %@", startCallIntent);
return NO;
}
[AppReadiness runNowOrWhenAppDidBecomeReady:^{
if (![self.tsAccountManager isRegisteredAndReady]) {
OWSLogInfo(@"Ignoring user activity; app not ready.");
return;
}
NSString *_Nullable phoneNumber = [self phoneNumberForIntentHandle:handle];
if (phoneNumber.length < 1) {
OWSLogWarn(@"ignoring attempt to initiate audio call to unknown user.");
return;
}
// if (AppEnvironment.shared.callService.call != nil) {
// OWSLogWarn(@"ignoring INStartAudioCallIntent due to ongoing WebRTC call.");
// return;
// }
//
// OutboundCallInitiator *outboundCallInitiator = AppEnvironment.shared.outboundCallInitiator;
// OWSAssertDebug(outboundCallInitiator);
// [outboundCallInitiator initiateCallWithHandle:phoneNumber];
}];
return YES;
} else {
OWSLogWarn(@"userActivity: %@, but not yet supported.", userActivity.activityType);
}
// TODO Something like...
// *phoneNumber = [[[[[[userActivity interaction] intent] contacts] firstObject] personHandle] value]
// thread = blah
// [callUIAdapter startCall:thread]
//
// Here's the Speakerbox Example for intent / NSUserActivity handling:
//
// func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([Any]?) -> Void) -> Bool {
// guard let handle = userActivity.startCallHandle else {
// print("Could not determine start call handle from user activity: \(userActivity)")
// return false
// }
//
// guard let video = userActivity.video else {
// print("Could not determine video from user activity: \(userActivity)")
// return false
// }
//
// callManager.startCall(handle: handle, video: video)
// return true
// }
return NO;
}
- (nullable NSString *)phoneNumberForIntentHandle:(NSString *)handle
{
// OWSAssertDebug(handle.length > 0);
//
// if ([handle hasPrefix:CallKitCallManager.kAnonymousCallHandlePrefix]) {
// NSString *_Nullable phoneNumber = [self.primaryStorage phoneNumberForCallKitId:handle];
// if (phoneNumber.length < 1) {
// OWSLogWarn(@"ignoring attempt to initiate audio call to unknown anonymous signal user.");
// return nil;
// }
// return phoneNumber;
// }
//
// for (PhoneNumber *phoneNumber in
// [PhoneNumber tryParsePhoneNumbersFromsUserSpecifiedText:handle
// clientPhoneNumber:[TSAccountManager localNumber]]) {
// return phoneNumber.toE164;
// }
return nil;
}
#pragma mark - Orientation
- (UIInterfaceOrientationMask)application:(UIApplication *)application
supportedInterfaceOrientationsForWindow:(nullable UIWindow *)window
{
if (self.hasCall) {
OWSLogInfo(@"has call");
// The call-banner window is only suitable for portrait display
return UIInterfaceOrientationMaskPortrait;
}
UIViewController *_Nullable rootViewController = self.window.rootViewController;
if (!rootViewController) {
return UIInterfaceOrientationMaskAllButUpsideDown;
}
return rootViewController.supportedInterfaceOrientations;
return UIInterfaceOrientationMaskPortrait;
}
- (BOOL)hasCall
@ -1108,7 +922,7 @@ static BOOL isUsingFullAPNs = YES;
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
return;
}
if (!(AppReadiness.isAppReady && [self.tsAccountManager isRegisteredAndReady])) {
@ -1130,7 +944,7 @@ static BOOL isUsingFullAPNs = YES;
publicChats = [LKDatabaseUtilities getAllPublicChats:transaction];
}];
for (LKPublicChat *publicChat in publicChats) {
if (![publicChat isKindOfClass:LKPublicChat.class]) { continue; } // For some reason publicChat is sometimes a base 64 encoded string...
if (![publicChat isKindOfClass:LKPublicChat.class]) { continue; }
LKPublicChatPoller *poller = [[LKPublicChatPoller alloc] initForPublicChat:publicChat];
[poller stop];
AnyPromise *fetchGroupMessagesPromise = [poller pollForNewMessages];
@ -1144,7 +958,7 @@ static BOOL isUsingFullAPNs = YES;
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
return;
}
if (!(AppReadiness.isAppReady && [self.tsAccountManager isRegisteredAndReady])) {
@ -1192,7 +1006,7 @@ static BOOL isUsingFullAPNs = YES;
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
return;
}
@ -1220,7 +1034,7 @@ static BOOL isUsingFullAPNs = YES;
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
completionHandler();
return;
}
@ -1251,12 +1065,12 @@ static BOOL isUsingFullAPNs = YES;
withResponseInfo:(NSDictionary *)responseInfo
completionHandler:(void (^)())completionHandler
{
OWSLogInfo(@"handling action with identifier: %@", identifier);
OWSLogInfo(@"Handling action with identifier: %@", identifier);
OWSAssertIsOnMainThread();
if (self.didAppLaunchFail) {
OWSFailDebug(@"app launch failed");
OWSFailDebug(@"App launch failed");
completionHandler();
return;
}
@ -1369,7 +1183,7 @@ static BOOL isUsingFullAPNs = YES;
if ([self.tsAccountManager isRegistered]) {
OWSLogInfo(@"localNumber: %@", [TSAccountManager localNumber]);
// This should happen at any launch, background or foreground.
// This should happen at any launch, background or foreground
__unused AnyPromise *pushTokenpromise =
[OWSSyncPushTokensJob runWithAccountManager:AppEnvironment.shared.accountManager
preferences:Environment.shared.preferences];
@ -1470,6 +1284,7 @@ static BOOL isUsingFullAPNs = YES;
readWriteWithBlock:^(YapDatabaseReadWriteTransaction *_Nonnull transaction) {
[ExperienceUpgradeFinder.sharedManager markAllAsSeenWithTransaction:transaction];
}];
// Start running the disappearing messages job in case the newly registered user
// enables this feature
[self.disappearingMessagesJob startIfNecessary];

@ -158,7 +158,7 @@ final class NewConversationButtonSet : UIView {
self.layoutIfNeeded()
button.frame = frame
button.layer.cornerRadius = size / 2
button.setGlow(to: size, with: Colors.newConversationButtonShadow)
button.setGlow(to: size, with: Colors.newConversationButtonShadow, animated: true)
button.backgroundColor = Colors.accent
}
}
@ -183,7 +183,7 @@ final class NewConversationButtonSet : UIView {
button.frame = frame
button.layer.cornerRadius = size / 2
let glowColor = isLightMode ? UIColor.black.withAlphaComponent(0.4) : UIColor.black
button.setGlow(to: size, with: glowColor)
button.setGlow(to: size, with: glowColor, animated: true)
button.backgroundColor = Colors.newConversationButtonCollapsedBackground
}
}
@ -230,7 +230,7 @@ private final class NewConversationButton : UIImageView {
let size = Values.newConversationButtonCollapsedSize
layer.cornerRadius = size / 2
let glowColor = isMainButton ? Colors.newConversationButtonShadow : (isLightMode ? UIColor.black.withAlphaComponent(0.4) : UIColor.black)
setGlow(to: size, with: glowColor)
setGlow(to: size, with: glowColor, animated: false)
layer.masksToBounds = false
let iconColor = (isMainButton && isLightMode) ? UIColor.white : Colors.text
image = icon.asTintedImage(color: iconColor)!
@ -240,9 +240,25 @@ private final class NewConversationButton : UIImageView {
}
// General
func setGlow(to size: CGFloat, with color: UIColor) {
layer.shadowPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: size, height: size))).cgPath
layer.shadowColor = color.cgColor
func setGlow(to size: CGFloat, with color: UIColor, animated isAnimated: Bool) {
let newPath = UIBezierPath(ovalIn: CGRect(origin: CGPoint(x: 0, y: 0), size: CGSize(width: size, height: size))).cgPath
if isAnimated {
let pathAnimation = CABasicAnimation(keyPath: "shadowPath")
pathAnimation.fromValue = layer.shadowPath
pathAnimation.toValue = newPath
pathAnimation.duration = 0.25
layer.add(pathAnimation, forKey: pathAnimation.keyPath)
}
layer.shadowPath = newPath
let newColor = color.cgColor
if isAnimated {
let colorAnimation = CABasicAnimation(keyPath: "shadowColor")
colorAnimation.fromValue = layer.shadowColor
colorAnimation.toValue = newColor
colorAnimation.duration = 0.25
layer.add(colorAnimation, forKey: colorAnimation.keyPath)
}
layer.shadowColor = newColor
layer.shadowOffset = CGSize(width: 0, height: 0.8)
layer.shadowOpacity = isLightMode ? 0.4 : 1
layer.shadowRadius = isLightMode ? 4 : 6

@ -0,0 +1,12 @@
extension UIView {
convenience init(wrapping view: UIView, withInsets insets: UIEdgeInsets) {
self.init()
addSubview(view)
view.pin(.leading, to: .leading, of: self, withInset: insets.left)
view.pin(.top, to: .top, of: self, withInset: insets.top)
self.pin(.trailing, to: .trailing, of: view, withInset: insets.right)
self.pin(.bottom, to: .bottom, of: view, withInset: insets.bottom)
}
}

@ -152,9 +152,8 @@ final class DisplayNameVC : BaseVC {
guard !OWSProfileManager.shared().isProfileNameTooLong(displayName) else {
return showError(title: NSLocalizedString("Please pick a shorter display name", comment: ""))
}
TSAccountManager.sharedInstance().didRegister()
OWSProfileManager.shared().updateLocalProfileName(displayName, avatarImage: nil, success: { }, failure: { _ in }, requiresSync: false) // Try to save the user name but ignore the result
let homeVC = HomeVC()
navigationController!.setViewControllers([ homeVC ], animated: true)
let pnModeVC = PNModeVC()
navigationController!.pushViewController(pnModeVC, animated: true)
}
}

@ -0,0 +1,212 @@
import PromiseKit
final class PNModeVC : BaseVC, OptionViewDelegate {
private var optionViews: [OptionView] {
[ apnsOptionView, backgroundPollingOptionView ]
}
private var selectedOptionView: OptionView? {
return optionViews.first { $0.isSelected }
}
// MARK: Components
private lazy var apnsOptionView = OptionView(title: NSLocalizedString("Apple Push Notification Service", comment: ""), explanation: NSLocalizedString("Session will use the Apple Push Notification Service to receive push notifications. Youll be notified of new messages reliably and immediately. Using APNs means that this device will communicate directly with Apples servers to retrieve push notifications, which will expose your IP address to Apple. Your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private.", comment: ""), delegate: self, isRecommended: true)
private lazy var backgroundPollingOptionView = OptionView(title: NSLocalizedString("Background Polling", comment: ""), explanation: NSLocalizedString("Session will occasionally check for new messages in the background. This guarantees full privacy protection, but message notifications may be significantly delayed.", comment: ""), delegate: self)
// MARK: Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
// Set gradient background
view.backgroundColor = .clear
let gradient = Gradients.defaultLokiBackground
view.setGradient(gradient)
// Set up navigation bar
let navigationBar = navigationController!.navigationBar
navigationBar.setBackgroundImage(UIImage(), for: UIBarMetrics.default)
navigationBar.shadowImage = UIImage()
navigationBar.isTranslucent = false
navigationBar.barTintColor = Colors.navigationBarBackground
// Set up logo image view
let logoImageView = UIImageView()
logoImageView.image = #imageLiteral(resourceName: "SessionGreen32")
logoImageView.contentMode = .scaleAspectFit
logoImageView.set(.width, to: 32)
logoImageView.set(.height, to: 32)
navigationItem.titleView = logoImageView
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize)
titleLabel.text = NSLocalizedString("Push Notifications", comment: "")
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
// Set up explanation label
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.smallFontSize)
explanationLabel.text = NSLocalizedString("There are two ways Session can handle push notifications. Make sure to read the descriptions carefully before you choose.", comment: "")
explanationLabel.numberOfLines = 0
explanationLabel.lineBreakMode = .byWordWrapping
// Set up spacers
let topSpacer = UIView.vStretchingSpacer()
let bottomSpacer = UIView.vStretchingSpacer()
let registerButtonBottomOffsetSpacer = UIView()
registerButtonBottomOffsetSpacer.set(.height, to: Values.onboardingButtonBottomOffset)
// Set up register button
let registerButton = Button(style: .prominentFilled, size: .large)
registerButton.setTitle(NSLocalizedString("Continue", comment: ""), for: UIControl.State.normal)
registerButton.titleLabel!.font = .boldSystemFont(ofSize: Values.mediumFontSize)
registerButton.addTarget(self, action: #selector(register), for: UIControl.Event.touchUpInside)
// Set up register button container
let registerButtonContainer = UIView(wrapping: registerButton, withInsets: UIEdgeInsets(top: 0, leading: Values.massiveSpacing, bottom: 0, trailing: Values.massiveSpacing))
// Set up options stack view
let optionsStackView = UIStackView(arrangedSubviews: optionViews)
optionsStackView.axis = .vertical
optionsStackView.spacing = Values.smallSpacing
optionsStackView.alignment = .fill
// Set up top stack view
let topStackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, optionsStackView ])
topStackView.axis = .vertical
topStackView.spacing = isSmallScreen ? Values.smallSpacing : Values.veryLargeSpacing
topStackView.alignment = .fill
// Set up top stack view container
let topStackViewContainer = UIView(wrapping: topStackView, withInsets: UIEdgeInsets(top: 0, leading: Values.veryLargeSpacing, bottom: 0, trailing: Values.veryLargeSpacing))
// Set up main stack view
let mainStackView = UIStackView(arrangedSubviews: [ topSpacer, topStackViewContainer, bottomSpacer, registerButtonContainer, registerButtonBottomOffsetSpacer ])
mainStackView.axis = .vertical
mainStackView.alignment = .fill
view.addSubview(mainStackView)
mainStackView.pin(to: view)
topSpacer.heightAnchor.constraint(equalTo: bottomSpacer.heightAnchor, multiplier: 1).isActive = true
}
// MARK: Interaction
fileprivate func optionViewDidActivate(_ optionView: OptionView) {
optionViews.filter { $0 != optionView }.forEach { $0.isSelected = false }
}
@objc private func register() {
guard selectedOptionView != nil else {
let title = NSLocalizedString("Please Pick an Option", comment: "")
let alert = UIAlertController(title: title, message: nil, preferredStyle: .alert)
alert.addAction(UIAlertAction(title: NSLocalizedString("OK", comment: ""), style: .default, handler: nil))
return present(alert, animated: true, completion: nil)
}
UserDefaults.standard[.isUsingFullAPNs] = (selectedOptionView == apnsOptionView)
TSAccountManager.sharedInstance().didRegister()
let homeVC = HomeVC()
navigationController!.setViewControllers([ homeVC ], animated: true)
let _: Promise<Void> = SyncPushTokensJob.run(accountManager: AppEnvironment.shared.accountManager, preferences: Environment.shared.preferences)
}
}
// MARK: Option View
private extension PNModeVC {
final class OptionView : UIView {
private let title: String
private let explanation: String
private let delegate: OptionViewDelegate
private let isRecommended: Bool
var isSelected = false { didSet { handleIsSelectedChanged() } }
init(title: String, explanation: String, delegate: OptionViewDelegate, isRecommended: Bool = false) {
self.title = title
self.explanation = explanation
self.delegate = delegate
self.isRecommended = isRecommended
super.init(frame: CGRect.zero)
setUpViewHierarchy()
}
override init(frame: CGRect) {
preconditionFailure("Use init(string:explanation:) instead.")
}
required init?(coder: NSCoder) {
preconditionFailure("Use init(string:explanation:) instead.")
}
private func setUpViewHierarchy() {
backgroundColor = Colors.pnOptionBackground
// Round corners
layer.cornerRadius = Values.pnOptionCornerRadius
// Set up border
layer.borderWidth = Values.borderThickness
layer.borderColor = Colors.pnOptionBorder.cgColor
// Set up shadow
layer.shadowColor = UIColor.black.cgColor
layer.shadowOffset = CGSize(width: 0, height: 0.8)
layer.shadowOpacity = isLightMode ? 0.4 : 1
layer.shadowRadius = isLightMode ? 4 : 6
// Set up title label
let titleLabel = UILabel()
titleLabel.textColor = Colors.text
titleLabel.font = .boldSystemFont(ofSize: Values.mediumFontSize)
titleLabel.text = title
titleLabel.numberOfLines = 0
titleLabel.lineBreakMode = .byWordWrapping
// Set up explanation label
let explanationLabel = UILabel()
explanationLabel.textColor = Colors.text
explanationLabel.font = .systemFont(ofSize: Values.verySmallFontSize)
explanationLabel.text = explanation
explanationLabel.numberOfLines = 0
explanationLabel.lineBreakMode = .byWordWrapping
// Set up stack view
let stackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel ])
stackView.axis = .vertical
stackView.spacing = 4
stackView.alignment = .fill
addSubview(stackView)
stackView.pin(.leading, to: .leading, of: self, withInset: 12)
stackView.pin(.top, to: .top, of: self, withInset: 12)
self.pin(.trailing, to: .trailing, of: stackView, withInset: 12)
self.pin(.bottom, to: .bottom, of: stackView, withInset: 12)
// Set up recommended label if needed
if isRecommended {
let recommendedLabel = UILabel()
recommendedLabel.textColor = Colors.accent
recommendedLabel.font = .boldSystemFont(ofSize: Values.verySmallFontSize)
recommendedLabel.text = NSLocalizedString("Recommended", comment: "")
stackView.addArrangedSubview(recommendedLabel)
}
// Set up tap gesture recognizer
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
addGestureRecognizer(tapGestureRecognizer)
}
@objc private func handleTap() {
isSelected = !isSelected
}
private func handleIsSelectedChanged() {
let animationDuration: TimeInterval = 0.25
// Animate border color
let newBorderColor = isSelected ? Colors.accent.cgColor : Colors.pnOptionBorder.cgColor
let borderAnimation = CABasicAnimation(keyPath: "borderColor")
borderAnimation.fromValue = layer.shadowColor
borderAnimation.toValue = newBorderColor
borderAnimation.duration = animationDuration
layer.add(borderAnimation, forKey: borderAnimation.keyPath)
layer.borderColor = newBorderColor
// Animate shadow color
let newShadowColor = isSelected ? Colors.newConversationButtonShadow.cgColor : UIColor.black.cgColor
let shadowAnimation = CABasicAnimation(keyPath: "shadowColor")
shadowAnimation.fromValue = layer.shadowColor
shadowAnimation.toValue = newShadowColor
shadowAnimation.duration = animationDuration
layer.add(shadowAnimation, forKey: shadowAnimation.keyPath)
layer.shadowColor = newShadowColor
// Notify delegate
if isSelected { delegate.optionViewDidActivate(self) }
}
}
}
// MARK: Option View Delegate
private protocol OptionViewDelegate {
func optionViewDidActivate(_ optionView: PNModeVC.OptionView)
}

@ -62,6 +62,21 @@
OWSPreferences *prefs = Environment.shared.preferences;
OWSTableSection *strategySection = [OWSTableSection new];
strategySection.headerTitle = NSLocalizedString(@"Notification Strategy", @"");
[strategySection addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"Use APNs", @"")
accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"push_notification_strategy")
isOnBlock:^{
return [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"];
}
isEnabledBlock:^{
return YES;
}
target:weakSelf
selector:@selector(didToggleAPNsSwitch:)]];
strategySection.footerTitle = NSLocalizedString(@"Session will use the Apple Push Notification Service to receive push notifications. Youll be notified of new messages reliably and immediately. Using APNs means that this device will communicate directly with Apples servers to retrieve push notifications, which will expose your IP address to Apple. Your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private.", @"");
[contents addSection:strategySection];
// Sounds section.
OWSTableSection *soundsSection = [OWSTableSection new];
@ -119,4 +134,10 @@
[Environment.shared.preferences setSoundInForeground:sender.on];
}
- (void)didToggleAPNsSwitch:(UISwitch *)sender
{
[NSUserDefaults.standardUserDefaults setBool:sender.on forKey:@"isUsingFullAPNs"];
__unused AnyPromise *promise = [OWSSyncPushTokensJob runWithAccountManager:AppEnvironment.shared.accountManager preferences:Environment.shared.preferences];
}
@end

@ -2809,3 +2809,12 @@
"Join Public Chat" = "Join Public Chat";
"No, thank you" = "No, thank you";
"Report" = "Report";
"Please Pick an Option" = "Please Pick an Option";
"There are two ways Session can handle push notifications. Make sure to read the descriptions carefully before you choose." = "There are two ways Session can handle push notifications. Make sure to read the descriptions carefully before you choose.";
"Apple Push Notification Service" = "Apple Push Notification Service";
"Session will use the Apple Push Notification Service to receive push notifications. Youll be notified of new messages reliably and immediately. Using APNs means that this device will communicate directly with Apples servers to retrieve push notifications, which will expose your IP address to Apple. Your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private." = "Session will use the Apple Push Notification Service to receive push notifications. Youll be notified of new messages reliably and immediately. Using APNs means that this device will communicate directly with Apples servers to retrieve push notifications, which will expose your IP address to Apple. Your messages will still be onion-routed and end-to-end encrypted, so the contents of your messages will remain completely private.";
"Background Polling" = "Background Polling";
"Session will occasionally check for new messages in the background. This guarantees full privacy protection, but message notifications may be significantly delayed." = "Session will occasionally check for new messages in the background. This guarantees full privacy protection, but message notifications may be significantly delayed.";
"Use APNs" = "Use APNs";
"Recommended" = "Recommended";
"Notification Strategy" = "Notification Strategy";

@ -36,4 +36,6 @@ public final class Colors : NSObject {
@objc public static var receivedMessageBackground = isLightMode ? UIColor(hex: 0xF5F5F5) : UIColor(hex: 0x222325)
@objc public static var sentMessageBackground = isLightMode ? UIColor(hex: 0x00E97B) : UIColor(hex: 0x3F4146)
@objc public static var newConversationButtonCollapsedBackground = isLightMode ? UIColor(hex: 0xF5F5F5) : UIColor(hex: 0x1F1F1F)
@objc public static var pnOptionBackground = isLightMode ? UIColor(hex: 0xFCFCFC) : UIColor(hex: 0x1B1B1B)
@objc public static var pnOptionBorder = UIColor(hex: 0x212121)
}

@ -46,6 +46,7 @@ public final class Values : NSObject {
@objc public static let composeViewTextFieldBorderThickness = 1 / UIScreen.main.scale
@objc public static let messageBubbleCornerRadius: CGFloat = 10
@objc public static let progressBarThickness: CGFloat = 2
@objc public static let pnOptionCornerRadius = CGFloat(8)
// MARK: - Distances
@objc public static let verySmallSpacing = CGFloat(4)

@ -296,13 +296,13 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa
failure:(void (^)(NSError *))failureHandler
remainingRetries:(int)remainingRetries
{
TSRequest *request =
[OWSRequestFactory registerForPushRequestWithPushIdentifier:pushToken voipIdentifier:voipToken];
[self.networkManager makeRequest:request
success:^(NSURLSessionDataTask *task, id responseObject) {
BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"];
if (isUsingFullAPNs) {
[LKPushNotificationManager registerWithToken:pushToken hexEncodedPublicKey:self.localNumber]
.then(^() {
successHandler();
}
failure:^(NSURLSessionDataTask *task, NSError *error) {
})
.catch(^(NSError *error) {
if (remainingRetries > 0) {
[self registerForPushNotificationsWithPushToken:pushToken
voipToken:voipToken
@ -315,7 +315,8 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa
}
failureHandler(error);
}
}];
});
}
}
- (void)registerWithPhoneNumber:(NSString *)phoneNumber

@ -1,3 +1,4 @@
import PromiseKit
@objc(LKPushNotificationManager)
public final class LokiPushNotificationManager : NSObject {
@ -51,8 +52,7 @@ public final class LokiPushNotificationManager : NSObject {
/// Registers the user for normal push notifications. Requires the user's device
/// token and their Session ID.
@objc(registerWithToken:hexEncodedPublicKey:)
static func register(with token: Data, hexEncodedPublicKey: String) {
static func register(with token: Data, hexEncodedPublicKey: String) -> Promise<Void> {
let hexEncodedToken = token.toHexString()
let userDefaults = UserDefaults.standard
let now = Date().timeIntervalSince1970
@ -60,7 +60,7 @@ public final class LokiPushNotificationManager : NSObject {
let url = URL(string: server + "register")!
let request = TSRequest(url: url, method: "POST", parameters: parameters)
request.allHTTPHeaderFields = [ "Content-Type" : "application/json" ]
TSNetworkManager.shared().makeRequest(request, success: { _, response in
let promise = TSNetworkManager.shared().makePromise(request: request).map { _, response in
guard let json = response as? JSON else {
return print("[Loki] Couldn't register device token.")
}
@ -70,9 +70,17 @@ public final class LokiPushNotificationManager : NSObject {
userDefaults[.deviceToken] = hexEncodedToken
userDefaults[.lastDeviceTokenUpload] = now
userDefaults[.isUsingFullAPNs] = true
}, failure: { _, error in
return
}
promise.catch { error in
print("[Loki] Couldn't register device token.")
})
}
return promise
}
@objc(registerWithToken:hexEncodedPublicKey:)
static func objc_register(with token: Data, hexEncodedPublicKey: String) -> AnyPromise {
return AnyPromise.from(register(with: token, hexEncodedPublicKey: hexEncodedPublicKey))
}
@objc(acknowledgeDeliveryForMessageWithHash:expiration:hexEncodedPublicKey:)

Loading…
Cancel
Save