Files
eqn.ios/Sources/Earthquake Network/Controllers/InApp/SubscriptionsViewController.swift
T
2025-07-24 16:46:29 +02:00

282 lines
11 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
@objc
class SubscriptionsViewController: UITableViewController {
// sezioni
private enum TableSection: CaseIterable {
case active
case description
case products
var sectionTitle: String? {
switch self {
case .products: NSLocalizedString("subscriptions_available", comment: "")
default: nil
}
}
}
private let sections = TableSection.allCases
/// All products retrieved from AppStore
private var products = [EQNInAppProducts]()
/// Product already bought by the user
private var productSubscribed: EQNInAppProducts?
/// 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: - Init
@objc
convenience init() {
self.init(style: .plain)
}
// MARK: - View Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
addObservers()
configureUI()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
loadData()
checkAvailabilities()
}
// 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() {
// 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
}
navigationItem.largeTitleDisplayMode = .never
tableView.rowHeight = UITableView.automaticDimension
tableView.estimatedRowHeight = 600.0
tableView.separatorStyle = .none
tableView.backgroundColor = .systemGroupedBackground
tableView.contentInset = EQNBaseContainerTableViewCell.EdgeInsets
// remove extra padding on top of each section header
tableView.sectionHeaderTopPadding = 0.0
tableView.registerCell(for: SubscriptionsActiveTableViewCell.self)
tableView.registerCell(for: SubscriptionsDescriptionTableViewCell.self)
tableView.registerCell(for: SubscriptionProductTableViewCell.self)
tableView.registerHeaderFooterView(for: SubscriptionsHeaderTableViewCell.self)
}
private func loadData() {
isLoading = true
EQNInAppProducts.store.requestProducts { [weak self] success, storeProducts in
self?.isLoading = false
guard let self = self, let storeProducts, success == true else { return }
let products = storeProducts.compactMap { EQNInAppProducts.from(product: $0) }
let purchased = products
.filter { (product) -> Bool in
// filter for subscriptions
let isPurchased = EQNInAppProducts.store.isProductPurchased(product.productIdentifier)
let isSubscription = product.isSubscription
return isPurchased && isSubscription
}.sorted { lProduct, rProduct in
// If user has more than one subscriptions,
// show first the Top10k.
let lIs10k = lProduct.isTop10k
let rIs10k = rProduct.isTop10k
// If left item is Top10k, order first
if lIs10k && !rIs10k {
return true
} else if !lProduct.isTop10k && rProduct.isTop10k {
// right product is Top10k, left no
return false
} else {
// both products Top10k or Top100k, keep existing order
return false
}
}
self.productSubscribed = purchased.first
self.products = products.sorted(by: { $0.productIdentifier > $1.productIdentifier })
self.tableView.reloadData()
}
}
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.tableView.reloadData()
}
}
}
// MARK: - Actions
@objc func closeTapped(_ sender: AnyObject) {
dismiss(animated: true, completion: nil)
}
// MARK: - Notifications
@objc func fail(_ notification: Notification){
EQNInAppProducts.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)
}
EQNInAppProducts.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]
switch tableSection.sectionTitle {
case .some(let title):
let view = tableView.dequeueHeaderFooterView(cellIdentifiable: SubscriptionsHeaderTableViewCell.self)
view.update(isLoading: isLoading, title: title)
return view
case .none:
return nil
}
}
override func tableView(_ tableView: UITableView, heightForHeaderInSection section: Int) -> CGFloat {
let tableSection = sections[section]
return switch tableSection.sectionTitle {
case .some: 50.0
case .none: 0.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 .products: return products.isEmpty ? 0 : 2
}
}
override func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let tableSection = sections[indexPath.section]
switch tableSection {
case .active:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionsActiveTableViewCell.self, for: indexPath)
cell.selectionStyle = .none
cell.update(with: productSubscribed)
cell.onTapRestore = { [weak self] in
guard let self else { return }
self.isRestorePurchase = true
EQNInAppProducts.store.restorePurchases()
}
return cell
case .description:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionsDescriptionTableViewCell.self, for: indexPath)
cell.selectionStyle = .none
return cell
case .products:
let cell = tableView.dequeueReusableCell(cellIdentifiable: SubscriptionProductTableViewCell.self, for: indexPath)
let category: EQNInAppProducts.Category = switch indexPath.row {
case 0: .top10k
case 1: .top100k
default: .top100k
}
cell.update(category: category, availability: availability)
return cell
}
}
override func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
tableView.deselectRow(at: indexPath, animated: true)
let products = availableProducts(for: indexPath)
if !products.isEmpty {
let controller = SubscriptionDetailsViewController(products: products)
navigationController?.pushViewController(controller, animated: true)
}
}
// MARK: - Helpers
private func availableProducts(for indexPath: IndexPath) -> [EQNInAppProducts] {
let section = sections[indexPath.section]
switch (section, indexPath.row) {
case (.products, 0): return products.filter { $0.isTop10k }
case (.products, 1): return products.filter { $0.isTop100k }
default: return []
}
}
}