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/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 new file mode 100644 index 0000000000..804c0242fc --- /dev/null +++ b/Sources/Support/DebugUI/DebugContentViews.swift @@ -0,0 +1,255 @@ +// +// 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 && os(iOS) && swift(>=5.8) + +import SwiftUI + +@available(iOS 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.configurationSection + + #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION + self.customerInfoSection + #endif + + 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 configurationSection: some View { + Section("Configuration") { + switch self.model.configuration { + case .loading: + 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") + LabeledContent("Offline Customer Info", + value: config.offlineCustomerInfoSupport ? "enabled" : "disabled") + LabeledContent("Entitlement Verification Mode", value: config.verificationMode.display) + } + } + } + + #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION + private var customerInfoSection: some View { + Section("Customer Info") { + switch self.model.customerInfo { + case .loading: + Text("Loading...") + + case let .loaded(info): + 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)) + } + + case let .failed(error): + Text("Error loading customer info: \(error.localizedDescription)") + } + } + } + #endif + + @ViewBuilder + private var offeringsSection: some View { + Section("Offerings") { + switch self.model.offerings { + case .loading: + Text("Loading...") + + case let .loaded(offerings): + 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) + } + +} + +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/DebugView.swift b/Sources/Support/DebugUI/DebugView.swift new file mode 100644 index 0000000000..07adc5738b --- /dev/null +++ b/Sources/Support/DebugUI/DebugView.swift @@ -0,0 +1,72 @@ +// +// 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 && os(iOS) && swift(>=5.8) + +import SwiftUI + +@available(iOS 16.0, *) +public extension View { + + /// 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: isPresented, + largestUndimmedIdentifier: .fraction(0.6), + cornerRadius: 10, + content: { + DebugSwiftUIRootView() + } + ) + } + +} + +#endif diff --git a/Sources/Support/DebugUI/DebugViewModel.swift b/Sources/Support/DebugUI/DebugViewModel.swift new file mode 100644 index 0000000000..8eeab6c1c2 --- /dev/null +++ b/Sources/Support/DebugUI/DebugViewModel.swift @@ -0,0 +1,130 @@ +// +// 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 && os(iOS) && swift(>=5.8) + +import SwiftUI + +@MainActor +@available(iOS 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 + var offerings: LoadingState = .loading + #if !ENABLE_CUSTOM_ENTITLEMENT_COMPUTATION + @Published + var customerInfo: LoadingState = .loading + #endif + + @Published + var navigationPath = NavigationPath() + + func load() async { + self.configuration = .loaded(.create()) + + self.diagnosticsResult = await .create { try await PurchasesDiagnostics.default.testSDKHealth() } + 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 + } + +} + +@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 { + + @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()) + } catch { + return .failed(error as NSError) + } + } + +} + +@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +private extension DebugViewModel.Configuration { + + static func create(with purchases: Purchases = .shared) -> Self { + return .init( + observerMode: purchases.observerMode, + sandbox: purchases.isSandbox, + storeKit2Enabled: purchases.storeKit2Setting.isEnabledAndAvailable, + offlineCustomerInfoSupport: purchases.offlineCustomerInfoEnabled, + verificationMode: purchases.responseVerificationMode + ) + } + +} + +#endif diff --git a/Sources/Support/DebugUI/DebugViewSheetPresentation.swift b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift new file mode 100644 index 0000000000..91b1e06376 --- /dev/null +++ b/Sources/Support/DebugUI/DebugViewSheetPresentation.swift @@ -0,0 +1,48 @@ +// +// 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 && os(iOS) && swift(>=5.8) + +import SwiftUI + +@available(iOS 16.0, *) +extension View { + + @ViewBuilder + func bottomSheet( + presentationDetents: Set, + isPresented: Binding, + largestUndimmedIdentifier: PresentationDetent = .large, + cornerRadius: CGFloat, + interactiveDismissDisabled: Bool = false, + @ViewBuilder content: @escaping () -> some View + ) -> some View { + self + .sheet(isPresented: isPresented) { + let result = content() + .presentationDetents(presentationDetents) + .presentationDragIndicator(.automatic) + .interactiveDismissDisabled(interactiveDismissDisabled) + + if #available(iOS 16.4, macOS 13.3, tvOS 16.4, watchOS 9.4, *) { + result + .presentationCornerRadius(cornerRadius) + .presentationBackgroundInteraction(.enabled(upThrough: largestUndimmedIdentifier)) + } else { + result + } + } + } +} + +#endif 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..acff811e40 --- /dev/null +++ b/Tests/APITesters/SwiftAPITester/SwiftAPITester/OtherAPI.swift @@ -0,0 +1,27 @@ +// +// OtherAPI.swift +// SwiftAPITester +// +// Created by Nacho Soto on 5/31/23. +// + +import Foundation + +import SwiftUI + +#if DEBUG && os(iOS) && swift(>=5.8) + +@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) + } + +} + +#endif