diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 7e91edaba..645856853 100644 --- a/Signal.xcodeproj/project.pbxproj +++ b/Signal.xcodeproj/project.pbxproj @@ -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 = ""; }; 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 = ""; }; + C3548F0524456447009433A8 /* PNModeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNModeVC.swift; sourceTree = ""; }; + C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Wrapping.swift"; sourceTree = ""; }; C354E75923FE2A7600CE22E3 /* BaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseVC.swift; sourceTree = ""; }; 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 = ""; }; @@ -2892,6 +2896,7 @@ B886B4A82398BA1500211ABE /* QRCode.swift */, B8783E9D23EB948D00404FB8 /* UILabel+Interaction.swift */, B83F2B87240CB75A000A54AB /* UIImage+Scaling.swift */, + C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */, ); path = Utilities; sourceTree = ""; @@ -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 */, diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 312da5e39..1ddef6ec3 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -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 () @@ -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]; diff --git a/Signal/src/Loki/Components/NewConversationButtonSet.swift b/Signal/src/Loki/Components/NewConversationButtonSet.swift index 882100af0..32591f69e 100644 --- a/Signal/src/Loki/Components/NewConversationButtonSet.swift +++ b/Signal/src/Loki/Components/NewConversationButtonSet.swift @@ -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 diff --git a/Signal/src/Loki/Utilities/UIView+Wrapping.swift b/Signal/src/Loki/Utilities/UIView+Wrapping.swift new file mode 100644 index 000000000..422a004b3 --- /dev/null +++ b/Signal/src/Loki/Utilities/UIView+Wrapping.swift @@ -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) + } +} diff --git a/Signal/src/Loki/View Controllers/DisplayNameVC.swift b/Signal/src/Loki/View Controllers/DisplayNameVC.swift index d603deda7..c65dad94d 100644 --- a/Signal/src/Loki/View Controllers/DisplayNameVC.swift +++ b/Signal/src/Loki/View Controllers/DisplayNameVC.swift @@ -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) } } diff --git a/Signal/src/Loki/View Controllers/PNModeVC.swift b/Signal/src/Loki/View Controllers/PNModeVC.swift new file mode 100644 index 000000000..91aea5ae6 --- /dev/null +++ b/Signal/src/Loki/View Controllers/PNModeVC.swift @@ -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. You’ll be notified of new messages reliably and immediately. Using APNs means that this device will communicate directly with Apple’s 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 = 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) +} diff --git a/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m b/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m index 382c0c291..c930e91a6 100644 --- a/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m +++ b/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m @@ -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. You’ll be notified of new messages reliably and immediately. Using APNs means that this device will communicate directly with Apple’s 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 diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index a6d46ba3c..c29376d96 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -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. You’ll be notified of new messages reliably and immediately. Using APNs means that this device will communicate directly with Apple’s 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. You’ll be notified of new messages reliably and immediately. Using APNs means that this device will communicate directly with Apple’s 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"; diff --git a/SignalMessaging/Loki/Redesign/Style Guide/Colors.swift b/SignalMessaging/Loki/Redesign/Style Guide/Colors.swift index daff51918..a783b18fa 100644 --- a/SignalMessaging/Loki/Redesign/Style Guide/Colors.swift +++ b/SignalMessaging/Loki/Redesign/Style Guide/Colors.swift @@ -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) } diff --git a/SignalMessaging/Loki/Redesign/Style Guide/Values.swift b/SignalMessaging/Loki/Redesign/Style Guide/Values.swift index 3f8798361..35f616454 100644 --- a/SignalMessaging/Loki/Redesign/Style Guide/Values.swift +++ b/SignalMessaging/Loki/Redesign/Style Guide/Values.swift @@ -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) diff --git a/SignalServiceKit/src/Account/TSAccountManager.m b/SignalServiceKit/src/Account/TSAccountManager.m index ecce2ee27..44635f0d0 100644 --- a/SignalServiceKit/src/Account/TSAccountManager.m +++ b/SignalServiceKit/src/Account/TSAccountManager.m @@ -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 diff --git a/SignalServiceKit/src/Loki/API/LokiPushNotificationManager.swift b/SignalServiceKit/src/Loki/API/LokiPushNotificationManager.swift index 963e6762c..5d9a248db 100644 --- a/SignalServiceKit/src/Loki/API/LokiPushNotificationManager.swift +++ b/SignalServiceKit/src/Loki/API/LokiPushNotificationManager.swift @@ -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 { 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:)