Files
lockdown-iOS-mirror/LockdowniOS/VPNPaywallViewController.swift
2024-08-15 23:16:08 +03:00

454 lines
17 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
//
// AdvancedWallViewController.swift
// LockdownSandbox
//
// Created by Алишер Ахметжанов on 25.04.2023.
//
import UIKit
import NetworkExtension
import PromiseKit
import CocoaLumberjackSwift
import StoreKit
protocol VPNPaywallViewControllerCloseDelegate: AnyObject {
func didClosePaywall()
}
final class VPNPaywallViewController: BaseViewController, Loadable {
private enum Tab {
case advanced
case anonymous
case universal
}
let shared: UserService = BaseUserService.shared
var parentVC: UIViewController?
enum Mode {
case newSubscription
case upgrade(active: [Subscription.PlanType])
}
var mode = Mode.newSubscription
weak var delegate: VPNPaywallViewControllerCloseDelegate?
var purchaseSuccessful: (()->Void)?
var purchaseFailed: ((Error)->Void)?
private var selectedTab = Tab.advanced {
didSet {
updateTabs()
}
}
private var needScrolToUniversal = false
//MARK: Properties
private var titleName = NSLocalizedString("Lockdown", comment: "")
//MARK: navigation
private lazy var navigationView: ConfiguredNavigationView = {
let view = ConfiguredNavigationView()
view.rightNavButton.setTitle(NSLocalizedString("RESTORE", comment: ""), for: .normal)
view.titleLabel.text = NSLocalizedString(titleName, comment: "")
view.leftNavButton.setTitle(NSLocalizedString("CLOSE", comment: ""), for: .normal)
view.leftNavButton.addTarget(self, action: #selector(closeButtonClicked), for: .touchUpInside)
view.rightNavButton.addTarget(self, action: #selector(restorePurchase), for: .touchUpInside)
return view
}()
//MARK: horizontal scroll menu
private lazy var hScrollView: UIScrollView = {
let view = UIScrollView()
view.isScrollEnabled = true
return view
}()
private lazy var advancedPlan: PlanView = {
let view = PlanView()
view.title.text = "Advanced"
view.isUserInteractionEnabled = true
view.setOnClickListener { [unowned self] in
self.selectedTab = .advanced
}
return view
}()
private lazy var anonymousPlan: PlanView = {
let view = PlanView()
view.title.text = "Anonymous"
view.isUserInteractionEnabled = true
view.setOnClickListener { [unowned self] in
selectedTab = .anonymous
}
return view
}()
private lazy var universalPlan: PlanView = {
let view = PlanView()
view.title.text = "Universal"
view.isUserInteractionEnabled = true
view.setOnClickListener { [unowned self] in
selectedTab = .universal
}
return view
}()
private lazy var plansStack: UIStackView = {
let stack = UIStackView()
stack.axis = .horizontal
stack.addArrangedSubview(advancedPlan)
stack.addArrangedSubview(anonymousPlan)
stack.addArrangedSubview(universalPlan)
stack.alignment = .leading
stack.distribution = .equalSpacing
stack.spacing = 16
return stack
}()
lazy var advancedView: PaywallView = {
let view = PaywallView(model: .advancedDetails())
view.topProduct.setOnClickListener { [unowned self] in
selectAdvancedYearly()
selectYearlyProduct(view, model: .advancedDetails())
}
view.bottomProduct.setOnClickListener { [unowned self] in
selectAdvancedMonthly()
selectMontlyProduct(view, model: .advancedDetails())
}
view.actionButton.setOnClickListener { [unowned self] in
startTrial()
}
return view
}()
lazy var anonymousView: PaywallView = {
let view = PaywallView(model: .anonymousDetails())
view.isHidden = true
view.topProduct.setOnClickListener { [unowned self] in
selectAnonymousYearly()
selectYearlyProduct(view, model: .anonymousDetails())
}
view.bottomProduct.setOnClickListener { [unowned self] in
selectAnonymousMonthly()
selectMontlyProduct(view, model: .anonymousDetails())
}
view.actionButton.setOnClickListener { [unowned self] in
startTrial()
}
return view
}()
lazy var universalView: PaywallView = {
let view = PaywallView(model: .universalDetails())
view.isHidden = true
view.topProduct.setOnClickListener { [unowned self] in
selectUniversalYearly()
selectYearlyProduct(view, model: .universalDetails())
}
view.bottomProduct.setOnClickListener { [unowned self] in
selectUniversalMonthly()
selectMontlyProduct(view, model: .universalDetails())
}
view.actionButton.setOnClickListener { [unowned self] in
startTrial()
}
return view
}()
private func selectYearlyProduct(_ view: PaywallView, model: PaywallViewModel) {
view.topProduct.setSelected(true)
view.bottomProduct.setSelected(false)
let anualPrice = VPNSubscription.getProductIdPrice(productId: model.annualProductId)
let monthlyPrice = VPNSubscription.getProductIdPriceMonthly(productId: model.annualProductId)
let trialDuation = VPNSubscription.trialDuration(productId: model.annualProductId) ?? ""
let title = trialDuation + " " + NSLocalizedString("free trial", comment: "") + "," + " then \(anualPrice) (\(monthlyPrice)/mo)"
view.trialDescriptionLabel.text = title
view.trialDescriptionLabel.isHidden = VPNSubscription.trialDuration(productId: model.annualProductId) == nil
view.updateCTATitle(for: model.annualProductId)
}
private func selectMontlyProduct(_ view: PaywallView, model: PaywallViewModel) {
view.bottomProduct.setSelected(true)
view.topProduct.setSelected(false)
let monthlyPrice = VPNSubscription.getProductIdPrice(productId: model.mounthProductId)
let trialDuation = VPNSubscription.trialDuration(productId: model.annualProductId) ?? ""
let title = trialDuation + " " + NSLocalizedString("free trial", comment: "") + "," + " then \(monthlyPrice)/mo"
view.trialDescriptionLabel.text = title
view.trialDescriptionLabel.isHidden = VPNSubscription.trialDuration(productId: model.mounthProductId) == nil
view.updateCTATitle(for: model.mounthProductId)
}
private lazy var privacyLabel: UILabel = {
let label = UILabel()
label.font = fontMedium11
label.textAlignment = .center
label.numberOfLines = 0
let attributedText = NSMutableAttributedString(string: NSLocalizedString("By continuing you agree with our ", comment: ""), attributes: [NSAttributedString.Key.font: fontMedium11, NSAttributedString.Key.foregroundColor: UIColor.smallGrey])
let termsRange = NSRange(location: attributedText.length, length: NSLocalizedString("Terms of Service", comment: "").count)
attributedText.append(NSAttributedString(string: NSLocalizedString("Terms of Service", comment: ""), attributes: [NSAttributedString.Key.font: fontMedium11, NSAttributedString.Key.foregroundColor: UIColor.white]))
attributedText.append(NSAttributedString(string: NSLocalizedString(" and ", comment: ""), attributes: [NSAttributedString.Key.font: fontMedium11, NSAttributedString.Key.foregroundColor: UIColor.smallGrey]))
let privacyRange = NSRange(location: attributedText.length, length: NSLocalizedString("Privacy Policy", comment: "").count)
attributedText.append(NSAttributedString(string: NSLocalizedString("\nPrivacy Policy", comment: ""), attributes: [NSAttributedString.Key.font: fontMedium11, NSAttributedString.Key.foregroundColor: UIColor.white]))
let paragraphStyle = NSMutableParagraphStyle()
paragraphStyle.alignment = .center
attributedText.addAttributes([NSAttributedString.Key.paragraphStyle: paragraphStyle], range: NSRange(location: 0, length: attributedText.length))
label.attributedText = attributedText
label.isUserInteractionEnabled = true
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(labelTapped(sender:)))
label.addGestureRecognizer(tapGesture)
return label
}()
//MARK: Lificycle
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .paywallNew
configureUI()
updateCurrentSelectedTab()
updateTabs()
updateVisibleTabs()
}
override func viewDidLayoutSubviews() {
super.viewDidLayoutSubviews()
if needScrolToUniversal {
scrollToRightTabs()
needScrolToUniversal = false
}
}
//MARK: ConfigureUI
private func configureUI() {
view.addSubview(navigationView)
navigationView.anchors.top.safeAreaPin(inset: 18)
navigationView.anchors.leading.marginsPin()
navigationView.anchors.trailing.marginsPin()
view.addSubview(hScrollView)
hScrollView.anchors.top.spacing(24, to: navigationView.anchors.bottom)
hScrollView.anchors.leading.pin(inset: 16)
hScrollView.anchors.trailing.pin()
hScrollView.anchors.height.equal(60)
hScrollView.showsHorizontalScrollIndicator = false
hScrollView.addSubview(plansStack)
plansStack.anchors.top.marginsPin()
plansStack.anchors.leading.equal(hScrollView.anchors.leading)
plansStack.anchors.trailing.equal(hScrollView.anchors.trailing)
plansStack.anchors.height.equal(hScrollView.anchors.height)
view.addSubview(privacyLabel)
privacyLabel.anchors.bottom.safeAreaPin()
privacyLabel.anchors.leading.marginsPin()
privacyLabel.anchors.trailing.marginsPin()
privacyLabel.anchors.height.equal(36)
view.addSubview(advancedView)
advancedView.anchors.top.spacing(24, to: hScrollView.anchors.bottom)
advancedView.anchors.leading.pin()
advancedView.anchors.trailing.pin()
advancedView.anchors.bottom.spacing(8, to: privacyLabel.anchors.top)
view.addSubview(anonymousView)
anonymousView.anchors.top.spacing(24, to: hScrollView.anchors.bottom)
anonymousView.anchors.leading.pin()
anonymousView.anchors.trailing.pin()
anonymousView.anchors.bottom.spacing(8, to: privacyLabel.anchors.top)
view.addSubview(universalView)
universalView.anchors.top.spacing(24, to: hScrollView.anchors.bottom)
universalView.anchors.leading.pin()
universalView.anchors.trailing.pin()
universalView.anchors.bottom.spacing(8, to: privacyLabel.anchors.top)
}
//MARK: Functions
@objc func closeButtonClicked() {
dismiss(animated: true)
}
@objc private func labelTapped(sender: UITapGestureRecognizer) {
let termsRange = NSRange(location: privacyLabel.attributedText!.length - NSLocalizedString("Terms of Service", comment: "").count - 18, length: NSLocalizedString("Terms of Service", comment: "").count)
let privacyRange = NSRange(location: privacyLabel.attributedText!.length - NSLocalizedString("Privacy Policy", comment: "").count, length: NSLocalizedString("Privacy Policy", comment: "").count)
if sender.didTapAttributedTextInLabel(label: privacyLabel, inRange: privacyRange),
let url = URL(string: "https://lockdownprivacy.com/privacy") {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
} else if sender.didTapAttributedTextInLabel(label: privacyLabel, inRange: termsRange),
let url = URL(string: "https://lockdownprivacy.com/terms") {
UIApplication.shared.open(url, options: [:], completionHandler: nil)
}
}
private func disable(button: UIButton) {
button.isEnabled = false
button.backgroundColor = .lightGray
}
private func isDisabledPlan(_ plan: Subscription.PlanType) -> Bool {
guard let subscription = shared.user.currentSubscription else {
return false
}
let planOrder: [Subscription.PlanType] = [
.advancedMonthly,
.advancedAnnual,
.anonymousMonthly,
.anonymousAnnual,
.universalMonthly,
.universalAnnual
]
guard let index = planOrder.firstIndex(of: plan),
let currentIndex = planOrder.firstIndex(of: subscription.planType) else {
return false
}
return index <= currentIndex
}
private func update(_ planView: PlanView, isSelected: Bool) {
if isSelected {
planView.iconImageView.image = UIImage(named: "fill-1")
planView.backgroundView.layer.borderColor = UIColor.borderBlue.cgColor
} else {
planView.iconImageView.image = UIImage(named: "grey-ellipse-1")
planView.backgroundView.layer.borderColor = UIColor.borderGray.cgColor
}
}
private func updateCurrentSelectedTab () {
guard let plan = shared.user.currentSubscription?.planType else {
selectedTab = .universal
needScrolToUniversal = true
return
}
if plan.isAdvanced {
selectedTab = .advanced
}
if plan.isAnonymous {
selectedTab = .anonymous
}
if plan.isUniversal {
selectedTab = .universal
}
}
private func scrollToRightTabs() {
DispatchQueue.main.async {
let offset = self.hScrollView.contentSize.width - self.hScrollView.frame.size.width
self.hScrollView.setContentOffset(CGPoint(x: offset, y: 0), animated: false)
}
}
private func updateTabs() {
switch selectedTab {
case .advanced:
selectAdvancedYearly()
case .anonymous:
selectAnonymousYearly()
case .universal:
selectUniversalYearly()
}
advancedView.isHidden = selectedTab != .advanced
anonymousView.isHidden = selectedTab != .anonymous
universalView.isHidden = selectedTab != .universal
update(advancedPlan, isSelected: selectedTab == .advanced)
update(anonymousPlan, isSelected: selectedTab == .anonymous)
update(universalPlan, isSelected: selectedTab == .universal)
}
private func updateVisibleTabs() {
guard let plan = shared.user.currentSubscription?.planType else {
advancedPlan.isHidden = false
anonymousPlan.isHidden = false
universalPlan.isHidden = false
return
}
if plan.isAdvanced {
advancedPlan.isHidden = false
anonymousPlan.isHidden = false
universalPlan.isHidden = false
}
if plan.isAnonymous {
advancedPlan.isHidden = true
}
if plan.isUniversal {
advancedPlan.isHidden = true
anonymousPlan.isHidden = true
}
}
}
extension VPNPaywallViewController: ProductPurchasable {
@objc private func restorePurchase() {
restorePurchases()
}
func selectAdvancedYearly() {
VPNSubscription.selectedProductId = VPNSubscription.productIdAdvancedYearly
updatePricingSubtitle()
}
func selectAdvancedMonthly() {
VPNSubscription.selectedProductId = VPNSubscription.productIdAdvancedMonthly
updatePricingSubtitle()
}
func selectAnonymousYearly() {
VPNSubscription.selectedProductId = VPNSubscription.productIdAnnual
updatePricingSubtitle()
}
func selectAnonymousMonthly() {
VPNSubscription.selectedProductId = VPNSubscription.productIdMonthly
updatePricingSubtitle()
}
func selectUniversalYearly() {
VPNSubscription.selectedProductId = VPNSubscription.productIdAnnualPro
updatePricingSubtitle()
}
func selectUniversalMonthly() {
VPNSubscription.selectedProductId = VPNSubscription.productIdMonthlyPro
updatePricingSubtitle()
}
func updatePricingSubtitle() {
let context: VPNSubscription.SubscriptionContext = {
switch mode {
case .newSubscription:
return .new
case .upgrade:
return .upgrade
}
}()
}
@objc func startTrial() {
showLoadingView()
VPNSubscription.purchase(
succeeded: {
self.dismiss(animated: true, completion: {
self.purchaseSuccessful?()
})
},
errored: { error in
self.hideLoadingView()
self.purchaseFailed?(error)
})
}
}