add support for purchase intents
This commit is contained in:
@@ -61,6 +61,7 @@ static NSString* DB_NAME = @"Limelight_iOS.sqlite";
|
||||
// Override point for command tool customization after application launch (works only when user default is nil)
|
||||
[CommandManager presetDefaultCommands];
|
||||
[GenericUtils installSegmentedControlPreviousSelectionTracking];
|
||||
[IAPManager shared];
|
||||
|
||||
// For iOS 12 and below, we need to manually create the window
|
||||
if (@available(iOS 13.0, *)) {
|
||||
|
||||
@@ -9131,6 +9131,52 @@
|
||||
}
|
||||
}
|
||||
},
|
||||
"In-app-purchase not available" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "无法使用 App 内购买"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "無法使用 App 內購買"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "無法使用 App 內購買"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"In-app-purchase not available on this device." : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
"zh-Hans" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "此设备无法使用该 App 内购买项目。"
|
||||
}
|
||||
},
|
||||
"zh-Hant" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "此裝置無法使用該 App 內購買項目。"
|
||||
}
|
||||
},
|
||||
"zh-HK" : {
|
||||
"stringUnit" : {
|
||||
"state" : "translated",
|
||||
"value" : "此裝置無法使用該 App 內購買項目。"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"Indicator offset: %.0f" : {
|
||||
"extractionState" : "manual",
|
||||
"localizations" : {
|
||||
@@ -17289,4 +17335,4 @@
|
||||
}
|
||||
},
|
||||
"version" : "1.1"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -702,6 +702,44 @@ import UIKit
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
@objc static func rootViewController() -> UIViewController? {
|
||||
if #available(iOS 13.0, *) {
|
||||
return UIApplication.shared.connectedScenes
|
||||
.compactMap { $0 as? UIWindowScene }
|
||||
.first { $0.activationState == .foregroundActive }?
|
||||
.windows
|
||||
.first { $0.isKeyWindow }?
|
||||
.rootViewController
|
||||
} else {
|
||||
return UIApplication.shared.keyWindow?.rootViewController
|
||||
}
|
||||
}
|
||||
|
||||
@objc static func topViewController() -> UIViewController? {
|
||||
return topViewController(from: rootViewController())
|
||||
}
|
||||
|
||||
private static func topViewController(from rootViewController: UIViewController?) -> UIViewController? {
|
||||
if let navigationController = rootViewController as? UINavigationController {
|
||||
return topViewController(from: navigationController.visibleViewController)
|
||||
}
|
||||
|
||||
if let tabBarController = rootViewController as? UITabBarController {
|
||||
return topViewController(from: tabBarController.selectedViewController)
|
||||
}
|
||||
|
||||
if let splitViewController = rootViewController as? UISplitViewController,
|
||||
let lastViewController = splitViewController.viewControllers.last {
|
||||
return topViewController(from: lastViewController)
|
||||
}
|
||||
|
||||
if let presentedViewController = rootViewController?.presentedViewController {
|
||||
return topViewController(from: presentedViewController)
|
||||
}
|
||||
|
||||
return rootViewController
|
||||
}
|
||||
}
|
||||
|
||||
extension UIView {
|
||||
|
||||
@@ -8,6 +8,7 @@
|
||||
|
||||
import Foundation
|
||||
import StoreKit
|
||||
import UIKit
|
||||
|
||||
@objc public enum PurchaseStatus: Int {
|
||||
case notPurchased
|
||||
@@ -135,6 +136,11 @@ import StoreKit
|
||||
await listenForTransactions()
|
||||
}
|
||||
}
|
||||
if #available(iOS 16.4, *) {
|
||||
Task {
|
||||
await listenForPurchaseIntents()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Fetch Products (StoreKit 2)
|
||||
@@ -199,6 +205,18 @@ import StoreKit
|
||||
|
||||
@available(iOS 15.0, *, *)
|
||||
private func purchaseStoreKit2(_ product: AddOnProduct) async {
|
||||
guard IAPManager.canPurchase(product) else {
|
||||
AlertControllerUtil.showAlert(
|
||||
in: GenericUtils.topViewController(),
|
||||
title: "In-app-purchase not available".localized,
|
||||
message: "In-app-purchase not available on this device.".localized,
|
||||
withCancel: false,
|
||||
buttonTitle: "OK".localized,
|
||||
countdown: 6
|
||||
)
|
||||
return
|
||||
}
|
||||
|
||||
let pid = product.productId()
|
||||
do {
|
||||
let products = try await Product.products(for: [pid])
|
||||
@@ -211,30 +229,7 @@ import StoreKit
|
||||
}
|
||||
|
||||
let result = try await sk2p.purchase()
|
||||
|
||||
switch result {
|
||||
case .success(let verification):
|
||||
switch verification {
|
||||
case .verified(let transaction):
|
||||
await MainActor.run {
|
||||
self.delegate?.iapManagerDidPurchase(product)
|
||||
}
|
||||
await transaction.finish()
|
||||
IAPManager.handlePurchaseSuccess(product)
|
||||
NotificationCenter.default.post(name: product.purchaseSucceededNotification(), object: nil)
|
||||
case .unverified(_, let err):
|
||||
NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.unlockNow, userInfo:["interruption": PurchaseInterruption.unlockNow.rawValue])
|
||||
await MainActor.run {
|
||||
self.delegate?.iapManagerDidFailWithError(err)
|
||||
}
|
||||
}
|
||||
case .userCancelled:
|
||||
NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.unlockNow, userInfo:["interruption": PurchaseInterruption.unlockNow.rawValue])
|
||||
break
|
||||
default:
|
||||
NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.unlockNow, userInfo:["interruption": PurchaseInterruption.unlockNow.rawValue])
|
||||
break
|
||||
}
|
||||
await handleStoreKit2PurchaseResult(result, product: product)
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.unlockNow, userInfo:["interruption": PurchaseInterruption.unlockNow.rawValue])
|
||||
@@ -243,6 +238,41 @@ import StoreKit
|
||||
}
|
||||
}
|
||||
|
||||
private static func canPurchase(_ product: AddOnProduct) -> Bool {
|
||||
switch product {
|
||||
case .PencilProPack:
|
||||
return GenericUtils.isIPad()
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 15.0, *)
|
||||
private func handleStoreKit2PurchaseResult(_ result: Product.PurchaseResult,
|
||||
product: AddOnProduct) async {
|
||||
switch result {
|
||||
case .success(let verification):
|
||||
switch verification {
|
||||
case .verified(let transaction):
|
||||
await MainActor.run {
|
||||
self.delegate?.iapManagerDidPurchase(product)
|
||||
}
|
||||
await transaction.finish()
|
||||
IAPManager.handlePurchaseSuccess(product)
|
||||
NotificationCenter.default.post(name: product.purchaseSucceededNotification(), object: nil)
|
||||
case .unverified(_, let err):
|
||||
NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.unlockNow, userInfo:["interruption": PurchaseInterruption.unlockNow.rawValue])
|
||||
await MainActor.run {
|
||||
self.delegate?.iapManagerDidFailWithError(err)
|
||||
}
|
||||
}
|
||||
case .userCancelled:
|
||||
NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.unlockNow, userInfo:["interruption": PurchaseInterruption.unlockNow.rawValue])
|
||||
default:
|
||||
NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.unlockNow, userInfo:["interruption": PurchaseInterruption.unlockNow.rawValue])
|
||||
}
|
||||
}
|
||||
|
||||
@objc static func handlePurchaseSuccess(_ product: AddOnProduct) {
|
||||
switch product {
|
||||
case .PencilProPack:
|
||||
@@ -318,6 +348,28 @@ import StoreKit
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@available(iOS 16.4, *)
|
||||
private func listenForPurchaseIntents() async {
|
||||
for await intent in PurchaseIntent.intents {
|
||||
guard let product = AddOnProduct.from(productId: intent.product.id) else {
|
||||
continue
|
||||
}
|
||||
guard IAPManager.canPurchase(product) else {
|
||||
continue
|
||||
}
|
||||
|
||||
do {
|
||||
let result = try await intent.product.purchase()
|
||||
await handleStoreKit2PurchaseResult(result, product: product)
|
||||
} catch {
|
||||
await MainActor.run {
|
||||
NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.unlockNow, userInfo:["interruption": PurchaseInterruption.unlockNow.rawValue])
|
||||
self.delegate?.iapManagerDidFailWithError(error)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: Utils
|
||||
@objc static public func checkPurchaseInfo(
|
||||
|
||||
Reference in New Issue
Block a user