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

512 lines
22 KiB
Swift

//
// SignupViewController.swift
// Tunnels
//
// Copyright © 2019 Confirmed Inc. All rights reserved.
//
import UIKit
import SwiftyStoreKit
import NetworkExtension
import PromiseKit
import CocoaLumberjackSwift
import StoreKit
final class OldSignupViewController: BaseViewController, Loadable {
// MARK: - VARIABLES
var parentVC: UIViewController?
enum Mode {
case newSubscription
case upgrade(active: [Subscription.PlanType])
}
var mode = Mode.newSubscription
@IBOutlet var monthlyPlanContainer: UIView!
@IBOutlet var monthlyPlanCheckbox: M13Checkbox!
@IBOutlet var monthlyProPlanContainer: UIView!
@IBOutlet var monthlyProPlanCheckbox: M13Checkbox!
@IBOutlet var annualPlanContainer: UIView!
@IBOutlet var annualPlanCheckbox: M13Checkbox!
@IBOutlet var annualProPlanContainer: UIView!
@IBOutlet var annualProPlanCheckbox: M13Checkbox!
@IBOutlet var upgradeSubscriptionHintLabel: UILabel!
@IBOutlet var startTrialButton: TKTransitionSubmitButton!
@IBOutlet var pricingSubtitle: UILabel!
@IBOutlet var restorePurchasesButton: TKTransitionSubmitButton!
override func viewDidLoad() {
super.viewDidLoad()
VPNSubscription.cacheLocalizedPrices()
switch mode {
case .newSubscription:
selectAnnual()
upgradeSubscriptionHintLabel.isHidden = true
case .upgrade(active: let activeSubscriptions):
configureWithActiveSubscriptions(activeSubscriptions)
restorePurchasesButton.isHidden = true
}
}
var disabledCheckboxes: Set<M13Checkbox> = []
@objc func selectMonthly() {
animatedSetChecked(monthlyPlanCheckbox, .checked)
animatedSetChecked(monthlyProPlanCheckbox, .unchecked)
animatedSetChecked(annualPlanCheckbox, .unchecked)
animatedSetChecked(annualProPlanCheckbox, .unchecked)
VPNSubscription.selectedProductId = VPNSubscription.productIdMonthly
updatePricingSubtitle()
}
@objc func selectMonthlyPro() {
animatedSetChecked(monthlyPlanCheckbox, .unchecked)
animatedSetChecked(monthlyProPlanCheckbox, .checked)
animatedSetChecked(annualPlanCheckbox, .unchecked)
animatedSetChecked(annualProPlanCheckbox, .unchecked)
VPNSubscription.selectedProductId = VPNSubscription.productIdMonthlyPro
updatePricingSubtitle()
}
@IBAction func monthlyTapped(_ sender: Any) {
selectMonthly()
}
@IBAction func monthlyProTapped(_ sender: Any) {
selectMonthlyPro()
}
@objc func selectAnnual() {
animatedSetChecked(monthlyPlanCheckbox, .unchecked)
animatedSetChecked(monthlyProPlanCheckbox, .unchecked)
animatedSetChecked(annualPlanCheckbox, .checked)
animatedSetChecked(annualProPlanCheckbox, .unchecked)
VPNSubscription.selectedProductId = VPNSubscription.productIdAnnual
updatePricingSubtitle()
}
@IBAction func annualTapped(_ sender: Any) {
selectAnnual()
}
@objc func selectAnnualPro() {
animatedSetChecked(monthlyPlanCheckbox, .unchecked)
animatedSetChecked(monthlyProPlanCheckbox, .unchecked)
animatedSetChecked(annualPlanCheckbox, .unchecked)
animatedSetChecked(annualProPlanCheckbox, .checked)
VPNSubscription.selectedProductId = VPNSubscription.productIdAnnualPro
updatePricingSubtitle()
}
@IBAction func annualProTapped(_ sender: Any) {
selectAnnualPro()
}
@objc func updatePricingSubtitle() {
let context: VPNSubscription.SubscriptionContext = {
switch mode {
case .newSubscription:
return .new
case .upgrade:
return .upgrade
}
}()
if monthlyPlanCheckbox.checkState == .checked, monthlyPlanCheckbox.isEnabled {
pricingSubtitle.text = VPNSubscription.getProductIdPrice(productId: VPNSubscription.productIdMonthly, for: context)
} else if annualPlanCheckbox.checkState == .checked, annualPlanCheckbox.isEnabled {
pricingSubtitle.text = VPNSubscription.getProductIdPrice(productId: VPNSubscription.productIdAnnual, for: context)
} else if annualProPlanCheckbox.checkState == .checked, annualProPlanCheckbox.isEnabled {
pricingSubtitle.text = VPNSubscription.getProductIdPrice(productId: VPNSubscription.productIdAnnualPro, for: context)
} else if monthlyProPlanCheckbox.checkState == .checked, monthlyProPlanCheckbox.isEnabled {
pricingSubtitle.text = VPNSubscription.getProductIdPrice(productId: VPNSubscription.productIdMonthlyPro, for: context)
}
}
@IBAction func dismissSignUpScreen() {
self.view.endEditing(true)
self.dismiss(animated: true, completion: {})
}
func toggleStartTrialButton(_ enabled: Bool) {
if enabled {
UIView.transition(with: self.pricingSubtitle,
duration: 0.15,
options: .transitionCrossDissolve,
animations: {
self.pricingSubtitle.alpha = 1.0
})
startTrialButton.isUserInteractionEnabled = true
startTrialButton.setOriginalState()
startTrialButton.layer.cornerRadius = 4
unblockUserInteraction()
} else {
UIView.transition(with: self.pricingSubtitle,
duration: 0.15,
options: .transitionCrossDissolve,
animations: {
self.pricingSubtitle.alpha = 0.0
})
startTrialButton.isUserInteractionEnabled = false
startTrialButton.startLoadingAnimation()
blockUserInteraction()
}
}
@IBAction func startTrial (_ sender: UIButton) {
toggleStartTrialButton(false)
VPNSubscription.purchase(
succeeded: {
self.dismiss(animated: true, completion: {
if let presentingViewController = self.parentVC as? AccountViewController {
presentingViewController.reloadTable()
}
// force refresh receipt, and sync with email if it exists, activate VPNte
if let apiCredentials = getAPICredentials(), getAPICredentialsConfirmed() == true {
DDLogInfo("purchase complete: syncing with confirmed email")
firstly {
try Client.signInWithEmail(email: apiCredentials.email, password: apiCredentials.password)
}
.then { (signin: SignIn) -> Promise<SubscriptionEvent> in
DDLogInfo("purchase complete: signin result: \(signin)")
return try Client.subscriptionEvent(forceRefresh: true)
}
.then { (result: SubscriptionEvent) -> Promise<GetKey> in
DDLogInfo("purchase complete: subscriptionevent result: \(result)")
return try Client.getKey()
}
.done { (getKey: GetKey) in
try setVPNCredentials(id: getKey.id, keyBase64: getKey.b64)
DDLogInfo("purchase complete: setting VPN creds with ID: \(getKey.id)")
VPNController.shared.setEnabled(true)
}
.catch { error in
DDLogError("purchase complete: Error: \(error)")
if self.popupErrorAsNSURLError("Error activating Secure Tunnel: \(error)") {
return
} else if let apiError = error as? ApiError {
switch apiError.code {
default:
_ = self.popupErrorAsApiError("API Error activating Secure Tunnel: \(error)")
}
}
}
} else {
firstly {
try Client.signIn(forceRefresh: true) // this will fetch and set latest receipt, then submit to API to get cookie
}
.then { _ in
// TODO: don't always do this -- if we already have a key, then only do it once per day max
try Client.getKey()
}
.done { (getKey: GetKey) in
try setVPNCredentials(id: getKey.id, keyBase64: getKey.b64)
VPNController.shared.setEnabled(true)
}
.catch { error in
DDLogError("purchase complete - no email: Error: \(error)")
if self.popupErrorAsNSURLError("Error activating Secure Tunnel: \(error)") {
return
} else if let apiError = error as? ApiError {
switch apiError.code {
default:
_ = self.popupErrorAsApiError("API Error activating Secure Tunnel: \(error)")
}
}
}
}
})
},
errored: { error in
self.toggleStartTrialButton(true)
DDLogError("Start Trial Failed: \(error)")
if let skError = error as? SKError {
var errorText = ""
switch skError.code {
case .unknown:
errorText = .localized("Unknown error. Please contact support at team@lockdownprivacy.com.")
case .clientInvalid:
errorText = .localized("Not allowed to make the payment")
case .paymentCancelled:
errorText = .localized("Payment was cancelled")
case .paymentInvalid:
errorText = .localized("The purchase identifier was invalid")
case .paymentNotAllowed:
errorText = .localized("""
Payment not allowed.\nEither this device is not allowed to make purchases, or In-App Purchases have been disabled. \
Please allow them in Settings App -> Screen Time -> Restrictions -> App Store -> In-app Purchases. Then try again.
""")
case .storeProductNotAvailable:
errorText = .localized("The product is not available in the current storefront")
case .cloudServicePermissionDenied:
errorText = .localized("Access to cloud service information is not allowed")
case .cloudServiceNetworkConnectionFailed:
errorText = .localized("Could not connect to the network")
case .cloudServiceRevoked:
errorText = .localized("User has revoked permission to use this cloud service")
default:
errorText = skError.localizedDescription
}
self.showPopupDialog(title: .localized("Error Starting Trial"), message: errorText, acceptButton: .localizedOkay)
} else if self.popupErrorAsNSURLError(error) {
return
} else if self.popupErrorAsApiError(error) {
return
} else {
self.showPopupDialog(
title: .localized("Error Starting Trial"),
message: .localized("Please contact team@lockdownprivacy.com.\n\nError details:\n") + "\(error)",
acceptButton: .localizedOkay)
}
})
}
func toggleRestorePurchasesButton(_ enabled: Bool) {
if enabled {
restorePurchasesButton.isUserInteractionEnabled = true
restorePurchasesButton.setOriginalState()
restorePurchasesButton.layer.cornerRadius = 4
unblockUserInteraction()
} else {
restorePurchasesButton.isUserInteractionEnabled = false
restorePurchasesButton.startLoadingAnimation()
blockUserInteraction()
}
}
@IBAction func restorePurchases(_ sender: Any) {
//toggleRestorePurchasesButton(false)
firstly {
try Client.signIn(forceRefresh: true)
}
.then { _ in
try Client.getKey()
}
.done { _ in
// we were able to get key, so subscription is valid -- follow pathway from HomeViewController to associate this with the email account if there is one
let presentingViewController = self.presentingViewController as? HomeViewController
self.dismiss(animated: true, completion: {
if presentingViewController != nil {
presentingViewController?.toggleVPN("me")
} else {
VPNController.shared.setEnabled(true)
}
})
}
.catch { error in
self.toggleRestorePurchasesButton(true)
DDLogError("Restore Failed: \(error)")
if let apiError = error as? ApiError {
switch apiError.code {
case kApiCodeNoSubscriptionInReceipt, kApiCodeNoActiveSubscription:
// now try email if it exists
if let apiCredentials = getAPICredentials(), getAPICredentialsConfirmed() == true {
DDLogInfo("restore: have confirmed API credentials, using them")
self.toggleRestorePurchasesButton(false)
firstly {
try Client.signInWithEmail(email: apiCredentials.email, password: apiCredentials.password)
}
.then { (signin: SignIn) -> Promise<GetKey> in
DDLogInfo("restore: signin result: \(signin)")
return try Client.getKey()
}
.done { (getKey: GetKey) in
self.toggleRestorePurchasesButton(true)
try setVPNCredentials(id: getKey.id, keyBase64: getKey.b64)
DDLogInfo("restore: setting VPN creds with ID and Dismissing: \(getKey.id)")
let presentingViewController = self.presentingViewController as? HomeViewController
self.dismiss(animated: true, completion: {
if presentingViewController != nil {
presentingViewController?.toggleVPN("me")
} else {
VPNController.shared.setEnabled(true)
}
})
}
.catch { error in
self.toggleRestorePurchasesButton(true)
DDLogError("restore: Error doing restore with email-login: \(error)")
if self.popupErrorAsNSURLError(error) {
return
} else if let apiError = error as? ApiError {
switch apiError.code {
case kApiCodeNoSubscriptionInReceipt, kApiCodeNoActiveSubscription:
self.showPopupDialog(title: .localized("No Active Subscription"),
message: .localized("""
Please ensure that you have an active subscription. If you're attempting to share a subscription from the same account, \
you'll need to sign in with the same email address. Otherwise, start your free trial or e-mail team@lockdownprivacy.com
"""),
acceptButton: .localizedOK)
default:
_ = self.popupErrorAsApiError(error)
}
}
}
} else {
let message = """
Please ensure that you have an active subscription. If you're attempting to share a subscription from the same account, \
you'll need to sign in with the same email address. Otherwise, start your free trial or e-mail team@lockdownprivacy.com
"""
self.showPopupDialog(title: .localized("No Active Subscription"),
message: .localized(message),
acceptButton: .localizedOK)
}
default:
let pleaseEmail: String = .localized("Please email team@lockdownprivacy.com with the following Error Code ")
self.showPopupDialog(
title: .localized("Error Restoring Subscription"),
message: pleaseEmail + "\(apiError.code) : \(apiError.message)",
acceptButton: .localizedOK)
}
} else {
let message: String = .localized("""
Please make sure your Internet connection is active. If this error persists, email team@lockdownprivacy.com with
the following error message: \
""")
self.showPopupDialog(title: .localized("Error Restoring Subscription"), message: message + "\(error)", acceptButton: .localizedOK)
}
}
}
@IBAction func openPrivacyPolicy (_ sender: Any) {
self.showPrivacyPolicyModal()
}
@IBAction func openTermsAndConditions (_ sender: Any) {
self.showTermsModal()
}
@IBAction func cancelButtonPressed (_ sender: Any) {
self.dismiss(animated: true, completion: nil)
}
override func touchesBegan(_ touches: Set<UITouch>, with event: UIEvent?) {
self.view.endEditing(true)
}
}
extension OldSignupViewController {
// MARK: - Functions for "Upgrade Plan" mode
private func configureWithActiveSubscriptions(_ activePlans: [Subscription.PlanType]) {
startTrialButton.setTitle(.localized("Upgrade Plan"), for: .normal)
for plan in activePlans {
configureForUnavailable(plan, reason: .purchased)
if let unavailableToUpgrade = plan.unavailableToUpgrade {
for unavailable in unavailableToUpgrade where unavailable != plan {
configureForUnavailable(unavailable, reason: .lowerTierThanPurchased)
}
}
}
selectAnnualOrFirstAvailable()
}
enum UnavailableReason {
case purchased
case lowerTierThanPurchased
}
private func configureForUnavailable(_ subscriptionPlanType: Subscription.PlanType, reason: UnavailableReason) {
switch subscriptionPlanType {
case .monthly:
markAsUnavailable(monthlyPlanCheckbox, plan: subscriptionPlanType, reason: reason, container: monthlyPlanContainer)
case .proMonthly:
markAsUnavailable(monthlyProPlanCheckbox, plan: subscriptionPlanType, reason: reason, container: monthlyProPlanContainer)
case .annual:
markAsUnavailable(annualPlanCheckbox, plan: subscriptionPlanType, reason: reason, container: annualPlanContainer)
case .proAnnual:
markAsUnavailable(annualProPlanCheckbox, plan: subscriptionPlanType, reason: reason, container: annualProPlanContainer)
default:
break
}
}
private func markAsUnavailable(_ checkbox: M13Checkbox, plan: Subscription.PlanType, reason: UnavailableReason, container: UIView) {
disabledCheckboxes.insert(checkbox)
checkbox.isEnabled = false
switch reason {
case .lowerTierThanPurchased:
container.isHidden = true
case .purchased:
container.alpha = 0.4
checkbox.setCheckState(.checked, animated: false)
checkbox.tintColor = UIColor.gray
checkbox.secondaryTintColor = UIColor.gray
checkbox.isEnabled = false
checkbox.accessibilityLabel = Accessibility.currentPlanCheckboxLabel(for: plan)
checkbox.accessibilityHint = Accessibility.currentPlanCheckboxHint()
}
}
private func animatedSetChecked(_ checkbox: M13Checkbox, _ state: M13Checkbox.CheckState) {
if disabledCheckboxes.contains(checkbox) {
return
} else {
checkbox.setCheckState(state, animated: true)
}
}
func selectAnnualOrFirstAvailable() {
if disabledCheckboxes.contains(annualPlanCheckbox) {
// Annual is unavailable, find first available option to select
if monthlyPlanCheckbox.isEnabled {
selectMonthly()
} else if annualPlanCheckbox.isEnabled {
selectAnnual()
} else if monthlyProPlanCheckbox.isEnabled {
selectMonthlyPro()
} else if annualProPlanCheckbox.isEnabled {
selectAnnualPro()
} else {
selectAnnualPro()
startTrialButton.isEnabled = false
startTrialButton.backgroundColor = UIColor.gray
startTrialButton.isUserInteractionEnabled = false
startTrialButton.alpha = 0.5
pricingSubtitle.alpha = 0.3
}
} else {
selectAnnual()
}
}
}
extension OldSignupViewController {
enum Accessibility {
static func currentPlanCheckboxLabel(for plan: Subscription.PlanType) -> String? {
switch plan {
case .monthly:
return .localized("\"iOS Monthly\" is your current plan")
case .proMonthly:
return .localized("\"Pro Monthly\" is your current plan")
case .annual:
return .localized("\"iOS Annual\" is your current plan")
case .proAnnual:
return .localized("\"Pro Annual\" is your current plan")
default:
return "\"\(plan.rawValue)\" is your current plan"
}
}
static func currentPlanCheckboxHint() -> String? {
return nil
}
}
}