// // SubscriptionsViewController.swift // Earthquake Network // // Created by Busi Andrea on 29/07/2020. // Copyright © 2020 Earthquake Network. All rights reserved. // import UIKit import StoreKit 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" } }