diff --git a/Sources/Paywall/Assets.xcassets/debugger.imageset/Contents.json b/Sources/Paywall/Assets.xcassets/debugger.imageset/Contents.json new file mode 100644 index 000000000..4ffa80454 --- /dev/null +++ b/Sources/Paywall/Assets.xcassets/debugger.imageset/Contents.json @@ -0,0 +1,16 @@ +{ + "images" : [ + { + "filename" : "debugger.pdf", + "idiom" : "universal" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + }, + "properties" : { + "preserves-vector-representation" : true, + "template-rendering-intent" : "template" + } +} diff --git a/Sources/Paywall/Assets.xcassets/debugger.imageset/debugger.pdf b/Sources/Paywall/Assets.xcassets/debugger.imageset/debugger.pdf new file mode 100644 index 000000000..4e9ea35b8 Binary files /dev/null and b/Sources/Paywall/Assets.xcassets/debugger.imageset/debugger.pdf differ diff --git a/Sources/Paywall/Assets.xcassets/superwall_logo.imageset/superwall_logo.pdf b/Sources/Paywall/Assets.xcassets/superwall_logo.imageset/superwall_logo.pdf index 06e18aa6a..47acd5e55 100644 Binary files a/Sources/Paywall/Assets.xcassets/superwall_logo.imageset/superwall_logo.pdf and b/Sources/Paywall/Assets.xcassets/superwall_logo.imageset/superwall_logo.pdf differ diff --git a/Sources/Paywall/Debug/SWConsoleViewController.swift b/Sources/Paywall/Debug/SWConsoleViewController.swift new file mode 100644 index 000000000..66f505105 --- /dev/null +++ b/Sources/Paywall/Debug/SWConsoleViewController.swift @@ -0,0 +1,142 @@ +// +// File.swift +// +// +// Created by Jake Mor on 9/13/21. +// + +import UIKit +import Foundation +import StoreKit + +internal class SWConsoleViewController: UIViewController { + + var products: [SKProduct] = [] + var tableViewCellData = [(String, String)]() + + lazy var productPicker: UIPickerView = { + let picker: UIPickerView = UIPickerView() + picker.delegate = self + picker.dataSource = self + picker.translatesAutoresizingMaskIntoConstraints = false + picker.tintColor = PrimaryColor + picker.backgroundColor = LightBackgroundColor + return picker + }() + + lazy var tableView: UITableView = { + let tv = UITableView() + tv.register(UITableViewCell.self, forCellReuseIdentifier: "cell") + tv.translatesAutoresizingMaskIntoConstraints = false + tv.backgroundView = nil + tv.backgroundColor = .clear + tv.delegate = self + tv.dataSource = self + tv.allowsSelection = false + tv.allowsMultipleSelection = false + return tv + + }() + + init(products: [SKProduct]) { + super.init(nibName: nil, bundle: nil) + self.products = products + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + override func viewDidLoad() { + super.viewDidLoad() + view.backgroundColor = DarkBackgroundColor + title = "Paywall Debugger" + view.addSubview(tableView) + view.addSubview(productPicker) + + NSLayoutConstraint.activate([ + productPicker.widthAnchor.constraint(equalTo: view.widthAnchor), + productPicker.heightAnchor.constraint(equalTo: view.widthAnchor, multiplier: 0.618), + productPicker.bottomAnchor.constraint(equalTo: view.bottomAnchor), + productPicker.centerXAnchor.constraint(equalTo: view.centerXAnchor), + + tableView.widthAnchor.constraint(equalTo: view.widthAnchor), + tableView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor), + tableView.bottomAnchor.constraint(equalTo: productPicker.topAnchor), + tableView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + ]) + + productPicker.reloadAllComponents() + reloadTableView() + } + + func reloadTableView() { + let index = productPicker.selectedRow(inComponent: 0) + + tableViewCellData = [] + let p = products[index] + for i in p.eventData { + tableViewCellData.append(i) + } + + tableViewCellData.sort { first, second in + let (a0, _) = first + let (a1, _) = second + return a0 < a1 + } + + tableView.reloadData() + } + +} + +extension SWConsoleViewController: UIPickerViewDelegate, UIPickerViewDataSource { + internal func numberOfComponents(in pickerView: UIPickerView) -> Int { + 1 + } + + internal func pickerView(_ pickerView: UIPickerView, numberOfRowsInComponent component: Int) -> Int { + products.count + } + + internal func pickerView(_ pickerView: UIPickerView, attributedTitleForRow row: Int, forComponent component: Int) -> NSAttributedString? { + let attributedString = NSAttributedString(string: products[row].productIdentifier, attributes: [NSAttributedString.Key.foregroundColor : PrimaryColor]) + return attributedString + } + + func pickerView(_ pickerView: UIPickerView, didSelectRow row: Int, inComponent component: Int) { + reloadTableView() + } +} + +extension SWConsoleViewController: UITableViewDelegate, UITableViewDataSource { + + func numberOfSections(in tableView: UITableView) -> Int { + return 1 + } + + func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int { + return tableViewCellData.count + } + + func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { + let cell = UITableViewCell(style: .subtitle, reuseIdentifier: "cell") + + let (key, value) = tableViewCellData[indexPath.row] + cell.textLabel?.text = value + cell.textLabel?.textColor = .white + cell.detailTextLabel?.text = "{{ \(key) }}" + cell.detailTextLabel?.textColor = UIColor.white.withAlphaComponent(0.618) + cell.backgroundView = nil + cell.backgroundColor = .clear + cell.contentView.backgroundColor = .clear + + + + return cell + } + + + + +} diff --git a/Sources/Paywall/Debug/SWDebugViewController.swift b/Sources/Paywall/Debug/SWDebugViewController.swift index a213eccc1..96472a062 100644 --- a/Sources/Paywall/Debug/SWDebugViewController.swift +++ b/Sources/Paywall/Debug/SWDebugViewController.swift @@ -6,6 +6,7 @@ // import UIKit import Foundation +import StoreKit internal var PrimaryColor = UIColor(hexString: "#75FFF1") internal var PrimaryButtonBackgroundColor = UIColor(hexString: "#203133") @@ -42,6 +43,16 @@ internal class SWDebugViewController: UIViewController { return b }() + lazy var consoleButton: SWBounceButton = { + let b = SWBounceButton() + let image = UIImage(named: "debugger", in: Bundle.module, compatibleWith: nil)! + b.setImage(image, for: .normal) + b.translatesAutoresizingMaskIntoConstraints = false + b.imageView?.tintColor = UIColor.white.withAlphaComponent(0.5) + b.addTarget(self, action: #selector(pressedConsoleButton), for: .primaryActionTriggered) + return b + }() + lazy var bottomButton: SWBounceButton = { let b = SWBounceButton() b.setTitle("Preview", for: .normal) @@ -119,6 +130,7 @@ internal class SWDebugViewController: UIViewController { view.addSubview(previewContainerView) view.addSubview(activityIndicator) view.addSubview(logoImageView) + view.addSubview(consoleButton) view.addSubview(exitButton) view.addSubview(bottomButton) previewContainerView.addSubview(previewPickerButton) @@ -134,10 +146,13 @@ internal class SWDebugViewController: UIViewController { previewContainerView.bottomAnchor.constraint(equalTo: bottomButton.topAnchor, constant: -30), logoImageView.widthAnchor.constraint(equalTo: view.layoutMarginsGuide.widthAnchor, constant: -10), - logoImageView.heightAnchor.constraint(equalToConstant: 23), + logoImageView.heightAnchor.constraint(equalToConstant: 20), logoImageView.topAnchor.constraint(equalTo: view.layoutMarginsGuide.topAnchor, constant: 20), logoImageView.centerXAnchor.constraint(equalTo: view.centerXAnchor), + consoleButton.centerXAnchor.constraint(equalTo: bottomButton.leadingAnchor), + consoleButton.centerYAnchor.constraint(equalTo: logoImageView.centerYAnchor), + exitButton.centerXAnchor.constraint(equalTo: bottomButton.trailingAnchor), exitButton.centerYAnchor.constraint(equalTo: logoImageView.centerYAnchor), @@ -281,10 +296,47 @@ internal class SWDebugViewController: UIViewController { } @objc func pressedExitButton() { - presentingViewController?.dismiss(animated: true, completion: nil) - + } + + @objc func pressedConsoleButton() { + + self.activityIndicator.startAnimating() + self.previewContainerView.isHidden = true + + Network.shared.paywalls { [weak self] result in + + switch(result){ + case .success(let response): + let paywalls = response.paywalls + + let paywallResponse = paywalls.first { p in + p.id == self?.paywallId + } + + if let paywallResponse = paywallResponse { + StoreKitManager.shared.get(productsWithIds: paywallResponse.productIds) { productsById in + OnMain { + let products = Array(productsById.values) + let vc = SWConsoleViewController(products: products) + let nc = UINavigationController(rootViewController: vc) + self?.present(nc, animated: true) + } + } + } + + case .failure(let error): + Logger.superwallDebug(string: "Debug Mode Error", error: error) + self?.activityIndicator.stopAnimating() + } + + OnMain { + self?.activityIndicator.stopAnimating() + self?.previewContainerView.isHidden = false + } + + } } @objc func pressedBottomButton() { diff --git a/Sources/Paywall/Misc/Extensions.swift b/Sources/Paywall/Misc/Extensions.swift index 9621817d2..2edeb67c8 100644 --- a/Sources/Paywall/Misc/Extensions.swift +++ b/Sources/Paywall/Misc/Extensions.swift @@ -53,16 +53,29 @@ internal extension SKProduct { var eventData: [String: String] { return [ + "rawPrice": "\(price)", "price": localizedPrice, "periodAlt": localizedSubscriptionPeriod, "period": period, "periodly": "\(period)ly", "weeklyPrice": weeklyPrice, + "dailyPrice": dailyPrice, + "monthlyPrice": monthlyPrice, + "yearlyPrice": yearlyPrice, "trialPeriodDays": trialPeriodDays, "trialPeriodWeeks": trialPeriodWeeks, "trialPeriodMonths": trialPeriodMonths, "trialPeriodYears": trialPeriodYears, - "trialPeriodText": trialPeriodText + "trialPeriodText": trialPeriodText, + "periodDays": periodDays, + "periodWeeks": periodWeeks, + "periodMonths": periodMonths, + "periodYears": periodYears, + "locale": priceLocale.identifier, + "languageCode": priceLocale.languageCode ?? "n/a", + "currencyCode": priceLocale.currencyCode ?? "n/a", + "currencySymbol": priceLocale.currencySymbol ?? "n/a", + ] } @@ -88,8 +101,6 @@ internal extension SKProduct { case .month: dateComponents = DateComponents(month: subscriptionPeriod.numberOfUnits) case .year: dateComponents = DateComponents(year: subscriptionPeriod.numberOfUnits) @unknown default: - print("WARNING: SwiftyStoreKit localizedSubscriptionPeriod does not handle all SKProduct.PeriodUnit cases.") - // Default to month units in the unlikely event a different unit type is added to a future OS version dateComponents = DateComponents(month: subscriptionPeriod.numberOfUnits) } @@ -125,37 +136,254 @@ internal extension SKProduct { } } + + var periodWeeks: String { + get { + + guard let period = subscriptionPeriod else { return "" } + let c = period.numberOfUnits + + if period.unit == .day { + return "\(Int((1 * c)/7))" + } + + if period.unit == .week { + return "\(Int(c))" + } + + if period.unit == .month { + return "\(Int(4 * c))" + } + + if period.unit == .year { + return "\(Int(52 * c))" + } - var weeklyPrice: String { + return "0" + } + } + + var periodMonths: String { + get { + + guard let period = subscriptionPeriod else { return "" } + let c = period.numberOfUnits + + if period.unit == .day { + return "\(Int((1 * c)/30))" + } + + if period.unit == .week { + return "\(Int(c / 4))" + } + + if period.unit == .month { + return "\(Int(c))" + } + + if period.unit == .year { + return "\(Int(12 * c))" + } + + return "0" + } + } + + var periodYears: String { + get { + + guard let period = subscriptionPeriod else { return "" } + let c = period.numberOfUnits + + if period.unit == .day { + return "\(Int(c / 365))" + } + + if period.unit == .week { + return "\(Int(c / 52))" + } + + if period.unit == .month { + return "\(Int(c / 12))" + } + + if period.unit == .year { + return "\(Int(c))" + } + + return "0" + } + } + + var periodDays: String { + get { + + guard let period = subscriptionPeriod else { return "" } + let c = period.numberOfUnits + + if period.unit == .day { + return "\(Int(1 * c))" + } + + if period.unit == .week { + return "\(Int(7 * c))" + } + + if period.unit == .month { + return "\(Int(30 * c))" + } + + if period.unit == .year { + return "\(Int(365 * c))" + } + + return "0" + } + } + + var dailyPrice: String { get { if price == NSDecimalNumber(decimal: 0.00) { return "$0.00" } - + let numberFormatter = NumberFormatter() let locale = priceLocale numberFormatter.numberStyle = .currency numberFormatter.locale = locale - let periodText = period - - if periodText == "year" { - return numberFormatter.string(from: NSDecimalNumber(decimal: (price as Decimal) / Decimal(52.0))) ?? "N/A" + guard let period = subscriptionPeriod else { return "n/a" } + let c = period.numberOfUnits + var periods = 1.0 as Decimal + let inputPrice = price as Decimal + + if period.unit == .year { + periods = Decimal(365 * c) } - - if periodText == "month" { - return numberFormatter.string(from: NSDecimalNumber(decimal: (price as Decimal) * Decimal(11.99) / Decimal(52.0))) ?? "N/A" + + if period.unit == .month { + periods = Decimal(30 * c) } - - if periodText == "week" { - return numberFormatter.string(from: NSDecimalNumber(decimal: (price as Decimal))) ?? "N/A" + + if period.unit == .week { + periods = Decimal(c) / Decimal(7) } - - if periodText == "day" { - return numberFormatter.string(from: NSDecimalNumber(decimal: (price as Decimal))) ?? "N/A" + + if period.unit == .day { + periods = Decimal(c) / Decimal(1) } + + return numberFormatter.string(from: NSDecimalNumber(decimal: inputPrice / periods)) ?? "N/A" + } + } - return "$0.00" + var weeklyPrice: String { + get { + if price == NSDecimalNumber(decimal: 0.00) { + return "$0.00" + } + + let numberFormatter = NumberFormatter() + let locale = priceLocale + numberFormatter.numberStyle = .currency + numberFormatter.locale = locale + + guard let period = subscriptionPeriod else { return "n/a" } + let c = period.numberOfUnits + var periods = 1.0 as Decimal + let inputPrice = price as Decimal + + if period.unit == .year { + periods = Decimal(52 * c) + } + + if period.unit == .month { + periods = Decimal(4 * c) + } + + if period.unit == .week { + periods = Decimal(c) / Decimal(1) + } + + if period.unit == .day { + periods = Decimal(c) / Decimal(7) + } + + return numberFormatter.string(from: NSDecimalNumber(decimal: inputPrice / periods)) ?? "N/A" + } + } + + var monthlyPrice: String { + get { + if price == NSDecimalNumber(decimal: 0.00) { + return "$0.00" + } + + let numberFormatter = NumberFormatter() + let locale = priceLocale + numberFormatter.numberStyle = .currency + numberFormatter.locale = locale + + guard let period = subscriptionPeriod else { return "n/a" } + let c = period.numberOfUnits + var periods = 1.0 as Decimal + let inputPrice = price as Decimal + + if period.unit == .year { + periods = Decimal(12 * c) + } + + if period.unit == .month { + periods = Decimal(1 * c) + } + + if period.unit == .week { + periods = Decimal(c) / Decimal(4) + } + + if period.unit == .day { + periods = Decimal(c) / Decimal(30) + } + + return numberFormatter.string(from: NSDecimalNumber(decimal: inputPrice / periods)) ?? "N/A" + + } + } + + var yearlyPrice: String { + get { + if price == NSDecimalNumber(decimal: 0.00) { + return "$0.00" + } + + let numberFormatter = NumberFormatter() + let locale = priceLocale + numberFormatter.numberStyle = .currency + numberFormatter.locale = locale + + guard let period = subscriptionPeriod else { return "n/a" } + let c = period.numberOfUnits + var periods = 1.0 as Decimal + let inputPrice = price as Decimal + + if period.unit == .year { + periods = Decimal(c) + } + + if period.unit == .month { + periods = Decimal(c) / Decimal(12) + } + + if period.unit == .week { + periods = Decimal(c) / Decimal(52) + } + + if period.unit == .day { + periods = Decimal(c) / Decimal(365) + } + + return numberFormatter.string(from: NSDecimalNumber(decimal: inputPrice / periods)) ?? "N/A" + } } @@ -202,7 +430,7 @@ internal extension SKProduct { let c = trialPeriod.numberOfUnits if trialPeriod.unit == .day { - return "" + return "\(Int(c / 7))" } if trialPeriod.unit == .month { @@ -229,7 +457,7 @@ internal extension SKProduct { let c = trialPeriod.numberOfUnits if trialPeriod.unit == .day { - return "" + return "\(Int(c / 30))" } if trialPeriod.unit == .month { @@ -237,7 +465,7 @@ internal extension SKProduct { } if trialPeriod.unit == .week { - return "" + return "\(Int(c / 4))" } if trialPeriod.unit == .year { @@ -256,15 +484,15 @@ internal extension SKProduct { let c = trialPeriod.numberOfUnits if trialPeriod.unit == .day { - return "" + return "\(Int(c / 365))" } if trialPeriod.unit == .month { - return "" + return "\(Int(c / 12))" } if trialPeriod.unit == .week { - return "" + return "\(Int(c / 52))" } if trialPeriod.unit == .year { diff --git a/Sources/Paywall/Network/Device.swift b/Sources/Paywall/Network/Device.swift index 5ddfb423f..59f73a93b 100644 --- a/Sources/Paywall/Network/Device.swift +++ b/Sources/Paywall/Network/Device.swift @@ -34,7 +34,7 @@ internal class DeviceHelper { } var locale: String { - get { Locale.autoupdatingCurrent.identifier ?? "" } + get { Locale.autoupdatingCurrent.identifier } } var languageCode: String { diff --git a/Sources/Paywall/Paywall/Events.swift b/Sources/Paywall/Paywall/Events.swift index b6f2cc05f..6310b0e05 100644 --- a/Sources/Paywall/Paywall/Events.swift +++ b/Sources/Paywall/Paywall/Events.swift @@ -6,6 +6,7 @@ // import Foundation +import StoreKit extension Paywall { @@ -223,15 +224,15 @@ extension Paywall { case paywallOpen(paywallId: String) case paywallClose(paywallId: String) - case transactionStart(paywallId: String, productId: String) - case transactionComplete(paywallId: String, productId: String) - case transactionFail(paywallId: String, productId: String, message: String) - case transactionAbandon(paywallId: String, productId: String) + case transactionStart(paywallId: String, product: SKProduct) + case transactionComplete(paywallId: String, product: SKProduct) + case transactionFail(paywallId: String, product: SKProduct?, message: String) + case transactionAbandon(paywallId: String, product: SKProduct) - case subscriptionStart(paywallId: String, productId: String) - case freeTrialStart(paywallId: String, productId: String) - case transactionRestore(paywallId: String, productId: String) - case nonRecurringProductPurchase(paywallId: String, productId: String) + case subscriptionStart(paywallId: String, product: SKProduct) + case freeTrialStart(paywallId: String, product: SKProduct) + case transactionRestore(paywallId: String, product: SKProduct?) + case nonRecurringProductPurchase(paywallId: String, product: SKProduct) } @@ -304,34 +305,60 @@ extension Paywall { } } + private static func eventParams(for product: SKProduct?, paywallId: String, otherParams: [String: Any]? = nil) -> [String: Any] { + var output: [String: Any] = [ + "paywall_id": paywallId + ] + + if let p = product { + output["product_id"] = p.productIdentifier + for k in p.eventData.keys { + if let v = p.eventData[k] { + output["product_\(k.camelCaseToSnakeCase())"] = v + } + } + } + + if let p = otherParams { + for k in p.keys { + if let v = p[k] { + output[k] = v + } + } + } + + return output + + } + internal static func track(_ event: InternalEvent, _ customParams: [String: Any] = [:]) { switch event { case .paywallWebviewLoadStart(let paywallId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId], customParams: customParams) + _track(eventName: name(for: event), params: eventParams(for: nil, paywallId: paywallId), customParams: customParams) case .paywallWebviewLoadFail(let paywallId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId], customParams: customParams) + _track(eventName: name(for: event), params: eventParams(for: nil, paywallId: paywallId), customParams: customParams) case .paywallWebviewLoadComplete(let paywallId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId], customParams: customParams) + _track(eventName: name(for: event), params: eventParams(for: nil, paywallId: paywallId), customParams: customParams) case .paywallOpen(let paywallId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId], customParams: customParams) + _track(eventName: name(for: event), params: eventParams(for: nil, paywallId: paywallId), customParams: customParams) case .paywallClose(let paywallId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId], customParams: customParams) - case .transactionStart(let paywallId, let productId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId, "product_id": productId], customParams: customParams) - case .transactionFail(let paywallId, let productId, let message): - _track(eventName: name(for: event), params: ["paywall_id": paywallId, "product_id": productId, "message": message], customParams: customParams) - case .transactionAbandon(let paywallId, let productId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId, "product_id": productId], customParams: customParams) - case .transactionComplete(let paywallId, let productId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId, "product_id": productId], customParams: customParams) - case .subscriptionStart(let paywallId, let productId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId, "product_id": productId], customParams: customParams) - case .freeTrialStart(let paywallId, let productId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId, "product_id": productId], customParams: customParams) - case .transactionRestore(let paywallId, let productId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId, "product_id": productId], customParams: customParams) - case .nonRecurringProductPurchase(let paywallId, let productId): - _track(eventName: name(for: event), params: ["paywall_id": paywallId, "product_id": productId], customParams: customParams) + _track(eventName: name(for: event), params: eventParams(for: nil, paywallId: paywallId), customParams: customParams) + case .transactionStart(let paywallId, let product): + _track(eventName: name(for: event), params: eventParams(for: product, paywallId: paywallId), customParams: customParams) + case .transactionFail(let paywallId, let product, let message): + _track(eventName: name(for: event), params: eventParams(for: product, paywallId: paywallId, otherParams: ["message": message]), customParams: customParams) + case .transactionAbandon(let paywallId, let product): + _track(eventName: name(for: event), params: eventParams(for: product, paywallId: paywallId), customParams: customParams) + case .transactionComplete(let paywallId, let product): + _track(eventName: name(for: event), params: eventParams(for: product, paywallId: paywallId), customParams: customParams) + case .subscriptionStart(let paywallId, let product): + _track(eventName: name(for: event), params: eventParams(for: product, paywallId: paywallId), customParams: customParams) + case .freeTrialStart(let paywallId, let product): + _track(eventName: name(for: event), params: eventParams(for: product, paywallId: paywallId), customParams: customParams) + case .transactionRestore(let paywallId, let product): + _track(eventName: name(for: event), params: eventParams(for: product, paywallId: paywallId), customParams: customParams) + case .nonRecurringProductPurchase(let paywallId, let product): + _track(eventName: name(for: event), params: eventParams(for: product, paywallId: paywallId), customParams: customParams) default: _track(eventName: name(for: event)) } @@ -550,3 +577,23 @@ extension Paywall { struct SuperwallEventError: LocalizedError { var message: String } + + +extension String { + func camelCaseToSnakeCase() -> String { + let acronymPattern = "([A-Z]+)([A-Z][a-z]|[0-9])" + let fullWordsPattern = "([a-z])([A-Z]|[0-9])" + let digitsFirstPattern = "([0-9])([A-Z])" + return self.processCamelCaseRegex(pattern: acronymPattern)? + .processCamelCaseRegex(pattern: fullWordsPattern)? + .processCamelCaseRegex(pattern:digitsFirstPattern)?.lowercased() ?? self.lowercased() + } + + + + fileprivate func processCamelCaseRegex(pattern: String) -> String? { + let regex = try? NSRegularExpression(pattern: pattern, options: []) + let range = NSRange(location: 0, length: count) + return regex?.stringByReplacingMatches(in: self, options: [], range: range, withTemplate: "$1_$2") + } +} diff --git a/Sources/Paywall/Paywall/Paywall.swift b/Sources/Paywall/Paywall/Paywall.swift index 4fdaba4a6..aac511eba 100644 --- a/Sources/Paywall/Paywall/Paywall.swift +++ b/Sources/Paywall/Paywall/Paywall.swift @@ -513,19 +513,19 @@ public class Paywall: NSObject { // purchase callbacks private func _transactionDidBegin(for product: SKProduct) { - Paywall.track(.transactionStart(paywallId: paywallId, productId: product.productIdentifier)) + Paywall.track(.transactionStart(paywallId: paywallId, product: product)) paywallViewController?.loadingState = .loadingPurchase } private func _transactionDidSucceed(for product: SKProduct) { - Paywall.track(.transactionComplete(paywallId: paywallId, productId: product.productIdentifier)) + Paywall.track(.transactionComplete(paywallId: paywallId, product: product)) if let ft = paywallResponse?.isFreeTrialAvailable { if ft { - Paywall.track(.freeTrialStart(paywallId: paywallId, productId: product.productIdentifier)) + Paywall.track(.freeTrialStart(paywallId: paywallId, product: product)) } else { - Paywall.track(.subscriptionStart(paywallId: paywallId, productId: product.productIdentifier)) + Paywall.track(.subscriptionStart(paywallId: paywallId, product: product)) } } @@ -543,7 +543,7 @@ public class Paywall: NSObject { Paywall.delegate?.shouldTryToRestore() self.didTryToAutoRestore = true } else { - Paywall.track(.transactionFail(paywallId: self.paywallId, productId: product.productIdentifier, message: error?.localizedDescription ?? "")) + Paywall.track(.transactionFail(paywallId: self.paywallId, product: product, message: error?.localizedDescription ?? "")) self.paywallViewController?.presentAlert(title: "Please try again", message: error?.localizedDescription ?? "", actionTitle: "Restore Purchase", action: { Paywall.delegate?.shouldTryToRestore() }) @@ -552,19 +552,19 @@ public class Paywall: NSObject { } private func _transactionWasAbandoned(for product: SKProduct) { - Paywall.track(.transactionAbandon(paywallId: paywallId, productId: product.productIdentifier)) + Paywall.track(.transactionAbandon(paywallId: paywallId, product: product)) paywallViewController?.loadingState = .ready } private func _transactionWasRestored() { - Paywall.track(.transactionRestore(paywallId: paywallId, productId: "")) + Paywall.track(.transactionRestore(paywallId: paywallId, product: nil)) _dismiss(userDidPurchase: true) } // if a parent needs to approve the purchase private func _transactionWasDeferred() { paywallViewController?.presentAlert(title: "Waiting for Approval", message: "Thank you! This purchase is pending approval from your parent. Please try again once it is approved.") - Paywall.track(.transactionFail(paywallId: paywallId, productId: "", message: "Needs parental approval")) + Paywall.track(.transactionFail(paywallId: paywallId, product: nil, message: "Needs parental approval")) } diff --git a/Sources/Paywall/Paywall/SWPaywallViewController.swift b/Sources/Paywall/Paywall/SWPaywallViewController.swift index c2997e8d7..7c6900f39 100644 --- a/Sources/Paywall/Paywall/SWPaywallViewController.swift +++ b/Sources/Paywall/Paywall/SWPaywallViewController.swift @@ -92,7 +92,7 @@ internal class SWPaywallViewController: UIViewController { self?.shimmerView.transform = .identity self?.purchaseLoadingIndicator.alpha = 0.0 self?.purchaseLoadingIndicator.transform = CGAffineTransform(scaleX: 0.05, y: 0.05) - }, completion: { [weak self] _ in + }, completion: { _ in // self?.purchaseLoadingIndicator.stopAnimating() }) case .ready: @@ -192,6 +192,7 @@ internal class SWPaywallViewController: UIViewController { wv.scrollView.minimumZoomScale = 1.0 wv.scrollView.backgroundColor = .clear wv.scrollView.isOpaque = false + return wv