Files
lockdown-iOS-mirror/LockdowniOS/AccountVC.swift
Alexander Parshakov 2bc6adf847 Release 1.6.1
2023-01-10 21:17:38 +05:00

476 lines
21 KiB
Swift

//
// AccountVC.swift
// Lockdown
//
// Created by Oleg Dreyman on 02.10.2020.
// Copyright © 2020 Confirmed Inc. All rights reserved.
//
import UIKit
import PopupDialog
import PromiseKit
import CocoaLumberjackSwift
final class AccountViewController: BaseViewController, Loadable {
let tableView = StaticTableView(frame: .zero, style: .plain)
var activePlans: [Subscription.PlanType] = []
override func viewDidLoad() {
super.viewDidLoad()
do {
view.addSubview(tableView)
tableView.anchors.edges.pin()
tableView.separatorStyle = .singleLine
tableView.cellLayoutMarginsFollowReadableWidth = true
tableView.showsVerticalScrollIndicator = false
tableView.deselectsCellsAutomatically = true
tableView.contentInset.top += 12
tableView.tableFooterView = UIView()
tableView.clear()
createTable()
}
do {
NotificationCenter.default.addObserver(self, selector: #selector(accountStateDidChange), name: AccountUI.accountStateDidChange, object: nil)
}
}
@objc private func accountStateDidChange() {
DispatchQueue.main.async {
self.reloadTable()
}
}
func reloadTable() {
tableView.clear()
createTable()
tableView.reloadData()
}
func createTable() {
// Remove top separator
tableView.tableHeaderView = UIView()
var title: String = .localized("⚠️ Not Signed In")
var message: String? = .localized("Sign up below to unlock benefits of a Lockdown account.")
var firstButton = MakeDefaultCell(title: .localized("Sign Up | Sign In")) {
// AccountViewController will update itself by observing
// AccountUI.accountStateDidChange notification
let signUpViewController = SignUpViewController(mode: .signUp)
let idiom = UIScreen.main.traitCollection.userInterfaceIdiom
signUpViewController.modalPresentationStyle = idiom == .pad ? .pageSheet : .fullScreen
self.present(signUpViewController, animated: true)
}
firstButton.backgroundView = UIView()
firstButton.backgroundView?.backgroundColor = .tunnelsBlue
firstButton.label.textColor = UIColor.white
if let apiCredentials = getAPICredentials() {
message = apiCredentials.email
if getAPICredentialsConfirmed() == true {
title = .localized("Signed In")
firstButton = MakeDefaultCell(title: .localizedSignOut) {
let confirm = PopupDialog(title: .localized("Sign Out?"),
message: .localized("You'll be signed out from this account."),
image: nil,
buttonAlignment: .horizontal,
transitionStyle: .bounceDown,
preferredWidth: 270,
tapGestureDismissal: true,
panGestureDismissal: false,
hideStatusBar: false,
completion: nil)
confirm.addButtons([
DefaultButton(title: .localizedCancel, dismissOnTap: true) {},
DefaultButton(title: .localizedSignOut, dismissOnTap: true) { [weak self] in
guard let self = self else { return }
URLCache.shared.removeAllCachedResponses()
Client.clearCookies()
clearAPICredentials()
setAPICredentialsConfirmed(confirmed: false)
self.reloadTable()
self.showPopupDialog(
title: .localized("Success"),
message: .localized("Signed out successfully."),
acceptButton: .localizedOkay)
},
])
self.present(confirm, animated: true, completion: nil)
}
firstButton.backgroundView?.backgroundColor = UIColor.clear
firstButton.label.textColor = UIColor.systemRed
} else {
title = "⚠️ Email Not Confirmed"
firstButton = MakeDefaultCell(title: .localized("Confirm Email")) {
self.showLoadingView()
firstly {
try Client.signInWithEmail(email: apiCredentials.email, password: apiCredentials.password)
}
.done { _ in
self.hideLoadingView()
// successfully signed in with no errors, show confirmation success
setAPICredentialsConfirmed(confirmed: true)
// logged in and confirmed - update this email with the receipt and refresh VPN credentials
firstly { () -> Promise<SubscriptionEvent> in
try Client.subscriptionEvent()
}
.then { _ -> Promise<GetKey> in
try Client.getKey()
}
.done { (getKey: GetKey) in
try setVPNCredentials(id: getKey.id, keyBase64: getKey.b64)
if getUserWantsVPNEnabled() {
VPNController.shared.restart()
}
}
.catch { error in
// it's okay for this to error out with "no subscription in receipt"
DDLogError("""
HomeViewController ConfirmEmail subscriptionevent error \
(ok for it to be \"no subscription in receipt\"): \(error)
""")
}
let message = """
Your account has been confirmed and you're now signed in. You'll get the latest \
block lists, access to Lockdown Mac, and get critical announcements.
"""
let popup = PopupDialog(title: .localized("Success! 🎉"),
message: .localized(message),
image: nil,
buttonAlignment: .horizontal,
transitionStyle: .bounceDown,
preferredWidth: 270,
tapGestureDismissal: true,
panGestureDismissal: false,
hideStatusBar: false,
completion: nil)
popup.addButtons([
DefaultButton(title: .localizedOkay, dismissOnTap: true) {
self.reloadTable()
}
])
self.present(popup, animated: true, completion: nil)
}
.catch { error in
self.hideLoadingView()
let clickConfirmation: String = .localized(
"To complete your signup, click the confirmation link we sent to",
comment: "Used in To complete your signup, click the confirmation link we sent to you@gmail.com")
let checkSpam: String = .localized("""
Be sure to check your spam folder in case it got stuck there.
You can also request a re-send of the confirmation.
""")
let popup = PopupDialog(title: .localized("Check Your Inbox"),
message: "\(clickConfirmation) \(apiCredentials.email). \(checkSpam)",
image: nil,
buttonAlignment: .vertical,
transitionStyle: .bounceDown,
preferredWidth: 270,
tapGestureDismissal: true,
panGestureDismissal: false,
hideStatusBar: false,
completion: nil)
popup.addButtons([
DefaultButton(title: .localizedOkay, dismissOnTap: true) {},
DefaultButton(title: .localizedSignOut, dismissOnTap: true) {
URLCache.shared.removeAllCachedResponses()
Client.clearCookies()
clearAPICredentials()
setAPICredentialsConfirmed(confirmed: false)
self.reloadTable()
self.showPopupDialog(
title: .localized("Success"),
message: .localized("Signed out successfully."),
acceptButton: .localizedOkay)
},
DefaultButton(title: .localized("Re-send"), dismissOnTap: true) {
firstly {
try Client.resendConfirmCode(email: apiCredentials.email)
}
.done { (success: Bool) in
var message: String = .localized("Successfully re-sent your email confirmation to ") + apiCredentials.email
if !success {
message = .localized("Failed to re-send email confirmation.")
}
self.showPopupDialog(title: "", message: message, acceptButton: .localizedOkay)
}
.catch { error in
if self.popupErrorAsNSURLError(error) {
return
} else if let apiError = error as? ApiError {
_ = self.popupErrorAsApiError(apiError)
} else {
self.showPopupDialog(title: .localized("Error Re-sending Email Confirmation"),
message: "\(error)",
acceptButton: .localizedOkay)
}
}
},
])
self.present(popup, animated: true, completion: nil)
}
}
}
}
let upgradeButton = MakeDefaultButtonCell(title: .localized("View or Upgrade Plan")) {
BasePaywallService.shared.showPaywall(on: self)
}
upgradeButton.button.isEnabled = true
upgradeButton.selectionStyle = .default
upgradeButton.backgroundView?.backgroundColor = UIColor.tunnelsDarkBlue
upgradeButton.button.setTitleColor(UIColor.white, for: UIControl.State())
if let currentSubscription = BaseUserService.shared.user.currentSubscription,
[.proAnnual, .proAnnualLTO].contains(currentSubscription.planType) {
upgradeButton.button.isEnabled = false
upgradeButton.selectionStyle = .none
upgradeButton.button.setTitle(.localized("Plan: Annual Pro"), for: UIControl.State())
}
let notificationsButton = MakeDefaultCell(title: "", action: { })
let updateNotificationButtonTitle = { (cell: DefaultCell) in
if PushNotifications.Authorization.getUserWantsNotificationsEnabled(forCategory: .weeklyUpdate) {
cell.label.text = .localized("Notifications: On")
} else {
cell.label.text = .localized("Notifications: Off")
}
}
updateNotificationButtonTitle(notificationsButton)
notificationsButton.onSelect { [unowned notificationsButton, unowned self] in
if PushNotifications.Authorization.getUserWantsNotificationsEnabled(forCategory: .weeklyUpdate) {
PushNotifications.Authorization.setUserWantsNotificationsEnabled(false, forCategory: .weeklyUpdate)
updateNotificationButtonTitle(notificationsButton)
} else {
UNUserNotificationCenter.current().getNotificationSettings { (settings) in
DispatchQueue.main.async {
switch settings.authorizationStatus {
case .denied, .notDetermined:
let enableNotificationsViewController = EnableNotificationsViewController {
updateNotificationButtonTitle(notificationsButton)
}
enableNotificationsViewController.modalPresentationStyle = .fullScreen
self.present(enableNotificationsViewController, animated: true)
case .authorized:
PushNotifications.Authorization.setUserWantsNotificationsEnabled(true, forCategory: .weeklyUpdate)
updateNotificationButtonTitle(notificationsButton)
default:
break
}
}
}
}
}
tableView.addRow { (contentView) in
let stack = UIStackView()
stack.axis = .vertical
stack.spacing = 8
contentView.addSubview(stack)
stack.anchors.edges.marginsPin(insets: .init(top: 8, left: 0, bottom: 8, right: 0))
let titleLabel = UILabel()
titleLabel.text = title
titleLabel.font = .boldLockdownFont(size: 18)
titleLabel.textAlignment = .center
titleLabel.numberOfLines = 0
stack.addArrangedSubview(titleLabel)
let messageLabel = UILabel()
messageLabel.text = message
messageLabel.font = .mediumLockdownFont(size: 18)
messageLabel.textAlignment = .center
messageLabel.numberOfLines = 0
stack.addArrangedSubview(messageLabel)
}
let firstButtons: [SelectableTableViewCell] = [
firstButton,
upgradeButton,
notificationsButton,
]
for cell in firstButtons {
tableView.addCell(cell)
}
let otherCells = [
MakeDefaultCell(title: .localized("Tutorial")) { [unowned self] in
self.startTutorial()
},
MakeDefaultCell(title: .localized("Why Trust Lockdown")) {
self.showWhyTrustPopup()
},
MakeDefaultCell(title: .localized("Privacy Policy")) {
self.showPrivacyPolicyModal()
},
MakeDefaultCell(title: .localized("What is VPN?")) {
self.performSegue(withIdentifier: "showWhatIsVPN", sender: self)
},
MakeDefaultCell(title: .localized("Support | Feedback")) {
let message = """
Remember to check our FAQs first, for answers to the most frequently asked questions.
If it's not answered there, we're happy to provide support and take feedback by email.
"""
self.showPopupDialog(
title: nil,
message: .localized(message),
buttons: [
.custom(title: .localized("View FAQs"), completion: {
self.showFAQsModal()
}),
.custom(title: .localized("Email Us"), completion: {
self.composeEmail(.helpOrFeedback, attachments: [.diagnostics])
}),
.cancel()
]
)
},
MakeDefaultCell(title: .localized("FAQs")) {
self.showFAQsModal()
},
MakeDefaultCell(title: .localized("Website")) {
self.showWebsiteModal()
},
]
for cell in otherCells {
tableView.addCell(cell)
}
if let creds = getAPICredentials() {
tableView.addCell(MakeDefaultCell(title: .localized("delete_account"), color: .tunnelsWarning) {
self.deleteAccount(userEmail: creds.email)
})
}
#if DEBUG
let fixVPNConfig = MakeDefaultCell(title: "_Fix Firewall Config", action: {
self.showFixFirewallConnectionDialog {
FirewallController.shared.deleteConfigurationAndAddAgain()
}
})
tableView.addCell(fixVPNConfig)
#endif
tableView.addRowCell { (cell) in
cell.textLabel?.text = Bundle.main.versionString
cell.textLabel?.font = .semiboldLockdownFont(size: 17)
cell.textLabel?.textColor = UIColor.systemGray
cell.textLabel?.textAlignment = .right
// removing the bottom separator
cell.separatorInset = .init(top: 0, left: 0, bottom: 0, right: .greatestFiniteMagnitude)
cell.directionalLayoutMargins = .zero
}
}
func startTutorial() {
if let tabBarController = tabBarController as? MainTabBarController {
tabBarController.selectedViewController = tabBarController.homeViewController
tabBarController.homeViewController?.startTutorial()
}
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
switch segue.identifier {
case "showWhatIsVPN":
if let vc = segue.destination as? WhatIsVpnViewController {
vc.parentVC = (tabBarController as? MainTabBarController)?.homeViewController
}
case "showUpgradePlanAccount":
if let vc = segue.destination as? OldSignupViewController {
vc.parentVC = self
if activePlans.isEmpty {
vc.mode = .newSubscription
} else {
vc.mode = .upgrade(active: activePlans)
}
}
default:
break
}
}
}
extension AccountViewController: EmailComposable {
private func deleteAccount(userEmail: String) {
let deleteAccountViewController = DeleteMyAccountViewController(userEmail: userEmail)
deleteAccountViewController.modalPresentationStyle = .formSheet
present(deleteAccountViewController, animated: true)
}
}
// MARK: - Helpers / Extensions
class DefaultButtonCell: SelectableTableViewCell {
let button = UIButton(type: .system)
}
func MakeDefaultButtonCell(title: String, action: @escaping () -> Void) -> DefaultButtonCell {
let cell = DefaultButtonCell()
cell.backgroundView = UIView()
cell.button.setTitle(title, for: .normal)
cell.button.isUserInteractionEnabled = false
cell.button.titleLabel?.font = .semiboldLockdownFont(size: 17)
cell.button.titleLabel?.adjustsFontSizeToFitWidth = true
cell.button.titleLabel?.lineBreakMode = .byClipping
cell.button.tintColor = .tunnelsBlue
cell.contentView.addSubview(cell.button)
cell.button.anchors.height.equal(21)
cell.button.anchors.edges.marginsPin(insets: .init(top: 8, left: 0, bottom: 8, right: 0))
return cell.onSelect(callback: action)
}
class DefaultCell: SelectableTableViewCell {
let label = UILabel()
}
func MakeDefaultCell(title: String, color: UIColor = .tunnelsBlue, action: @escaping () -> Void) -> DefaultCell {
let cell = DefaultCell()
cell.label.text = title
cell.label.font = .semiboldLockdownFont(size: 17)
cell.label.textColor = color
cell.label.textAlignment = .center
cell.contentView.addSubview(cell.label)
cell.label.anchors.edges.marginsPin(insets: .init(top: 8, left: 0, bottom: 8, right: 0))
return cell.onSelect(callback: action)
}
fileprivate extension DefaultButtonCell {
func startActivityIndicator() {
let activity = UIActivityIndicatorView()
if let label = button.titleLabel {
label.addSubview(activity)
activity.translatesAutoresizingMaskIntoConstraints = false
activity.centerYAnchor.constraint(equalTo: label.centerYAnchor).isActive = true
activity.leadingAnchor.constraint(equalToSystemSpacingAfter: label.trailingAnchor, multiplier: 1).isActive = true
activity.startAnimating()
}
}
func stopActivityIndicator() {
if let label = button.titleLabel {
let indicators = label.subviews.compactMap { $0 as? UIActivityIndicatorView }
for indicator in indicators {
indicator.stopAnimating()
indicator.removeFromSuperview()
}
}
}
}