mirror of
https://github.com/confirmedcode/Lockdown-iOS.git
synced 2025-12-21 12:14:02 +01:00
- VERSION: Bump build 4 - Remove MallocStackLogging on default build scheme - AccountVC - always reload on reloadTable, disregard isViewLoaded - AccountVC - call new showUpgradePlanAccount segue - ENHANCEMENT: "Cannot load your plan" -> Tappable "Error Loading Plan: Retry" - FIX: Storyboard add segue from Account VC to SignUpVC - Always enableVPNAfterSubscribe - SignupViewController - call reloadTable if parentVC was AccountVC
387 lines
15 KiB
Swift
387 lines
15 KiB
Swift
//
|
|
// SignupViewController.swift
|
|
// Tunnels
|
|
//
|
|
// Copyright © 2019 Confirmed Inc. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
import SwiftyStoreKit
|
|
import NetworkExtension
|
|
import PromiseKit
|
|
import CocoaLumberjackSwift
|
|
|
|
class SignupViewController: BaseViewController {
|
|
|
|
//MARK: - VARIABLES
|
|
|
|
var parentVC: UIViewController?
|
|
|
|
enum Mode {
|
|
case newSubscription
|
|
case upgrade(active: [Subscription.PlanType])
|
|
}
|
|
|
|
var enableVPNAfterSubscribe = true
|
|
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()
|
|
}
|
|
if self.enableVPNAfterSubscribe {
|
|
VPNController.shared.setEnabled(true)
|
|
}
|
|
})
|
|
},
|
|
errored: { error in
|
|
self.toggleStartTrialButton(true)
|
|
DDLogError("Start Trial Failed: \(error)")
|
|
|
|
if (self.popupErrorAsNSURLError(error)) {
|
|
return
|
|
}
|
|
else if (self.popupErrorAsApiError(error)) {
|
|
return
|
|
}
|
|
else {
|
|
self.showPopupDialog(title: NSLocalizedString("Error Starting Trial", comment: ""),
|
|
message: NSLocalizedString("Please contact team@lockdownprivacy.com.\n\nError details:\n", comment: "") + "\(error)",
|
|
acceptButton: "Okay")
|
|
}
|
|
})
|
|
}
|
|
|
|
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 { (signin: SignIn) -> Promise<GetKey> in
|
|
try Client.getKey()
|
|
}
|
|
.done { (getKey: GetKey) 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:
|
|
self.showPopupDialog(title: NSLocalizedString("No Active Subscription", comment: ""),
|
|
message: NSLocalizedString("Please make sure your Internet connection is active and that you have an active subscription. Otherwise, please start your free trial or e-mail team@lockdownprivacy.com", comment: ""),
|
|
acceptButton: NSLocalizedString("OK", comment: ""))
|
|
default:
|
|
self.showPopupDialog(title: NSLocalizedString("Error Restoring Subscription", comment: ""),
|
|
message: NSLocalizedString("Please email team@lockdownprivacy.com with the following Error Code ", comment: "") + "\(apiError.code) : \(apiError.message)",
|
|
acceptButton: NSLocalizedString("OK", comment: ""))
|
|
}
|
|
}
|
|
else {
|
|
self.showPopupDialog(title: NSLocalizedString("Error Restoring Subscription", comment: ""),
|
|
message: NSLocalizedString("Please make sure your Internet connection is active. If this error persists, email team@lockdownprivacy.com with the following error message: ", comment: "") + "\(error)",
|
|
acceptButton: NSLocalizedString("OK", comment: ""))
|
|
}
|
|
}
|
|
}
|
|
|
|
@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 SignupViewController {
|
|
|
|
// MARK: - Functions for "Upgrade Plan" mode
|
|
|
|
private func configureWithActiveSubscriptions(_ activePlans: [Subscription.PlanType]) {
|
|
startTrialButton.setTitle("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 SignupViewController {
|
|
enum Accessibility {
|
|
static func currentPlanCheckboxLabel(for plan: Subscription.PlanType) -> String? {
|
|
switch plan {
|
|
case .monthly:
|
|
return NSLocalizedString("\"iOS Monthly\" is your current plan", comment: "")
|
|
case .proMonthly:
|
|
return NSLocalizedString("\"Pro Monthly\" is your current plan", comment: "")
|
|
case .annual:
|
|
return NSLocalizedString("\"iOS Annual\" is your current plan", comment: "")
|
|
case .proAnnual:
|
|
return NSLocalizedString("\"Pro Annual\" is your current plan", comment: "")
|
|
default:
|
|
return "\"\(plan.rawValue)\" is your current plan"
|
|
}
|
|
}
|
|
|
|
static func currentPlanCheckboxHint() -> String? {
|
|
return nil
|
|
}
|
|
}
|
|
}
|