mirror of
https://github.com/confirmedcode/Lockdown-iOS.git
synced 2025-12-21 12:14:02 +01:00
454 lines
17 KiB
Swift
454 lines
17 KiB
Swift
//
|
||
// 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)
|
||
})
|
||
}
|
||
}
|