diff --git a/.circleci/config.yml b/.circleci/config.yml index 484394edea..f9939eeee6 100644 --- a/.circleci/config.yml +++ b/.circleci/config.yml @@ -393,6 +393,10 @@ jobs: steps: - checkout - update-spm-installation-commit + - run: + name: SPM RevenueCatUI Release Build + command: swift build -c release --target RevenueCatUI + no_output_timeout: 5m - run: name: SPM RevenueCatUI Tests command: bundle exec fastlane test_revenuecatui @@ -414,6 +418,28 @@ jobs: path: fastlane/test_output destination: scan-test-output + spm-revenuecat-ui-watchos: + <<: *base-job + steps: + - checkout + - update-spm-installation-commit + - run: + name: SPM RevenueCatUI Tests + command: bundle exec fastlane test_revenuecatui + no_output_timeout: 15m + environment: + SCAN_PLATFORM: watchOS Simulator + SCAN_DEVICE: Apple Watch Series 8 (41mm),OS=9.4 + BUILD_SDK: watchsimulator + - compress_result_bundle: + directory: fastlane/test_output + bundle_name: revenuecatui + - store_test_results: + path: fastlane/test_output/revenuecatui/tests.xml + - store_artifacts: + path: fastlane/test_output + destination: scan-test-output + run-test-ios-17: <<: *base-job resource_class: macos.m1.large.gen1 @@ -1017,6 +1043,8 @@ workflows: xcode_version: '14.3.0' - spm-revenuecat-ui-ios-17: xcode_version: '15.1' + - spm-revenuecat-ui-watchos: + xcode_version: '14.3.0' - run-test-ios-17: xcode_version: '15.0.1' - run-test-ios-16: diff --git a/RevenueCatUI/Data/TemplateViewConfiguration.swift b/RevenueCatUI/Data/TemplateViewConfiguration.swift index 2fdc916135..c23b6781d8 100644 --- a/RevenueCatUI/Data/TemplateViewConfiguration.swift +++ b/RevenueCatUI/Data/TemplateViewConfiguration.swift @@ -76,6 +76,13 @@ extension TemplateViewConfiguration.PackageConfiguration { } } + var singleIfNotMultiple: TemplateViewConfiguration.Package? { + switch self { + case let .single(package): return package + case .multiple: return nil + } + } + /// Returns all packages, useful for templates that expect multiple packages. var all: [TemplateViewConfiguration.Package] { switch self { diff --git a/RevenueCatUI/Data/TestData.swift b/RevenueCatUI/Data/TestData.swift index 48e6fb5ac7..3d32536a34 100644 --- a/RevenueCatUI/Data/TestData.swift +++ b/RevenueCatUI/Data/TestData.swift @@ -24,7 +24,8 @@ internal enum TestData { productType: .autoRenewableSubscription, localizedDescription: "PRO weekly", subscriptionGroupIdentifier: "group", - subscriptionPeriod: .init(value: 1, unit: .week) + subscriptionPeriod: .init(value: 1, unit: .week), + locale: Self.locale ) static let monthlyProduct = TestStoreProduct( localizedTitle: "Monthly", @@ -35,7 +36,8 @@ internal enum TestData { localizedDescription: "PRO monthly", subscriptionGroupIdentifier: "group", subscriptionPeriod: .init(value: 1, unit: .month), - introductoryDiscount: Self.intro(7, .day) + introductoryDiscount: Self.intro(7, .day), + locale: Self.locale ) static let threeMonthProduct = TestStoreProduct( localizedTitle: "3 months", @@ -46,7 +48,8 @@ internal enum TestData { localizedDescription: "PRO monthly", subscriptionGroupIdentifier: "group", subscriptionPeriod: .init(value: 3, unit: .month), - introductoryDiscount: Self.intro(7, .day) + introductoryDiscount: Self.intro(7, .day), + locale: Self.locale ) static let sixMonthProduct = TestStoreProduct( localizedTitle: "6 months", @@ -57,7 +60,8 @@ internal enum TestData { localizedDescription: "PRO monthly", subscriptionGroupIdentifier: "group", subscriptionPeriod: .init(value: 6, unit: .month), - introductoryDiscount: Self.intro(7, .day) + introductoryDiscount: Self.intro(7, .day), + locale: Self.locale ) static let annualProduct = TestStoreProduct( localizedTitle: "Annual", @@ -68,7 +72,8 @@ internal enum TestData { localizedDescription: "PRO annual", subscriptionGroupIdentifier: "group", subscriptionPeriod: .init(value: 1, unit: .year), - introductoryDiscount: Self.intro(14, .day, priceString: "$1.99") + introductoryDiscount: Self.intro(14, .day, priceString: "$1.99"), + locale: Self.locale ) static let lifetimeProduct = TestStoreProduct( localizedTitle: "Lifetime", @@ -78,7 +83,8 @@ internal enum TestData { productType: .nonConsumable, localizedDescription: "Lifetime purchase", subscriptionGroupIdentifier: "group", - subscriptionPeriod: nil + subscriptionPeriod: nil, + locale: Self.locale ) static let productWithIntroOffer = TestStoreProduct( localizedTitle: "PRO monthly", @@ -98,7 +104,8 @@ internal enum TestData { numberOfPeriods: 1, type: .introductory ), - discounts: [] + discounts: [], + locale: Self.locale ) static let productWithNoIntroOffer = TestStoreProduct( localizedTitle: "PRO annual", @@ -110,7 +117,8 @@ internal enum TestData { subscriptionGroupIdentifier: "group", subscriptionPeriod: .init(value: 1, unit: .year), introductoryDiscount: nil, - discounts: [] + discounts: [], + locale: Self.locale ) static let weeklyPackage = Package( identifier: PackageType.weekly.identifier, @@ -504,6 +512,13 @@ internal enum TestData { ) static let paywallAssetBaseURL = URL(string: "https://assets.pawwalls.com")! + #if os(watchOS) + // `Locale.current` in watchOS produces `en_001` when running tests + static let locale: Locale = .init(identifier: "en_US") + #else + static let locale: Locale = .current + #endif + private static let offeringIdentifier = "offering" private static func intro( @@ -521,6 +536,7 @@ internal enum TestData { type: .introductory ) } + } // MARK: - diff --git a/RevenueCatUI/Data/UserInterfaceIdiom.swift b/RevenueCatUI/Data/UserInterfaceIdiom.swift index 174e785d9a..d231fc7ddc 100644 --- a/RevenueCatUI/Data/UserInterfaceIdiom.swift +++ b/RevenueCatUI/Data/UserInterfaceIdiom.swift @@ -14,13 +14,16 @@ enum UserInterfaceIdiom { case phone case pad case mac + case watch case unknown } extension UserInterfaceIdiom { - #if canImport(UIKit) && !os(watchOS) + #if os(watchOS) + static let `default`: Self = .watch + #elseif canImport(UIKit) static let `default`: Self = UIDevice.interfaceIdiom #elseif os(macOS) static let `default`: Self = .mac diff --git a/RevenueCatUI/Helpers/ColorInformation+MultiScheme.swift b/RevenueCatUI/Helpers/ColorInformation+MultiScheme.swift index 3e7cb7dfb5..c431d04795 100644 --- a/RevenueCatUI/Helpers/ColorInformation+MultiScheme.swift +++ b/RevenueCatUI/Helpers/ColorInformation+MultiScheme.swift @@ -21,6 +21,9 @@ extension PaywallData.Configuration.ColorInformation { /// - Returns: `PaywallData.Configuration.Colors` combining `light` and `dark` if they're available @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) var multiScheme: PaywallData.Configuration.Colors { + #if os(watchOS) + return self.dark ?? self.light + #else let light = self.light guard let dark = self.dark else { // With no dark information, simply use `light`. @@ -28,6 +31,7 @@ extension PaywallData.Configuration.ColorInformation { } return .combine(light: light, dark: dark) + #endif } } diff --git a/RevenueCatUI/Helpers/PaywallData+Default.swift b/RevenueCatUI/Helpers/PaywallData+Default.swift index d353159891..086d079bbe 100644 --- a/RevenueCatUI/Helpers/PaywallData+Default.swift +++ b/RevenueCatUI/Helpers/PaywallData+Default.swift @@ -17,7 +17,7 @@ import SwiftUI #if canImport(SwiftUI) -@available(iOS 15.0, macOS 12.0, tvOS 15.0, *) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) extension PaywallData { /// Default `PaywallData` to display when attempting to present a ``PaywallView`` with an offering @@ -47,7 +47,8 @@ extension PaywallData { ), localization: Self.localization(Localization.localizedBundle(locale)), assetBaseURL: Self.defaultTemplateBaseURL, - revision: Self.revisionID + revision: Self.revisionID, + locale: locale ) } @@ -123,14 +124,17 @@ private extension PaywallData { #if DEBUG @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(macOS, unavailable) @available(tvOS, unavailable) struct DefaultPaywall_Previews: PreviewProvider { static var previews: some View { PreviewableTemplate(offering: Self.offering) { + #if os(watchOS) + WatchTemplateView($0) + #else Template2View($0) + #endif } } diff --git a/RevenueCatUI/Helpers/PreviewHelpers.swift b/RevenueCatUI/Helpers/PreviewHelpers.swift index 0f0af95f4b..9c12aa7fa0 100644 --- a/RevenueCatUI/Helpers/PreviewHelpers.swift +++ b/RevenueCatUI/Helpers/PreviewHelpers.swift @@ -17,7 +17,6 @@ import SwiftUI #if DEBUG @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(macOS, unavailable) @MainActor enum PreviewHelpers { @@ -41,7 +40,6 @@ enum PreviewHelpers { /// } /// ``` @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(macOS, unavailable) @available(tvOS, unavailable) struct PreviewableTemplate: View { @@ -118,7 +116,7 @@ struct PreviewableTemplate: View { } -@available(iOS 15.0, macOS 12.0, tvOS 15.0, *) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) private extension PaywallViewMode { var layout: PreviewLayout { diff --git a/RevenueCatUI/PaywallView.swift b/RevenueCatUI/PaywallView.swift index 45778c4bb3..f2493367a2 100644 --- a/RevenueCatUI/PaywallView.swift +++ b/RevenueCatUI/PaywallView.swift @@ -14,14 +14,13 @@ import RevenueCat import SwiftUI -#if !os(macOS) && !os(tvOS) && !os(watchOS) +#if !os(macOS) && !os(tvOS) /// A SwiftUI view for displaying a `PaywallData` for an `Offering`. /// /// ### Related Articles /// [Documentation](https://rev.cat/paywalls) @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable, message: "RevenueCatUI does not support watchOS yet") @available(macOS, unavailable, message: "RevenueCatUI does not support macOS yet") @available(tvOS, unavailable, message: "RevenueCatUI does not support tvOS yet") public struct PaywallView: View { @@ -209,8 +208,7 @@ public struct PaywallView: View { } -@available(iOS 15.0, macOS 12.0, tvOS 15.0, *) -@available(watchOS, unavailable) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) @available(macOS, unavailable) @available(tvOS, unavailable) private extension PaywallView { @@ -236,7 +234,6 @@ private extension PaywallView { // MARK: - @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(macOS, unavailable) @available(tvOS, unavailable) struct LoadedOfferingPaywallView: View { @@ -343,7 +340,6 @@ struct LoadedOfferingPaywallView: View { } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(macOS, unavailable) @available(tvOS, unavailable) private extension LoadedOfferingPaywallView { @@ -369,7 +365,6 @@ private extension LoadedOfferingPaywallView { #if DEBUG @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(macOS, unavailable) @available(tvOS, unavailable) struct PaywallView_Previews: PreviewProvider { diff --git a/RevenueCatUI/Templates/Other platforms/WatchTemplateView.swift b/RevenueCatUI/Templates/Other platforms/WatchTemplateView.swift new file mode 100644 index 0000000000..c2f7fcf7ff --- /dev/null +++ b/RevenueCatUI/Templates/Other platforms/WatchTemplateView.swift @@ -0,0 +1,236 @@ +// +// 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 +// +// WatchTemplateView.swift +// +// Created by Nacho Soto. + +import RevenueCat +import SwiftUI + +#if os(watchOS) + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS, unavailable) +@available(macOS, unavailable) +@available(tvOS, unavailable) +struct WatchTemplateView: TemplateViewType { + + let configuration: TemplateViewConfiguration + + @State + private var selectedPackage: TemplateViewConfiguration.Package + + @Environment(\.userInterfaceIdiom) + var userInterfaceIdiom + + @EnvironmentObject + private var introEligibilityViewModel: IntroEligibilityViewModel + @EnvironmentObject + private var purchaseHandler: PurchaseHandler + + init(_ configuration: TemplateViewConfiguration) { + self._selectedPackage = .init(initialValue: configuration.packages.default) + self.configuration = configuration + } + + var body: some View { + ScrollView { + VStack(spacing: self.defaultVerticalPaddingLength) { + Group { + Text(.init(self.selectedLocalization.title)) + .font(self.font(for: .title3)) + .fontWeight(.semibold) + + if let subtitle = self.selectedLocalization.subtitle { + Text(.init(subtitle)) + .font(self.font(for: .subheadline)) + } + } + + if let package = self.configuration.packages.singleIfNotMultiple { + self.offerDetails(package: package, selected: false) + .padding(.top, self.defaultVerticalPaddingLength) + } + + self.packages + .padding(.top, self.defaultVerticalPaddingLength) + + self.button + + FooterView(configuration: self.configuration, + purchaseHandler: self.purchaseHandler) + } + .foregroundColor(self.configuration.colors.text1Color) + .multilineTextAlignment(.center) + .defaultHorizontalPadding() + } + .animation(Constants.fastAnimation, value: self.selectedPackage) + .background { + TemplateBackgroundImageView(configuration: self.configuration) + } + } + + @ViewBuilder + private var packages: some View { + if self.configuration.packages.all.count > 1 { + VStack(spacing: 8) { + ForEach(self.configuration.packages.all, id: \.content.id) { package in + let isSelected = self.selectedPackage.content === package.content + + Button { + self.selectedPackage = package + } label: { + self.packageButton(package, selected: isSelected) + } + .buttonStyle(PackageButtonStyle()) + } + } + + Spacer() + } + } + + @ViewBuilder + private func packageButton(_ package: TemplateViewConfiguration.Package, selected: Bool) -> some View { + VStack(alignment: .leading, spacing: 5) { + self.packageButtonTitle(package, selected: selected) + .font(self.font(for: .body).weight(.medium)) + + self.offerDetails(package: package, selected: selected) + .font(self.font(for: .caption2)) + } + .multilineTextAlignment(.leading) + .frame(maxWidth: .infinity, alignment: .leading) + .defaultPadding() + .overlay { + if selected { + EmptyView() + } else { + self.roundedRectangle + .stroke(self.configuration.colors.text1Color, + lineWidth: 1) + } + } + .background { + if selected { + self.roundedRectangle + .foregroundColor(self.configuration.colors.callToActionBackgroundColor) + } else { + if self.configuration.backgroundImageURLToDisplay != nil { + #if swift(>=5.9) + if #available(watchOS 10.0, *) { + // Blur background if there is a background image. + self.roundedRectangle.foregroundStyle(.thinMaterial) + } else { + self.fadedBackgroundRectangle + } + #else + self.fadedBackgroundRectangle + #endif + } else { + // Otherwise the text should have enough contrast with the selected background color. + EmptyView() + } + } + } + } + + private var fadedBackgroundRectangle: some View { + self.roundedRectangle + .opacity(0.3) + } + + private func offerDetails(package: TemplateViewConfiguration.Package, selected: Bool) -> some View { + IntroEligibilityStateView( + display: .offerDetails, + localization: package.localization, + introEligibility: self.introEligibility[package.content], + foregroundColor: self.textColor(selected) + ) + .fixedSize(horizontal: false, vertical: true) + .font(self.font(for: .body)) + } + + private func packageButtonTitle( + _ package: TemplateViewConfiguration.Package, + selected: Bool + ) -> some View { + HStack { + Constants.checkmarkImage + .hidden(if: !selected) + .overlay { + if selected { + EmptyView() + } else { + Circle() + .foregroundColor(self.configuration.colors.callToActionBackgroundColor.opacity(0.3)) + } + } + + Text(package.localization.offerName ?? package.content.productName) + } + .foregroundColor(self.textColor(selected)) + } + + @ViewBuilder + private var button: some View { + PurchaseButton( + packages: self.configuration.packages, + selectedPackage: self.selectedPackage, + configuration: self.configuration + ) + } + + private var roundedRectangle: some Shape { + RoundedRectangle(cornerRadius: Constants.defaultPackageCornerRadius, style: .continuous) + } + + private func textColor(_ selected: Bool) -> Color { + return selected + ? self.configuration.colors.accent1Color + : self.configuration.colors.text1Color + } + + // MARK: - + + private var introEligibility: [Package: IntroEligibilityStatus] { + return self.introEligibilityViewModel.allEligibility + } + + var selectedLocalization: ProcessedLocalizedConfiguration { + return self.selectedPackage.localization + } + +} + +#if DEBUG + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS, unavailable) +@available(macOS, unavailable) +@available(tvOS, unavailable) +struct WatchTemplateView_Previews: PreviewProvider { + + static var previews: some View { + ForEach([ + TestData.offeringWithIntroOffer, + TestData.offeringWithMultiPackagePaywall + ], id: \.self) { offering in + PreviewableTemplate(offering: offering) { + WatchTemplateView($0) + } + } + } + +} + +#endif + +#endif diff --git a/RevenueCatUI/Templates/Template2View.swift b/RevenueCatUI/Templates/Template2View.swift index a21d05945a..083911cc96 100644 --- a/RevenueCatUI/Templates/Template2View.swift +++ b/RevenueCatUI/Templates/Template2View.swift @@ -14,7 +14,7 @@ import RevenueCat import SwiftUI -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 10.0, *) struct Template2View: TemplateViewType { let configuration: TemplateViewConfiguration @@ -280,7 +280,7 @@ struct Template2View: TemplateViewType { // MARK: - Extensions -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 10.0, *) private extension Template2View { var selectedLocalization: ProcessedLocalizedConfiguration { @@ -289,7 +289,7 @@ private extension Template2View { } -@available(iOS 15.0, macOS 12.0, tvOS 15.0, *) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) private extension PaywallViewMode { var shouldDisplayPackages: Bool { @@ -321,7 +321,7 @@ private extension Bundle { #if DEBUG -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 10.0, *) @available(watchOS, unavailable) @available(macOS, unavailable) @available(tvOS, unavailable) diff --git a/RevenueCatUI/Templates/Template4View.swift b/RevenueCatUI/Templates/Template4View.swift index a14d147eb8..4f2639a3c5 100644 --- a/RevenueCatUI/Templates/Template4View.swift +++ b/RevenueCatUI/Templates/Template4View.swift @@ -401,7 +401,7 @@ private struct PackageButton: View { // MARK: - Extensions -@available(iOS 15.0, macOS 12.0, tvOS 15.0, *) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) private extension PaywallViewMode { var shouldDisplayPackages: Bool { diff --git a/RevenueCatUI/Templates/Template5View.swift b/RevenueCatUI/Templates/Template5View.swift index 9b5113f1f4..bbec1e0c2a 100644 --- a/RevenueCatUI/Templates/Template5View.swift +++ b/RevenueCatUI/Templates/Template5View.swift @@ -301,7 +301,7 @@ private extension Template5View { } -@available(iOS 15.0, macOS 12.0, tvOS 15.0, *) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) private extension PaywallViewMode { var shouldDisplayPackages: Bool { diff --git a/RevenueCatUI/Templates/TemplateViewType.swift b/RevenueCatUI/Templates/TemplateViewType.swift index 8150b8a40e..6066652393 100644 --- a/RevenueCatUI/Templates/TemplateViewType.swift +++ b/RevenueCatUI/Templates/TemplateViewType.swift @@ -70,7 +70,6 @@ private extension PaywallTemplate { } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(macOS, unavailable) @available(tvOS, unavailable) extension PaywallData { @@ -132,6 +131,9 @@ extension PaywallData { @ViewBuilder private static func createView(template: PaywallTemplate, configuration: TemplateViewConfiguration) -> some View { + #if os(watchOS) + WatchTemplateView(configuration) + #else switch template { case .template1: Template1View(configuration) @@ -144,12 +146,12 @@ extension PaywallData { case .template5: Template5View(configuration) } + #endif } } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) extension View { func adaptTemplateView(with configuration: TemplateViewConfiguration) -> some View { @@ -187,8 +189,7 @@ extension View { // MARK: - Private -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 10.0, *) -@available(watchOS, unavailable) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) private extension TemplateViewConfiguration { @ViewBuilder @@ -196,6 +197,8 @@ private extension TemplateViewConfiguration { switch self.mode { case .fullScreen: self.backgroundContent + + #if !os(watchOS) case .footer, .condensedFooter: self.backgroundContent #if canImport(UIKit) @@ -205,6 +208,7 @@ private extension TemplateViewConfiguration { edgesIgnoringSafeArea: .all ) #endif + #endif } } @@ -214,7 +218,21 @@ private extension TemplateViewConfiguration { .edgesIgnoringSafeArea(.all) if self.configuration.blurredBackgroundImage { + #if os(watchOS) + #if swift(>=5.9) + if #available(watchOS 10.0, *) { + view.foregroundStyle(.thinMaterial) + } else { + // Blur is done by `TemplateBackgroundImageView` + view + } + #else + view + #endif + #else + // Blur background if there is a background image. view.foregroundStyle(.thinMaterial) + #endif } else { view.foregroundStyle(self.colors.backgroundColor) } diff --git a/RevenueCatUI/View+PresentPaywall.swift b/RevenueCatUI/View+PresentPaywall.swift index e06c5f3128..041c35f588 100644 --- a/RevenueCatUI/View+PresentPaywall.swift +++ b/RevenueCatUI/View+PresentPaywall.swift @@ -14,10 +14,9 @@ import RevenueCat import SwiftUI -#if !os(macOS) && !os(tvOS) && !os(watchOS) +#if !os(macOS) && !os(tvOS) @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable, message: "RevenueCatUI does not support watchOS yet") @available(macOS, unavailable, message: "RevenueCatUI does not support macOS yet") @available(tvOS, unavailable, message: "RevenueCatUI does not support tvOS yet") extension View { @@ -143,7 +142,6 @@ extension View { } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(macOS, unavailable) @available(tvOS, unavailable) private struct PresentingPaywallModifier: ViewModifier { diff --git a/RevenueCatUI/View+PurchaseRestoreCompleted.swift b/RevenueCatUI/View+PurchaseRestoreCompleted.swift index f980ca1214..e528e9bd2a 100644 --- a/RevenueCatUI/View+PurchaseRestoreCompleted.swift +++ b/RevenueCatUI/View+PurchaseRestoreCompleted.swift @@ -22,7 +22,6 @@ public typealias PurchaseCompletedHandler = @MainActor @Sendable (_ transaction: _ customerInfo: CustomerInfo) -> Void @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable, message: "RevenueCatUI does not support watchOS yet") @available(macOS, unavailable, message: "RevenueCatUI does not support macOS yet") extension View { diff --git a/RevenueCatUI/Views/FooterView.swift b/RevenueCatUI/Views/FooterView.swift index 7cc94195c2..12f36a23ad 100644 --- a/RevenueCatUI/Views/FooterView.swift +++ b/RevenueCatUI/Views/FooterView.swift @@ -67,7 +67,7 @@ struct FooterView: View { if self.mode.displayAllPlansButton, let binding = self.displayingAllPlans { Self.allPlansButton(binding) - if self.configuration.displayRestorePurchases || self.hasTOS || self.hasPrivacy { + if self.configuration.displayRestorePurchases || self.tosURL != nil || self.privacyURL != nil { self.separator } } @@ -75,23 +75,23 @@ struct FooterView: View { if self.configuration.displayRestorePurchases { RestorePurchasesButton(purchaseHandler: self.purchaseHandler) - if self.hasTOS || self.hasPrivacy { + if self.tosURL != nil || self.privacyURL != nil { self.separator } } - if let url = self.configuration.termsOfServiceURL { + if let url = self.tosURL { LinkButton( url: url, titles: "Terms and conditions", "Terms" ) - if self.hasPrivacy { + if self.privacyURL != nil { self.separator } } - if let url = self.configuration.privacyURL { + if let url = self.privacyURL { LinkButton( url: url, titles: "Privacy policy", "Privacy" @@ -127,8 +127,21 @@ struct FooterView: View { return self.boldPreferred && self.interfaceIdiom != .pad } - private var hasTOS: Bool { self.configuration.termsOfServiceURL != nil } - private var hasPrivacy: Bool { self.configuration.privacyURL != nil } + private var tosURL: URL? { + #if os(watchOS) + return nil + #else + self.configuration.termsOfServiceURL + #endif + } + private var privacyURL: URL? { + #if os(watchOS) + return nil + #else + self.configuration.privacyURL + #endif + } + private var fontWeight: Font.Weight { self.bold ? .bold : .regular } fileprivate var font: Font.TextStyle { @@ -172,16 +185,16 @@ private struct RestorePurchasesButton: View { self.displayRestoredAlert = true } } label: { - if #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) { - let largestText = Text("Restore purchases", bundle: .module) + let largestText = Text("Restore purchases", bundle: .module) + if #available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) { ViewThatFits { largestText Text("Restore", bundle: .module) } .accessibilityLabel(largestText) } else { - Text("Restore purchases", bundle: .module) + largestText } } .frame(minHeight: Constants.minimumButtonHeight) diff --git a/RevenueCatUI/Views/IntroEligibilityStateView.swift b/RevenueCatUI/Views/IntroEligibilityStateView.swift index 4cc7f83c3f..ba2d70e69f 100644 --- a/RevenueCatUI/Views/IntroEligibilityStateView.swift +++ b/RevenueCatUI/Views/IntroEligibilityStateView.swift @@ -153,7 +153,7 @@ private extension IntroEligibilityStateView { } -@available(iOS 15.0, macOS 12.0, tvOS 15.0, *) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) private extension Optional { var isEligibleForIntro: Bool { diff --git a/RevenueCatUI/Views/LoadingPaywallView.swift b/RevenueCatUI/Views/LoadingPaywallView.swift index 8985c993b4..83f8b270da 100644 --- a/RevenueCatUI/Views/LoadingPaywallView.swift +++ b/RevenueCatUI/Views/LoadingPaywallView.swift @@ -14,11 +14,10 @@ import RevenueCat import SwiftUI -#if !os(macOS) && !os(tvOS) && !os(watchOS) +#if !os(macOS) && !os(tvOS) /// A `PaywallView` suitable to be displayed as a loading placeholder. @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(macOS, unavailable) @available(tvOS, unavailable) @MainActor @@ -79,7 +78,6 @@ struct LoadingPaywallView: View { } @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(macOS, unavailable) @available(tvOS, unavailable) private extension LoadingPaywallView { @@ -227,8 +225,7 @@ private extension View { #if DEBUG -@available(iOS 15.0, macOS 12.0, tvOS 15.0, *) -@available(watchOS, unavailable) +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) @available(macOS, unavailable) @available(tvOS, unavailable) struct LoadingPaywallView_Previews: PreviewProvider { diff --git a/RevenueCatUI/Views/PurchaseButton.swift b/RevenueCatUI/Views/PurchaseButton.swift index 9a109e35d9..e435d83eae 100644 --- a/RevenueCatUI/Views/PurchaseButton.swift +++ b/RevenueCatUI/Views/PurchaseButton.swift @@ -83,7 +83,9 @@ struct PurchaseButton: View { ) } .frame(maxWidth: .infinity) + #if !os(watchOS) .padding() + #endif .hidden(if: !self.isEnabled) .overlay { if !self.isEnabled { @@ -172,7 +174,6 @@ private struct PurchaseButtonLabel: View { #if DEBUG && canImport(SwiftUI) && canImport(UIKit) @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(tvOS, unavailable) struct PurchaseButton_Previews: PreviewProvider { diff --git a/RevenueCatUI/Views/TemplateBackgroundImageView.swift b/RevenueCatUI/Views/TemplateBackgroundImageView.swift index 60df5ea606..bfc4a4cd4c 100644 --- a/RevenueCatUI/Views/TemplateBackgroundImageView.swift +++ b/RevenueCatUI/Views/TemplateBackgroundImageView.swift @@ -54,6 +54,15 @@ struct TemplateBackgroundImageView: View { RemoteImage(url: url) .blur(radius: 40) .opacity(0.7) + .background { + // Simulate dark materials in pre-watchOS 10.0 + // where `Material` isn't available. + #if os(watchOS) + if #unavailable(watchOS 10.0) { + Color.black + } + #endif + } } else { RemoteImage(url: url) } @@ -66,7 +75,6 @@ struct TemplateBackgroundImageView: View { #if DEBUG @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -@available(watchOS, unavailable) @available(macOS, unavailable) @available(tvOS, unavailable) struct TemplateBackgroundImageView_Previews: PreviewProvider { diff --git a/Sources/Paywalls/PaywallColor.swift b/Sources/Paywalls/PaywallColor.swift index 4facdca2c8..2845967834 100644 --- a/Sources/Paywalls/PaywallColor.swift +++ b/Sources/Paywalls/PaywallColor.swift @@ -65,7 +65,7 @@ extension PaywallColor { } } - #if canImport(UIKit) && !os(watchOS) + #if canImport(UIKit) /// Creates a dynamic color for 2 ``ColorScheme``s. @available(iOS 14.0, macOS 11.0, tvOS 14.0, watchOS 7.0, *) @@ -152,11 +152,14 @@ private extension PaywallColor { // MARK: - Extensions -#if canImport(UIKit) && !os(watchOS) +#if canImport(UIKit) private extension UIColor { @available(iOS 13.0, tvOS 13.0, macCatalyst 13.1, macOS 10.15, watchOS 6.2, *) convenience init(light: UIColor, dark: UIColor) { + #if os(watchOS) + self.init(cgColor: dark.cgColor) + #else self.init { trait in switch trait.userInterfaceStyle { case .dark: @@ -167,6 +170,7 @@ private extension UIColor { return light } } + #endif } } @@ -187,7 +191,6 @@ public extension Color { #if canImport(UIKit) - #if !os(watchOS) @available(iOS 13.0, tvOS 13.0, macOS 10.15, watchOS 6.2, *) private extension Color { @@ -201,7 +204,6 @@ public extension Color { } } - #endif @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) public extension UIColor { diff --git a/Sources/Paywalls/PaywallData.swift b/Sources/Paywalls/PaywallData.swift index bbf7674f84..964c5a7774 100644 --- a/Sources/Paywalls/PaywallData.swift +++ b/Sources/Paywalls/PaywallData.swift @@ -394,14 +394,13 @@ extension PaywallData { config: Configuration, localization: LocalizedConfiguration, assetBaseURL: URL, - revision: Int = 0 + revision: Int = 0, + locale: Locale = .current ) { - let locale = Locale.current.identifier - self.init( templateName: templateName, config: config, - localization: [locale: localization], + localization: [locale.identifier: localization], assetBaseURL: assetBaseURL, revision: revision ) diff --git a/Sources/Paywalls/PaywallViewMode.swift b/Sources/Paywalls/PaywallViewMode.swift index 9716088277..c903bb5b84 100644 --- a/Sources/Paywalls/PaywallViewMode.swift +++ b/Sources/Paywalls/PaywallViewMode.swift @@ -21,10 +21,12 @@ public enum PaywallViewMode { /// Paywall can be displayed as an overlay on top of your own content. /// Multi-package templates will display the package selection. + @available(watchOS, unavailable) case footer /// Paywall can be displayed as an overlay on top of your own content. /// Multi-package templates will include a button to make the package selection visible. + @available(watchOS, unavailable) case condensedFooter /// The default ``PaywallViewMode``: ``PaywallViewMode/fullScreen``. @@ -58,7 +60,23 @@ extension PaywallViewMode { // MARK: - Extensions -extension PaywallViewMode: CaseIterable {} +extension PaywallViewMode: CaseIterable { + + // swiftlint:disable:next missing_docs + public static var allCases: [PaywallViewMode] { + #if os(watchOS) + return [.fullScreen] + #else + return [ + .fullScreen, + .footer, + .condensedFooter + ] + #endif + } + +} + extension PaywallViewMode: Sendable {} extension PaywallViewMode: Hashable {} diff --git a/Tests/RevenueCatUITests/BaseSnapshotTest.swift b/Tests/RevenueCatUITests/BaseSnapshotTest.swift index c2fb502b41..c6ca26ed28 100644 --- a/Tests/RevenueCatUITests/BaseSnapshotTest.swift +++ b/Tests/RevenueCatUITests/BaseSnapshotTest.swift @@ -18,6 +18,8 @@ import SnapshotTesting import SwiftUI import XCTest +#if !os(watchOS) && !os(macOS) + /// Base class for Snapshot tests /// /// ### Automation: @@ -124,3 +126,5 @@ extension View { } } + +#endif diff --git a/Tests/RevenueCatUITests/Data/PaywallDataValidationTests.swift b/Tests/RevenueCatUITests/Data/PaywallDataValidationTests.swift index b687dd1b06..a852a87fa9 100644 --- a/Tests/RevenueCatUITests/Data/PaywallDataValidationTests.swift +++ b/Tests/RevenueCatUITests/Data/PaywallDataValidationTests.swift @@ -22,7 +22,7 @@ class PaywallDataValidationTests: TestCase { func testValidateMissingPaywall() { let offering = TestData.offeringWithNoPaywall - let result = TestData.offeringWithNoPaywall.validatedPaywall() + let result = TestData.offeringWithNoPaywall.validatedPaywall(locale: TestData.locale) Self.verifyPackages(in: result.displayablePaywall, match: offering.availablePackages) Self.snapshot(result.displayablePaywall) @@ -42,7 +42,7 @@ class PaywallDataValidationTests: TestCase { func testValidateValidPaywall() { let offering = TestData.offeringWithSinglePackageFeaturesPaywall - let result = offering.validatedPaywall() + let result = offering.validatedPaywall(locale: TestData.locale) expect(result.displayablePaywall) == offering.paywall expect(result.error).to(beNil()) @@ -53,7 +53,7 @@ class PaywallDataValidationTests: TestCase { let originalOffering = TestData.offeringWithMultiPackagePaywall let offering = originalOffering.with(templateName: templateName) - let result = offering.validatedPaywall() + let result = offering.validatedPaywall(locale: TestData.locale) Self.verifyPackages(in: result.displayablePaywall, match: originalOffering.paywall) Self.snapshot(result.displayablePaywall) @@ -69,7 +69,7 @@ class PaywallDataValidationTests: TestCase { callToAction: "{{ future_variable }}", offerDetails: nil )) - let result = offering.validatedPaywall() + let result = offering.validatedPaywall(locale: TestData.locale) Self.verifyPackages(in: result.displayablePaywall, match: originalOffering.paywall) Self.snapshot(result.displayablePaywall) @@ -86,7 +86,7 @@ class PaywallDataValidationTests: TestCase { ] let offering = originalOffering.with(localization: localization) - let result = offering.validatedPaywall() + let result = offering.validatedPaywall(locale: TestData.locale) Self.verifyPackages(in: result.displayablePaywall, match: originalOffering.paywall) Self.snapshot(result.displayablePaywall) @@ -103,7 +103,7 @@ class PaywallDataValidationTests: TestCase { ] let offering = originalOffering.with(localization: localization) - let result = offering.validatedPaywall() + let result = offering.validatedPaywall(locale: TestData.locale) Self.verifyPackages(in: result.displayablePaywall, match: originalOffering.paywall) Self.snapshot(result.displayablePaywall) @@ -148,11 +148,17 @@ private extension PaywallDataValidationTests { testName: String = #function, line: UInt = #line ) { + #if os(watchOS) + let test = testName + "-watchOS" + #else + let test = testName + #endif + assertSnapshot( matching: paywall.withTestAssetBaseURL, as: .formattedJson, file: file, - testName: testName, + testName: test, line: line ) } diff --git a/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedIconsGeneratesDefaultPaywall-watchOS.1.json b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedIconsGeneratesDefaultPaywall-watchOS.1.json new file mode 100644 index 0000000000..b09ef23812 --- /dev/null +++ b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedIconsGeneratesDefaultPaywall-watchOS.1.json @@ -0,0 +1,42 @@ +{ + "asset_base_url" : "https://assets.pawwalls.com", + "config" : { + "blurred_background_image" : true, + "colors" : { + "light" : { + "background" : "#FFFFFF", + "call_to_action_background" : "#FFFFFF", + "call_to_action_foreground" : "#FFFFFF", + "text1" : "#FFFFFF" + } + }, + "display_restore_purchases" : true, + "images" : { + "background" : "background.jpg", + "header" : null, + "icon" : "revenuecatui_default_paywall_app_icon" + }, + "packages" : [ + "$rc_annual", + "$rc_monthly" + ], + "privacy_url" : null, + "tos_url" : null + }, + "localized_strings" : { + "en_US" : { + "call_to_action" : "Continue", + "call_to_action_with_intro_offer" : null, + "features" : [ + + ], + "offer_details" : "{{ total_price_and_per_month }}", + "offer_details_with_intro_offer" : "Start your {{ sub_offer_duration }} trial, then {{ total_price_and_per_month }}.", + "offer_name" : null, + "subtitle" : null, + "title" : "{{ app_name }}" + } + }, + "revision" : -1, + "template_name" : "2" +} \ No newline at end of file diff --git a/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedTemplateNameGeneratesDefaultPaywall-watchOS.1.json b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedTemplateNameGeneratesDefaultPaywall-watchOS.1.json new file mode 100644 index 0000000000..b09ef23812 --- /dev/null +++ b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedTemplateNameGeneratesDefaultPaywall-watchOS.1.json @@ -0,0 +1,42 @@ +{ + "asset_base_url" : "https://assets.pawwalls.com", + "config" : { + "blurred_background_image" : true, + "colors" : { + "light" : { + "background" : "#FFFFFF", + "call_to_action_background" : "#FFFFFF", + "call_to_action_foreground" : "#FFFFFF", + "text1" : "#FFFFFF" + } + }, + "display_restore_purchases" : true, + "images" : { + "background" : "background.jpg", + "header" : null, + "icon" : "revenuecatui_default_paywall_app_icon" + }, + "packages" : [ + "$rc_annual", + "$rc_monthly" + ], + "privacy_url" : null, + "tos_url" : null + }, + "localized_strings" : { + "en_US" : { + "call_to_action" : "Continue", + "call_to_action_with_intro_offer" : null, + "features" : [ + + ], + "offer_details" : "{{ total_price_and_per_month }}", + "offer_details_with_intro_offer" : "Start your {{ sub_offer_duration }} trial, then {{ total_price_and_per_month }}.", + "offer_name" : null, + "subtitle" : null, + "title" : "{{ app_name }}" + } + }, + "revision" : -1, + "template_name" : "2" +} \ No newline at end of file diff --git a/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedVariableGeneratesDefaultPaywall-watchOS.1.json b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedVariableGeneratesDefaultPaywall-watchOS.1.json new file mode 100644 index 0000000000..b09ef23812 --- /dev/null +++ b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedVariableGeneratesDefaultPaywall-watchOS.1.json @@ -0,0 +1,42 @@ +{ + "asset_base_url" : "https://assets.pawwalls.com", + "config" : { + "blurred_background_image" : true, + "colors" : { + "light" : { + "background" : "#FFFFFF", + "call_to_action_background" : "#FFFFFF", + "call_to_action_foreground" : "#FFFFFF", + "text1" : "#FFFFFF" + } + }, + "display_restore_purchases" : true, + "images" : { + "background" : "background.jpg", + "header" : null, + "icon" : "revenuecatui_default_paywall_app_icon" + }, + "packages" : [ + "$rc_annual", + "$rc_monthly" + ], + "privacy_url" : null, + "tos_url" : null + }, + "localized_strings" : { + "en_US" : { + "call_to_action" : "Continue", + "call_to_action_with_intro_offer" : null, + "features" : [ + + ], + "offer_details" : "{{ total_price_and_per_month }}", + "offer_details_with_intro_offer" : "Start your {{ sub_offer_duration }} trial, then {{ total_price_and_per_month }}.", + "offer_name" : null, + "subtitle" : null, + "title" : "{{ app_name }}" + } + }, + "revision" : -1, + "template_name" : "2" +} \ No newline at end of file diff --git a/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedVariableInFeaturesGeneratesDefaultPaywall-watchOS.1.json b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedVariableInFeaturesGeneratesDefaultPaywall-watchOS.1.json new file mode 100644 index 0000000000..b09ef23812 --- /dev/null +++ b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testUnrecognizedVariableInFeaturesGeneratesDefaultPaywall-watchOS.1.json @@ -0,0 +1,42 @@ +{ + "asset_base_url" : "https://assets.pawwalls.com", + "config" : { + "blurred_background_image" : true, + "colors" : { + "light" : { + "background" : "#FFFFFF", + "call_to_action_background" : "#FFFFFF", + "call_to_action_foreground" : "#FFFFFF", + "text1" : "#FFFFFF" + } + }, + "display_restore_purchases" : true, + "images" : { + "background" : "background.jpg", + "header" : null, + "icon" : "revenuecatui_default_paywall_app_icon" + }, + "packages" : [ + "$rc_annual", + "$rc_monthly" + ], + "privacy_url" : null, + "tos_url" : null + }, + "localized_strings" : { + "en_US" : { + "call_to_action" : "Continue", + "call_to_action_with_intro_offer" : null, + "features" : [ + + ], + "offer_details" : "{{ total_price_and_per_month }}", + "offer_details_with_intro_offer" : "Start your {{ sub_offer_duration }} trial, then {{ total_price_and_per_month }}.", + "offer_name" : null, + "subtitle" : null, + "title" : "{{ app_name }}" + } + }, + "revision" : -1, + "template_name" : "2" +} \ No newline at end of file diff --git a/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testValidateMissingPaywall-watchOS.1.json b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testValidateMissingPaywall-watchOS.1.json new file mode 100644 index 0000000000..8233a78336 --- /dev/null +++ b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testValidateMissingPaywall-watchOS.1.json @@ -0,0 +1,42 @@ +{ + "asset_base_url" : "https://assets.pawwalls.com", + "config" : { + "blurred_background_image" : true, + "colors" : { + "light" : { + "background" : "#FFFFFF", + "call_to_action_background" : "#FFFFFF", + "call_to_action_foreground" : "#FFFFFF", + "text1" : "#FFFFFF" + } + }, + "display_restore_purchases" : true, + "images" : { + "background" : "background.jpg", + "header" : null, + "icon" : "revenuecatui_default_paywall_app_icon" + }, + "packages" : [ + "$rc_monthly", + "$rc_annual" + ], + "privacy_url" : null, + "tos_url" : null + }, + "localized_strings" : { + "en_US" : { + "call_to_action" : "Continue", + "call_to_action_with_intro_offer" : null, + "features" : [ + + ], + "offer_details" : "{{ total_price_and_per_month }}", + "offer_details_with_intro_offer" : "Start your {{ sub_offer_duration }} trial, then {{ total_price_and_per_month }}.", + "offer_name" : null, + "subtitle" : null, + "title" : "{{ app_name }}" + } + }, + "revision" : -1, + "template_name" : "2" +} \ No newline at end of file diff --git a/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testValidateMissingPaywallWithSpanishLocalization-watchOS.1.json b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testValidateMissingPaywallWithSpanishLocalization-watchOS.1.json new file mode 100644 index 0000000000..2b0797d9d4 --- /dev/null +++ b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testValidateMissingPaywallWithSpanishLocalization-watchOS.1.json @@ -0,0 +1,42 @@ +{ + "asset_base_url" : "https://assets.pawwalls.com", + "config" : { + "blurred_background_image" : true, + "colors" : { + "light" : { + "background" : "#FFFFFF", + "call_to_action_background" : "#FFFFFF", + "call_to_action_foreground" : "#FFFFFF", + "text1" : "#FFFFFF" + } + }, + "display_restore_purchases" : true, + "images" : { + "background" : "background.jpg", + "header" : null, + "icon" : "revenuecatui_default_paywall_app_icon" + }, + "packages" : [ + "$rc_monthly", + "$rc_annual" + ], + "privacy_url" : null, + "tos_url" : null + }, + "localized_strings" : { + "es_ES" : { + "call_to_action" : "Continuar", + "call_to_action_with_intro_offer" : null, + "features" : [ + + ], + "offer_details" : "{{ total_price_and_per_month }}", + "offer_details_with_intro_offer" : "{{ sub_offer_duration }} gratis y luego {{ total_price_and_per_month }}.", + "offer_name" : null, + "subtitle" : null, + "title" : "{{ app_name }}" + } + }, + "revision" : -1, + "template_name" : "2" +} \ No newline at end of file diff --git a/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testValidateMissingPaywallWithSpanishLocalization.1.json b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testValidateMissingPaywallWithSpanishLocalization.1.json index 27ab527f78..1e59e2b438 100644 --- a/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testValidateMissingPaywallWithSpanishLocalization.1.json +++ b/Tests/RevenueCatUITests/Data/__Snapshots__/PaywallDataValidationTests/testValidateMissingPaywallWithSpanishLocalization.1.json @@ -26,7 +26,7 @@ "tos_url" : null }, "localized_strings" : { - "en_US" : { + "es_ES" : { "call_to_action" : "Continuar", "call_to_action_with_intro_offer" : null, "features" : [ diff --git a/Tests/RevenueCatUITests/Helpers/DataExtensions.swift b/Tests/RevenueCatUITests/Helpers/DataExtensions.swift index 6750e197d6..c568da0311 100644 --- a/Tests/RevenueCatUITests/Helpers/DataExtensions.swift +++ b/Tests/RevenueCatUITests/Helpers/DataExtensions.swift @@ -52,7 +52,7 @@ extension PaywallData { } /// For snapshot tests to be able to produce a consistent `assetBaseURL` - @available(iOS 15.0, macOS 12.0, tvOS 15.0, *) + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) var withTestAssetBaseURL: Self { var copy = self copy.assetBaseURL = TestData.paywallAssetBaseURL diff --git a/Tests/RevenueCatUITests/PaywallFooterTests.swift b/Tests/RevenueCatUITests/PaywallFooterTests.swift index a2d208bf58..8bf5e34a37 100644 --- a/Tests/RevenueCatUITests/PaywallFooterTests.swift +++ b/Tests/RevenueCatUITests/PaywallFooterTests.swift @@ -17,7 +17,7 @@ import RevenueCat import SwiftUI import XCTest -#if !os(macOS) +#if !os(watchOS) && !os(macOS) @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) @MainActor diff --git a/Tests/RevenueCatUITests/PaywallViewEventsTests.swift b/Tests/RevenueCatUITests/PaywallViewEventsTests.swift index ef626478af..935ccd3979 100644 --- a/Tests/RevenueCatUITests/PaywallViewEventsTests.swift +++ b/Tests/RevenueCatUITests/PaywallViewEventsTests.swift @@ -17,7 +17,7 @@ import RevenueCat import SwiftUI import XCTest -#if !os(macOS) +#if !os(watchOS) && !os(macOS) @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) @MainActor diff --git a/Tests/RevenueCatUITests/PresentIfNeededTests.swift b/Tests/RevenueCatUITests/PresentIfNeededTests.swift index a3a8f772fd..ea036e79bd 100644 --- a/Tests/RevenueCatUITests/PresentIfNeededTests.swift +++ b/Tests/RevenueCatUITests/PresentIfNeededTests.swift @@ -17,7 +17,7 @@ import RevenueCat import SwiftUI import XCTest -#if !os(macOS) +#if !os(watchOS) && !os(macOS) @available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) @MainActor diff --git a/Tests/RevenueCatUITests/PurchaseCompletedHandlerTests.swift b/Tests/RevenueCatUITests/PurchaseCompletedHandlerTests.swift index b2ad9bde9e..f72db0f20a 100644 --- a/Tests/RevenueCatUITests/PurchaseCompletedHandlerTests.swift +++ b/Tests/RevenueCatUITests/PurchaseCompletedHandlerTests.swift @@ -17,7 +17,7 @@ import RevenueCat import SwiftUI import XCTest -#if !os(macOS) +#if !os(watchOS) && !os(macOS) @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) @MainActor diff --git a/Tests/RevenueCatUITests/Templates/OtherPaywallViewTests.swift b/Tests/RevenueCatUITests/Templates/OtherPaywallViewTests.swift index 8a5d09a94b..48d6782919 100644 --- a/Tests/RevenueCatUITests/Templates/OtherPaywallViewTests.swift +++ b/Tests/RevenueCatUITests/Templates/OtherPaywallViewTests.swift @@ -10,7 +10,7 @@ import RevenueCat @testable import RevenueCatUI import SnapshotTesting -#if !os(macOS) +#if !os(watchOS) && !os(macOS) @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) class OtherPaywallViewTests: BaseSnapshotTest { diff --git a/Tests/RevenueCatUITests/Templates/PaywallViewDynamicTypeTests.swift b/Tests/RevenueCatUITests/Templates/PaywallViewDynamicTypeTests.swift index 117b2f08ac..64ae1ec4c2 100644 --- a/Tests/RevenueCatUITests/Templates/PaywallViewDynamicTypeTests.swift +++ b/Tests/RevenueCatUITests/Templates/PaywallViewDynamicTypeTests.swift @@ -11,6 +11,8 @@ import RevenueCat import SnapshotTesting import SwiftUI +#if !os(watchOS) && !os(macOS) + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) class PaywallViewDynamicTypeTests: BaseSnapshotTest { @@ -74,3 +76,5 @@ private extension PaywallViewDynamicTypeTests { } } + +#endif diff --git a/Tests/RevenueCatUITests/Templates/PaywallViewLocalizationTests.swift b/Tests/RevenueCatUITests/Templates/PaywallViewLocalizationTests.swift index 006eb767e8..d56ffe77b0 100644 --- a/Tests/RevenueCatUITests/Templates/PaywallViewLocalizationTests.swift +++ b/Tests/RevenueCatUITests/Templates/PaywallViewLocalizationTests.swift @@ -11,6 +11,8 @@ import RevenueCat import SnapshotTesting import SwiftUI +#if !os(watchOS) && !os(macOS) + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) class PaywallViewLocalizationTests: BaseSnapshotTest { @@ -84,3 +86,5 @@ private extension PaywallViewLocalizationTests { ) } + +#endif diff --git a/Tests/RevenueCatUITests/Templates/Template1ViewTests.swift b/Tests/RevenueCatUITests/Templates/Template1ViewTests.swift index 888f2bc5d9..dbcfc95e3a 100644 --- a/Tests/RevenueCatUITests/Templates/Template1ViewTests.swift +++ b/Tests/RevenueCatUITests/Templates/Template1ViewTests.swift @@ -15,7 +15,7 @@ import RevenueCat import SnapshotTesting import SwiftUI -#if !os(macOS) +#if !os(watchOS) && !os(macOS) @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) class Template1ViewTests: BaseSnapshotTest { diff --git a/Tests/RevenueCatUITests/Templates/Template2ViewTests.swift b/Tests/RevenueCatUITests/Templates/Template2ViewTests.swift index f0d6bf8920..f2d8ec94d7 100644 --- a/Tests/RevenueCatUITests/Templates/Template2ViewTests.swift +++ b/Tests/RevenueCatUITests/Templates/Template2ViewTests.swift @@ -14,7 +14,7 @@ import RevenueCat @testable import RevenueCatUI import SnapshotTesting -#if !os(macOS) +#if !os(watchOS) && !os(macOS) @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) class Template2ViewTests: BaseSnapshotTest { diff --git a/Tests/RevenueCatUITests/Templates/Template3ViewTests.swift b/Tests/RevenueCatUITests/Templates/Template3ViewTests.swift index b06a18cb07..426358dfe1 100644 --- a/Tests/RevenueCatUITests/Templates/Template3ViewTests.swift +++ b/Tests/RevenueCatUITests/Templates/Template3ViewTests.swift @@ -14,7 +14,7 @@ import RevenueCat @testable import RevenueCatUI import SnapshotTesting -#if !os(macOS) +#if !os(watchOS) && !os(macOS) @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) class Template3ViewTests: BaseSnapshotTest { diff --git a/Tests/RevenueCatUITests/Templates/Template4ViewTests.swift b/Tests/RevenueCatUITests/Templates/Template4ViewTests.swift index c5e6200b0b..f1f9c77b98 100644 --- a/Tests/RevenueCatUITests/Templates/Template4ViewTests.swift +++ b/Tests/RevenueCatUITests/Templates/Template4ViewTests.swift @@ -14,7 +14,7 @@ import RevenueCat @testable import RevenueCatUI import SnapshotTesting -#if !os(macOS) +#if !os(watchOS) && !os(macOS) @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) class Template4ViewTests: BaseSnapshotTest { diff --git a/Tests/RevenueCatUITests/Templates/Template5ViewTests.swift b/Tests/RevenueCatUITests/Templates/Template5ViewTests.swift index 3b06f5bb78..e32f266108 100644 --- a/Tests/RevenueCatUITests/Templates/Template5ViewTests.swift +++ b/Tests/RevenueCatUITests/Templates/Template5ViewTests.swift @@ -14,7 +14,7 @@ import RevenueCat @testable import RevenueCatUI import SnapshotTesting -#if !os(macOS) +#if !os(watchOS) && !os(macOS) @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) class Template5ViewTests: BaseSnapshotTest { diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/project.pbxproj b/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/project.pbxproj index b8623c50a1..2ca3244248 100644 --- a/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/project.pbxproj +++ b/Tests/TestingApps/PaywallsTester/PaywallsTester.xcodeproj/project.pbxproj @@ -399,6 +399,9 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_WKWatchOnly = NO; + "INFOPLIST_KEY_WKWatchOnly[sdk=watchos*]" = YES; + "INFOPLIST_KEY_WKWatchOnly[sdk=watchsimulator*]" = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; @@ -407,13 +410,14 @@ PRODUCT_BUNDLE_IDENTIFIER = com.revenuecat.PaywallsTester; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator watchsimulator watchos"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,6,7"; + TARGETED_DEVICE_FAMILY = "1,2,3,4,5,6,7"; + WATCHOS_DEPLOYMENT_TARGET = 8.0; }; name = Debug; }; @@ -443,6 +447,9 @@ "INFOPLIST_KEY_UIStatusBarStyle[sdk=iphonesimulator*]" = UIStatusBarStyleDefault; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPad = "UIInterfaceOrientationPortrait UIInterfaceOrientationPortraitUpsideDown UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; INFOPLIST_KEY_UISupportedInterfaceOrientations_iPhone = "UIInterfaceOrientationPortrait UIInterfaceOrientationLandscapeLeft UIInterfaceOrientationLandscapeRight"; + INFOPLIST_KEY_WKWatchOnly = NO; + "INFOPLIST_KEY_WKWatchOnly[sdk=watchos*]" = YES; + "INFOPLIST_KEY_WKWatchOnly[sdk=watchsimulator*]" = YES; IPHONEOS_DEPLOYMENT_TARGET = 15.0; LD_RUNPATH_SEARCH_PATHS = "@executable_path/Frameworks"; "LD_RUNPATH_SEARCH_PATHS[sdk=macosx*]" = "@executable_path/../Frameworks"; @@ -451,13 +458,14 @@ PRODUCT_BUNDLE_IDENTIFIER = com.revenuecat.PaywallsTester; PRODUCT_NAME = "$(TARGET_NAME)"; SDKROOT = auto; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator xros xrsimulator watchsimulator watchos"; SUPPORTS_MACCATALYST = YES; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SUPPORTS_XR_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; SWIFT_VERSION = 5.0; - TARGETED_DEVICE_FAMILY = "1,2,6,7"; + TARGETED_DEVICE_FAMILY = "1,2,3,4,5,6,7"; + WATCHOS_DEPLOYMENT_TARGET = 8.0; }; name = Release; }; diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester/Assets.xcassets/AppIcon.appiconset/Contents.json b/Tests/TestingApps/PaywallsTester/PaywallsTester/Assets.xcassets/AppIcon.appiconset/Contents.json index b886cb34d4..168168499a 100644 --- a/Tests/TestingApps/PaywallsTester/PaywallsTester/Assets.xcassets/AppIcon.appiconset/Contents.json +++ b/Tests/TestingApps/PaywallsTester/PaywallsTester/Assets.xcassets/AppIcon.appiconset/Contents.json @@ -7,54 +7,10 @@ "size" : "1024x1024" }, { - "idiom" : "mac", - "scale" : "1x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "16x16" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "32x32" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "128x128" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "256x256" - }, - { - "idiom" : "mac", - "scale" : "1x", - "size" : "512x512" - }, - { - "idiom" : "mac", - "scale" : "2x", - "size" : "512x512" + "filename" : "appstore1024 1.png", + "idiom" : "universal", + "platform" : "watchos", + "size" : "1024x1024" } ], "info" : { diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester/Assets.xcassets/AppIcon.appiconset/appstore1024 1.png b/Tests/TestingApps/PaywallsTester/PaywallsTester/Assets.xcassets/AppIcon.appiconset/appstore1024 1.png new file mode 100644 index 0000000000..a243629909 Binary files /dev/null and b/Tests/TestingApps/PaywallsTester/PaywallsTester/Assets.xcassets/AppIcon.appiconset/appstore1024 1.png differ diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/AppContentView.swift b/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/AppContentView.swift index 0afc6ea20f..e19a01a8af 100644 --- a/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/AppContentView.swift +++ b/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/AppContentView.swift @@ -112,15 +112,17 @@ struct AppContentView: View { self.showingDefaultPaywall.toggle() } + #if !os(watchOS) ProminentButton(title: "Present PaywallViewController") { self.presentPaywallViewController() } + #endif } .padding(.horizontal) .padding(.bottom, 80) .frame(maxWidth: .infinity, maxHeight: .infinity) .navigationTitle("Simple App") - #if DEBUG + #if DEBUG && !os(watchOS) .overlay { if #available(iOS 16.0, macOS 13.0, *) { DebugView() @@ -154,6 +156,7 @@ struct AppContentView: View { } } + #if !os(watchOS) private func presentPaywallViewController() { let paywall = PaywallViewController(displayCloseButton: true) paywall.modalPresentationStyle = .pageSheet @@ -169,6 +172,7 @@ struct AppContentView: View { rootController.present(paywall, animated: true) } + #endif } @@ -185,7 +189,9 @@ private struct ProminentButton: View { .frame(maxWidth: .infinity) } .buttonStyle(.borderedProminent) + #if !os(watchOS) .controlSize(.large) + #endif .tint(self.background) .foregroundColor(.white) } @@ -218,6 +224,8 @@ extension CustomerInfo { } +#if !os(watchOS) + private extension UIApplication { @available(iOS 13.0, macCatalyst 13.1, *) @@ -234,12 +242,13 @@ private extension UIApplication { } +#endif #if DEBUG @testable import RevenueCatUI -@available(iOS 16.0, macOS 13.0, tvOS 16.0, *) +@available(iOS 16.0, macOS 13.0, tvOS 16.0, watchOS 9.0, *) struct AppContentView_Previews: PreviewProvider { static var previews: some View { diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/CustomPaywall.swift b/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/CustomPaywall.swift index a69b5905c1..e20d1458dc 100644 --- a/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/CustomPaywall.swift +++ b/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/CustomPaywall.swift @@ -5,7 +5,7 @@ // Created by Nacho Soto on 8/9/23. // -#if DEBUG +#if DEBUG && !os(watchOS) import RevenueCat @testable import RevenueCatUI diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/DebugView.swift b/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/DebugView.swift index 5a3947eaf8..76d901f773 100644 --- a/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/DebugView.swift +++ b/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/DebugView.swift @@ -8,7 +8,7 @@ import SwiftUI import RevenueCat -#if DEBUG +#if DEBUG && !os(watchOS) @available(iOS 16.0, macOS 13.0, *) struct DebugView: View { diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/OfferingsList.swift b/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/OfferingsList.swift index ae54651f03..c76e3f8d3b 100644 --- a/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/OfferingsList.swift +++ b/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/OfferingsList.swift @@ -106,9 +106,11 @@ struct OfferingsList: View { OfferButton(offering: offering, paywall: paywall) { self.presentedPaywall = .init(offering: offering, mode: .default) } - .contextMenu { - self.contextMenu(for: offering) - } + #if !os(watchOS) + .contextMenu { + self.contextMenu(for: offering) + } + #endif #endif } else { Text(offering.serverDescription) @@ -124,12 +126,14 @@ struct OfferingsList: View { } } + #if !os(watchOS) @ViewBuilder private func contextMenu(for offering: Offering) -> some View { ForEach(PaywallViewMode.allCases, id: \.self) { mode in self.button(for: mode, offering: offering) } } + #endif @ViewBuilder private func button(for selectedMode: PaywallViewMode, offering: Offering) -> some View { @@ -167,13 +171,14 @@ private struct PaywallPresenter: View { var offering: Offering var mode: PaywallViewMode - var displayCloseButton: Bool = true + var displayCloseButton: Bool = Self.defaultDisplayCloseButton var body: some View { switch self.mode { case .fullScreen: PaywallView(offering: self.offering, displayCloseButton: self.displayCloseButton) + #if !os(watchOS) case .footer: CustomPaywallContent() .paywallFooter(offering: self.offering) @@ -181,9 +186,16 @@ private struct PaywallPresenter: View { case .condensedFooter: CustomPaywallContent() .paywallFooter(offering: self.offering, condensed: true) + #endif } } + #if os(watchOS) + private static let defaultDisplayCloseButton = false + #else + private static let defaultDisplayCloseButton = true + #endif + } extension OfferingsList.Template: CustomStringConvertible { diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/SamplePaywallsList.swift b/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/SamplePaywallsList.swift index c953b2e8e2..cf31006101 100644 --- a/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/SamplePaywallsList.swift +++ b/Tests/TestingApps/PaywallsTester/PaywallsTester/Views/SamplePaywallsList.swift @@ -36,29 +36,33 @@ struct SamplePaywallsList: View { case .fullScreen: PaywallView(offering: Self.loader.offering(for: template), customerInfo: Self.loader.customerInfo, - displayCloseButton: true, + displayCloseButton: Self.displayCloseButton, introEligibility: Self.introEligibility, purchaseHandler: .default()) + #if !os(watchOS) case .footer, .condensedFooter: CustomPaywall(offering: Self.loader.offering(for: template), customerInfo: Self.loader.customerInfo, condensed: mode == .condensedFooter, introEligibility: Self.introEligibility, purchaseHandler: .default()) + #endif } case let .customFont(template): PaywallView(offering: Self.loader.offering(for: template), customerInfo: Self.loader.customerInfo, fonts: Self.customFontProvider, - displayCloseButton: true, + displayCloseButton: Self.displayCloseButton, introEligibility: Self.introEligibility, purchaseHandler: .default()) + #if !os(watchOS) case let .customPaywall(mode): CustomPaywall(customerInfo: Self.loader.customerInfo, condensed: mode == .condensedFooter) + #endif case .missingPaywall: PaywallView(offering: Self.loader.offeringWithDefaultPaywall(), @@ -96,6 +100,7 @@ struct SamplePaywallsList: View { } Section("Other") { + #if !os(watchOS) Button { self.display = .customPaywall(.footer) } label: { @@ -109,6 +114,7 @@ struct SamplePaywallsList: View { TemplateLabel(name: "Custom + condensed footer", icon: PaywallViewMode.condensedFooter.icon) } + #endif Button { self.display = .missingPaywall @@ -127,7 +133,14 @@ struct SamplePaywallsList: View { .buttonStyle(.plain) } + #if os(watchOS) + private static let customFontProvider = CustomPaywallFontProvider(fontName: "Courier New") + private static let displayCloseButton = false + #else private static let customFontProvider = CustomPaywallFontProvider(fontName: "Papyrus") + private static let displayCloseButton = true + #endif + private static let loader: SamplePaywallLoader = .init() private static let introEligibility: TrialOrIntroEligibilityChecker = .init { packages in return Dictionary( @@ -165,6 +178,7 @@ private extension SamplePaywallsList { case template(PaywallTemplate, PaywallViewMode) case customFont(PaywallTemplate) + @available(watchOS, unavailable) case customPaywall(PaywallViewMode) case missingPaywall case unrecognizedPaywall diff --git a/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTests.swift b/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTests.swift index 9fa137f15a..dbbaae03f7 100644 --- a/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTests.swift +++ b/Tests/UnitTests/Networking/Backend/BackendPostReceiptDataTests.swift @@ -405,7 +405,7 @@ class BackendPostReceiptDataTests: BaseBackendPostReceiptDataTests { offeringIdentifier: offeringIdentifier, paywallRevision: 5, sessionID: .init(uuidString: "73616D70-6C65-2073-7472-696E67000000")!, - displayMode: .condensedFooter, + displayMode: .fullScreen, localeIdentifier: "en_US", darkMode: true ) diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS12-testPostsReceiptDataWithPresentedPaywall.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS12-testPostsReceiptDataWithPresentedPaywall.1.json index f8477bd633..7e090ac5f8 100644 --- a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS12-testPostsReceiptDataWithPresentedPaywall.1.json +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS12-testPostsReceiptDataWithPresentedPaywall.1.json @@ -18,7 +18,7 @@ "observer_mode" : false, "paywall" : { "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "locale" : "en_US", "revision" : 5, "session_id" : "73616D70-6C65-2073-7472-696E67000000" diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS13-testPostsReceiptDataWithPresentedPaywall.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS13-testPostsReceiptDataWithPresentedPaywall.1.json index 292b17c66a..9955cb841d 100644 --- a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS13-testPostsReceiptDataWithPresentedPaywall.1.json +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS13-testPostsReceiptDataWithPresentedPaywall.1.json @@ -18,7 +18,7 @@ "observer_mode" : false, "paywall" : { "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "locale" : "en_US", "revision" : 5, "session_id" : "73616D70-6C65-2073-7472-696E67000000" diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS14-testPostsReceiptDataWithPresentedPaywall.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS14-testPostsReceiptDataWithPresentedPaywall.1.json index 1538291042..2ee76b116e 100644 --- a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS14-testPostsReceiptDataWithPresentedPaywall.1.json +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS14-testPostsReceiptDataWithPresentedPaywall.1.json @@ -18,7 +18,7 @@ "observer_mode" : false, "paywall" : { "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "locale" : "en_US", "revision" : 5, "session_id" : "73616D70-6C65-2073-7472-696E67000000" diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS15-testPostsReceiptDataWithPresentedPaywall.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS15-testPostsReceiptDataWithPresentedPaywall.1.json index 1538291042..2ee76b116e 100644 --- a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS15-testPostsReceiptDataWithPresentedPaywall.1.json +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS15-testPostsReceiptDataWithPresentedPaywall.1.json @@ -18,7 +18,7 @@ "observer_mode" : false, "paywall" : { "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "locale" : "en_US", "revision" : 5, "session_id" : "73616D70-6C65-2073-7472-696E67000000" diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS16-testPostsReceiptDataWithPresentedPaywall.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS16-testPostsReceiptDataWithPresentedPaywall.1.json index 1538291042..2ee76b116e 100644 --- a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS16-testPostsReceiptDataWithPresentedPaywall.1.json +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS16-testPostsReceiptDataWithPresentedPaywall.1.json @@ -18,7 +18,7 @@ "observer_mode" : false, "paywall" : { "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "locale" : "en_US", "revision" : 5, "session_id" : "73616D70-6C65-2073-7472-696E67000000" diff --git a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS17-testPostsReceiptDataWithPresentedPaywall.1.json b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS17-testPostsReceiptDataWithPresentedPaywall.1.json index 1538291042..2ee76b116e 100644 --- a/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS17-testPostsReceiptDataWithPresentedPaywall.1.json +++ b/Tests/UnitTests/Networking/Backend/__Snapshots__/BackendPostReceiptDataTests/iOS17-testPostsReceiptDataWithPresentedPaywall.1.json @@ -18,7 +18,7 @@ "observer_mode" : false, "paywall" : { "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "locale" : "en_US", "revision" : 5, "session_id" : "73616D70-6C65-2073-7472-696E67000000" diff --git a/Tests/UnitTests/Paywalls/Events/PaywallEventsBackendTests.swift b/Tests/UnitTests/Paywalls/Events/PaywallEventsBackendTests.swift index 50658e17b3..ebb6818717 100644 --- a/Tests/UnitTests/Paywalls/Events/PaywallEventsBackendTests.swift +++ b/Tests/UnitTests/Paywalls/Events/PaywallEventsBackendTests.swift @@ -84,7 +84,7 @@ private extension BackendPaywallEventTests { offeringIdentifier: "offering_1", paywallRevision: 5, sessionID: .init(uuidString: "98CC0F1D-7665-4093-9624-1D7308FFF4DB")!, - displayMode: .condensedFooter, + displayMode: .fullScreen, localeIdentifier: "es_ES", darkMode: true ) diff --git a/Tests/UnitTests/Paywalls/Events/PaywallEventsRequestTests.swift b/Tests/UnitTests/Paywalls/Events/PaywallEventsRequestTests.swift index f90ea5ad8c..0200491dcc 100644 --- a/Tests/UnitTests/Paywalls/Events/PaywallEventsRequestTests.swift +++ b/Tests/UnitTests/Paywalls/Events/PaywallEventsRequestTests.swift @@ -60,7 +60,7 @@ class PaywallEventsRequestTests: TestCase { offeringIdentifier: "offering", paywallRevision: 5, sessionID: .init(uuidString: "98CC0F1D-7665-4093-9624-1D7308FFF4DB")!, - displayMode: .condensedFooter, + displayMode: .fullScreen, localeIdentifier: "es_ES", darkMode: true ) diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json index 5b3d840919..eaa2430f5e 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithMultipleEvents.1.json @@ -8,7 +8,7 @@ { "app_user_id" : "user", "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "id" : "72164C05-2BDC-4807-8918-A4105F727DEB", "locale" : "es_ES", "offering_id" : "offering_1", diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json index 7b710c2dad..d2c78dfe5b 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS15-testPostPaywallEventsWithOneEvent.1.json @@ -8,7 +8,7 @@ { "app_user_id" : "user", "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "id" : "72164C05-2BDC-4807-8918-A4105F727DEB", "locale" : "es_ES", "offering_id" : "offering_1", diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json index 5b3d840919..eaa2430f5e 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithMultipleEvents.1.json @@ -8,7 +8,7 @@ { "app_user_id" : "user", "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "id" : "72164C05-2BDC-4807-8918-A4105F727DEB", "locale" : "es_ES", "offering_id" : "offering_1", diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json index 7b710c2dad..d2c78dfe5b 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS16-testPostPaywallEventsWithOneEvent.1.json @@ -8,7 +8,7 @@ { "app_user_id" : "user", "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "id" : "72164C05-2BDC-4807-8918-A4105F727DEB", "locale" : "es_ES", "offering_id" : "offering_1", diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS17-testPostPaywallEventsWithMultipleEvents.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS17-testPostPaywallEventsWithMultipleEvents.1.json index 5b3d840919..eaa2430f5e 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS17-testPostPaywallEventsWithMultipleEvents.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS17-testPostPaywallEventsWithMultipleEvents.1.json @@ -8,7 +8,7 @@ { "app_user_id" : "user", "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "id" : "72164C05-2BDC-4807-8918-A4105F727DEB", "locale" : "es_ES", "offering_id" : "offering_1", diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS17-testPostPaywallEventsWithOneEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS17-testPostPaywallEventsWithOneEvent.1.json index 7b710c2dad..d2c78dfe5b 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS17-testPostPaywallEventsWithOneEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsBackendTests/iOS17-testPostPaywallEventsWithOneEvent.1.json @@ -8,7 +8,7 @@ { "app_user_id" : "user", "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "id" : "72164C05-2BDC-4807-8918-A4105F727DEB", "locale" : "es_ES", "offering_id" : "offering_1", diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json index 6dcff1bf05..58699346a2 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCancelEvent.1.json @@ -1,7 +1,7 @@ { "app_user_id" : "Jack Shepard", "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "id" : "72164C05-2BDC-4807-8918-A4105F727DEB", "locale" : "es_ES", "offering_id" : "offering", diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json index f33763282b..d360699389 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testCloseEvent.1.json @@ -1,7 +1,7 @@ { "app_user_id" : "Jack Shepard", "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "id" : "72164C05-2BDC-4807-8918-A4105F727DEB", "locale" : "es_ES", "offering_id" : "offering", diff --git a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testImpressionEvent.1.json b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testImpressionEvent.1.json index fc23c5d280..ce4b71fe98 100644 --- a/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testImpressionEvent.1.json +++ b/Tests/UnitTests/Paywalls/Events/__Snapshots__/PaywallEventsRequestTests/testImpressionEvent.1.json @@ -1,7 +1,7 @@ { "app_user_id" : "Jack Shepard", "dark_mode" : true, - "display_mode" : "condensed_footer", + "display_mode" : "full_screen", "id" : "72164C05-2BDC-4807-8918-A4105F727DEB", "locale" : "es_ES", "offering_id" : "offering", diff --git a/fastlane/Fastfile b/fastlane/Fastfile index 827854a506..da5d336c43 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -190,7 +190,10 @@ platform :ios do desc "Runs all RevenueCatUI tests" lane :test_revenuecatui do |options| generate_snapshots = ENV["CIRCLECI_TESTS_GENERATE_REVENUECAT_UI_SNAPSHOTS"] == "true" - + platform = ENV['SCAN_PLATFORM'] || 'iOS Simulator' + destination = ENV['SCAN_DEVICE'] || "iPhone 14,OS=16.4" + sdk = ENV['BUILD_SDK'] || 'iphonesimulator' + fetch_snapshots begin @@ -198,8 +201,8 @@ platform :ios do xcodebuild( workspace: '.', scheme: 'RevenueCatUI', - destination: "platform=iOS Simulator,name=" + (ENV['SCAN_DEVICE'] || "iPhone 14,OS=16.4"), - sdk: 'iphonesimulator', + destination: "platform=" + platform + ",name=" + destination, + sdk: sdk, output_style: :basic, result_bundle_path: 'fastlane/test_output/revenuecatui.xcresult', report_formats: [:junit],