From a2071e5e519f92b4c60f601f7fe10e9eaca78bfb Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Tue, 30 May 2023 16:39:46 -0700 Subject: [PATCH 01/14] Introduced `debugRevenueCatOverlay()`: new SwiftUI debug overlay --- RevenueCat.xcodeproj/project.pbxproj | 24 ++ Sources/Misc/SystemInfo.swift | 7 +- .../Support/DebugUI/DebugContentViews.swift | 222 ++++++++++++++++++ Sources/Support/DebugUI/DebugView.swift | 40 ++++ Sources/Support/DebugUI/DebugViewModel.swift | 92 ++++++++ .../DebugUI/DebugViewSheetPresentation.swift | 65 +++++ 6 files changed, 449 insertions(+), 1 deletion(-) create mode 100644 Sources/Support/DebugUI/DebugContentViews.swift create mode 100644 Sources/Support/DebugUI/DebugView.swift create mode 100644 Sources/Support/DebugUI/DebugViewModel.swift create mode 100644 Sources/Support/DebugUI/DebugViewSheetPresentation.swift diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index 3e260f9bcc..e0f51357de 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -212,6 +212,10 @@ 4F54DF432A1D8D0700FD72BF /* MockTransactionPoster.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F54DF412A1D8D0700FD72BF /* MockTransactionPoster.swift */; }; 4F69EB092A14406E00ED6D4B /* Matchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F69EB082A14406E00ED6D4B /* Matchers.swift */; }; 4F69EB0A2A14406E00ED6D4B /* Matchers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F69EB082A14406E00ED6D4B /* Matchers.swift */; }; + 4F6BED592A26A14400CD9322 /* DebugView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6BED582A26A14400CD9322 /* DebugView.swift */; }; + 4F6BEDD92A26B55C00CD9322 /* DebugViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6BEDD82A26B55C00CD9322 /* DebugViewModel.swift */; }; + 4F6BEDE02A26B65900CD9322 /* DebugViewSheetPresentation.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6BEDDF2A26B65900CD9322 /* DebugViewSheetPresentation.swift */; }; + 4F6BEDE22A26B69500CD9322 /* DebugContentViews.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6BEDE12A26B69500CD9322 /* DebugContentViews.swift */; }; 4F6BEE1F2A27B02400CD9322 /* CustomEntitlementsComputationIntegrationTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6BEE022A27ADF900CD9322 /* CustomEntitlementsComputationIntegrationTests.swift */; }; 4F6BEE272A27B02400CD9322 /* Nimble in Frameworks */ = {isa = PBXBuildFile; productRef = 4F6BEE092A27B02400CD9322 /* Nimble */; }; 4F6BEE282A27B02400CD9322 /* RevenueCat_CustomEntitlementComputation in Frameworks */ = {isa = PBXBuildFile; productRef = 4F6BEE0D2A27B02400CD9322 /* RevenueCat_CustomEntitlementComputation */; }; @@ -914,6 +918,10 @@ 4F54DF3E2A1D8C7500FD72BF /* MockStoreKit2TransactionFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStoreKit2TransactionFetcher.swift; sourceTree = ""; }; 4F54DF412A1D8D0700FD72BF /* MockTransactionPoster.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockTransactionPoster.swift; sourceTree = ""; }; 4F69EB082A14406E00ED6D4B /* Matchers.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Matchers.swift; sourceTree = ""; }; + 4F6BED582A26A14400CD9322 /* DebugView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugView.swift; sourceTree = ""; }; + 4F6BEDD82A26B55C00CD9322 /* DebugViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugViewModel.swift; sourceTree = ""; }; + 4F6BEDDF2A26B65900CD9322 /* DebugViewSheetPresentation.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugViewSheetPresentation.swift; sourceTree = ""; }; + 4F6BEDE12A26B69500CD9322 /* DebugContentViews.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DebugContentViews.swift; sourceTree = ""; }; 4F6BEE022A27ADF900CD9322 /* CustomEntitlementsComputationIntegrationTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomEntitlementsComputationIntegrationTests.swift; sourceTree = ""; }; 4F6BEE312A27B02400CD9322 /* BackendCustomEntitlementsIntegrationTests.xctest */ = {isa = PBXFileReference; explicitFileType = wrapper.cfbundle; includeInIndex = 0; path = BackendCustomEntitlementsIntegrationTests.xctest; sourceTree = BUILT_PRODUCTS_DIR; }; 4F7DBFBC2A1E986C00A2F511 /* StoreKit2TransactionFetcher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreKit2TransactionFetcher.swift; sourceTree = ""; }; @@ -1994,6 +2002,7 @@ 35E840C1270FB45600899AE2 /* Support */ = { isa = PBXGroup; children = ( + 4F6BED572A26A13800CD9322 /* DebugUI */, A5F0104D2717B3150090732D /* BeginRefundRequestHelper.swift */, 35E840C5270FB47C00899AE2 /* ManageSubscriptionsHelper.swift */, 578C5F2B28DB82DD00A56F02 /* PurchasesDiagnostics.swift */, @@ -2091,6 +2100,17 @@ path = BasicTypes; sourceTree = ""; }; + 4F6BED572A26A13800CD9322 /* DebugUI */ = { + isa = PBXGroup; + children = ( + 4F6BED582A26A14400CD9322 /* DebugView.swift */, + 4F6BEDD82A26B55C00CD9322 /* DebugViewModel.swift */, + 4F6BEDDF2A26B65900CD9322 /* DebugViewSheetPresentation.swift */, + 4F6BEDE12A26B69500CD9322 /* DebugContentViews.swift */, + ); + path = DebugUI; + sourceTree = ""; + }; 4FD291BC2A1E9A180098D1B9 /* StoreKit2 */ = { isa = PBXGroup; children = ( @@ -3058,6 +3078,7 @@ 2DD58DD827F240EB000FDFE3 /* EmptyFile.swift in Sources */, 2CD72942268A823900BFC976 /* Data+Extensions.swift in Sources */, 5766AA3E283C750300FA6091 /* Operators+Extensions.swift in Sources */, + 4F6BEDE22A26B69500CD9322 /* DebugContentViews.swift in Sources */, B3B5FBBC269D121B00104A0C /* Offerings.swift in Sources */, 9A65E03B25918B0900DE00B0 /* CustomerInfoStrings.swift in Sources */, 57CFB98427FE2258002A6730 /* StoreKit2Setting.swift in Sources */, @@ -3253,6 +3274,7 @@ B35042C626CDD3B100905B95 /* PurchasesDelegate.swift in Sources */, 0313FD41268A506400168386 /* DateProvider.swift in Sources */, 57FDAABA284937A0009A48F1 /* SandboxEnvironmentDetector.swift in Sources */, + 4F6BED592A26A14400CD9322 /* DebugView.swift in Sources */, 5733B18E27FF586A00EC2045 /* BackendError.swift in Sources */, B39E811A268E849900D31189 /* AttributionNetwork.swift in Sources */, 57488C7629CB90F90000EE7E /* CustomerInfo+OfflineEntitlements.swift in Sources */, @@ -3269,6 +3291,8 @@ B34605C1279A6E380031CA74 /* NetworkOperation.swift in Sources */, 57488C7829CB90F90000EE7E /* PurchasedSK2Product.swift in Sources */, 5766AA56283D4C5400FA6091 /* IgnoreHashable.swift in Sources */, + 4F6BEDE02A26B65900CD9322 /* DebugViewSheetPresentation.swift in Sources */, + 4F6BEDD92A26B55C00CD9322 /* DebugViewModel.swift in Sources */, 5766C622282DAA700067D886 /* GetIntroEligibilityResponse.swift in Sources */, 35E840CC270FB70D00899AE2 /* ManageSubscriptionsHelper.swift in Sources */, 6E38843A0CAFD551013D0A3F /* StoreProduct.swift in Sources */, diff --git a/Sources/Misc/SystemInfo.swift b/Sources/Misc/SystemInfo.swift index 1a6641bef8..edbb939106 100644 --- a/Sources/Misc/SystemInfo.swift +++ b/Sources/Misc/SystemInfo.swift @@ -163,8 +163,13 @@ class SystemInfo { #if os(iOS) || os(tvOS) var sharedUIApplication: UIApplication? { - UIApplication.value(forKey: "sharedApplication") as? UIApplication + return Self.sharedUIApplication } + + static var sharedUIApplication: UIApplication? { + return UIApplication.value(forKey: "sharedApplication") as? UIApplication + } + #endif static func isAppleSubscription(managementURL: URL) -> Bool { diff --git a/Sources/Support/DebugUI/DebugContentViews.swift b/Sources/Support/DebugUI/DebugContentViews.swift new file mode 100644 index 0000000000..3930556fb8 --- /dev/null +++ b/Sources/Support/DebugUI/DebugContentViews.swift @@ -0,0 +1,222 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// DebugViewContent.swift +// +// Created by Nacho Soto on 5/30/23. + +#if DEBUG && canImport(SwiftUI) && !os(macOS) + +import SwiftUI + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +struct DebugSwiftUIRootView: View { + + @StateObject + private var model = DebugViewModel() + + var body: some View { + NavigationStack(path: self.$model.navigationPath) { + DebugSummaryView(model: self.model) + .navigationDestination(for: DebugViewPath.self) { path in + switch path { + case let .offering(offering): + DebugOfferingView(offering: offering) + + case let .package(package): + DebugPackageView(package: package) + } + } + .background( + Rectangle() + .foregroundStyle(Material.thinMaterial) + .edgesIgnoringSafeArea(.all) + ) + } + .task { + await self.model.load() + } + } + +} + +private enum DebugViewPath: Hashable { + + case offering(Offering) + case package(Package) + +} + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +private struct DebugSummaryView: View { + + @ObservedObject + var model: DebugViewModel + + var body: some View { + List { + self.diagnosticsSection + + self.customerInfoSection + + self.offeringsSection + } + .listStyle(.insetGrouped) + .scrollContentBackground(.hidden) + .navigationTitle("RevenueCat Debug View") + } + + private var diagnosticsSection: some View { + Section("Diagnostics") { + LabeledContent("Status") { + HStack { + Text(self.model.diagnosticsStatus) + self.model.diagnosticsIcon + } + } + } + } + + private var customerInfoSection: some View { + Section("Customer Info") { + switch self.model.customerInfo { + case .loading: + Text("Loading...") + + case let .loaded(info): + VStack { + LabeledContent("User ID", value: info.originalAppUserId) + + if let latestExpiration = info.latestExpirationDate { + LabeledContent("Latest Expiration Date", + value: latestExpiration.formatted(date: .abbreviated, + time: .omitted)) + } + } + + case let .failed(error): + Text("Error loading customer info: \(error.localizedDescription)") + } + } + } + + @ViewBuilder + private var offeringsSection: some View { + Section("Offerings") { + switch self.model.offerings { + case .loading: + Text("Loading...") + + case let .loaded(offerings): + VStack { + ForEach(Array(offerings.all.values)) { offering in + NavigationLink(value: DebugViewPath.offering(offering)) { + VStack { + LabeledContent( + offering.identifier, + value: "\(offering.availablePackages.count) package(s)" + ) + } + } + } + } + + case let .failed(error): + Text("Error loading offerings: \(error.localizedDescription)") + } + } + } + +} + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +private struct DebugOfferingView: View { + + var offering: Offering + + var body: some View { + List { + Section("Data") { + LabeledContent("Identifier", value: self.offering.id) + LabeledContent("Description", value: self.offering.serverDescription) + } + + Section("Packages") { + ForEach(self.offering.availablePackages) { package in + NavigationLink(value: DebugViewPath.package(package)) { + Text(package.identifier) + } + } + } + } + .navigationTitle("Offering") + } + +} + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +private struct DebugPackageView: View { + + var package: Package + + @State private var error: NSError? { + didSet { + if self.error != nil { + self.displayError = true + } + } + } + + @State private var displayError: Bool = false + @State private var purchasing: Bool = false + + var body: some View { + List { + Section("Data") { + LabeledContent("Identifier", value: self.package.identifier) + LabeledContent("Price", value: self.package.localizedPriceString) + LabeledContent("Product", value: self.package.storeProduct.productIdentifier) + LabeledContent("Type", value: self.package.packageType.description ?? "") + } + + Section("Purchasing") { + Button { + _ = Task { + do { + self.purchasing = true + try await self.purchase() + } catch { + self.error = error as NSError + } + + self.purchasing = false + } + } label: { + Text("Purchase") + } + .disabled(self.purchasing) + } + } + .navigationTitle("Package") + .alert( + "Error", + isPresented: self.$displayError, + presenting: self.error + ) { error in + Text(error.localizedDescription) + } + } + + private func purchase() async throws { + _ = try await Purchases.shared.purchase(package: self.package) + } + +} + +#endif diff --git a/Sources/Support/DebugUI/DebugView.swift b/Sources/Support/DebugUI/DebugView.swift new file mode 100644 index 0000000000..4e019c14a6 --- /dev/null +++ b/Sources/Support/DebugUI/DebugView.swift @@ -0,0 +1,40 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// DebugView.swift +// +// Created by Nacho Soto on 5/30/23. + +#if DEBUG && canImport(SwiftUI) && !os(macOS) + +import SwiftUI + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +public extension View { + + // TODO: document + func debugRevenueCatOverlay() -> some View { + self.bottomSheet( + presentationDetents: [ + .fraction(0.2), + .fraction(0.6), + .large + ], + isPresented: .constant(true), + cornerRadius: 10, + transparentBackground: true, + content: { + DebugSwiftUIRootView() + } + ) + } + +} + +#endif diff --git a/Sources/Support/DebugUI/DebugViewModel.swift b/Sources/Support/DebugUI/DebugViewModel.swift new file mode 100644 index 0000000000..c825208d5c --- /dev/null +++ b/Sources/Support/DebugUI/DebugViewModel.swift @@ -0,0 +1,92 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// DebugViewModel.swift +// +// Created by Nacho Soto on 5/30/23. + +import Foundation + +#if DEBUG && canImport(SwiftUI) && !os(macOS) + +import SwiftUI + +@MainActor +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +final class DebugViewModel: ObservableObject { + + @Published + var diagnosticsResult: LoadingState<(), NSError> = .loading + @Published + var customerInfo: LoadingState = .loading + @Published + var offerings: LoadingState = .loading + + @Published + var navigationPath = NavigationPath() + + func load() async { + self.diagnosticsResult = await .create { try await PurchasesDiagnostics.default.testSDKHealth() } + self.customerInfo = await .create { try await Purchases.shared.customerInfo() } + self.offerings = await .create { try await Purchases.shared.offerings() } + } + +} + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +extension DebugViewModel { + + var diagnosticsStatus: String { + switch self.diagnosticsResult { + case .loading: return "Loading..." + case .loaded: return "Configuration OK" + case let .failed(error): return "Error: \(error.localizedDescription)" + } + } + + @ViewBuilder + var diagnosticsIcon: some View { + switch self.diagnosticsResult { + case .loading: + Image(systemName: "gear.circle") + .foregroundColor(.gray) + case .loaded: + Image(systemName: "checkmark.circle.fill") + .foregroundColor(.green) + case .failed: + Image(systemName: "exclamationmark.triangle") + .foregroundColor(.red) + } + } + +} + +// MARK: - + +enum LoadingState { + + case loading + case loaded(Value) + case failed(Error) + +} + +extension LoadingState where Error == NSError { + + static func create(_ loader: @Sendable () async throws -> Value) async -> Self { + do { + return .loaded(try await loader()) + } catch { + return .failed(error as NSError) + } + } + +} + +#endif diff --git a/Sources/Support/DebugUI/DebugViewSheetPresentation.swift b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift new file mode 100644 index 0000000000..4ddc644641 --- /dev/null +++ b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift @@ -0,0 +1,65 @@ +// +// Copyright RevenueCat Inc. All Rights Reserved. +// +// Licensed under the MIT License (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://opensource.org/licenses/MIT +// +// DebugViewSheetPresentation.swift +// +// Created by Nacho Soto on 5/30/23. + +#if DEBUG && canImport(SwiftUI) && !os(macOS) + +import SwiftUI + +@available(iOS 16.0, tvOS 16.0, *) +extension View { + + @ViewBuilder + func bottomSheet( + presentationDetents: Set, + isPresented: Binding, + largestUndimmedIdentifier: UISheetPresentationController.Detent.Identifier = .large, + cornerRadius: CGFloat, + transparentBackground: Bool = false, + interactiveDismissDisabled: Bool = false, + @ViewBuilder content: @escaping () -> some View, + onDismiss: (() -> Void)? = nil + ) -> some View { + self + .sheet(isPresented: isPresented) { + onDismiss?() + } content: { + content() + .presentationDetents(presentationDetents) + .presentationDragIndicator(.automatic) + .interactiveDismissDisabled(interactiveDismissDisabled) + .onAppear { + guard let scene = SystemInfo.sharedUIApplication?.connectedScenes.first as? UIWindowScene, + let rootController = scene.windows.first?.rootViewController, + let presentedController = rootController.presentedViewController, + let sheet = presentedController.presentationController + as? UISheetPresentationController else { + Logger.appleWarning("Sheet not found, unable to configure") + return + } + + // These are only available on `UISheetPresentationController` but not SwiftUI + sheet.largestUndimmedDetentIdentifier = largestUndimmedIdentifier + sheet.preferredCornerRadius = cornerRadius + + // UIKit workaround: avoid tint breaking after dismissing + presentedController.presentingViewController?.view.tintAdjustmentMode = .normal + + if transparentBackground { + presentedController.view.backgroundColor = .clear + } + } + } + } +} + +#endif From 2d7be1e62a33df6992db01ce5b51362438c379d7 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Wed, 31 May 2023 11:22:26 -0700 Subject: [PATCH 02/14] API tester --- .../SwiftAPITester.xcodeproj/project.pbxproj | 4 ++++ .../SwiftAPITester/OtherAPI.swift | 24 +++++++++++++++++++ 2 files changed, 28 insertions(+) create mode 100644 Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift diff --git a/Tests/APITesters/SwiftAPITester/SwiftAPITester.xcodeproj/project.pbxproj b/Tests/APITesters/SwiftAPITester/SwiftAPITester.xcodeproj/project.pbxproj index fa7f630389..76d57a510e 100644 --- a/Tests/APITesters/SwiftAPITester/SwiftAPITester.xcodeproj/project.pbxproj +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester.xcodeproj/project.pbxproj @@ -19,6 +19,7 @@ 2DD778ED270E23460079CBD4 /* OfferingAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D614C726EBE7EA007DDB75 /* OfferingAPI.swift */; }; 2DD778EE270E23460079CBD4 /* EntitlementInfosAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D614C526EBE7EA007DDB75 /* EntitlementInfosAPI.swift */; }; 2DD778EF270E23460079CBD4 /* EntitlementInfoAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D614C826EBE7EA007DDB75 /* EntitlementInfoAPI.swift */; }; + 4F6BEE752A27C77C00CD9322 /* OtherAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4F6BEE742A27C77C00CD9322 /* OtherAPI.swift */; }; 570FAF562864EE1D00D3C769 /* NonSubscriptionTransactionAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 570FAF552864EE1D00D3C769 /* NonSubscriptionTransactionAPI.swift */; }; 5738F40C27866DD00096D623 /* StoreProductDiscountAPI.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5738F40B27866DD00096D623 /* StoreProductDiscountAPI.swift */; }; 5738F42127866F8F0096D623 /* main.swift in Sources */ = {isa = PBXBuildFile; fileRef = A5D614C626EBE7EA007DDB75 /* main.swift */; }; @@ -54,6 +55,7 @@ /* Begin PBXFileReference section */ 2C396F5B281C64AF00669657 /* AdServices.framework */ = {isa = PBXFileReference; lastKnownFileType = wrapper.framework; name = AdServices.framework; path = System/Library/Frameworks/AdServices.framework; sourceTree = SDKROOT; }; 2DD778D0270E233F0079CBD4 /* SwiftAPITester.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = SwiftAPITester.app; sourceTree = BUILT_PRODUCTS_DIR; }; + 4F6BEE742A27C77C00CD9322 /* OtherAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OtherAPI.swift; sourceTree = ""; }; 570FAF552864EE1D00D3C769 /* NonSubscriptionTransactionAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NonSubscriptionTransactionAPI.swift; sourceTree = ""; }; 5738F40B27866DD00096D623 /* StoreProductDiscountAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StoreProductDiscountAPI.swift; sourceTree = ""; }; 5738F429278673A80096D623 /* SubscriptionPeriodAPI.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SubscriptionPeriodAPI.swift; sourceTree = ""; }; @@ -138,6 +140,7 @@ 570FAF552864EE1D00D3C769 /* NonSubscriptionTransactionAPI.swift */, A5D614C726EBE7EA007DDB75 /* OfferingAPI.swift */, A5D614C326EBE7EA007DDB75 /* OfferingsAPI.swift */, + 4F6BEE742A27C77C00CD9322 /* OtherAPI.swift */, A5D614C926EBE7EA007DDB75 /* PackageAPI.swift */, B3A4C833280DE72600D4AE17 /* PromotionalOfferAPI.swift */, A5D614CE26EBE7EA007DDB75 /* PurchasesAPI.swift */, @@ -233,6 +236,7 @@ A513AD35272B4C0100E0C1BA /* RefundRequestStatusAPI.swift in Sources */, 2DD778E8270E23460079CBD4 /* PackageAPI.swift in Sources */, 57918A1328F4C49500BF4963 /* PurchasesDiagnosticsAPI.swift in Sources */, + 4F6BEE752A27C77C00CD9322 /* OtherAPI.swift in Sources */, 570FAF562864EE1D00D3C769 /* NonSubscriptionTransactionAPI.swift in Sources */, 57DE807528074D9C008D6C6F /* StorefrontAPI.swift in Sources */, 2DD778E6270E23460079CBD4 /* PurchasesAPI.swift in Sources */, diff --git a/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift b/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift new file mode 100644 index 0000000000..d3855a2e39 --- /dev/null +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift @@ -0,0 +1,24 @@ +// +// OtherAPI.swift +// SwiftAPITester +// +// Created by Nacho Soto on 5/31/23. +// + +import Foundation + +import SwiftUI + +#if DEBUG && !os(macOS) + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +struct AppView: View { + + var body: some View { + EmptyView() + .debugRevenueCatOverlay() + } + +} + +#endif From 5144fcb67124a97553315a21c51d5af6f707164f Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Wed, 31 May 2023 15:03:58 -0700 Subject: [PATCH 03/14] Documentation --- Sources/Support/DebugUI/DebugView.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Sources/Support/DebugUI/DebugView.swift b/Sources/Support/DebugUI/DebugView.swift index 4e019c14a6..422adc70dd 100644 --- a/Sources/Support/DebugUI/DebugView.swift +++ b/Sources/Support/DebugUI/DebugView.swift @@ -18,7 +18,8 @@ import SwiftUI @available(iOS 16.0, macOS 13.0, tvOS 16.0, *) public extension View { - // TODO: document + /// Adds a bottom sheet overlay to the current view which allows debugging the current setup + /// of ``Offerings`` and ``StoreProduct``s. func debugRevenueCatOverlay() -> some View { self.bottomSheet( presentationDetents: [ From 12c3f2713ccb5edc715df04372a199b3c77f17e4 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Wed, 31 May 2023 16:15:02 -0700 Subject: [PATCH 04/14] Added missing `@available` --- Sources/Support/DebugUI/DebugViewModel.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Support/DebugUI/DebugViewModel.swift b/Sources/Support/DebugUI/DebugViewModel.swift index c825208d5c..5662998823 100644 --- a/Sources/Support/DebugUI/DebugViewModel.swift +++ b/Sources/Support/DebugUI/DebugViewModel.swift @@ -79,6 +79,7 @@ enum LoadingState { extension LoadingState where Error == NSError { + @available(iOS 13.0, macOS 10.15, tvOS 13.0, watchOS 6.2, *) static func create(_ loader: @Sendable () async throws -> Value) async -> Self { do { return .loaded(try await loader()) From 4d88091188cf16f740a5aa46f83f2b3293dd1ea1 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Thu, 1 Jun 2023 16:41:29 -0700 Subject: [PATCH 05/14] Added configuration --- Sources/Purchasing/Purchases/Purchases.swift | 12 ++++ .../Support/DebugUI/DebugContentViews.swift | 60 ++++++++++++++----- Sources/Support/DebugUI/DebugViewModel.swift | 29 +++++++++ 3 files changed, 85 insertions(+), 16 deletions(-) diff --git a/Sources/Purchasing/Purchases/Purchases.swift b/Sources/Purchasing/Purchases/Purchases.swift index c2da88647f..af24ab18fd 100644 --- a/Sources/Purchasing/Purchases/Purchases.swift +++ b/Sources/Purchasing/Purchases/Purchases.swift @@ -1413,6 +1413,10 @@ internal extension Purchases { return self.systemInfo.isSandbox } + var observerMode: Bool { + return self.systemInfo.observerMode + } + var configuredUserDefaults: UserDefaults { return self.userDefaults } @@ -1421,6 +1425,14 @@ internal extension Purchases { return self.backend.offlineCustomerInfoEnabled } + var storeKit2Setting: StoreKit2Setting { + return self.systemInfo.storeKit2Setting + } + + var responseVerificationMode: Signing.ResponseVerificationMode { + return self.systemInfo.responseVerificationMode + } + var publicKey: Signing.PublicKey? { return self.systemInfo.responseVerificationMode.publicKey } diff --git a/Sources/Support/DebugUI/DebugContentViews.swift b/Sources/Support/DebugUI/DebugContentViews.swift index 3930556fb8..9d52559811 100644 --- a/Sources/Support/DebugUI/DebugContentViews.swift +++ b/Sources/Support/DebugUI/DebugContentViews.swift @@ -63,6 +63,8 @@ private struct DebugSummaryView: View { List { self.diagnosticsSection + self.configurationSection + self.customerInfoSection self.offeringsSection @@ -83,6 +85,23 @@ private struct DebugSummaryView: View { } } + private var configurationSection: some View { + Section("Configuration") { + switch self.model.configuration { + case .loading: + Text("Loading...") + + case let .loaded(config): + LabeledContent("Observer mode", value: config.observerMode.description) + LabeledContent("Sandbox", value: config.sandbox.description) + LabeledContent("StoreKit 2", value: config.storeKit2Enabled ? "on" : "off") + LabeledContent("Offline Customer Info", + value: config.offlineCustomerInfoSupport ? "enabled" : "disabled") + LabeledContent("Entitlement Verification Mode", value: config.verificationMode.display) + } + } + } + private var customerInfoSection: some View { Section("Customer Info") { switch self.model.customerInfo { @@ -90,14 +109,13 @@ private struct DebugSummaryView: View { Text("Loading...") case let .loaded(info): - VStack { - LabeledContent("User ID", value: info.originalAppUserId) + LabeledContent("User ID", value: info.originalAppUserId) + LabeledContent("Active Entitlements", value: info.entitlements.active.count.description) - if let latestExpiration = info.latestExpirationDate { - LabeledContent("Latest Expiration Date", - value: latestExpiration.formatted(date: .abbreviated, - time: .omitted)) - } + if let latestExpiration = info.latestExpirationDate { + LabeledContent("Latest Expiration Date", + value: latestExpiration.formatted(date: .abbreviated, + time: .omitted)) } case let .failed(error): @@ -114,15 +132,13 @@ private struct DebugSummaryView: View { Text("Loading...") case let .loaded(offerings): - VStack { - ForEach(Array(offerings.all.values)) { offering in - NavigationLink(value: DebugViewPath.offering(offering)) { - VStack { - LabeledContent( - offering.identifier, - value: "\(offering.availablePackages.count) package(s)" - ) - } + ForEach(Array(offerings.all.values)) { offering in + NavigationLink(value: DebugViewPath.offering(offering)) { + VStack { + LabeledContent( + offering.identifier, + value: "\(offering.availablePackages.count) package(s)" + ) } } } @@ -219,4 +235,16 @@ private struct DebugPackageView: View { } +private extension Signing.ResponseVerificationMode { + + var display: String { + switch self { + case .disabled: return "disabled" + case .informational: return "informational" + case .enforced: return "enforced" + } + } + +} + #endif diff --git a/Sources/Support/DebugUI/DebugViewModel.swift b/Sources/Support/DebugUI/DebugViewModel.swift index 5662998823..ea5ba3a38a 100644 --- a/Sources/Support/DebugUI/DebugViewModel.swift +++ b/Sources/Support/DebugUI/DebugViewModel.swift @@ -21,6 +21,18 @@ import SwiftUI @available(iOS 16.0, macOS 13.0, tvOS 16.0, *) final class DebugViewModel: ObservableObject { + struct Configuration { + + let observerMode: Bool + let sandbox: Bool + let storeKit2Enabled: Bool + let offlineCustomerInfoSupport: Bool + let verificationMode: Signing.ResponseVerificationMode + + } + + var configuration: LoadingState = .loading + @Published var diagnosticsResult: LoadingState<(), NSError> = .loading @Published @@ -32,6 +44,8 @@ final class DebugViewModel: ObservableObject { var navigationPath = NavigationPath() func load() async { + self.configuration = .loaded(.withSharedPurchases()) + self.diagnosticsResult = await .create { try await PurchasesDiagnostics.default.testSDKHealth() } self.customerInfo = await .create { try await Purchases.shared.customerInfo() } self.offerings = await .create { try await Purchases.shared.offerings() } @@ -90,4 +104,19 @@ extension LoadingState where Error == NSError { } +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +private extension DebugViewModel.Configuration { + + static func withSharedPurchases() -> Self { + return .init( + observerMode: Purchases.shared.observerMode, + sandbox: Purchases.shared.isSandbox, + storeKit2Enabled: Purchases.shared.storeKit2Setting.isEnabledAndAvailable, + offlineCustomerInfoSupport: Purchases.shared.offlineCustomerInfoEnabled, + verificationMode: Purchases.shared.responseVerificationMode + ) + } + +} + #endif From bc550ff9d36f46d857500c2b42499f263004a3a0 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Fri, 2 Jun 2023 09:57:56 -0700 Subject: [PATCH 06/14] Only available on iOS --- Sources/Support/DebugUI/DebugContentViews.swift | 4 ++-- Sources/Support/DebugUI/DebugView.swift | 4 ++-- Sources/Support/DebugUI/DebugViewModel.swift | 4 ++-- Sources/Support/DebugUI/DebugViewSheetPresentation.swift | 4 ++-- Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift | 2 +- 5 files changed, 9 insertions(+), 9 deletions(-) diff --git a/Sources/Support/DebugUI/DebugContentViews.swift b/Sources/Support/DebugUI/DebugContentViews.swift index 9d52559811..edab6bf7d9 100644 --- a/Sources/Support/DebugUI/DebugContentViews.swift +++ b/Sources/Support/DebugUI/DebugContentViews.swift @@ -11,11 +11,11 @@ // // Created by Nacho Soto on 5/30/23. -#if DEBUG && canImport(SwiftUI) && !os(macOS) +#if DEBUG && os(iOS) import SwiftUI -@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +@available(iOS 16.0, *) struct DebugSwiftUIRootView: View { @StateObject diff --git a/Sources/Support/DebugUI/DebugView.swift b/Sources/Support/DebugUI/DebugView.swift index 422adc70dd..e67cc34150 100644 --- a/Sources/Support/DebugUI/DebugView.swift +++ b/Sources/Support/DebugUI/DebugView.swift @@ -11,11 +11,11 @@ // // Created by Nacho Soto on 5/30/23. -#if DEBUG && canImport(SwiftUI) && !os(macOS) +#if DEBUG && os(iOS) import SwiftUI -@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +@available(iOS 16.0, *) public extension View { /// Adds a bottom sheet overlay to the current view which allows debugging the current setup diff --git a/Sources/Support/DebugUI/DebugViewModel.swift b/Sources/Support/DebugUI/DebugViewModel.swift index ea5ba3a38a..dea2780700 100644 --- a/Sources/Support/DebugUI/DebugViewModel.swift +++ b/Sources/Support/DebugUI/DebugViewModel.swift @@ -13,12 +13,12 @@ import Foundation -#if DEBUG && canImport(SwiftUI) && !os(macOS) +#if DEBUG && os(iOS) import SwiftUI @MainActor -@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +@available(iOS 16.0, *) final class DebugViewModel: ObservableObject { struct Configuration { diff --git a/Sources/Support/DebugUI/DebugViewSheetPresentation.swift b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift index 4ddc644641..3f42d83a7c 100644 --- a/Sources/Support/DebugUI/DebugViewSheetPresentation.swift +++ b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift @@ -11,11 +11,11 @@ // // Created by Nacho Soto on 5/30/23. -#if DEBUG && canImport(SwiftUI) && !os(macOS) +#if DEBUG && os(iOS) import SwiftUI -@available(iOS 16.0, tvOS 16.0, *) +@available(iOS 16.0, *) extension View { @ViewBuilder diff --git a/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift b/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift index d3855a2e39..f2bdb27779 100644 --- a/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift @@ -9,7 +9,7 @@ import Foundation import SwiftUI -#if DEBUG && !os(macOS) +#if DEBUG && os(iOS) @available(iOS 16.0, macOS 13.0, tvOS 16.0, *) struct AppView: View { From 18190578f316b567bf997be2978ca694d87bfef7 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Fri, 2 Jun 2023 09:58:28 -0700 Subject: [PATCH 07/14] Manual display --- Sources/Support/DebugUI/DebugView.swift | 37 +++++++++++++++++-- .../SwiftAPITester/OtherAPI.swift | 3 ++ 2 files changed, 37 insertions(+), 3 deletions(-) diff --git a/Sources/Support/DebugUI/DebugView.swift b/Sources/Support/DebugUI/DebugView.swift index e67cc34150..c7b7111c9d 100644 --- a/Sources/Support/DebugUI/DebugView.swift +++ b/Sources/Support/DebugUI/DebugView.swift @@ -18,16 +18,47 @@ import SwiftUI @available(iOS 16.0, *) public extension View { - /// Adds a bottom sheet overlay to the current view which allows debugging the current setup - /// of ``Offerings`` and ``StoreProduct``s. + /// Adds a bottom sheet overlay to the current view which allows debugging the current SDK setup. + /// + /// Usage: + /// ```swift + /// var body: some View { + /// YourViewContent() + /// .debugRevenueCatOverlay() + /// } + /// ``` + /// + /// - Note: This will present the overlay automatically on launch. + /// To manage the presentation manually, use `debugRevenueCatOverlay(isPresented:)` func debugRevenueCatOverlay() -> some View { + return self.debugRevenueCatOverlay(isPresented: .constant(true)) + } + + /// Adds a bottom sheet overlay to the current view which allows debugging the current SDK setup. + /// + /// Usage: + /// ```swift + /// @State private var debugOverlayVisible: Bool = false + /// + /// var body: some View { + /// YourViewContent() + /// .debugRevenueCatOverlay(isPresented: self.debugOverlayVisible) + /// + /// Button { + /// self.debugOverlayVisible.toggle() + /// } label: { + /// Text("RevenueCat Debug view") + /// } + /// } + /// ``` + func debugRevenueCatOverlay(isPresented: Binding) -> some View { self.bottomSheet( presentationDetents: [ .fraction(0.2), .fraction(0.6), .large ], - isPresented: .constant(true), + isPresented: isPresented, cornerRadius: 10, transparentBackground: true, content: { diff --git a/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift b/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift index f2bdb27779..2f8f0791af 100644 --- a/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift @@ -14,9 +14,12 @@ import SwiftUI @available(iOS 16.0, macOS 13.0, tvOS 16.0, *) struct AppView: View { + @State private var debugOverlayVisible: Bool = false + var body: some View { EmptyView() .debugRevenueCatOverlay() + .debugRevenueCatOverlay(isPresented: self.$debugOverlayVisible) } } From 98aa9f92785510be4c5a98b2074311c714a74881 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Fri, 2 Jun 2023 10:05:30 -0700 Subject: [PATCH 08/14] Fixed custom entitlement computation build --- Sources/Support/DebugUI/DebugContentViews.swift | 4 ++++ Sources/Support/DebugUI/DebugViewModel.swift | 10 +++++++--- 2 files changed, 11 insertions(+), 3 deletions(-) diff --git a/Sources/Support/DebugUI/DebugContentViews.swift b/Sources/Support/DebugUI/DebugContentViews.swift index edab6bf7d9..9aef3d734e 100644 --- a/Sources/Support/DebugUI/DebugContentViews.swift +++ b/Sources/Support/DebugUI/DebugContentViews.swift @@ -65,7 +65,9 @@ private struct DebugSummaryView: View { self.configurationSection + #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION self.customerInfoSection + #endif self.offeringsSection } @@ -102,6 +104,7 @@ private struct DebugSummaryView: View { } } + #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION private var customerInfoSection: some View { Section("Customer Info") { switch self.model.customerInfo { @@ -123,6 +126,7 @@ private struct DebugSummaryView: View { } } } + #endif @ViewBuilder private var offeringsSection: some View { diff --git a/Sources/Support/DebugUI/DebugViewModel.swift b/Sources/Support/DebugUI/DebugViewModel.swift index dea2780700..2d7c2fa072 100644 --- a/Sources/Support/DebugUI/DebugViewModel.swift +++ b/Sources/Support/DebugUI/DebugViewModel.swift @@ -36,9 +36,11 @@ final class DebugViewModel: ObservableObject { @Published var diagnosticsResult: LoadingState<(), NSError> = .loading @Published - var customerInfo: LoadingState = .loading - @Published var offerings: LoadingState = .loading + #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION + @Published + var customerInfo: LoadingState = .loading + #endif @Published var navigationPath = NavigationPath() @@ -47,8 +49,10 @@ final class DebugViewModel: ObservableObject { self.configuration = .loaded(.withSharedPurchases()) self.diagnosticsResult = await .create { try await PurchasesDiagnostics.default.testSDKHealth() } - self.customerInfo = await .create { try await Purchases.shared.customerInfo() } self.offerings = await .create { try await Purchases.shared.offerings() } + #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION + self.customerInfo = await .create { try await Purchases.shared.customerInfo() } + #endif } } From b34566f456635529bb13aa9ce8410726d4786740 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Fri, 2 Jun 2023 11:50:25 -0700 Subject: [PATCH 09/14] Disable on Xcode 13.x --- Sources/Support/DebugUI/DebugContentViews.swift | 2 +- Sources/Support/DebugUI/DebugView.swift | 2 +- Sources/Support/DebugUI/DebugViewModel.swift | 2 +- Sources/Support/DebugUI/DebugViewSheetPresentation.swift | 2 +- Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift | 2 +- 5 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/Support/DebugUI/DebugContentViews.swift b/Sources/Support/DebugUI/DebugContentViews.swift index 9aef3d734e..95a2bdbff4 100644 --- a/Sources/Support/DebugUI/DebugContentViews.swift +++ b/Sources/Support/DebugUI/DebugContentViews.swift @@ -11,7 +11,7 @@ // // Created by Nacho Soto on 5/30/23. -#if DEBUG && os(iOS) +#if DEBUG && os(iOS) && swift(>=5.8) import SwiftUI diff --git a/Sources/Support/DebugUI/DebugView.swift b/Sources/Support/DebugUI/DebugView.swift index c7b7111c9d..5062135a45 100644 --- a/Sources/Support/DebugUI/DebugView.swift +++ b/Sources/Support/DebugUI/DebugView.swift @@ -11,7 +11,7 @@ // // Created by Nacho Soto on 5/30/23. -#if DEBUG && os(iOS) +#if DEBUG && os(iOS) && swift(>=5.8) import SwiftUI diff --git a/Sources/Support/DebugUI/DebugViewModel.swift b/Sources/Support/DebugUI/DebugViewModel.swift index 2d7c2fa072..df77e844d6 100644 --- a/Sources/Support/DebugUI/DebugViewModel.swift +++ b/Sources/Support/DebugUI/DebugViewModel.swift @@ -13,7 +13,7 @@ import Foundation -#if DEBUG && os(iOS) +#if DEBUG && os(iOS) && swift(>=5.8) import SwiftUI diff --git a/Sources/Support/DebugUI/DebugViewSheetPresentation.swift b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift index 3f42d83a7c..cafd2e0c66 100644 --- a/Sources/Support/DebugUI/DebugViewSheetPresentation.swift +++ b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift @@ -11,7 +11,7 @@ // // Created by Nacho Soto on 5/30/23. -#if DEBUG && os(iOS) +#if DEBUG && os(iOS) && swift(>=5.8) import SwiftUI diff --git a/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift b/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift index 2f8f0791af..acff811e40 100644 --- a/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift @@ -9,7 +9,7 @@ import Foundation import SwiftUI -#if DEBUG && os(iOS) +#if DEBUG && os(iOS) && swift(>=5.8) @available(iOS 16.0, macOS 13.0, tvOS 16.0, *) struct AppView: View { From 3b3c19f6e306daa7de5192043e1d72612a577e2f Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Fri, 2 Jun 2023 12:26:32 -0700 Subject: [PATCH 10/14] Added framework version --- Sources/Support/DebugUI/DebugContentViews.swift | 1 + 1 file changed, 1 insertion(+) diff --git a/Sources/Support/DebugUI/DebugContentViews.swift b/Sources/Support/DebugUI/DebugContentViews.swift index 95a2bdbff4..804c0242fc 100644 --- a/Sources/Support/DebugUI/DebugContentViews.swift +++ b/Sources/Support/DebugUI/DebugContentViews.swift @@ -94,6 +94,7 @@ private struct DebugSummaryView: View { Text("Loading...") case let .loaded(config): + LabeledContent("SDK version", value: SystemInfo.frameworkVersion) LabeledContent("Observer mode", value: config.observerMode.description) LabeledContent("Sandbox", value: config.sandbox.description) LabeledContent("StoreKit 2", value: config.storeKit2Enabled ? "on" : "off") From 51ed2f88f82f740fdd8369e21189cd4addb3a72a Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Tue, 6 Jun 2023 10:37:06 -0700 Subject: [PATCH 11/14] Removed hacks --- Sources/Support/DebugUI/DebugView.swift | 1 - .../DebugUI/DebugViewSheetPresentation.swift | 32 ++++++------------- 2 files changed, 9 insertions(+), 24 deletions(-) diff --git a/Sources/Support/DebugUI/DebugView.swift b/Sources/Support/DebugUI/DebugView.swift index 5062135a45..dd9324ec99 100644 --- a/Sources/Support/DebugUI/DebugView.swift +++ b/Sources/Support/DebugUI/DebugView.swift @@ -60,7 +60,6 @@ public extension View { ], isPresented: isPresented, cornerRadius: 10, - transparentBackground: true, content: { DebugSwiftUIRootView() } diff --git a/Sources/Support/DebugUI/DebugViewSheetPresentation.swift b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift index cafd2e0c66..9e08615b43 100644 --- a/Sources/Support/DebugUI/DebugViewSheetPresentation.swift +++ b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift @@ -22,9 +22,8 @@ extension View { func bottomSheet( presentationDetents: Set, isPresented: Binding, - largestUndimmedIdentifier: UISheetPresentationController.Detent.Identifier = .large, + largestUndimmedIdentifier: PresentationDetent = .large, cornerRadius: CGFloat, - transparentBackground: Bool = false, interactiveDismissDisabled: Bool = false, @ViewBuilder content: @escaping () -> some View, onDismiss: (() -> Void)? = nil @@ -33,31 +32,18 @@ extension View { .sheet(isPresented: isPresented) { onDismiss?() } content: { - content() + let result = content() .presentationDetents(presentationDetents) .presentationDragIndicator(.automatic) .interactiveDismissDisabled(interactiveDismissDisabled) - .onAppear { - guard let scene = SystemInfo.sharedUIApplication?.connectedScenes.first as? UIWindowScene, - let rootController = scene.windows.first?.rootViewController, - let presentedController = rootController.presentedViewController, - let sheet = presentedController.presentationController - as? UISheetPresentationController else { - Logger.appleWarning("Sheet not found, unable to configure") - return - } - // These are only available on `UISheetPresentationController` but not SwiftUI - sheet.largestUndimmedDetentIdentifier = largestUndimmedIdentifier - sheet.preferredCornerRadius = cornerRadius - - // UIKit workaround: avoid tint breaking after dismissing - presentedController.presentingViewController?.view.tintAdjustmentMode = .normal - - if transparentBackground { - presentedController.view.backgroundColor = .clear - } - } + if #available(iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4, *) { + result + .presentationCornerRadius(cornerRadius) + .presentationBackgroundInteraction(.enabled(upThrough: largestUndimmedIdentifier)) + } else { + result + } } } } From 5485e1b3ca908f169ad2be64dbcfcd3f770b0424 Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Tue, 6 Jun 2023 10:41:46 -0700 Subject: [PATCH 12/14] Simplified implementation --- Sources/Support/DebugUI/DebugView.swift | 1 + Sources/Support/DebugUI/DebugViewSheetPresentation.swift | 5 +---- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/Sources/Support/DebugUI/DebugView.swift b/Sources/Support/DebugUI/DebugView.swift index dd9324ec99..07adc5738b 100644 --- a/Sources/Support/DebugUI/DebugView.swift +++ b/Sources/Support/DebugUI/DebugView.swift @@ -59,6 +59,7 @@ public extension View { .large ], isPresented: isPresented, + largestUndimmedIdentifier: .fraction(0.6), cornerRadius: 10, content: { DebugSwiftUIRootView() diff --git a/Sources/Support/DebugUI/DebugViewSheetPresentation.swift b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift index 9e08615b43..91b1e06376 100644 --- a/Sources/Support/DebugUI/DebugViewSheetPresentation.swift +++ b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift @@ -25,13 +25,10 @@ extension View { largestUndimmedIdentifier: PresentationDetent = .large, cornerRadius: CGFloat, interactiveDismissDisabled: Bool = false, - @ViewBuilder content: @escaping () -> some View, - onDismiss: (() -> Void)? = nil + @ViewBuilder content: @escaping () -> some View ) -> some View { self .sheet(isPresented: isPresented) { - onDismiss?() - } content: { let result = content() .presentationDetents(presentationDetents) .presentationDragIndicator(.automatic) From 4772581b73d14478015dde01d4831953a74a6eaa Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Tue, 6 Jun 2023 13:52:08 -0700 Subject: [PATCH 13/14] Added `customerInfoStream` --- Sources/Support/DebugUI/DebugViewModel.swift | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/Sources/Support/DebugUI/DebugViewModel.swift b/Sources/Support/DebugUI/DebugViewModel.swift index df77e844d6..631f780599 100644 --- a/Sources/Support/DebugUI/DebugViewModel.swift +++ b/Sources/Support/DebugUI/DebugViewModel.swift @@ -52,6 +52,10 @@ final class DebugViewModel: ObservableObject { self.offerings = await .create { try await Purchases.shared.offerings() } #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION self.customerInfo = await .create { try await Purchases.shared.customerInfo() } + + for await info in Purchases.shared.customerInfoStream { + self.customerInfo = .loaded(info) + } #endif } From fdc749b03a08da42292d5fa4f9433b25b301c24f Mon Sep 17 00:00:00 2001 From: NachoSoto Date: Tue, 6 Jun 2023 14:37:38 -0700 Subject: [PATCH 14/14] Simplified `Configuration` creation --- Sources/Support/DebugUI/DebugViewModel.swift | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/Sources/Support/DebugUI/DebugViewModel.swift b/Sources/Support/DebugUI/DebugViewModel.swift index 631f780599..8eeab6c1c2 100644 --- a/Sources/Support/DebugUI/DebugViewModel.swift +++ b/Sources/Support/DebugUI/DebugViewModel.swift @@ -46,7 +46,7 @@ final class DebugViewModel: ObservableObject { var navigationPath = NavigationPath() func load() async { - self.configuration = .loaded(.withSharedPurchases()) + self.configuration = .loaded(.create()) self.diagnosticsResult = await .create { try await PurchasesDiagnostics.default.testSDKHealth() } self.offerings = await .create { try await Purchases.shared.offerings() } @@ -115,13 +115,13 @@ extension LoadingState where Error == NSError { @available(iOS 16.0, macOS 13.0, tvOS 16.0, *) private extension DebugViewModel.Configuration { - static func withSharedPurchases() -> Self { + static func create(with purchases: Purchases = .shared) -> Self { return .init( - observerMode: Purchases.shared.observerMode, - sandbox: Purchases.shared.isSandbox, - storeKit2Enabled: Purchases.shared.storeKit2Setting.isEnabledAndAvailable, - offlineCustomerInfoSupport: Purchases.shared.offlineCustomerInfoEnabled, - verificationMode: Purchases.shared.responseVerificationMode + observerMode: purchases.observerMode, + sandbox: purchases.isSandbox, + storeKit2Enabled: purchases.storeKit2Setting.isEnabledAndAvailable, + offlineCustomerInfoSupport: purchases.offlineCustomerInfoEnabled, + verificationMode: purchases.responseVerificationMode ) }