// // 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 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 = [] @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: { let presentingViewController = self.presentingViewController as? HomeViewController self.dismiss(animated: true, completion: { if self.enableVPNAfterSubscribe { if presentingViewController != nil { presentingViewController?.toggleVPN("me") } else { 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@lockdownhq.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 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@lockdownhq.com", comment: ""), acceptButton: NSLocalizedString("OK", comment: "")) default: self.showPopupDialog(title: NSLocalizedString("Error Restoring Subscription", comment: ""), message: NSLocalizedString("Please email team@lockdownhq.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@lockdownhq.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, 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 } } }