From 6f31336a7db897417843776b08f8cd0173141958 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 14 Apr 2020 15:07:37 +1000 Subject: [PATCH 1/8] Implement preliminary push notification UI --- Signal.xcodeproj/project.pbxproj | 8 + .../src/Loki/Utilities/UIView+Wrapping.swift | 12 ++ .../src/Loki/View Controllers/PNModeVC.swift | 184 ++++++++++++++++++ .../Loki/Redesign/Style Guide/Colors.swift | 2 + .../Loki/Redesign/Style Guide/Values.swift | 1 + 5 files changed, 207 insertions(+) create mode 100644 Signal/src/Loki/Utilities/UIView+Wrapping.swift create mode 100644 Signal/src/Loki/View Controllers/PNModeVC.swift 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/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/PNModeVC.swift b/Signal/src/Loki/View Controllers/PNModeVC.swift new file mode 100644 index 000000000..8d0d1922c --- /dev/null +++ b/Signal/src/Loki/View Controllers/PNModeVC.swift @@ -0,0 +1,184 @@ + +final class PNModeVC : BaseVC, OptionViewDelegate { + + private var optionViews: [OptionView] { + [ apnsOptionView, backgroundPollingOptionView, noPNsOptionView ] + } + + private var selectedOptionView: OptionView? { + return optionViews.first { $0.isSelected } + } + + // MARK: Components + private lazy var apnsOptionView = OptionView(title: "Apple Push Notification Service", explanation: "The app will use the Apple Push Notification Service. You'll be notified of new messages immediately. This mode entails a slight privacy sacrifice as Apple will know your IP. The contents of your messages will still be fully encrypted, your data will still be stored in a decentralized manner and your messages will still be onion routed.", delegate: self) + private lazy var backgroundPollingOptionView = OptionView(title: "Background Polling", explanation: "The app will occassionally check for new messages when it's in the background. This provides full privacy but notifications may be significantly delayed.", delegate: self) + private lazy var noPNsOptionView = OptionView(title: "No Push Notifications", explanation: "You will not be notified of new messages when the app is closed. This provides full privacy.", 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 = "Push Notifications" + 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 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + 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() { + // TODO: Implement + } +} + +// MARK: Option View +private extension PNModeVC { + + final class OptionView : UIView { + private let title: String + private let explanation: String + private let delegate: OptionViewDelegate + var isSelected = false { didSet { handleIsSelectedChanged() } } + + init(title: String, explanation: String, delegate: OptionViewDelegate) { + self.title = title + self.explanation = explanation + self.delegate = delegate + 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.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 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 + UIView.animate(withDuration: animationDuration) { + self.backgroundColor = self.isSelected ? Colors.accent : Colors.buttonBackground + } + 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 + if isSelected { delegate.optionViewDidActivate(self) } + } + } +} + +// MARK: Option View Delegate +private protocol OptionViewDelegate { + + func optionViewDidActivate(_ optionView: PNModeVC.OptionView) +} 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) From 0386b856a17feac0d7c16584c8cf05e31af89f8f Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 14 Apr 2020 16:11:22 +1000 Subject: [PATCH 2/8] Fix animation and hook up PN mode screen --- .../Components/NewConversationButtonSet.swift | 28 +++++++++++++++---- .../Loki/View Controllers/DisplayNameVC.swift | 5 ++-- .../src/Loki/View Controllers/PNModeVC.swift | 17 ++++++++--- 3 files changed, 37 insertions(+), 13 deletions(-) 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/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 index 8d0d1922c..d9330256c 100644 --- a/Signal/src/Loki/View Controllers/PNModeVC.swift +++ b/Signal/src/Loki/View Controllers/PNModeVC.swift @@ -87,7 +87,9 @@ final class PNModeVC : BaseVC, OptionViewDelegate { } @objc private func register() { - // TODO: Implement + TSAccountManager.sharedInstance().didRegister() + let homeVC = HomeVC() + navigationController!.setViewControllers([ homeVC ], animated: true) } } @@ -162,9 +164,15 @@ private extension PNModeVC { private func handleIsSelectedChanged() { let animationDuration: TimeInterval = 0.25 - UIView.animate(withDuration: animationDuration) { - self.backgroundColor = self.isSelected ? Colors.accent : Colors.buttonBackground - } + // 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 @@ -172,6 +180,7 @@ private extension PNModeVC { shadowAnimation.duration = animationDuration layer.add(shadowAnimation, forKey: shadowAnimation.keyPath) layer.shadowColor = newShadowColor + // Notify delegate if isSelected { delegate.optionViewDidActivate(self) } } } From 5c5b28e0d9387425aba75f5420b0e522163b3ed8 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 14 Apr 2020 16:32:10 +1000 Subject: [PATCH 3/8] Actually use chosen setting --- Signal/src/AppDelegate.m | 6 ++---- Signal/src/Loki/View Controllers/PNModeVC.swift | 1 + 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index 312da5e39..c0b11bdcd 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -61,7 +61,6 @@ static NSTimeInterval launchStartedAt; // Debug settings static BOOL isInternalTestVersion = NO; -static BOOL isUsingFullAPNs = YES; @interface AppDelegate () @@ -589,12 +588,10 @@ static BOOL isUsingFullAPNs = YES; } OWSLogInfo(@"Registered 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 @@ -709,6 +706,7 @@ static BOOL isUsingFullAPNs = YES; - (void)enableBackgroundRefreshIfNecessary { + BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"]; if (isUsingFullAPNs) { return; } [AppReadiness runNowOrWhenAppDidBecomeReady:^{ [UIApplication.sharedApplication setMinimumBackgroundFetchInterval:UIApplicationBackgroundFetchIntervalMinimum]; diff --git a/Signal/src/Loki/View Controllers/PNModeVC.swift b/Signal/src/Loki/View Controllers/PNModeVC.swift index d9330256c..dd82f36fa 100644 --- a/Signal/src/Loki/View Controllers/PNModeVC.swift +++ b/Signal/src/Loki/View Controllers/PNModeVC.swift @@ -87,6 +87,7 @@ final class PNModeVC : BaseVC, OptionViewDelegate { } @objc private func register() { + UserDefaults.standard[.isUsingFullAPNs] = (selectedOptionView == apnsOptionView) TSAccountManager.sharedInstance().didRegister() let homeVC = HomeVC() navigationController!.setViewControllers([ homeVC ], animated: true) From 026bb4aa45ac62cc4ae4d9d1c189d03748a6f3a7 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Tue, 14 Apr 2020 16:57:32 +1000 Subject: [PATCH 4/8] Add APNs option to notification settings screen as well --- .../src/Loki/View Controllers/PNModeVC.swift | 6 ++++++ .../NotificationSettingsViewController.m | 20 +++++++++++++++++++ .../translations/en.lproj/Localizable.strings | 4 ++++ 3 files changed, 30 insertions(+) diff --git a/Signal/src/Loki/View Controllers/PNModeVC.swift b/Signal/src/Loki/View Controllers/PNModeVC.swift index dd82f36fa..5294b9338 100644 --- a/Signal/src/Loki/View Controllers/PNModeVC.swift +++ b/Signal/src/Loki/View Controllers/PNModeVC.swift @@ -87,6 +87,12 @@ final class PNModeVC : BaseVC, OptionViewDelegate { } @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() diff --git a/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m b/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m index 382c0c291..ba3410b42 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, @"notification_strategy") + isOnBlock:^{ + return [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"]; + } + isEnabledBlock:^{ + return YES; + } + target:weakSelf + selector:@selector(didToggleAPNsSwitch:)]]; + strategySection.footerTitle = NSLocalizedString(@"The app will use the Apple Push Notification Service. You'll be notified of new messages immediately. This mode entails a slight privacy sacrifice as Apple will know your IP. The contents of your messages will still be fully encrypted, your data will still be stored in a decentralized manner and your messages will still be onion routed.", @""); + [contents addSection:strategySection]; + // Sounds section. OWSTableSection *soundsSection = [OWSTableSection new]; @@ -119,4 +134,9 @@ [Environment.shared.preferences setSoundInForeground:sender.on]; } +- (void)didToggleAPNsSwitch:(UISwitch *)sender +{ + [NSUserDefaults.standardUserDefaults setBool:sender.on forKey:@"isUsingFullAPNs"]; +} + @end diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index a6d46ba3c..0f00948d3 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2809,3 +2809,7 @@ "Join Public Chat" = "Join Public Chat"; "No, thank you" = "No, thank you"; "Report" = "Report"; +"Please Pick an Option" = "Please Pick an Option"; +"Notification Strategy" = "Notification Strategy"; +"Use APNs" = "Use APNs"; +"The app will use the Apple Push Notification Service. You'll be notified of new messages immediately. This mode entails a slight privacy sacrifice as Apple will know your IP. The contents of your messages will still be fully encrypted, your data will still be stored in a decentralized manner and your messages will still be onion routed." = "The app will use the Apple Push Notification Service. You'll be notified of new messages immediately. This mode entails a slight privacy sacrifice as Apple will know your IP. The contents of your messages will still be fully encrypted, your data will still be stored in a decentralized manner and your messages will still be onion routed."; From a971a4f609071fedc42d7a9089ca63f44257c483 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 15 Apr 2020 13:40:19 +1000 Subject: [PATCH 5/8] Hook Session's PN setup into Signal's PN setup --- Signal/src/AppDelegate.m | 269 +++--------------- .../src/Loki/View Controllers/PNModeVC.swift | 18 +- .../src/Account/TSAccountManager.m | 15 +- .../API/LokiPushNotificationManager.swift | 18 +- 4 files changed, 80 insertions(+), 240 deletions(-) diff --git a/Signal/src/AppDelegate.m b/Signal/src/AppDelegate.m index c0b11bdcd..1ddef6ec3 100644 --- a/Signal/src/AppDelegate.m +++ b/Signal/src/AppDelegate.m @@ -54,8 +54,8 @@ 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; @@ -177,7 +177,7 @@ static BOOL isInternalTestVersion = NO; - (void)applicationDidEnterBackground:(UIApplication *)application { - OWSLogInfo(@"applicationDidEnterBackground."); + OWSLogInfo(@"applicationDidEnterBackground"); [DDLog flushLog]; @@ -188,17 +188,17 @@ static BOOL isInternalTestVersion = NO; - (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]; @@ -229,7 +229,7 @@ static BOOL isInternalTestVersion = NO; [DebugLogger.sharedLogger enableFileLogging]; } - OWSLogWarn(@"application: didFinishLaunchingWithOptions."); + OWSLogWarn(@"application:didFinishLaunchingWithOptions"); [Cryptography seedRandom]; // XXX - careful when moving this. It must happen before we initialize OWSPrimaryStorage. @@ -251,7 +251,7 @@ static BOOL isInternalTestVersion = NO; // // ensureIsReadyForAppExtensions will show a failure mode UI that // lets users report this error. - OWSLogInfo(@"application: didFinishLaunchingWithOptions failed."); + OWSLogInfo(@"application:didFinishLaunchingWithOptions failed"); return YES; } @@ -375,7 +375,7 @@ static BOOL isInternalTestVersion = NO; } 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", @@ -451,7 +451,7 @@ static BOOL isInternalTestVersion = NO; } if (error) { - OWSFailDebug(@"database conversion failed: %@", error); + OWSFailDebug(@"Database conversion failed: %@", error); [self showLaunchFailureUI:error]; return NO; } @@ -490,7 +490,7 @@ static BOOL isInternalTestVersion = NO; 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]; @@ -503,7 +503,7 @@ static BOOL isInternalTestVersion = NO; NSString *databaseFilePath = [OWSPrimaryStorage legacyDatabaseFilePath]; if (![[NSFileManager defaultManager] fileExistsAtPath:databaseFilePath]) { - OWSLogVerbose(@"no legacy database file found"); + OWSLogVerbose(@"No legacy database file found"); return nil; } @@ -583,11 +583,11 @@ static BOOL isInternalTestVersion = NO; 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]; @@ -599,13 +599,13 @@ static BOOL isInternalTestVersion = NO; 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]); @@ -619,11 +619,11 @@ static BOOL isInternalTestVersion = NO; 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]; } @@ -635,7 +635,7 @@ static BOOL isInternalTestVersion = NO; OWSAssertIsOnMainThread(); if (self.didAppLaunchFail) { - OWSFailDebug(@"app launch failed"); + OWSFailDebug(@"App launch failed"); return NO; } @@ -660,7 +660,7 @@ static BOOL isInternalTestVersion = NO; [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)); } } @@ -677,11 +677,11 @@ static BOOL isInternalTestVersion = NO; OWSAssertIsOnMainThread(); if (self.didAppLaunchFail) { - OWSFailDebug(@"app launch failed"); + OWSFailDebug(@"App launch failed"); return; } - OWSLogWarn(@"applicationDidBecomeActive."); + OWSLogWarn(@"applicationDidBecomeActive"); if (CurrentAppContext().isRunningTests) { return; } @@ -701,7 +701,7 @@ static BOOL isInternalTestVersion = NO; // On every activation, clear old temp directories. ClearOldTemporaryDirectories(); - OWSLogInfo(@"applicationDidBecomeActive completed."); + OWSLogInfo(@"applicationDidBecomeActive completed"); } - (void)enableBackgroundRefreshIfNecessary @@ -728,7 +728,7 @@ static BOOL isInternalTestVersion = NO; { OWSAssertIsOnMainThread(); - OWSLogWarn(@"handleActivation."); + OWSLogWarn(@"handleActivation"); // Always check prekeys after app launches, and sometimes check on app activation. [TSPreKeyManager checkPreKeysIfNecessary]; @@ -741,7 +741,7 @@ static BOOL isInternalTestVersion = NO; // 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. @@ -761,7 +761,7 @@ static BOOL isInternalTestVersion = NO; [[[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]; @@ -811,7 +811,7 @@ static BOOL isInternalTestVersion = NO; } 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 @@ -823,7 +823,7 @@ static BOOL isInternalTestVersion = NO; 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 = @@ -835,7 +835,7 @@ static BOOL isInternalTestVersion = NO; }); } - OWSLogInfo(@"handleActivation completed."); + OWSLogInfo(@"handleActivation completed"); } - (void)applicationWillResignActive:(UIApplication *)application @@ -843,11 +843,11 @@ static BOOL isInternalTestVersion = NO; OWSAssertIsOnMainThread(); if (self.didAppLaunchFail) { - OWSFailDebug(@"app launch failed"); + OWSFailDebug(@"App launch failed"); return; } - OWSLogWarn(@"applicationWillResignActive."); + OWSLogWarn(@"applicationWillResignActive"); [self clearAllNotificationsAndRestoreBadgeCount]; @@ -870,7 +870,7 @@ static BOOL isInternalTestVersion = NO; OWSAssertIsOnMainThread(); if (self.didAppLaunchFail) { - OWSFailDebug(@"app launch failed"); + OWSFailDebug(@"App launch failed"); completionHandler(NO); return; } @@ -902,197 +902,13 @@ static BOOL isInternalTestVersion = NO; }]; } -/** - * 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 @@ -1106,7 +922,7 @@ static BOOL isInternalTestVersion = NO; OWSAssertIsOnMainThread(); if (self.didAppLaunchFail) { - OWSFailDebug(@"app launch failed"); + OWSFailDebug(@"App launch failed"); return; } if (!(AppReadiness.isAppReady && [self.tsAccountManager isRegisteredAndReady])) { @@ -1128,7 +944,7 @@ static BOOL isInternalTestVersion = NO; 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]; @@ -1142,7 +958,7 @@ static BOOL isInternalTestVersion = NO; OWSAssertIsOnMainThread(); if (self.didAppLaunchFail) { - OWSFailDebug(@"app launch failed"); + OWSFailDebug(@"App launch failed"); return; } if (!(AppReadiness.isAppReady && [self.tsAccountManager isRegisteredAndReady])) { @@ -1190,7 +1006,7 @@ static BOOL isInternalTestVersion = NO; OWSAssertIsOnMainThread(); if (self.didAppLaunchFail) { - OWSFailDebug(@"app launch failed"); + OWSFailDebug(@"App launch failed"); return; } @@ -1218,7 +1034,7 @@ static BOOL isInternalTestVersion = NO; OWSAssertIsOnMainThread(); if (self.didAppLaunchFail) { - OWSFailDebug(@"app launch failed"); + OWSFailDebug(@"App launch failed"); completionHandler(); return; } @@ -1249,12 +1065,12 @@ static BOOL isInternalTestVersion = NO; 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; } @@ -1367,7 +1183,7 @@ static BOOL isInternalTestVersion = NO; 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]; @@ -1468,6 +1284,7 @@ static BOOL isInternalTestVersion = NO; 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/View Controllers/PNModeVC.swift b/Signal/src/Loki/View Controllers/PNModeVC.swift index 5294b9338..ceef862f1 100644 --- a/Signal/src/Loki/View Controllers/PNModeVC.swift +++ b/Signal/src/Loki/View Controllers/PNModeVC.swift @@ -1,3 +1,4 @@ +import PromiseKit final class PNModeVC : BaseVC, OptionViewDelegate { @@ -10,7 +11,7 @@ final class PNModeVC : BaseVC, OptionViewDelegate { } // MARK: Components - private lazy var apnsOptionView = OptionView(title: "Apple Push Notification Service", explanation: "The app will use the Apple Push Notification Service. You'll be notified of new messages immediately. This mode entails a slight privacy sacrifice as Apple will know your IP. The contents of your messages will still be fully encrypted, your data will still be stored in a decentralized manner and your messages will still be onion routed.", delegate: self) + private lazy var apnsOptionView = OptionView(title: "Apple Push Notification Service", explanation: "The app will use the Apple Push Notification Service. You'll be notified of new messages immediately. This mode entails a slight privacy sacrifice as Apple will know your IP. The contents of your messages will still be fully encrypted, your data will still be stored in a decentralized manner and your messages will still be onion routed.", delegate: self, isRecommended: true) private lazy var backgroundPollingOptionView = OptionView(title: "Background Polling", explanation: "The app will occassionally check for new messages when it's in the background. This provides full privacy but notifications may be significantly delayed.", delegate: self) private lazy var noPNsOptionView = OptionView(title: "No Push Notifications", explanation: "You will not be notified of new messages when the app is closed. This provides full privacy.", delegate: self) @@ -97,6 +98,9 @@ final class PNModeVC : BaseVC, OptionViewDelegate { TSAccountManager.sharedInstance().didRegister() let homeVC = HomeVC() navigationController!.setViewControllers([ homeVC ], animated: true) + if (selectedOptionView == apnsOptionView || selectedOptionView == backgroundPollingOptionView) { + let _: Promise = SyncPushTokensJob.run(accountManager: AppEnvironment.shared.accountManager, preferences: Environment.shared.preferences) + } } } @@ -107,12 +111,14 @@ private extension PNModeVC { 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) { + 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() } @@ -160,6 +166,14 @@ private extension PNModeVC { 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 = "*Recommended" + stackView.addArrangedSubview(recommendedLabel) + } // Set up tap gesture recognizer let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap)) addGestureRecognizer(tapGestureRecognizer) 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 946c2ef1d..f62283c8f 100644 --- a/SignalServiceKit/src/Loki/API/LokiPushNotificationManager.swift +++ b/SignalServiceKit/src/Loki/API/LokiPushNotificationManager.swift @@ -1,3 +1,4 @@ +import PromiseKit @objc(LKPushNotificationManager) final class LokiPushNotificationManager : NSObject { @@ -51,8 +52,7 @@ 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 @@ 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 @@ 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:) From 9c17bd9aa5e0f93394e28314d33ffeba69456f2f Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 15 Apr 2020 13:57:03 +1000 Subject: [PATCH 6/8] Clean --- Signal/src/Loki/View Controllers/PNModeVC.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Signal/src/Loki/View Controllers/PNModeVC.swift b/Signal/src/Loki/View Controllers/PNModeVC.swift index ceef862f1..849a7ea9c 100644 --- a/Signal/src/Loki/View Controllers/PNModeVC.swift +++ b/Signal/src/Loki/View Controllers/PNModeVC.swift @@ -160,6 +160,7 @@ private extension PNModeVC { // 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) From 47325b2a41f433d3624dc60587d211c0cf9d604e Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 15 Apr 2020 14:39:55 +1000 Subject: [PATCH 7/8] Add missing PN token sync --- Signal/src/Loki/View Controllers/PNModeVC.swift | 7 ++----- .../AppSettings/NotificationSettingsViewController.m | 1 + 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Signal/src/Loki/View Controllers/PNModeVC.swift b/Signal/src/Loki/View Controllers/PNModeVC.swift index 849a7ea9c..1eaa04b23 100644 --- a/Signal/src/Loki/View Controllers/PNModeVC.swift +++ b/Signal/src/Loki/View Controllers/PNModeVC.swift @@ -3,7 +3,7 @@ import PromiseKit final class PNModeVC : BaseVC, OptionViewDelegate { private var optionViews: [OptionView] { - [ apnsOptionView, backgroundPollingOptionView, noPNsOptionView ] + [ apnsOptionView, backgroundPollingOptionView ] } private var selectedOptionView: OptionView? { @@ -13,7 +13,6 @@ final class PNModeVC : BaseVC, OptionViewDelegate { // MARK: Components private lazy var apnsOptionView = OptionView(title: "Apple Push Notification Service", explanation: "The app will use the Apple Push Notification Service. You'll be notified of new messages immediately. This mode entails a slight privacy sacrifice as Apple will know your IP. The contents of your messages will still be fully encrypted, your data will still be stored in a decentralized manner and your messages will still be onion routed.", delegate: self, isRecommended: true) private lazy var backgroundPollingOptionView = OptionView(title: "Background Polling", explanation: "The app will occassionally check for new messages when it's in the background. This provides full privacy but notifications may be significantly delayed.", delegate: self) - private lazy var noPNsOptionView = OptionView(title: "No Push Notifications", explanation: "You will not be notified of new messages when the app is closed. This provides full privacy.", delegate: self) // MARK: Lifecycle override func viewDidLoad() { @@ -98,9 +97,7 @@ final class PNModeVC : BaseVC, OptionViewDelegate { TSAccountManager.sharedInstance().didRegister() let homeVC = HomeVC() navigationController!.setViewControllers([ homeVC ], animated: true) - if (selectedOptionView == apnsOptionView || selectedOptionView == backgroundPollingOptionView) { - let _: Promise = SyncPushTokensJob.run(accountManager: AppEnvironment.shared.accountManager, preferences: Environment.shared.preferences) - } + let _: Promise = SyncPushTokensJob.run(accountManager: AppEnvironment.shared.accountManager, preferences: Environment.shared.preferences) } } diff --git a/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m b/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m index ba3410b42..b8a0e9c3f 100644 --- a/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m +++ b/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m @@ -137,6 +137,7 @@ - (void)didToggleAPNsSwitch:(UISwitch *)sender { [NSUserDefaults.standardUserDefaults setBool:sender.on forKey:@"isUsingFullAPNs"]; + __unused AnyPromise *promise = [OWSSyncPushTokensJob runWithAccountManager:AppEnvironment.shared.accountManager preferences:Environment.shared.preferences]; } @end From 09c6ea4c8c42dedcc3de8838cac1e01a7a7d4fc8 Mon Sep 17 00:00:00 2001 From: nielsandriesse Date: Wed, 15 Apr 2020 15:55:32 +1000 Subject: [PATCH 8/8] Fix copy --- Signal/src/Loki/View Controllers/PNModeVC.swift | 10 +++++----- .../AppSettings/NotificationSettingsViewController.m | 4 ++-- Signal/translations/en.lproj/Localizable.strings | 9 +++++++-- 3 files changed, 14 insertions(+), 9 deletions(-) diff --git a/Signal/src/Loki/View Controllers/PNModeVC.swift b/Signal/src/Loki/View Controllers/PNModeVC.swift index 1eaa04b23..91aea5ae6 100644 --- a/Signal/src/Loki/View Controllers/PNModeVC.swift +++ b/Signal/src/Loki/View Controllers/PNModeVC.swift @@ -11,8 +11,8 @@ final class PNModeVC : BaseVC, OptionViewDelegate { } // MARK: Components - private lazy var apnsOptionView = OptionView(title: "Apple Push Notification Service", explanation: "The app will use the Apple Push Notification Service. You'll be notified of new messages immediately. This mode entails a slight privacy sacrifice as Apple will know your IP. The contents of your messages will still be fully encrypted, your data will still be stored in a decentralized manner and your messages will still be onion routed.", delegate: self, isRecommended: true) - private lazy var backgroundPollingOptionView = OptionView(title: "Background Polling", explanation: "The app will occassionally check for new messages when it's in the background. This provides full privacy but notifications may be significantly delayed.", delegate: self) + 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() { @@ -38,14 +38,14 @@ final class PNModeVC : BaseVC, OptionViewDelegate { let titleLabel = UILabel() titleLabel.textColor = Colors.text titleLabel.font = .boldSystemFont(ofSize: isSmallScreen ? Values.largeFontSize : Values.veryLargeFontSize) - titleLabel.text = "Push Notifications" + 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 = "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat." + 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 @@ -169,7 +169,7 @@ private extension PNModeVC { let recommendedLabel = UILabel() recommendedLabel.textColor = Colors.accent recommendedLabel.font = .boldSystemFont(ofSize: Values.verySmallFontSize) - recommendedLabel.text = "*Recommended" + recommendedLabel.text = NSLocalizedString("Recommended", comment: "") stackView.addArrangedSubview(recommendedLabel) } // Set up tap gesture recognizer diff --git a/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m b/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m index b8a0e9c3f..c930e91a6 100644 --- a/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m +++ b/Signal/src/ViewControllers/AppSettings/NotificationSettingsViewController.m @@ -65,7 +65,7 @@ OWSTableSection *strategySection = [OWSTableSection new]; strategySection.headerTitle = NSLocalizedString(@"Notification Strategy", @""); [strategySection addItem:[OWSTableItem switchItemWithText:NSLocalizedString(@"Use APNs", @"") - accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"notification_strategy") + accessibilityIdentifier:ACCESSIBILITY_IDENTIFIER_WITH_NAME(self, @"push_notification_strategy") isOnBlock:^{ return [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"]; } @@ -74,7 +74,7 @@ } target:weakSelf selector:@selector(didToggleAPNsSwitch:)]]; - strategySection.footerTitle = NSLocalizedString(@"The app will use the Apple Push Notification Service. You'll be notified of new messages immediately. This mode entails a slight privacy sacrifice as Apple will know your IP. The contents of your messages will still be fully encrypted, your data will still be stored in a decentralized manner and your messages will still be onion routed.", @""); + 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. diff --git a/Signal/translations/en.lproj/Localizable.strings b/Signal/translations/en.lproj/Localizable.strings index 0f00948d3..c29376d96 100644 --- a/Signal/translations/en.lproj/Localizable.strings +++ b/Signal/translations/en.lproj/Localizable.strings @@ -2810,6 +2810,11 @@ "No, thank you" = "No, thank you"; "Report" = "Report"; "Please Pick an Option" = "Please Pick an Option"; -"Notification Strategy" = "Notification Strategy"; +"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"; -"The app will use the Apple Push Notification Service. You'll be notified of new messages immediately. This mode entails a slight privacy sacrifice as Apple will know your IP. The contents of your messages will still be fully encrypted, your data will still be stored in a decentralized manner and your messages will still be onion routed." = "The app will use the Apple Push Notification Service. You'll be notified of new messages immediately. This mode entails a slight privacy sacrifice as Apple will know your IP. The contents of your messages will still be fully encrypted, your data will still be stored in a decentralized manner and your messages will still be onion routed."; +"Recommended" = "Recommended"; +"Notification Strategy" = "Notification Strategy";