mirror of
https://github.com/oxen-io/session-ios.git
synced 2023-12-13 21:30:14 +01:00
Add PN mode sheet & fix unsubscribing
This commit is contained in:
parent
c596d5b2a3
commit
cbc94dad70
|
@ -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 = "<group>"; };
|
||||
B9EB5ABC1884C002007CBB57 /* MessageUI.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = MessageUI.framework; path = System/Library/Frameworks/MessageUI.framework; sourceTree = SDKROOT; };
|
||||
C34C8F7323A7830A00D82669 /* SpaceMono-Bold.ttf */ = {isa = PBXFileReference; lastKnownFileType = file; path = "SpaceMono-Bold.ttf"; sourceTree = "<group>"; };
|
||||
C353F8F6244808E90011121A /* PNModeSheet.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNModeSheet.swift; sourceTree = "<group>"; };
|
||||
C353F8F8244809150011121A /* PNOptionView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNOptionView.swift; sourceTree = "<group>"; };
|
||||
C3548F0524456447009433A8 /* PNModeVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PNModeVC.swift; sourceTree = "<group>"; };
|
||||
C3548F0724456AB6009433A8 /* UIView+Wrapping.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIView+Wrapping.swift"; sourceTree = "<group>"; };
|
||||
C354E75923FE2A7600CE22E3 /* BaseVC.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BaseVC.swift; sourceTree = "<group>"; };
|
||||
|
@ -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 */,
|
||||
|
|
|
@ -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];
|
||||
}
|
||||
}
|
||||
|
||||
|
|
106
Signal/src/Loki/Components/PNOptionView.swift
Normal file
106
Signal/src/Loki/Components/PNOptionView.swift
Normal file
|
@ -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)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
87
Signal/src/Loki/View Controllers/PNModeSheet.swift
Normal file
87
Signal/src/Loki/View Controllers/PNModeSheet.swift
Normal file
|
@ -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<Void> = SyncPushTokensJob.run(accountManager: AppEnvironment.shared.accountManager, preferences: Environment.shared.preferences)
|
||||
close()
|
||||
}
|
||||
|
||||
override func close() {
|
||||
UserDefaults.standard[.hasSeenPNModeSheet] = true
|
||||
super.close()
|
||||
}
|
||||
}
|
|
@ -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<Void> = SyncPushTokensJob.run(accountManager: AppEnvironment.shared.accountManager, preferences: Environment.shared.preferences)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Option View
|
||||
private extension PNModeVC {
|
||||
|
||||
final class OptionView : UIView {
|
||||
private let title: String
|
||||
private let explanation: String
|
||||
private let delegate: OptionViewDelegate
|
||||
private let isRecommended: Bool
|
||||
var isSelected = false { didSet { handleIsSelectedChanged() } }
|
||||
|
||||
init(title: String, explanation: String, delegate: OptionViewDelegate, isRecommended: Bool = false) {
|
||||
self.title = title
|
||||
self.explanation = explanation
|
||||
self.delegate = delegate
|
||||
self.isRecommended = isRecommended
|
||||
super.init(frame: CGRect.zero)
|
||||
setUpViewHierarchy()
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
preconditionFailure("Use init(string:explanation:) instead.")
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
preconditionFailure("Use init(string:explanation:) instead.")
|
||||
}
|
||||
|
||||
private func setUpViewHierarchy() {
|
||||
backgroundColor = Colors.pnOptionBackground
|
||||
// Round corners
|
||||
layer.cornerRadius = Values.pnOptionCornerRadius
|
||||
// Set up border
|
||||
layer.borderWidth = Values.borderThickness
|
||||
layer.borderColor = Colors.pnOptionBorder.cgColor
|
||||
// Set up shadow
|
||||
layer.shadowColor = UIColor.black.cgColor
|
||||
layer.shadowOffset = CGSize(width: 0, height: 0.8)
|
||||
layer.shadowOpacity = isLightMode ? 0.4 : 1
|
||||
layer.shadowRadius = isLightMode ? 4 : 6
|
||||
// Set up title label
|
||||
let titleLabel = UILabel()
|
||||
titleLabel.textColor = Colors.text
|
||||
titleLabel.font = .boldSystemFont(ofSize: Values.mediumFontSize)
|
||||
titleLabel.text = title
|
||||
titleLabel.numberOfLines = 0
|
||||
titleLabel.lineBreakMode = .byWordWrapping
|
||||
// Set up explanation label
|
||||
let explanationLabel = UILabel()
|
||||
explanationLabel.textColor = Colors.text
|
||||
explanationLabel.font = .systemFont(ofSize: Values.verySmallFontSize)
|
||||
explanationLabel.text = explanation
|
||||
explanationLabel.numberOfLines = 0
|
||||
explanationLabel.lineBreakMode = .byWordWrapping
|
||||
// Set up stack view
|
||||
let stackView = UIStackView(arrangedSubviews: [ titleLabel, explanationLabel ])
|
||||
stackView.axis = .vertical
|
||||
stackView.spacing = 4
|
||||
stackView.alignment = .fill
|
||||
addSubview(stackView)
|
||||
stackView.pin(.leading, to: .leading, of: self, withInset: 12)
|
||||
stackView.pin(.top, to: .top, of: self, withInset: 12)
|
||||
self.pin(.trailing, to: .trailing, of: stackView, withInset: 12)
|
||||
self.pin(.bottom, to: .bottom, of: stackView, withInset: 12)
|
||||
// Set up recommended label if needed
|
||||
if isRecommended {
|
||||
let recommendedLabel = UILabel()
|
||||
recommendedLabel.textColor = Colors.accent
|
||||
recommendedLabel.font = .boldSystemFont(ofSize: Values.verySmallFontSize)
|
||||
recommendedLabel.text = NSLocalizedString("Recommended", comment: "")
|
||||
stackView.addArrangedSubview(recommendedLabel)
|
||||
}
|
||||
// Set up tap gesture recognizer
|
||||
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(handleTap))
|
||||
addGestureRecognizer(tapGestureRecognizer)
|
||||
}
|
||||
|
||||
@objc private func handleTap() {
|
||||
isSelected = !isSelected
|
||||
}
|
||||
|
||||
private func handleIsSelectedChanged() {
|
||||
let animationDuration: TimeInterval = 0.25
|
||||
// Animate border color
|
||||
let newBorderColor = isSelected ? Colors.accent.cgColor : Colors.pnOptionBorder.cgColor
|
||||
let borderAnimation = CABasicAnimation(keyPath: "borderColor")
|
||||
borderAnimation.fromValue = layer.shadowColor
|
||||
borderAnimation.toValue = newBorderColor
|
||||
borderAnimation.duration = animationDuration
|
||||
layer.add(borderAnimation, forKey: borderAnimation.keyPath)
|
||||
layer.borderColor = newBorderColor
|
||||
// Animate shadow color
|
||||
let newShadowColor = isSelected ? Colors.newConversationButtonShadow.cgColor : UIColor.black.cgColor
|
||||
let shadowAnimation = CABasicAnimation(keyPath: "shadowColor")
|
||||
shadowAnimation.fromValue = layer.shadowColor
|
||||
shadowAnimation.toValue = newShadowColor
|
||||
shadowAnimation.duration = animationDuration
|
||||
layer.add(shadowAnimation, forKey: shadowAnimation.keyPath)
|
||||
layer.shadowColor = newShadowColor
|
||||
// Notify delegate
|
||||
if isSelected { delegate.optionViewDidActivate(self) }
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Option View Delegate
|
||||
private protocol OptionViewDelegate {
|
||||
|
||||
func optionViewDidActivate(_ optionView: PNModeVC.OptionView)
|
||||
}
|
||||
|
|
|
@ -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.";
|
||||
|
|
|
@ -297,17 +297,15 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa
|
|||
remainingRetries:(int)remainingRetries
|
||||
{
|
||||
BOOL isUsingFullAPNs = [NSUserDefaults.standardUserDefaults boolForKey:@"isUsingFullAPNs"];
|
||||
if (isUsingFullAPNs) {
|
||||
[LKPushNotificationManager registerWithToken:pushToken hexEncodedPublicKey:self.localNumber]
|
||||
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
|
||||
[self registerForPushNotificationsWithPushToken:pushToken voipToken:voipToken success:successHandler failure:failureHandler
|
||||
remainingRetries:remainingRetries - 1];
|
||||
} else {
|
||||
if (!IsNSErrorNetworkFailure(error)) {
|
||||
|
@ -317,7 +315,6 @@ NSString *const TSAccountManager_NeedsAccountAttributesUpdateKey = @"TSAccountMa
|
|||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
- (void)registerWithPhoneNumber:(NSString *)phoneNumber
|
||||
captchaToken:(nullable NSString *)captchaToken
|
||||
|
|
|
@ -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<Void> {
|
||||
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<Void> { $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<Void> { $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))
|
||||
|
|
|
@ -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
|
||||
|
|
Loading…
Reference in a new issue