Skip to content
This repository has been archived by the owner. It is now read-only.
Permalink
Browse files

[IP-624] [Subscriptions UI] Loading indicator after clicking on subsc…

…ribe (#361)

* Fix a possible bug in BaseUpgradeViewController with telemetry

* Show loading indicator when tapping buy button

* Fix a nil issue in VPNEndPointManager

* Blur UpgradeViewController when loading

* Fix iffies, viewsies
  • Loading branch information
Daniel Jilg committed Jun 25, 2019
1 parent 9f48158 commit 9bd2b9c493bceadd78aa6bd34747233f3c46adbb
@@ -135,8 +135,8 @@ class VPN {
VPN.shared.lastStatus = .invalid VPN.shared.lastStatus = .invalid
} }


let country = VPNEndPointManager.shared.selectedCountry guard let country = VPNEndPointManager.shared.selectedCountry,
guard let creds = VPNEndPointManager.shared.getCredentials(country: country), let creds = VPNEndPointManager.shared.getCredentials(country: country),
!country.endpoint.isEmpty else { return } !country.endpoint.isEmpty else { return }


NEVPNManager.shared().loadFromPreferences { (error) in NEVPNManager.shared().loadFromPreferences { (error) in
@@ -180,9 +180,11 @@ class VPN {
} }
catch (let error) { catch (let error) {
print("VPN Connecttion failed --- \(error)") print("VPN Connecttion failed --- \(error)")
LegacyTelemetryHelper.logVPN(action: "error", if let selectedCountry = VPNEndPointManager.shared.selectedCountry {
location: VPNEndPointManager.shared.selectedCountry.id, LegacyTelemetryHelper.logVPN(action: "error",
location: selectedCountry.id,
connectionTime: 0) connectionTime: 0)
}
VPN.shared.retryCount = 0 VPN.shared.retryCount = 0
} }


@@ -173,7 +173,7 @@ class VPNEndPointManager {
} }


//MARK:- VPN Countries //MARK:- VPN Countries
var selectedCountry: VPNCountry { var selectedCountry: VPNCountry? {
set { set {
UserDefaults.standard.set(try? PropertyListEncoder().encode(newValue), forKey: SelectedCountryKey) UserDefaults.standard.set(try? PropertyListEncoder().encode(newValue), forKey: SelectedCountryKey)
UserDefaults.standard.synchronize() UserDefaults.standard.synchronize()
@@ -182,7 +182,7 @@ class VPNEndPointManager {
if let data = UserDefaults.standard.value(forKey: SelectedCountryKey) as? Data, let country = try? PropertyListDecoder().decode(VPNCountry.self, from: data) { if let data = UserDefaults.standard.value(forKey: SelectedCountryKey) as? Data, let country = try? PropertyListDecoder().decode(VPNCountry.self, from: data) {
return country return country
} }
return countries.first! return countries.first
} }
} }


@@ -262,8 +262,9 @@ class VPNViewController: UIViewController {
//start timer //start timer
timer = Timer.scheduledTimer(timeInterval: 0.95, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true) timer = Timer.scheduledTimer(timeInterval: 0.95, target: self, selector: #selector(timerFired), userInfo: nil, repeats: true)
timer?.fire() timer?.fire()
LegacyTelemetryHelper.logVPN(action: "connect", if let selectedCountry = VPNEndPointManager.shared.selectedCountry {
location: VPNEndPointManager.shared.selectedCountry.id) LegacyTelemetryHelper.logVPN(action: "connect", location: selectedCountry.id)
}
} }
else if VPNStatus == .disconnected { else if VPNStatus == .disconnected {
if self.connectButton.currentState == .Connecting || self.connectButton.currentState == .Connect { if self.connectButton.currentState == .Connecting || self.connectButton.currentState == .Connect {
@@ -314,10 +315,12 @@ class VPNViewController: UIViewController {
if (VPN.shared.status == .connected) { if (VPN.shared.status == .connected) {
VPN.disconnectVPN() VPN.disconnectVPN()
LegacyTelemetryHelper.logVPN(action: "click", target: "toggle", state: "off") LegacyTelemetryHelper.logVPN(action: "click", target: "toggle", state: "off")


LegacyTelemetryHelper.logVPN(action: "disconnect", if let selectedCountry = VPNEndPointManager.shared.selectedCountry {
location: VPNEndPointManager.shared.selectedCountry.id, LegacyTelemetryHelper.logVPN(action: "disconnect",
location: selectedCountry.id,
connectionTime: getConnectionTime()) connectionTime: getConnectionTime())
}
} else { } else {
guard SubscriptionController.shared.isVPNEnabled() else { guard SubscriptionController.shared.isVPNEnabled() else {
displayUnlockVPNAlert() displayUnlockVPNAlert()
@@ -426,7 +429,7 @@ extension VPNViewController: UITableViewDataSource {
cell.textLabel?.text = NSLocalizedString("Choose VPN Location", tableName: "Lumen", comment: "[VPN] vpn choose location") cell.textLabel?.text = NSLocalizedString("Choose VPN Location", tableName: "Lumen", comment: "[VPN] vpn choose location")
cell.textLabel?.textColor = Lumen.VPN.selectTextColor(lumenTheme, .Normal) cell.textLabel?.textColor = Lumen.VPN.selectTextColor(lumenTheme, .Normal)
cell.backgroundColor = .clear cell.backgroundColor = .clear
cell.detailTextLabel?.text = VPNEndPointManager.shared.selectedCountry.name cell.detailTextLabel?.text = VPNEndPointManager.shared.selectedCountry?.name
cell.detailTextLabel?.textColor = Lumen.VPN.selectDetailTextColor(lumenTheme, .Normal) cell.detailTextLabel?.textColor = Lumen.VPN.selectDetailTextColor(lumenTheme, .Normal)
cell.selectionStyle = .none cell.selectionStyle = .none


@@ -458,7 +461,7 @@ extension VPNViewController: VPNCountrySelectionDelegate {
func didSelectCountry(country: VPNCountry) { func didSelectCountry(country: VPNCountry) {
LegacyTelemetryHelper.logVPN(action: "click", LegacyTelemetryHelper.logVPN(action: "click",
target: "location", target: "location",
location: VPNEndPointManager.shared.selectedCountry.id) location: country.id)
//country changed, reconnect if necessary //country changed, reconnect if necessary
VPN.countryDidChange(country: country) VPN.countryDidChange(country: country)


@@ -16,6 +16,7 @@ public typealias ProductsRequestCompletionHandler = (_ success: Bool, _ products
extension Notification.Name { extension Notification.Name {
static let ProductPurchaseSuccessNotification = Notification.Name("ProductPurchaseSuccessNotification") static let ProductPurchaseSuccessNotification = Notification.Name("ProductPurchaseSuccessNotification")
static let ProductPurchaseErrorNotification = Notification.Name("ProductPurchaseErrorNotification") static let ProductPurchaseErrorNotification = Notification.Name("ProductPurchaseErrorNotification")
static let ProductPurchaseCancelledNotification = Notification.Name("ProductPurchaseCancelledNotification")
static let SubscriptionRefreshNotification = Notification.Name("SubscriptionRefreshNotification") static let SubscriptionRefreshNotification = Notification.Name("SubscriptionRefreshNotification")
} }


@@ -53,13 +53,17 @@ class RevenueCatService: NSObject, IAPService {


public func buyProduct(_ product: SKProduct) { public func buyProduct(_ product: SKProduct) {
print("Buying \(product.productIdentifier)...") print("Buying \(product.productIdentifier)...")
purchases?.makePurchase(product, { (transaction, purchaserInfo, error) in purchases?.makePurchase(product) { (transaction, purchaserInfo, error) in
if let error = error as? SKError, error.code != .paymentCancelled { if let error = error as? SKError {
NotificationCenter.default.post(name: .ProductPurchaseErrorNotification, object: error.localizedDescription) if error.code == .paymentCancelled {
NotificationCenter.default.post(name: .ProductPurchaseCancelledNotification, object: nil)
} else {
NotificationCenter.default.post(name: .ProductPurchaseErrorNotification, object: nil)
}
} else if let purchaserInfo = purchaserInfo { } else if let purchaserInfo = purchaserInfo {
self.processPurchaseInfo(purchaserInfo) self.processPurchaseInfo(purchaserInfo)
} }
}) }
} }


public func isUserPromoEligible(productID:String, completion: @escaping (Bool) -> Void) { public func isUserPromoEligible(productID:String, completion: @escaping (Bool) -> Void) {
@@ -78,13 +82,17 @@ class RevenueCatService: NSObject, IAPService {
} }


public func restorePurchases() { public func restorePurchases() {
purchases?.restoreTransactions({ (purchaserInfo, error) in purchases?.restoreTransactions() { (purchaserInfo, error) in
if let error = error as? SKError, error.code != .paymentCancelled { if let error = error as? SKError {
NotificationCenter.default.post(name: .ProductPurchaseErrorNotification, object: error.localizedDescription) if error.code == .paymentCancelled {
NotificationCenter.default.post(name: .ProductPurchaseCancelledNotification, object: nil)
} else {
NotificationCenter.default.post(name: .ProductPurchaseErrorNotification, object: error.localizedDescription)
}
} else if let purchaserInfo = purchaserInfo { } else if let purchaserInfo = purchaserInfo {
self.processPurchaseInfo(purchaserInfo) self.processPurchaseInfo(purchaserInfo)
} }
}) }
} }


public func getSubscriptionUserId() -> String? { public func getSubscriptionUserId() -> String? {
@@ -104,12 +112,13 @@ extension RevenueCatService: PurchasesDelegate {
private func processPurchaseInfo(_ purchaserInfo: PurchaserInfo) { private func processPurchaseInfo(_ purchaserInfo: PurchaserInfo) {
guard let identifier = getLastestIdentifier(purchaserInfo), guard let identifier = getLastestIdentifier(purchaserInfo),
let expirationDate = purchaserInfo.expirationDate(forProductIdentifier: identifier) else { let expirationDate = purchaserInfo.expirationDate(forProductIdentifier: identifier) else {
NotificationCenter.default.post(name: .ProductPurchaseCancelledNotification, object: nil)
return return
} }
let lumenPurchaseInfo = LumenPurchaseInfo(productIdentifier: identifier, expirationDate: expirationDate) let lumenPurchaseInfo = LumenPurchaseInfo(productIdentifier: identifier, expirationDate: expirationDate)
print("processPurchaseInfo -> identifier: \(identifier), expirationDate: \(expirationDate)") print("processPurchaseInfo -> identifier: \(identifier), expirationDate: \(expirationDate)")
observable.onNext(lumenPurchaseInfo) observable.onNext(lumenPurchaseInfo)
NotificationCenter.default.post(name: .ProductPurchaseSuccessNotification, object: identifier) NotificationCenter.default.post(name: .ProductPurchaseSuccessNotification, object: nil)
} }


fileprivate func getLastestIdentifier(_ purchaserInfo: PurchaserInfo) -> String? { fileprivate func getLastestIdentifier(_ purchaserInfo: PurchaserInfo) -> String? {
@@ -20,6 +20,12 @@ class UpgradLumenNavigationController: UINavigationController {
class BaseUpgradeViewController: UIViewController { class BaseUpgradeViewController: UIViewController {
#if PAID #if PAID
let loadingView = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge) let loadingView = UIActivityIndicatorView(activityIndicatorStyle: .whiteLarge)
let loadingOverlay: UIView = {
let blurEffect = UIBlurEffect(style: .dark)
let blurredEffectView = UIVisualEffectView(effect: blurEffect)
return blurredEffectView
}()

let dataSource: SubscriptionDataSource let dataSource: SubscriptionDataSource


var selectedProduct: LumenSubscriptionProduct? var selectedProduct: LumenSubscriptionProduct?
@@ -40,6 +46,9 @@ class BaseUpgradeViewController: UIViewController {
NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseSuccessNotification(_:)), NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseSuccessNotification(_:)),
name: .ProductPurchaseSuccessNotification, name: .ProductPurchaseSuccessNotification,
object: nil) object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseCancelledNotification(_:)),
name: .ProductPurchaseCancelledNotification,
object: nil)
NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseErrorNotification(_:)), NotificationCenter.default.addObserver(self, selector: #selector(handlePurchaseErrorNotification(_:)),
name: .ProductPurchaseErrorNotification, name: .ProductPurchaseErrorNotification,
object: nil) object: nil)
@@ -65,13 +74,19 @@ class BaseUpgradeViewController: UIViewController {
self.selectedProduct = nil self.selectedProduct = nil
self.dismiss(animated: true) self.dismiss(animated: true)
} }

@objc func handlePurchaseCancelledNotification(_ notification: Notification) {
self.selectedProduct = nil
self.stopLoadingAnimation()
}


@objc func handlePurchaseErrorNotification(_ notification: Notification) { @objc func handlePurchaseErrorNotification(_ notification: Notification) {
self.stopLoadingAnimation()
guard let lumenProduct = self.selectedProduct else { guard let lumenProduct = self.selectedProduct else {
return return
} }
self.selectedProduct = nil self.selectedProduct = nil
let telemetrySignals = self.dataSource.telemeterySignals() let telemetrySignals = self.dataSource.telemeterySignals(product: lumenProduct)
LegacyTelemetryHelper.logPromoPayment(action: "error", target: telemetrySignals["target"], view: telemetrySignals["view"]) LegacyTelemetryHelper.logPromoPayment(action: "error", target: telemetrySignals["target"], view: telemetrySignals["view"])
let errorDescirption = NSLocalizedString("We are sorry, but something went wrong. The payment was not successful, please try again.", tableName: "Lumen", comment: "Error message when there is failing payment transaction") let errorDescirption = NSLocalizedString("We are sorry, but something went wrong. The payment was not successful, please try again.", tableName: "Lumen", comment: "Error message when there is failing payment transaction")
let alertController = UIAlertController(title: "", message: errorDescirption, preferredStyle: .alert) let alertController = UIAlertController(title: "", message: errorDescirption, preferredStyle: .alert)
@@ -126,14 +141,37 @@ class BaseUpgradeViewController: UIViewController {
} }
} }


private func startLoadingAnimation() { // MARK: - Loading
self.loadingView.startAnimating() func startLoadingAnimation() {

loadingOverlay.alpha = 0
view.addSubview(loadingOverlay)
loadingOverlay.snp.makeConstraints { (make) in
make.top.equalTo(view)
make.bottom.equalTo(view)
make.left.equalTo(view)
make.right.equalTo(view)
}

view.bringSubview(toFront: loadingView)
loadingView.startAnimating()

UIView.animate(withDuration: 0.5) {
self.loadingOverlay.alpha = 0.8
}
} }


private func stopLoadingAnimation() { func stopLoadingAnimation() {
self.loadingView.stopAnimating() self.loadingView.stopAnimating()

UIView.animate(withDuration: 0.25, animations: {
self.loadingOverlay.alpha = 0
}) { finished in
self.loadingOverlay.removeFromSuperview()
}
} }


// MARK: - Alerts
private func showProductsRetrievalFailedAlert() { private func showProductsRetrievalFailedAlert() {
let errorDescirption = NSLocalizedString("Sorry, Lumen cannot connect to the Internet.", tableName: "Lumen", comment: "Error when can't get list of available subscriptions") let errorDescirption = NSLocalizedString("Sorry, Lumen cannot connect to the Internet.", tableName: "Lumen", comment: "Error when can't get list of available subscriptions")
let alertController = UIAlertController(title: "", message: errorDescirption, preferredStyle: .alert) let alertController = UIAlertController(title: "", message: errorDescirption, preferredStyle: .alert)
@@ -165,6 +165,7 @@ class UpgradLumenViewController: BaseUpgradeViewController {
@objc func restoreSubscription() { @objc func restoreSubscription() {
self.telemetrySignals?["target"] = "restore" self.telemetrySignals?["target"] = "restore"
LegacyTelemetryHelper.logPayment(action: "click", target: self.telemetrySignals?["target"]) LegacyTelemetryHelper.logPayment(action: "click", target: self.telemetrySignals?["target"])
startLoadingAnimation()
SubscriptionController.shared.restorePurchases() SubscriptionController.shared.restorePurchases()
} }


@@ -266,6 +267,7 @@ extension UpgradLumenViewController: UITableViewDelegate, UITableViewDataSource
self?.selectedProduct = subscriptionProduct self?.selectedProduct = subscriptionProduct
self?.telemetrySignals = subscriptionInfo?.telemetrySignals self?.telemetrySignals = subscriptionInfo?.telemetrySignals
LegacyTelemetryHelper.logPayment(action: "click", target: self?.telemetrySignals?["target"]) LegacyTelemetryHelper.logPayment(action: "click", target: self?.telemetrySignals?["target"])
self?.startLoadingAnimation()
} }
return cell return cell
} }

0 comments on commit 9bd2b9c

Please sign in to comment.