From ca17c1cc667c5f3d52fe34938043d5fbae18a8f9 Mon Sep 17 00:00:00 2001 From: True Zhuanjia Date: Mon, 22 Jun 2026 12:30:17 +0800 Subject: [PATCH] - handle iap purchase on app launch - localization fix - iap workflow fixes --- .../Input/CustomOSC/OSCProfilesManager.swift | 2 +- VoidLink/Localization/Localizable.xcstrings | 2 +- VoidLink/Utility/GenericUtils.swift | 14 +++ VoidLink/Utility/IAPManager.swift | 101 +++++++----------- .../ViewControllers/MainFrameViewController.m | 6 +- .../ViewControllers/SettingsViewController.m | 6 +- 6 files changed, 63 insertions(+), 68 deletions(-) diff --git a/VoidLink/Input/CustomOSC/OSCProfilesManager.swift b/VoidLink/Input/CustomOSC/OSCProfilesManager.swift index 5070c8d1..9b7feb08 100644 --- a/VoidLink/Input/CustomOSC/OSCProfilesManager.swift +++ b/VoidLink/Input/CustomOSC/OSCProfilesManager.swift @@ -308,7 +308,7 @@ class OSCProfilesManager: NSObject { let defaults = UserDefaults.standard let needImportDefaultTemplates = defaults.object(forKey: Self.widgetProfileUpdatedKey) == nil - if profiles.count == 0 || needImportDefaultTemplates { + if (profiles.count == 0 || needImportDefaultTemplates) && !GenericUtils.pencilProPurchaseProcessedWithImportingWidgetTemplates { importDefaultTemplates() defaults.set(true, forKey: Self.widgetProfileUpdatedKey) defaults.synchronize() diff --git a/VoidLink/Localization/Localizable.xcstrings b/VoidLink/Localization/Localizable.xcstrings index 3b1d4ae6..cdefd250 100644 --- a/VoidLink/Localization/Localizable.xcstrings +++ b/VoidLink/Localization/Localizable.xcstrings @@ -1765,7 +1765,7 @@ "en" : { "stringUnit" : { "state" : "translated", - "value" : "= FPS Example (Keyboard & Mouse)" + "value" : "FPS Example (Keyboard & Mouse)" } }, "zh-Hans" : { diff --git a/VoidLink/Utility/GenericUtils.swift b/VoidLink/Utility/GenericUtils.swift index 5993297f..09dce520 100644 --- a/VoidLink/Utility/GenericUtils.swift +++ b/VoidLink/Utility/GenericUtils.swift @@ -262,6 +262,20 @@ import UIKit } } + @objc static var pencilProPurchaseProcessedWithImportingWidgetTemplates: Bool = false + @objc static func handleAddOnProductPurchaseIntent(for product:AddOnProduct) { + let key = "addOnProduct_\(product.productId())_purchased" + let defaults = UserDefaults.standard + let purchased = defaults.bool(forKey: key) + if !purchased { + IAPManager.checkPurchaseInfo(product) { info in + if info.valid { + IAPManager.handlePurchaseSuccess(product) + defaults.set(true, forKey: key) + } + } + } + } @objc static var hasTappedOnscreenGyroButton = false @objc static func isFirstTappingOnscreenGyroButton() -> Bool { diff --git a/VoidLink/Utility/IAPManager.swift b/VoidLink/Utility/IAPManager.swift index f17d0d72..2400bc51 100644 --- a/VoidLink/Utility/IAPManager.swift +++ b/VoidLink/Utility/IAPManager.swift @@ -246,7 +246,7 @@ import UIKit return true } } - + @available(iOS 15.0, *) private func handleStoreKit2PurchaseResult(_ result: Product.PurchaseResult, product: AddOnProduct) async { @@ -282,7 +282,10 @@ import UIKit dataMan.saveData() let profileMan = OSCProfilesManager.sharedManager(.zero) var toolkitProfileIndex = profileMan.getIndex(byName: "Pencil Pro") - if toolkitProfileIndex == nil {profileMan.importDefaultTemplates()} + if toolkitProfileIndex == nil { + profileMan.importDefaultTemplates() + GenericUtils.pencilProPurchaseProcessedWithImportingWidgetTemplates = true + } toolkitProfileIndex = profileMan.getIndex(byName: "Pencil Pro") profileMan.setProfileToSelected(toolkitProfileIndex ?? 1) default: @@ -305,34 +308,43 @@ import UIKit } // MARK: - Restore (StoreKit 2) - - @objc public func restorePurchasesStoreKit2() { - if #available(iOS 15.0, *, *) { - Task { - await restoreStoreKit2() - } - } - else{ - restorePurchasesLegacy() - } - } - - @available(iOS 15.0, *, *) - private func restoreStoreKit2() async { - for await result in Transaction.currentEntitlements { - if case .verified(let transaction) = result, - let adp = AddOnProduct.from(productId: transaction.productID) { - await MainActor.run { - self.delegate?.iapManagerDidRestore(adp) + @objc static func restore(product: AddOnProduct, in viewController: UIViewController){ + if #available(iOS 15.0, *) { + Task{ + do { + try await AppStore.sync() + IAPManager.checkPurchaseInfo(product) { info in + if info.valid { + IAPManager.handlePurchaseSuccess(product) + AlertControllerUtil.showAlert( + in: viewController, + title: "", + message: LocalizationHelper.localizedString(forKey:"[%@] has been restored", product.productName()), + withCancel: false, + buttonTitle: "OK".localized, + countdown: 0 + ) + } + else { + NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.restore, userInfo:["interruption": PurchaseInterruption.restore.rawValue]) + AlertControllerUtil.showAlert( + in: viewController, + title: "", + message: LocalizationHelper.localizedString(forKey:"Unable to detect purchased product: %@", product.productName()), + withCancel: false, + buttonTitle: "OK".localized, + countdown: 0 + ) + } + } + + } catch { + NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.restore, userInfo:["interruption": PurchaseInterruption.restore.rawValue]) + print("Restore failed: \(error)") } } } } - - private func restorePurchasesLegacy() { - SKPaymentQueue.default().restoreCompletedTransactions() - } - // MARK: - Listen update @@ -431,42 +443,7 @@ import UIKit let restoreAction = UIAlertAction(title: LocalizationHelper.localizedString(forKey: "Restore Purchase"), style: .default) { _ in if #available(iOS 13.0, *) { - Task{ - do { - if #available(iOS 15.0, *) { - try await AppStore.sync() - IAPManager.checkPurchaseInfo(product) { info in - if info.valid { - IAPManager.handlePurchaseSuccess(product) - AlertControllerUtil.showAlert( - in: viewController, - title: "", - message: LocalizationHelper.localizedString(forKey:"[%@] has been restored", product.productName()), - withCancel: false, - buttonTitle: "OK".localized, - countdown: 0 - ) - } - else { - NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.restore, userInfo:["interruption": PurchaseInterruption.restore.rawValue]) - AlertControllerUtil.showAlert( - in: viewController, - title: "", - message: LocalizationHelper.localizedString(forKey:"Unable to detect purchased product: %@", product.productName()), - withCancel: false, - buttonTitle: "OK".localized, - countdown: 0 - ) - } - } - } - } catch { - NotificationCenter.default.post(name: product.purchaseAbortedNotification(), object: PurchaseInterruption.restore, userInfo:["interruption": PurchaseInterruption.restore.rawValue]) - print("Restore failed: \(error)") - } - } - } else { - // Fallback on earlier versions + IAPManager.restore(product: .PencilProPack, in: viewController) } } diff --git a/VoidLink/ViewControllers/MainFrameViewController.m b/VoidLink/ViewControllers/MainFrameViewController.m index 06745752..e9dd893e 100644 --- a/VoidLink/ViewControllers/MainFrameViewController.m +++ b/VoidLink/ViewControllers/MainFrameViewController.m @@ -1875,10 +1875,12 @@ static NSMutableSet* hostList; [self prewarmSoftKeyboard]; - [IAPManager.shared fetchProducts]; - [self changeDefaultSettings]; [self updatePartialSettings]; + + [IAPManager.shared fetchProducts]; + [GenericUtils handleAddOnProductPurchaseIntentFor:AddOnProductPencilProPack]; + /* if (@available(iOS 15.0, *)) { [IAPManager checkPurchaseInfo:AddOnProductPencilProPack completion:^(PurchaseInfo* info) { diff --git a/VoidLink/ViewControllers/SettingsViewController.m b/VoidLink/ViewControllers/SettingsViewController.m index 3bcdcd47..fe4946ab 100644 --- a/VoidLink/ViewControllers/SettingsViewController.m +++ b/VoidLink/ViewControllers/SettingsViewController.m @@ -3848,8 +3848,10 @@ BOOL isCustomResolution(int resolutionSelected) { } - (void)pencilProPurchaseSucceeded:(NSNotification *)notification{ - self.onScreenWidgetSelector.selectedSegmentIndex = OnScreenControlsLevelCustom; - self.pencilTickSelector.selectedSegmentIndex = ManualTick; + dispatch_async(dispatch_get_main_queue(), ^{ + self.onScreenWidgetSelector.selectedSegmentIndex = OnScreenControlsLevelCustom; + self.pencilTickSelector.selectedSegmentIndex = ManualTick; + }); } - (void)pencilTickIntervalSliderMoved:(UISlider* )sender{