345 lines
13 KiB
Swift
345 lines
13 KiB
Swift
//
|
||
// SubscriptionsViewController.swift
|
||
// Earthquake Network
|
||
//
|
||
// Created by Busi Andrea on 29/07/2020.
|
||
// Copyright © 2020 Earthquake Network. All rights reserved.
|
||
//
|
||
|
||
import UIKit
|
||
import StoreKit
|
||
import Shogun
|
||
|
||
|
||
class SubscriptionsViewController: UITableViewController {
|
||
|
||
private static let SegueIdentifierSubscriptionDetail = "ShowSubscriptionDetail"
|
||
private static let CellHeightDescription: CGFloat = 320.0
|
||
|
||
// sezioni
|
||
private enum TableSection: CaseIterable {
|
||
case active
|
||
case description
|
||
case monthly
|
||
case yearly
|
||
case perpetual
|
||
|
||
var sectionTitle: String? {
|
||
switch self {
|
||
case .monthly: return NSLocalizedString("inapp_monthly_subscriptions", comment: "")
|
||
case .yearly: return NSLocalizedString("inapp_yearly_subscriptions", comment: "")
|
||
case .perpetual: return NSLocalizedString("inapp_lifetime_subscriptions", comment: "")
|
||
default: return nil
|
||
}
|
||
}
|
||
}
|
||
|
||
private let sections = TableSection.allCases
|
||
|
||
private var allProducts = [SKProduct]()
|
||
private var monthlyProducts = [SKProduct]()
|
||
private var yearlyProducts = [SKProduct]()
|
||
private var perpetualProducts = [SKProduct]()
|
||
/// Product already bought by the user
|
||
private var subscribedProduct: SKProduct?
|
||
/// Availability for subscriptions
|
||
private var availability: EQNPurchaseAvailability?
|
||
/// Tells if products are loading
|
||
private var isLoading = false
|
||
/// Tells if a restore is in progress
|
||
private var isRestorePurchase = false
|
||
|
||
// MARK: - View Lifecycle
|
||
|
||
override func viewDidLoad() {
|
||
super.viewDidLoad()
|
||
|
||
addObservers()
|
||
configureUI()
|
||
}
|
||
|
||
override func viewWillAppear(_ animated: Bool) {
|
||
super.viewWillAppear(animated)
|
||
|
||
loadData()
|
||
checkAvailabilities()
|
||
}
|
||
|
||
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
|
||
if segue.identifier == Self.SegueIdentifierSubscriptionDetail,
|
||
let controller = segue.destination as? SubscriptionDetailViewController,
|
||
let product = sender as? SKProduct {
|
||
controller.product = product
|
||
}
|
||
}
|
||
|
||
// MARK: - Private
|
||
|
||
private func addObservers() {
|
||
NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseNotification(_:)),
|
||
name: .EQNInAppPurchaseDidComplete,
|
||
object: nil)
|
||
|
||
NotificationCenter.default.addObserver(self, selector: #selector(fail(_:)),
|
||
name: .EQNInAppPurchaseDidFail,
|
||
object: nil)
|
||
|
||
NotificationCenter.default.addObserver(self, selector: #selector(handleNoTransactionsNotification(_:)),
|
||
name: .EQNInAppPurchaseNoTransactions,
|
||
object: nil)
|
||
}
|
||
|
||
private func configureUI() {
|
||
let restoreButton = UIBarButtonItem(title: NSLocalizedString("purchase_pro_restore", comment: ""),
|
||
style: .plain,
|
||
target: self,
|
||
action: #selector(restoreTapped(_:)))
|
||
navigationItem.rightBarButtonItem = restoreButton
|
||
|
||
// if is presented in Simulator, add done button
|
||
if navigationController?.viewControllers.first == self {
|
||
let doneButton = UIBarButtonItem(barButtonSystemItem: .done, target: self, action: #selector(closeTapped(_:)))
|
||
navigationItem.leftBarButtonItem = doneButton
|
||
}
|
||
|
||
tableView.rowHeight = UITableView.automaticDimension
|
||
tableView.estimatedRowHeight = Self.CellHeightDescription;
|
||
}
|
||
|
||
private func updateUI() {
|
||
monthlyProducts.removeAll()
|
||
yearlyProducts.removeAll()
|
||
perpetualProducts.removeAll()
|
||
|
||
// creates list to show
|
||
let isDiscountAvailable = checkDiscountPrice()
|
||
allProducts.forEach { (product) in
|
||
if isDiscountAvailable {
|
||
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kMonthly ||
|
||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kMonthly {
|
||
monthlyProducts.append(product)
|
||
} else if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kYearlyDiscounted ||
|
||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kYearlyDiscounted {
|
||
yearlyProducts.append(product)
|
||
}
|
||
} else {
|
||
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kMonthly ||
|
||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kMonthly {
|
||
monthlyProducts.append(product)
|
||
}
|
||
else if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kYearly ||
|
||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kYearly {
|
||
yearlyProducts.append(product)
|
||
}
|
||
}
|
||
// perpetual scribuscriptions doesn't have discounted version
|
||
if product.productIdentifier == VersioneProProducts.Identifier.Subscription10kPerpetual ||
|
||
product.productIdentifier == VersioneProProducts.Identifier.Subscription100kPerpetual {
|
||
perpetualProducts.append(product)
|
||
}
|
||
}
|
||
|
||
tableView.reloadData()
|
||
}
|
||
|
||
private func loadData() {
|
||
isLoading = true
|
||
|
||
VersioneProProducts.store.requestProducts{ [weak self] success, products in
|
||
self?.isLoading = false
|
||
|
||
guard let self = self, let products = products, success == true else { return }
|
||
|
||
let purchased = products.filter { (product) -> Bool in
|
||
let isPurchased = VersioneProProducts.store.isProductPurchased(product.productIdentifier)
|
||
let isSubscription = VersioneProProducts.isSubscription(for: product.productIdentifier)
|
||
return isPurchased && isSubscription
|
||
}
|
||
self.subscribedProduct = purchased.first
|
||
self.allProducts = products.sorted(by: { $0.productIdentifier > $1.productIdentifier })
|
||
|
||
self.updateUI()
|
||
}
|
||
}
|
||
|
||
private func checkDiscountPrice() -> Bool {
|
||
let downloaded = EQNManager.manager().rete_smartphone?.subscriptionsDiscounted
|
||
return downloaded ?? false
|
||
}
|
||
|
||
private func checkAvailabilities() {
|
||
EQNPurchaseUtility.availableSubscriptions { (availability) in
|
||
DispatchQueue.main.async {
|
||
self.availability = availability
|
||
self.updateUI()
|
||
}
|
||
}
|
||
}
|
||
|
||
// MARK: - Actions
|
||
|
||
@objc func restoreTapped(_ sender: AnyObject) {
|
||
isRestorePurchase = true
|
||
VersioneProProducts.store.restorePurchases()
|
||
}
|
||
|
||
@objc func closeTapped(_ sender: AnyObject) {
|
||
dismiss(animated: true, completion: nil)
|
||
}
|
||
|
||
// MARK: - Notifications
|
||
|
||
@objc func fail(_ notification: Notification){
|
||
VersioneProProducts.store.loadPurchase()
|
||
}
|
||
|
||
@objc func handlePurchaseNotification(_ notification: Notification) {
|
||
if isRestorePurchase {
|
||
isRestorePurchase = false
|
||
|
||
var product: String = "unknown"
|
||
if let productIdentifier = notification.object as? String, let productName = resourceNameForProductIdentifier(productIdentifier) {
|
||
product = productName
|
||
}
|
||
|
||
let message = "\(NSLocalizedString("purchase_pro_restore_alert_message", comment: ""))\n\n(\(product))"
|
||
let alert = UIAlertController(title: NSLocalizedString("purchase_pro_restore_alert_title", comment: ""),
|
||
message: message, preferredStyle: .alert)
|
||
alert.addAction(UIAlertAction(title: "ok", style: .default, handler: nil))
|
||
present(alert, animated: true, completion: nil)
|
||
}
|
||
|
||
VersioneProProducts.store.loadPurchase()
|
||
loadData()
|
||
}
|
||
|
||
@objc func handleNoTransactionsNotification(_ notification: Notification) {
|
||
let alert = UIAlertController(title: NSLocalizedString("attention", comment: ""),
|
||
message: NSLocalizedString("purchase_pro_no_subscriptions_alert_message", comment: ""), preferredStyle: .alert)
|
||
alert.addAction(UIAlertAction(title: "ok", style: .default, handler: nil))
|
||
present(alert, animated: true, completion: nil)
|
||
}
|
||
|
||
// MARK: - Table view data source
|
||
|
||
override func numberOfSections(in tableView: UITableView) -> Int {
|
||
sections.count
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, viewForHeaderInSection section: Int) -> UIView? {
|
||
let tableSection = sections[section]
|
||
if let cell = tableView.dequeueReusableCell(withIdentifier: "SectionHeaderCell") as? SubscriptionsHeaderTableViewCell {
|
||
cell.title = tableSection.sectionTitle
|
||
cell.isLoading = isLoading
|
||
return cell
|
||
}
|
||
return nil
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
|
||
let tableSection = sections[section]
|
||
if tableSection.sectionTitle != nil {
|
||
return 50
|
||
}
|
||
return 0
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
|
||
let tableSection = sections[section]
|
||
switch tableSection {
|
||
case .active: return 1
|
||
case .description: return 1
|
||
case .monthly,
|
||
.yearly,
|
||
.perpetual:
|
||
return availableProducts(for: tableSection).count
|
||
}
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
|
||
let tableSection = sections[indexPath.section]
|
||
if tableSection == .description {
|
||
// autolayout in description doesn't work 🤷♂️
|
||
return Self.CellHeightDescription
|
||
}
|
||
return UITableView.automaticDimension
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) {
|
||
let tableSection = sections[indexPath.section]
|
||
if tableSection == .active || tableSection == .description {
|
||
return
|
||
}
|
||
|
||
// add round borders to first and last row in products cells
|
||
let cornerRadius = AppTheme.shared.cardCornerRadius
|
||
var corners: UIRectCorner = []
|
||
|
||
if indexPath.row == 0 {
|
||
corners.update(with: .topLeft)
|
||
corners.update(with: .topRight)
|
||
}
|
||
|
||
if indexPath.row == tableView.numberOfRows(inSection: indexPath.section) - 1 {
|
||
corners.update(with: .bottomLeft)
|
||
corners.update(with: .bottomRight)
|
||
}
|
||
|
||
let maskLayer = CAShapeLayer()
|
||
maskLayer.path = UIBezierPath(roundedRect: cell.bounds,
|
||
byRoundingCorners: corners,
|
||
cornerRadii: CGSize(width: cornerRadius, height: cornerRadius)).cgPath
|
||
cell.layer.mask = maskLayer
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
|
||
let tableSection = sections[indexPath.section]
|
||
if tableSection == .active {
|
||
let cell = tableView.dequeueReusableCell(withIdentifier: "ActiveSubscriptionsCell", for: indexPath) as! SubscriptionsActiveTableViewCell
|
||
cell.product = subscribedProduct
|
||
return cell
|
||
}
|
||
if tableSection == .description {
|
||
let cell = tableView.dequeueReusableCell(withIdentifier: "DescriptionCell", for: indexPath) as! SubscriptionsDescriptionTableViewCell
|
||
return cell
|
||
}
|
||
|
||
let products = availableProducts(for: tableSection)
|
||
let cell = tableView.dequeueReusableCell(withIdentifier: "SubscriptionCell", for: indexPath) as! SubscriptionProductTableViewCell
|
||
cell.product = products[indexPath.row]
|
||
cell.availability = availability
|
||
return cell
|
||
}
|
||
|
||
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
|
||
tableView.deselectRow(at: indexPath, animated: true)
|
||
|
||
let tableSection = sections[indexPath.section]
|
||
let products = availableProducts(for: tableSection)
|
||
if !products.isEmpty {
|
||
performSegue(withIdentifier: Self.SegueIdentifierSubscriptionDetail, sender: products[indexPath.row])
|
||
}
|
||
}
|
||
|
||
// MARK: - Helpers
|
||
|
||
private func availableProducts(for section: TableSection) -> [SKProduct] {
|
||
switch section {
|
||
case .monthly: return monthlyProducts
|
||
case .yearly: return yearlyProducts
|
||
case .perpetual: return perpetualProducts
|
||
default: return []
|
||
}
|
||
}
|
||
}
|
||
|
||
extension SubscriptionsViewController: StoryboardInitializable {
|
||
static var storyboardName: String {
|
||
"Main"
|
||
}
|
||
|
||
static var storyboardControllerId: String {
|
||
"subscriptionsController"
|
||
}
|
||
}
|