From cbc94dad704740e1e605b03873861abfed9cb2ea Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Thu, 16 Apr 2020 13:58:43 +1000 Subject: [PATCH] Add PN mode sheet & fix unsubscribing --- Signal.xcodeproj/project.pbxproj | 8 ++ Signal/src/AppDelegate.m | 4 +- Signal/src/Loki/Components/PNOptionView.swift | 106 ++++++++++++++++ Signal/src/Loki/View Controllers/HomeVC.swift | 14 +-- .../Loki/View Controllers/PNModeSheet.swift | 87 ++++++++++++++ .../src/Loki/View Controllers/PNModeVC.swift | 113 +----------------- .../translations/en.lproj/Localizable.strings | 1 + .../src/Account/TSAccountManager.m | 35 +++--- .../API/LokiPushNotificationManager.swift | 27 +++-- .../src/Loki/Utilities/LKUserDefaults.swift | 1 + 10 files changed, 251 insertions(+), 145 deletions(-) create mode 100644 Signal/src/Loki/Components/PNOptionView.swift create mode 100644 Signal/src/Loki/View Controllers/PNModeSheet.swift diff --git a/Signal.xcodeproj/project.pbxproj b/Signal.xcodeproj/project.pbxproj index 645856853..e2fdf4da3 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 */; }; + C353F8F7244808E90011121A /* PNModeSheet.swift in Sources */ = {isa = PBXBuildFile; fileRef = C353F8F6244808E90011121A /* PNModeSheet.swift */; }; + C353F8F9244809150011121A /* PNOptionView.swift in Sources */ = {isa = PBXBuildFile; fileRef = C353F8F8244809150011121A /* PNOptionView.swift */; }; 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 */; }; @@ -1501,6 +1503,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 = ""; }; + C353F8F6244808E90011121A /* PNModeSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNModeSheet.swift; sourceTree = ""; }; + C353F8F8244809150011121A /* PNOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNOptionView.swift; 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 = ""; }; @@ -2877,6 +2881,7 @@ B8B26C8E234D629C004ED98C /* MentionCandidateSelectionView.swift */, B8B26C90234D8CBD004ED98C /* MentionCandidateSelectionViewDelegate.swift */, B83F2B85240C7B8F000A54AB /* NewConversationButtonSet.swift */, + C353F8F8244809150011121A /* PNOptionView.swift */, B8BB82B02390C37000BA5194 /* SearchBar.swift */, B85357BE23A1AE0800AAF6CD /* SeedReminderView.swift */, B85357C023A1B81900AAF6CD /* SeedReminderViewDelegate.swift */, @@ -2922,6 +2927,7 @@ B8CCF63623961D6D0091D419 /* NewPrivateChatVC.swift */, B894D0742339EDCF00B4D94D /* NukeDataModal.swift */, C3DFFAC723E970080058DAF8 /* OpenGroupSuggestionSheet.swift */, + C353F8F6244808E90011121A /* PNModeSheet.swift */, C3548F0524456447009433A8 /* PNModeVC.swift */, B886B4A62398B23E00211ABE /* QRCodeVC.swift */, B82B408B239A068800A248E7 /* RegisterVC.swift */, @@ -4101,6 +4107,7 @@ B82584A02315024B001B41CB /* LokiRSSFeedPoller.swift in Sources */, 24A830A22293CD0100F4CAC0 /* LokiP2PServer.swift in Sources */, 349ED990221B0194008045B0 /* Onboarding2FAViewController.swift in Sources */, + C353F8F7244808E90011121A /* PNModeSheet.swift in Sources */, 45D231771DC7E8F10034FA89 /* SessionResetJob.swift in Sources */, 340FC8A9204DAC8D007AEB0F /* NotificationSettingsOptionsViewController.m in Sources */, C3548F0624456447009433A8 /* PNModeVC.swift in Sources */, @@ -4199,6 +4206,7 @@ 340FC8BB204DAC8D007AEB0F /* OWSAddToContactViewController.m in Sources */, 45F32C232057297A00A300D5 /* MediaPageViewController.swift in Sources */, 452C468F1E427E200087B011 /* OutboundCallInitiator.swift in Sources */, + C353F8F9244809150011121A /* PNOptionView.swift in Sources */, B82B4094239DF15900A248E7 /* ConversationTitleView.swift in Sources */, 34D2CCDA2062E7D000CB1A14 /* OWSScreenLockUI.m in Sources */, 45F170BB1E2FC5D3003FC1F2 /* CallAudioService.swift in Sources */, diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 1ddef6ec3..eb2bfe7c6 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -590,7 +590,9 @@ static BOOL isInternalTestVersion = NO; OWSLogInfo(@"Registering for push notifications with token: %@.", deviceToken); BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"]; if (isUsingFullAPNs) { - [LKPushNotificationManager registerWithToken:deviceToken hexEncodedPublicKey:self.tsAccountManager.localNumber]; + __unused AnyPromise *promise = [LKPushNotificationManager registerWithToken:deviceToken hexEncodedPublicKey:self.tsAccountManager.localNumber]; + } else { + __unused AnyPromise *promise = [LKPushNotificationManager registerWithToken:deviceToken]; } } diff --git a/Signal/src/Loki/Components/PNOptionView.swift b/Signal/src/Loki/Components/PNOptionView.swift new file mode 100644 index 000000000..743024c36 --- /dev/null +++ b/Signal/src/Loki/Components/PNOptionView.swift @@ -0,0 +1,106 @@ + +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 +protocol OptionViewDelegate { + + func optionViewDidActivate(_ optionView: OptionView) +} diff --git a/Signal/src/Loki/View Controllers/HomeVC.swift b/Signal/src/Loki/View Controllers/HomeVC.swift index 0fa506bb3..d2b42b17a 100644 --- a/Signal/src/Loki/View Controllers/HomeVC.swift +++ b/Signal/src/Loki/View Controllers/HomeVC.swift @@ -143,13 +143,13 @@ final class HomeVC : BaseVC, UITableViewDataSource, UITableViewDelegate, UIScrol override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) isViewVisible = true -// let hasSeenOpenGroupSuggestionSheet = UserDefaults.standard[.hasSeenOpenGroupSuggestionSheet] -// if !hasSeenOpenGroupSuggestionSheet { -// let openGroupSuggestionSheet = OpenGroupSuggestionSheet() -// openGroupSuggestionSheet.modalPresentationStyle = .overFullScreen -// openGroupSuggestionSheet.modalTransitionStyle = .crossDissolve -// present(openGroupSuggestionSheet, animated: true, completion: nil) -// } + let hasSeenPNModeSheet = UserDefaults.standard[.hasSeenPNModeSheet] + if !hasSeenPNModeSheet { + let pnModeSheet = PNModeSheet() + pnModeSheet.modalPresentationStyle = .overFullScreen + pnModeSheet.modalTransitionStyle = .crossDissolve + present(pnModeSheet, animated: true, completion: nil) + } UserDefaults.standard[.hasLaunchedOnce] = true } diff --git a/Signal/src/Loki/View Controllers/PNModeSheet.swift b/Signal/src/Loki/View Controllers/PNModeSheet.swift new file mode 100644 index 000000000..242a9ec30 --- /dev/null +++ b/Signal/src/Loki/View Controllers/PNModeSheet.swift @@ -0,0 +1,87 @@ +import PromiseKit + +final class PNModeSheet : Sheet, 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 populateContentView() { + // 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("Session now features two ways to handle push notifications. Make sure to read the descriptions carefully before you choose.", comment: "") + explanationLabel.numberOfLines = 0 + explanationLabel.lineBreakMode = .byWordWrapping + // Set up options stack view + let optionsStackView = UIStackView(arrangedSubviews: optionViews) + optionsStackView.axis = .vertical + optionsStackView.spacing = Values.smallSpacing + optionsStackView.alignment = .fill + // Set up confirm button + let confirmButton = Button(style: .prominentOutline, size: .medium) + confirmButton.set(.width, to: 240) + confirmButton.setTitle(NSLocalizedString("Confirm", comment: ""), for: UIControl.State.normal) + confirmButton.addTarget(self, action: #selector(confirm), for: UIControl.Event.touchUpInside) + // Set up dismiss button + let skipButton = Button(style: .regular, size: .medium) + skipButton.set(.width, to: 240) + skipButton.setTitle(NSLocalizedString("Skip", comment: ""), for: UIControl.State.normal) + skipButton.addTarget(self, action: #selector(close), for: UIControl.Event.touchUpInside) + // Set up button stack view + let bottomStackView = UIStackView(arrangedSubviews: [ confirmButton, skipButton ]) + bottomStackView.axis = .vertical + bottomStackView.spacing = isSmallScreen ? Values.smallSpacing : Values.mediumSpacing + bottomStackView.alignment = .fill + // Set up main stack view + let stackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel, optionsStackView, bottomStackView ]) + stackView.axis = .vertical + stackView.spacing = isSmallScreen ? 12 : Values.largeSpacing + stackView.alignment = .center + // Set up constraints + contentView.addSubview(stackView) + stackView.pin(.leading, to: .leading, of: contentView, withInset: isSmallScreen ? Values.mediumSpacing : Values.largeSpacing) + stackView.pin(.top, to: .top, of: contentView, withInset: isSmallScreen ? Values.mediumSpacing : Values.largeSpacing) + contentView.pin(.trailing, to: .trailing, of: stackView, withInset: isSmallScreen ? Values.mediumSpacing : Values.largeSpacing) + contentView.pin(.bottom, to: .bottom, of: stackView, withInset: (isSmallScreen ? Values.mediumSpacing : Values.veryLargeSpacing) + overshoot) + } + + // MARK: Interaction + func optionViewDidActivate(_ optionView: OptionView) { + optionViews.filter { $0 != optionView }.forEach { $0.isSelected = false } + } + + @objc private func confirm() { + 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) + let _: Promise = SyncPushTokensJob.run(accountManager: AppEnvironment.shared.accountManager, preferences: Environment.shared.preferences) + close() + } + + override func close() { + UserDefaults.standard[.hasSeenPNModeSheet] = true + super.close() + } +} diff --git a/Signal/src/Loki/View Controllers/PNModeVC.swift b/Signal/src/Loki/View Controllers/PNModeVC.swift index 91aea5ae6..09f739d2e 100644 --- a/Signal/src/Loki/View Controllers/PNModeVC.swift +++ b/Signal/src/Loki/View Controllers/PNModeVC.swift @@ -82,7 +82,7 @@ final class PNModeVC : BaseVC, OptionViewDelegate { } // MARK: Interaction - fileprivate func optionViewDidActivate(_ optionView: OptionView) { + func optionViewDidActivate(_ optionView: OptionView) { optionViews.filter { $0 != optionView }.forEach { $0.isSelected = false } } @@ -94,119 +94,10 @@ final class PNModeVC : BaseVC, OptionViewDelegate { return present(alert, animated: true, completion: nil) } UserDefaults.standard[.isUsingFullAPNs] = (selectedOptionView == apnsOptionView) + UserDefaults.standard[.hasSeenPNModeSheet] = true // Shouldn't be shown to users who've done the new onboarding 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/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index c29376d96..9c89f72d5 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2818,3 +2818,4 @@ "Use APNs" = "Use APNs"; "Recommended" = "Recommended"; "Notification Strategy" = "Notification Strategy"; +"Session now features two ways to handle push notifications. Make sure to read the descriptions carefully before you choose." = "Session now features two ways to handle push notifications. Make sure to read the descriptions carefully before you choose."; diff --git a/SignalServiceKit/src/Account/TSAccountManager.m b/SignalServiceKit/src/Account/TSAccountManager.m index 44635f0d0..ebcbad8f8 100644 --- a/SignalServiceKit/src/Account/TSAccountManager.m +++ b/SignalServiceKit/src/Account/TSAccountManager.m @@ -297,26 +297,23 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa remainingRetries:(int)remainingRetries { BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"]; - if (isUsingFullAPNs) { - [LKPushNotificationManager registerWithToken:pushToken hexEncodedPublicKey:self.localNumber] - .then(^() { - successHandler(); - }) - .catch(^(NSError *error) { - if (remainingRetries > 0) { - [self registerForPushNotificationsWithPushToken:pushToken - voipToken:voipToken - success:successHandler - failure:failureHandler - remainingRetries:remainingRetries - 1]; - } else { - if (!IsNSErrorNetworkFailure(error)) { - OWSProdError([OWSAnalyticsEvents accountsErrorRegisterPushTokensFailed]); - } - failureHandler(error); + AnyPromise *promise = isUsingFullAPNs ? [LKPushNotificationManager registerWithToken:pushToken hexEncodedPublicKey:self.localNumber] + : [LKPushNotificationManager registerWithToken:pushToken]; + promise + .then(^() { + successHandler(); + }) + .catch(^(NSError *error) { + if (remainingRetries > 0) { + [self registerForPushNotificationsWithPushToken:pushToken voipToken:voipToken success:successHandler failure:failureHandler + remainingRetries:remainingRetries - 1]; + } else { + if (!IsNSErrorNetworkFailure(error)) { + OWSProdError([OWSAnalyticsEvents accountsErrorRegisterPushTokensFailed]); } - }); - } + failureHandler(error); + } + }); } - (void)registerWithPhoneNumber:(NSString *)phoneNumber diff --git a/SignalServiceKit/src/Loki/API/LokiPushNotificationManager.swift b/SignalServiceKit/src/Loki/API/LokiPushNotificationManager.swift index 5d9a248db..75a64630a 100644 --- a/SignalServiceKit/src/Loki/API/LokiPushNotificationManager.swift +++ b/SignalServiceKit/src/Loki/API/LokiPushNotificationManager.swift @@ -17,8 +17,7 @@ public final class LokiPushNotificationManager : NSObject { // MARK: Registration /// Registers the user for silent push notifications (that then trigger the app /// into fetching messages). Only the user's device token is needed for this. - @objc(registerWithToken:) - static func register(with token: Data) { + static func register(with token: Data) -> Promise { let hexEncodedToken = token.toHexString() let userDefaults = UserDefaults.standard let oldToken = userDefaults[.deviceToken] @@ -26,16 +25,18 @@ public final class LokiPushNotificationManager : NSObject { let isUsingFullAPNs = userDefaults[.isUsingFullAPNs] let now = Date().timeIntervalSince1970 guard hexEncodedToken != oldToken || now - lastUploadTime < tokenExpirationInterval else { - return print("[Loki] Device token hasn't changed; no need to re-upload.") + print("[Loki] Device token hasn't changed; no need to re-upload.") + return Promise { $0.fulfill(()) } } guard !isUsingFullAPNs else { - return print("[Loki] Using full APNs; ignoring call to register(with:).") + print("[Loki] Using full APNs; ignoring call to register(with:).") + return Promise { $0.fulfill(()) } } let parameters = [ "token" : hexEncodedToken ] 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.") } @@ -45,9 +46,19 @@ public final class LokiPushNotificationManager : NSObject { userDefaults[.deviceToken] = hexEncodedToken userDefaults[.lastDeviceTokenUpload] = now userDefaults[.isUsingFullAPNs] = false - }, failure: { _, error in + return + } + promise.catch { error in print("[Loki] Couldn't register device token.") - }) + } + return promise + } + + /// Registers the user for silent push notifications (that then trigger the app + /// into fetching messages). Only the user's device token is needed for this. + @objc(registerWithToken:) + static func objc_register(with token: Data) -> AnyPromise { + return AnyPromise.from(register(with: token)) } /// Registers the user for normal push notifications. Requires the user's device @@ -78,6 +89,8 @@ public final class LokiPushNotificationManager : NSObject { return promise } + /// Registers the user for normal push notifications. Requires the user's device + /// token and their Session ID. @objc(registerWithToken:hexEncodedPublicKey:) static func objc_register(with token: Data, hexEncodedPublicKey: String) -> AnyPromise { return AnyPromise.from(register(with: token, hexEncodedPublicKey: hexEncodedPublicKey)) diff --git a/SignalServiceKit/src/Loki/Utilities/LKUserDefaults.swift b/SignalServiceKit/src/Loki/Utilities/LKUserDefaults.swift index bcaefcdfb..8aae3a111 100644 --- a/SignalServiceKit/src/Loki/Utilities/LKUserDefaults.swift +++ b/SignalServiceKit/src/Loki/Utilities/LKUserDefaults.swift @@ -5,6 +5,7 @@ public enum LKUserDefaults { public enum Bool : Swift.String { case hasLaunchedOnce case hasSeenOpenGroupSuggestionSheet + case hasSeenPNModeSheet case hasViewedSeed /// Whether the device was unlinked as a slave device (used to notify the user on the landing screen). case wasUnlinked