From 54f45839b92472f6268129c85b70f54da678035c Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Fri, 1 Nov 2024 10:08:07 -0500 Subject: [PATCH] Apply state and conditions ONLY for text component (#4417) * Improved JSON format for ButtonComponent codables * Started to remove PackageGroup * Removed PackageGroup and added Template 5 SwiftUI preview * Remove PackageGroup * Fixed some thread, lint, and selection stuff * PurchaseButton is now a stack like button * Added image partials * Added stack partials * Apply localized partials (state and conditions) for text component * Added visible and updated example in paywalls tester to test things out * A bit more cleanup * Code improvements * Fix tests and lint * Partials are now in one ComponenOverrides struct with states, conditions, and intro offer * Improvements * Fix lint --- RevenueCat.xcodeproj/project.pbxproj | 26 ++- .../Components/LocalizedPartials.swift | 45 ++++ .../Packages/Package/ComponentViewState.swift | 39 ++++ .../Package/PackageComponentView.swift | 51 ++++- ...> TemplateComponentsView+Extensions.swift} | 0 .../Components/TemplateComponentsView.swift | 44 +++- .../Components/Text/TextComponentView.swift | 120 ++++++++++- .../Text/TextComponentViewModel.swift | 201 ++++++++++++++++-- .../Common/ComponentOverrides.swift | 71 +++++++ .../Components/Common/PartialComponent.swift | 41 ---- .../Components/PaywallImageComponent.swift | 12 +- .../Components/PaywallStackComponent.swift | 53 ++--- .../Components/PaywallTextComponent.swift | 16 +- .../Config/SamplePaywalls.swift | 75 ++++++- .../Components/PartialComponentTests.swift | 2 +- 15 files changed, 664 insertions(+), 132 deletions(-) create mode 100644 RevenueCatUI/Templates/Components/LocalizedPartials.swift create mode 100644 RevenueCatUI/Templates/Components/Packages/Package/ComponentViewState.swift rename RevenueCatUI/Templates/Components/{TemplateComponentsView+extensions.swift => TemplateComponentsView+Extensions.swift} (100%) create mode 100644 Sources/Paywalls/Components/Common/ComponentOverrides.swift delete mode 100644 Sources/Paywalls/Components/Common/PartialComponent.swift diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index 9e09561e92..08153ac3e1 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -29,7 +29,9 @@ 2C8EC6DB2CCC23B700D6CCF8 /* Template5Preview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8EC6DA2CCC23B700D6CCF8 /* Template5Preview.swift */; }; 2C8EC6DD2CCC7C5B00D6CCF8 /* PackageValidator.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8EC6DC2CCC7C5500D6CCF8 /* PackageValidator.swift */; }; 2C8EC6DF2CCD27A500D6CCF8 /* PartialComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8EC6DE2CCD27A100D6CCF8 /* PartialComponentTests.swift */; }; - 2C8EC7832CD407B900D6CCF8 /* PartialComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8EC6E02CCD7BA300D6CCF8 /* PartialComponent.swift */; }; + 2C8EC6E12CCD7BA700D6CCF8 /* ComponentOverrides.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8EC6E02CCD7BA300D6CCF8 /* ComponentOverrides.swift */; }; + 2C8EC71B2CCDD43900D6CCF8 /* ComponentViewState.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8EC71A2CCDD43500D6CCF8 /* ComponentViewState.swift */; }; + 2C8EC7202CCF276100D6CCF8 /* LocalizedPartials.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C8EC71F2CCF275E00D6CCF8 /* LocalizedPartials.swift */; }; 2CAB87F72CAAB13200247013 /* CornerBorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAB87F62CAAB13200247013 /* CornerBorder.swift */; }; 2CB8CF9327BF538F00C34DE3 /* PlatformInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CB8CF9227BF538F00C34DE3 /* PlatformInfo.swift */; }; 2CC791552CC0452100FBE120 /* PurchaseButtonComponentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC791522CC0452100FBE120 /* PurchaseButtonComponentViewModel.swift */; }; @@ -869,7 +871,7 @@ 88B1BB132C81479F001B7EE5 /* PaywallComponentTypeTransformers.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88B1BAE72C813A3C001B7EE5 /* PaywallComponentTypeTransformers.swift */; }; 88DE93E12C211CBC0086B6D8 /* RevenueCat.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 2DC5621624EC63420031F69B /* RevenueCat.framework */; }; 88E679472C7503C1007E69D5 /* PaywallStackComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88E679462C7503C1007E69D5 /* PaywallStackComponent.swift */; }; - 88EA80ED2C8771A7003E6675 /* TemplateComponentsView+extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88EA80EC2C8771A7003E6675 /* TemplateComponentsView+extensions.swift */; }; + 88EA80ED2C8771A7003E6675 /* TemplateComponentsView+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 88EA80EC2C8771A7003E6675 /* TemplateComponentsView+Extensions.swift */; }; 9A65DFDE258AD60A00DE00B0 /* LogIntent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A65DFDD258AD60A00DE00B0 /* LogIntent.swift */; }; 9A65E03625918B0500DE00B0 /* ConfigureStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A65E03525918B0500DE00B0 /* ConfigureStrings.swift */; }; 9A65E03B25918B0900DE00B0 /* CustomerInfoStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A65E03A25918B0900DE00B0 /* CustomerInfoStrings.swift */; }; @@ -1187,10 +1189,13 @@ 2C6CC1152B8D2B6800432E4D /* PurchasesSyncAttributesAndOfferingsIfNeededTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchasesSyncAttributesAndOfferingsIfNeededTests.swift; sourceTree = ""; }; 2C7F0AD22B8EEB4600381179 /* RateLimiter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimiter.swift; sourceTree = ""; }; 2C7F0AD42B8EEF0B00381179 /* RateLimiterRests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimiterRests.swift; sourceTree = ""; }; + 2C8EC6AE2CCBD33E00D6CCF8 /* ButtonComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonComponentTests.swift; sourceTree = ""; }; 2C8EC6DA2CCC23B700D6CCF8 /* Template5Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Template5Preview.swift; sourceTree = ""; }; 2C8EC6DC2CCC7C5500D6CCF8 /* PackageValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageValidator.swift; sourceTree = ""; }; 2C8EC6DE2CCD27A100D6CCF8 /* PartialComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialComponentTests.swift; sourceTree = ""; }; - 2C8EC6E02CCD7BA300D6CCF8 /* PartialComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialComponent.swift; sourceTree = ""; }; + 2C8EC6E02CCD7BA300D6CCF8 /* ComponentOverrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentOverrides.swift; sourceTree = ""; }; + 2C8EC71A2CCDD43500D6CCF8 /* ComponentViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentViewState.swift; sourceTree = ""; }; + 2C8EC71F2CCF275E00D6CCF8 /* LocalizedPartials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPartials.swift; sourceTree = ""; }; 2CAB87F62CAAB13200247013 /* CornerBorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerBorder.swift; sourceTree = ""; }; 2CB8CF9227BF538F00C34DE3 /* PlatformInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformInfo.swift; sourceTree = ""; }; 2CC7914B2CC0452100FBE120 /* PackageComponentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageComponentView.swift; sourceTree = ""; }; @@ -2062,7 +2067,7 @@ 88B1BAE82C813A3C001B7EE5 /* StackComponentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackComponentView.swift; sourceTree = ""; }; 88B1BAE92C813A3C001B7EE5 /* StackComponentViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackComponentViewModel.swift; sourceTree = ""; }; 88E679462C7503C1007E69D5 /* PaywallStackComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallStackComponent.swift; sourceTree = ""; }; - 88EA80EC2C8771A7003E6675 /* TemplateComponentsView+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TemplateComponentsView+extensions.swift"; sourceTree = ""; }; + 88EA80EC2C8771A7003E6675 /* TemplateComponentsView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TemplateComponentsView+Extensions.swift"; sourceTree = ""; }; 9A65DFDD258AD60A00DE00B0 /* LogIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogIntent.swift; sourceTree = ""; }; 9A65E03525918B0500DE00B0 /* ConfigureStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigureStrings.swift; sourceTree = ""; }; 9A65E03A25918B0900DE00B0 /* CustomerInfoStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomerInfoStrings.swift; sourceTree = ""; }; @@ -2324,6 +2329,7 @@ 2CC7913E2CC0450900FBE120 /* Recovered References */ = { isa = PBXGroup; children = ( + 2C8EC6AE2CCBD33E00D6CCF8 /* ButtonComponentTests.swift */, ); name = "Recovered References"; sourceTree = ""; @@ -2331,6 +2337,7 @@ 2CC7914D2CC0452100FBE120 /* Package */ = { isa = PBXGroup; children = ( + 2C8EC71A2CCDD43500D6CCF8 /* ComponentViewState.swift */, 2CC7914B2CC0452100FBE120 /* PackageComponentView.swift */, 2CC7914C2CC0452100FBE120 /* PackageComponentViewModel.swift */, ); @@ -2358,7 +2365,7 @@ 2CC791612CC0493600FBE120 /* Common */ = { isa = PBXGroup; children = ( - 2C8EC6E02CCD7BA300D6CCF8 /* PartialComponent.swift */, + 2C8EC6E02CCD7BA300D6CCF8 /* ComponentOverrides.swift */, 2CC7915B2CC0493600FBE120 /* Border.swift */, 2CC7915C2CC0493600FBE120 /* Dimension.swift */, 2CC7915D2CC0493600FBE120 /* PaywallComponentBase.swift */, @@ -4427,13 +4434,14 @@ 88AD01352C74196600AA1F2B /* Components */ = { isa = PBXGroup; children = ( + 2C8EC71F2CCF275E00D6CCF8 /* LocalizedPartials.swift */, 2C8EC6DC2CCC7C5500D6CCF8 /* PackageValidator.swift */, 778360772CCA85D1000785B8 /* StickyFooter */, 778360742CCA84FA000785B8 /* Root */, 88B1BAE72C813A3C001B7EE5 /* PaywallComponentTypeTransformers.swift */, 88B1BAD92C813A3C001B7EE5 /* PaywallComponentViewModel.swift */, 88B1BAE32C813A3C001B7EE5 /* TemplateComponentsView.swift */, - 88EA80EC2C8771A7003E6675 /* TemplateComponentsView+extensions.swift */, + 88EA80EC2C8771A7003E6675 /* TemplateComponentsView+Extensions.swift */, 2CAB87F62CAAB13200247013 /* CornerBorder.swift */, 2C2AEB0D2CA64DA900A50F38 /* TemplateComponentsViewPreviews */, 7707A94A2CAD936A006E0313 /* Button */, @@ -5511,7 +5519,6 @@ 2D00A41D2767C08300FC3DD8 /* ManageSubscriptionsStrings.swift in Sources */, 2DD9F4BE274EADC20031AE2C /* Purchases+async.swift in Sources */, B3C4AAD526B8911300E1B3C8 /* Backend.swift in Sources */, - 2C8EC7832CD407B900D6CCF8 /* PartialComponent.swift in Sources */, 35AAEB452BBB14D000A12548 /* DiagnosticsFileHandler.swift in Sources */, B34D2AA626976FC700D88C3A /* ErrorCode.swift in Sources */, 4F15B4A12A6774C9005BEFE8 /* CustomerInfo+NonSubscriptions.swift in Sources */, @@ -5694,6 +5701,7 @@ 4FBBD4E62A620573001CBA21 /* PaywallColor.swift in Sources */, B35042C626CDD3B100905B95 /* PurchasesDelegate.swift in Sources */, 0313FD41268A506400168386 /* DateProvider.swift in Sources */, + 2C8EC6E12CCD7BA700D6CCF8 /* ComponentOverrides.swift in Sources */, 7783606D2CCA7E14000785B8 /* PaywallStickyFooterComponent.swift in Sources */, 57FDAABA284937A0009A48F1 /* SandboxEnvironmentDetector.swift in Sources */, 2CC791622CC0493600FBE120 /* PaywallComponentPropertyTypes.swift in Sources */, @@ -6136,6 +6144,7 @@ 887A60702C1D037000E1A461 /* PaywallTemplate.swift in Sources */, 887A60882C1D037000E1A461 /* MockPurchases.swift in Sources */, 887A60BC2C1D037000E1A461 /* Template5View.swift in Sources */, + 2C8EC7202CCF276100D6CCF8 /* LocalizedPartials.swift in Sources */, 7707A9502CAD9775006E0313 /* ButtonComponentView.swift in Sources */, 887A60BE2C1D037000E1A461 /* PaywallFooterViewController.swift in Sources */, 887A608A2C1D037000E1A461 /* PurchaseHandler.swift in Sources */, @@ -6155,6 +6164,7 @@ 887A60862C1D037000E1A461 /* FooterHidingModifier.swift in Sources */, 887A60C02C1D037000E1A461 /* AsyncButton.swift in Sources */, 887A60892C1D037000E1A461 /* PaywallPurchasesType.swift in Sources */, + 2C8EC71B2CCDD43900D6CCF8 /* ComponentViewState.swift in Sources */, 77089F9E2CD39EC100848CD5 /* ShadowModifier.swift in Sources */, 3537566F2C382C2800A1B8D6 /* WrongPlatformView.swift in Sources */, 887A60C22C1D037000E1A461 /* ErrorDisplay.swift in Sources */, @@ -6162,7 +6172,7 @@ 3537566E2C382C2800A1B8D6 /* RestorePurchasesAlert.swift in Sources */, 887A60692C1D037000E1A461 /* IntroEligibilityViewModel.swift in Sources */, 2C2AEB0F2CA64E0E00A50F38 /* Template1Preview.swift in Sources */, - 88EA80ED2C8771A7003E6675 /* TemplateComponentsView+extensions.swift in Sources */, + 88EA80ED2C8771A7003E6675 /* TemplateComponentsView+Extensions.swift in Sources */, 88A543E32C37A4970039C6A5 /* Template7View.swift in Sources */, 887A60CB2C1D037000E1A461 /* TemplateBackgroundImageView.swift in Sources */, 353FDC112CA4472B0055F328 /* StoreProduct+Extensions.swift in Sources */, diff --git a/RevenueCatUI/Templates/Components/LocalizedPartials.swift b/RevenueCatUI/Templates/Components/LocalizedPartials.swift new file mode 100644 index 0000000000..e7e069357b --- /dev/null +++ b/RevenueCatUI/Templates/Components/LocalizedPartials.swift @@ -0,0 +1,45 @@ +// +// 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 +// +// LocalizedPartials.swift +// +// Created by Josh Holtz on 10/27/24. +// +// swiftlint:disable missing_docs + +import Foundation +import RevenueCat + +#if PAYWALL_COMPONENTS + +protocol LocalizedPartial {} + +struct LocalizedOverrides { + + public let introOffer: LocalizedPartial? + public let states: LocalizedStates? + public let conditions: LocalizedConditions? + +} + +struct LocalizedStates { + + let selected: T? + +} + +struct LocalizedConditions { + + let compact: T? + let medium: T? + let expanded: T? + +} + +#endif diff --git a/RevenueCatUI/Templates/Components/Packages/Package/ComponentViewState.swift b/RevenueCatUI/Templates/Components/Packages/Package/ComponentViewState.swift new file mode 100644 index 0000000000..7463bcfe5d --- /dev/null +++ b/RevenueCatUI/Templates/Components/Packages/Package/ComponentViewState.swift @@ -0,0 +1,39 @@ +// +// 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 +// +// ComponentViewState.swift +// +// Created by Josh Holtz on 10/26/24. + +import Foundation +import SwiftUI + +#if PAYWALL_COMPONENTS + +enum ComponentViewState { + case `default` + case selected +} + +struct ComponentViewStateKey: EnvironmentKey { + + static let defaultValue: ComponentViewState = .default + +} + +extension EnvironmentValues { + + var componentViewState: ComponentViewState { + get { self[ComponentViewStateKey.self] } + set { self[ComponentViewStateKey.self] = newValue } + } + +} + +#endif diff --git a/RevenueCatUI/Templates/Components/Packages/Package/PackageComponentView.swift b/RevenueCatUI/Templates/Components/Packages/Package/PackageComponentView.swift index 36ee80a392..cfdd31ba5b 100644 --- a/RevenueCatUI/Templates/Components/Packages/Package/PackageComponentView.swift +++ b/RevenueCatUI/Templates/Components/Packages/Package/PackageComponentView.swift @@ -26,6 +26,15 @@ struct PackageComponentView: View { let viewModel: PackageComponentViewModel let onDismiss: () -> Void + private var componentViewState: ComponentViewState { + guard let selectedPackage = paywallState.selectedPackage, + let package = viewModel.package else { + return .default + } + + return selectedPackage.identifier == package.identifier ? .selected : .default + } + var body: some View { if let package = self.viewModel.package { Button { @@ -35,6 +44,7 @@ struct PackageComponentView: View { viewModel: self.viewModel.stackViewModel, onDismiss: self.onDismiss ) + .environment(\.componentViewState, componentViewState) } } else { EmptyView() @@ -48,6 +58,16 @@ struct PackageComponentView: View { @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) struct PackageComponentView_Previews: PreviewProvider { + static var package: Package { + return .init(identifier: "weekly", + packageType: .weekly, + storeProduct: .init(sk1Product: .init()), + offeringIdentifier: "default") + } + + static let paywallState = PaywallState(selectedPackage: nil) + static let paywallStateSelected = PaywallState(selectedPackage: package) + static var stack: PaywallComponent.StackComponent { return .init( components: [ @@ -75,13 +95,6 @@ struct PackageComponentView_Previews: PreviewProvider { ) } - static var package: Package { - return .init(identifier: "weekly", - packageType: .weekly, - storeProduct: .init(sk1Product: .init()), - offeringIdentifier: "default") - } - static var previews: some View { // Package PackageComponentView( @@ -102,6 +115,30 @@ struct PackageComponentView_Previews: PreviewProvider { availablePackages: [package]) ), onDismiss: {} ) + .environmentObject(paywallState) + .previewLayout(.sizeThatFits) + .previewDisplayName("Package") + + // Package - Selected + PackageComponentView( + // swiftlint:disable:next force_try + viewModel: try! .init( + packageValidator: PackageValidator(), + localizedStrings: [ + "name": .string("Weekly"), + "detail": .string("Get for $39.99/wk") + ], + component: .init( + packageID: "weekly", + isSelectedByDefault: false, + stack: stack + ), + offering: .init(identifier: "default", + serverDescription: "", + availablePackages: [package]) + ), onDismiss: {} + ) + .environmentObject(paywallStateSelected) .previewLayout(.sizeThatFits) .previewDisplayName("Package") } diff --git a/RevenueCatUI/Templates/Components/TemplateComponentsView+extensions.swift b/RevenueCatUI/Templates/Components/TemplateComponentsView+Extensions.swift similarity index 100% rename from RevenueCatUI/Templates/Components/TemplateComponentsView+extensions.swift rename to RevenueCatUI/Templates/Components/TemplateComponentsView+Extensions.swift diff --git a/RevenueCatUI/Templates/Components/TemplateComponentsView.swift b/RevenueCatUI/Templates/Components/TemplateComponentsView.swift index 5d7229ad16..78f0ea131b 100644 --- a/RevenueCatUI/Templates/Components/TemplateComponentsView.swift +++ b/RevenueCatUI/Templates/Components/TemplateComponentsView.swift @@ -31,16 +31,53 @@ enum PackageGroupValidationError: Error { } +enum ScreenCondition { + + case compact, medium, expanded + + static func from(_ sizeClass: UserInterfaceSizeClass?) -> Self { + guard let sizeClass else { + return .compact + } + + switch sizeClass { + case .compact: + return .compact + case .regular: + return .medium + @unknown default: + return .compact + } + } + +} + +struct ScreenConditionKey: EnvironmentKey { + static let defaultValue = ScreenCondition.compact +} + +extension EnvironmentValues { + + var screenCondition: ScreenCondition { + get { self[ScreenConditionKey.self] } + set { self[ScreenConditionKey.self] = newValue } + } + +} + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) struct TemplateComponentsView: View { - let paywallComponentsData: PaywallComponentsData - let componentViewModel: PaywallComponentViewModel - private let onDismiss: () -> Void + @Environment(\.horizontalSizeClass) + private var horizontalSizeClass @StateObject private var paywallState: PaywallState + private let paywallComponentsData: PaywallComponentsData + private let componentViewModel: PaywallComponentViewModel + private let onDismiss: () -> Void + public init(paywallComponentsData: PaywallComponentsData, offering: Offering, onDismiss: @escaping () -> Void) { self.paywallComponentsData = paywallComponentsData self.onDismiss = onDismiss @@ -105,6 +142,7 @@ struct TemplateComponentsView: View { .frame(maxHeight: .infinity, alignment: .topLeading) .edgesIgnoringSafeArea(.top) .environmentObject(self.paywallState) + .environment(\.screenCondition, ScreenCondition.from(self.horizontalSizeClass)) } static func chooseLocalization( diff --git a/RevenueCatUI/Templates/Components/Text/TextComponentView.swift b/RevenueCatUI/Templates/Components/Text/TextComponentView.swift index 3f1c7a1217..7a83581934 100644 --- a/RevenueCatUI/Templates/Components/Text/TextComponentView.swift +++ b/RevenueCatUI/Templates/Components/Text/TextComponentView.swift @@ -20,6 +20,12 @@ import SwiftUI @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) struct TextComponentView: View { + @Environment(\.componentViewState) + private var componentViewState + + @Environment(\.screenCondition) + private var screenCondition + private let viewModel: TextComponentViewModel internal init(viewModel: TextComponentViewModel) { @@ -27,15 +33,26 @@ struct TextComponentView: View { } var body: some View { - Text(viewModel.text) - .font(viewModel.textStyle) - .fontWeight(viewModel.fontWeight) - .fixedSize(horizontal: false, vertical: true) - .multilineTextAlignment(viewModel.horizontalAlignment) - .foregroundStyle(viewModel.color) - .padding(viewModel.padding) - .background(viewModel.backgroundColor) - .padding(viewModel.margin) + viewModel.styles( + state: self.componentViewState, + condition: self.screenCondition + ) { style in + Group { + if style.visible { + Text(style.text) + .font(style.textStyle) + .fontWeight(style.fontWeight) + .fixedSize(horizontal: false, vertical: true) + .multilineTextAlignment(style.horizontalAlignment) + .foregroundStyle(style.color) + .padding(style.padding) + .background(style.backgroundColor) + .padding(style.margin) + } else { + EmptyView() + } + } + } } } @@ -89,6 +106,91 @@ struct TextComponentView_Previews: PreviewProvider { ) .previewLayout(.sizeThatFits) .previewDisplayName("Customizations") + + // State - Selected + TextComponentView( + // swiftlint:disable:next force_try + viewModel: try! .init( + localizedStrings: [ + "id_1": .string("Hello, world") + ], + component: .init( + text: "id_1", + color: .init(light: "#000000"), + overrides: .init( + states: .init( + selected: .init( + fontWeight: .black, + color: .init(light: "#ff0000"), + backgroundColor: .init(light: "#0000ff"), + padding: .init(top: 10, + bottom: 10, + leading: 10, + trailing: 10), + margin: .init(top: 10, + bottom: 10, + leading: 10, + trailing: 10), + textStyle: .title + ) + ) + ) + ) + ) + ) + .environment(\.componentViewState, .selected) + .previewLayout(.sizeThatFits) + .previewDisplayName("State - Selected") + + // Condition - Medium + TextComponentView( + // swiftlint:disable:next force_try + viewModel: try! .init( + localizedStrings: [ + "id_1": .string("THIS TEXT SHOULDN'T SHOW"), + "id_2": .string("Showing medium condition") + ], + component: .init( + text: "id_1", + color: .init(light: "#000000"), + overrides: .init( + conditions: .init( + medium: .init( + text: "id_2" + ) + ) + ) + ) + ) + ) + .environment(\.screenCondition, .medium) + .previewLayout(.sizeThatFits) + .previewDisplayName("Condition - Medium") + + // Condition - Has medium but not medium + TextComponentView( + // swiftlint:disable:next force_try + viewModel: try! .init( + localizedStrings: [ + "id_1": .string("Showing compact condition"), + "id_2": .string("SHOULDN'T SHOW MEDIUM") + ], + component: .init( + text: "id_1", + color: .init(light: "#000000"), + overrides: .init( + conditions: .init( + medium: .init( + text: "id_2" + ) + ) + ) + ) + ) + ) + .environment(\.screenCondition, .compact) + .previewLayout(.sizeThatFits) + .previewDisplayName("Condition - Has medium but not medium") } } diff --git a/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift b/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift index 7af88acc8c..74ba669277 100644 --- a/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift +++ b/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift @@ -16,50 +16,213 @@ import SwiftUI #if PAYWALL_COMPONENTS +struct LocalizedTextPartial: LocalizedPartial { + + let text: String? + let partial: PaywallComponent.PartialTextComponent + +} + @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) class TextComponentViewModel { private let localizedStrings: PaywallComponent.LocalizationDictionary private let component: PaywallComponent.TextComponent - let text: String + private let text: String + private let localizedOverrides: LocalizedOverrides? init(localizedStrings: PaywallComponent.LocalizationDictionary, component: PaywallComponent.TextComponent) throws { self.localizedStrings = localizedStrings self.component = component self.text = try localizedStrings.string(key: component.text) - } - var fontFamily: String? { - component.fontFamily - } + self.localizedOverrides = try self.component.overrides.flatMap({ overrides in + LocalizedOverrides( + introOffer: try overrides.introOffer.flatMap({ partial in + try LocalizedTextPartial.create(from: partial, using: localizedStrings) + }), + states: try overrides.states.flatMap({ states in + LocalizedStates( + selected: try states.selected.flatMap({ partial in + try LocalizedTextPartial.create(from: partial, using: localizedStrings) + }) + ) + }), + conditions: try overrides.conditions.flatMap({ condition in + LocalizedConditions( + compact: try condition.compact.flatMap({ partial in + try LocalizedTextPartial.create(from: partial, using: localizedStrings) + }), + medium: try condition.medium.flatMap({ partial in + try LocalizedTextPartial.create(from: partial, using: localizedStrings) + }), + expanded: try condition.expanded.flatMap({ partial in + try LocalizedTextPartial.create(from: partial, using: localizedStrings) + }) + ) + }) + ) + }) - var fontWeight: Font.Weight { - component.fontWeight.fontWeight } - var color: Color { - component.color.toDyanmicColor() + @ViewBuilder + func styles( + state: ComponentViewState, + condition: ScreenCondition, + apply: @escaping (TextComponentStyle) -> some View + ) -> some View { + let localalizedPartial = self.buildPartial(state: state, condition: condition) + let partial = localalizedPartial?.partial + + let style = TextComponentStyle( + visible: partial?.visible ?? true, + text: localalizedPartial?.text ?? self.text, + fontFamily: partial?.fontFamily ?? self.component.fontFamily, + fontWeight: partial?.fontWeight ?? self.component.fontWeight, + color: partial?.color ?? self.component.color, + backgroundColor: partial?.backgroundColor ?? self.component.backgroundColor, + padding: partial?.padding ?? self.component.padding, + margin: partial?.margin ?? self.component.margin, + textStyle: partial?.textStyle ?? self.component.textStyle, + horizontalAlignment: partial?.horizontalAlignment ?? self.component.horizontalAlignment + ) + + apply(style) } - var textStyle: Font { - component.textStyle.font + func buildPartial( + state: ComponentViewState, + condition: ScreenCondition + ) -> LocalizedTextPartial? { + var partial = self.buildConditionPartial(for: condition) + + switch state { + case .default: + break + case .selected: + partial = self.combine(partial, with: self.localizedOverrides?.states?.selected) + } + + // WIP: Get partial for intro offer + return partial } - var horizontalAlignment: TextAlignment { - component.horizontalAlignment.textAlignment + private func buildConditionPartial( + for conditionType: ScreenCondition + ) -> LocalizedTextPartial? { + + // Get all conditions to include + let conditionTypesToApply: [PaywallComponent.ComponentConditionsType] + switch conditionType { + case .compact: + conditionTypesToApply = [.compact] + case .medium: + conditionTypesToApply = [.compact, .medium] + case .expanded: + conditionTypesToApply = [.compact, .medium, .expanded] + } + + var combinedPartial: LocalizedTextPartial? + + // Apply compact on top of existing partial + if let compact = self.localizedOverrides?.conditions?.compact, + conditionTypesToApply.contains(.compact) { + combinedPartial = combine(combinedPartial, with: compact) + } + + // Apply medium on top of existing partial + if let medium = self.localizedOverrides?.conditions?.medium, + conditionTypesToApply.contains(.medium) { + combinedPartial = combine(combinedPartial, with: medium) + } + + // Apply expanded on top of existing partial + if let expanded = self.localizedOverrides?.conditions?.expanded, + conditionTypesToApply.contains(.expanded) { + combinedPartial = combine(combinedPartial, with: expanded) + } + + // Return the combined partial if it's not empty, otherwise return nil + return combinedPartial } - var backgroundColor: Color { - component.backgroundColor?.toDyanmicColor() ?? Color.clear + private func combine(_ base: LocalizedTextPartial?, with other: LocalizedTextPartial?) -> LocalizedTextPartial { + let otherPartial = other?.partial + let basePartial = base?.partial + + return LocalizedTextPartial( + text: other?.text ?? base?.text, + partial: PaywallComponent.PartialTextComponent( + visible: otherPartial?.visible ?? basePartial?.visible, + text: otherPartial?.text ?? basePartial?.text, + fontFamily: otherPartial?.fontFamily ?? basePartial?.fontFamily, + fontWeight: otherPartial?.fontWeight ?? basePartial?.fontWeight, + color: otherPartial?.color ?? basePartial?.color, + backgroundColor: otherPartial?.backgroundColor ?? basePartial?.backgroundColor, + padding: otherPartial?.padding ?? basePartial?.padding, + margin: otherPartial?.margin ?? basePartial?.margin, + textStyle: otherPartial?.textStyle ?? basePartial?.textStyle, + horizontalAlignment: otherPartial?.horizontalAlignment ?? basePartial?.horizontalAlignment + ) + ) } - var padding: EdgeInsets { - component.padding.edgeInsets +} + +extension LocalizedTextPartial { + + static func create( + from partial: PaywallComponent.PartialTextComponent, + using localizedStrings: PaywallComponent.LocalizationDictionary + ) throws -> LocalizedTextPartial { + return LocalizedTextPartial( + text: try partial.text.flatMap { key in + try localizedStrings.string(key: key) + }, + partial: partial + ) } - var margin: EdgeInsets { - component.margin.edgeInsets +} + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +struct TextComponentStyle { + + let visible: Bool + let text: String + let fontFamily: String? + let fontWeight: Font.Weight + let color: Color + let textStyle: Font + let horizontalAlignment: TextAlignment + let backgroundColor: Color + let padding: EdgeInsets + let margin: EdgeInsets + + init( + visible: Bool, + text: PaywallComponent.LocalizationKey, + fontFamily: String?, + fontWeight: PaywallComponent.FontWeight, + color: PaywallComponent.ColorInfo, + backgroundColor: PaywallComponent.ColorInfo?, + padding: PaywallComponent.Padding, + margin: PaywallComponent.Padding, + textStyle: PaywallComponent.TextStyle, + horizontalAlignment: PaywallComponent.HorizontalAlignment + ) { + self.visible = visible + self.text = text + self.fontFamily = fontFamily + self.fontWeight = fontWeight.fontWeight + self.color = color.toDyanmicColor() + self.textStyle = textStyle.font + self.horizontalAlignment = horizontalAlignment.textAlignment + self.backgroundColor = backgroundColor?.toDyanmicColor() ?? Color.clear + self.padding = padding.edgeInsets + self.margin = margin.edgeInsets } } diff --git a/Sources/Paywalls/Components/Common/ComponentOverrides.swift b/Sources/Paywalls/Components/Common/ComponentOverrides.swift new file mode 100644 index 0000000000..44e238b3b3 --- /dev/null +++ b/Sources/Paywalls/Components/Common/ComponentOverrides.swift @@ -0,0 +1,71 @@ +// +// 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 +// +// ComponentOverrides.swift +// +// Created by Josh Holtz on 10/26/24. +// +// swiftlint:disable missing_docs + +import Foundation + +#if PAYWALL_COMPONENTS + +public extension PaywallComponent { + + protocol PartialComponent: PaywallComponentBase {} + + struct ComponentOverrides: PaywallComponentBase { + + public init( + introOffer: T? = nil, + states: PaywallComponent.ComponentStates? = nil, + conditions: PaywallComponent.ComponentConditions? = nil + ) { + self.introOffer = introOffer + self.states = states + self.conditions = conditions + } + + public let introOffer: T? + public let states: ComponentStates? + public let conditions: ComponentConditions? + + } + + struct ComponentStates: PaywallComponentBase { + + public init(selected: T? = nil) { + self.selected = selected + } + + public let selected: T? + + } + + enum ComponentConditionsType { + case compact, medium, expanded + } + + struct ComponentConditions: PaywallComponentBase { + public init(compact: T? = nil, medium: T? = nil, expanded: T? = nil) { + self.compact = compact + self.medium = medium + self.expanded = expanded + } + + public let compact: T? + public let medium: T? + public let expanded: T? + + } + +} + +#endif diff --git a/Sources/Paywalls/Components/Common/PartialComponent.swift b/Sources/Paywalls/Components/Common/PartialComponent.swift deleted file mode 100644 index 13645983c2..0000000000 --- a/Sources/Paywalls/Components/Common/PartialComponent.swift +++ /dev/null @@ -1,41 +0,0 @@ -// -// 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 -// -// PartialComponent.swift -// -// Created by Josh Holtz on 10/26/24. -// -// swiftlint:disable missing_docs - -import Foundation - -#if PAYWALL_COMPONENTS - -public extension PaywallComponent { - - protocol PartialComponent: PaywallComponentBase {} - - struct ComponentState: PaywallComponentBase { - - let selected: T? - let introOffer: T? - - } - - struct ComponentConditions: PaywallComponentBase { - - let compact: T? - let medium: T? - let expanded: T? - - } - -} - -#endif diff --git a/Sources/Paywalls/Components/PaywallImageComponent.swift b/Sources/Paywalls/Components/PaywallImageComponent.swift index 8f0204f7fb..3af77bc7d3 100644 --- a/Sources/Paywalls/Components/PaywallImageComponent.swift +++ b/Sources/Paywalls/Components/PaywallImageComponent.swift @@ -22,8 +22,7 @@ public extension PaywallComponent { public let maxHeight: CGFloat? public let fitMode: FitMode - public let state: ComponentState? - public let conditions: ComponentConditions? + public let overrides: ComponentOverrides? public init( source: ThemeImageUrls, @@ -32,8 +31,7 @@ public extension PaywallComponent { maxHeight: CGFloat? = nil, cornerRadiuses: CornerRadiuses? = nil, gradientColors: [ColorHex]? = [], - state: ComponentState? = nil, - conditions: ComponentConditions? = nil + overrides: ComponentOverrides? = nil ) { self.type = .image self.source = source @@ -42,14 +40,14 @@ public extension PaywallComponent { self.maxHeight = maxHeight self.cornerRadiuses = cornerRadiuses self.gradientColors = gradientColors - self.state = state - self.conditions = conditions + self.overrides = overrides } } struct PartialImageComponent: PartialComponent { + public let visible: Bool? public let source: ThemeImageUrls? public let overrideSourceLid: LocalizationKey? public let cornerRadiuses: CornerRadiuses? @@ -58,6 +56,7 @@ public extension PaywallComponent { public let fitMode: FitMode? public init( + visible: Bool? = true, source: ThemeImageUrls? = nil, overrideSourceLid: LocalizationKey? = nil, fitMode: FitMode? = nil, @@ -65,6 +64,7 @@ public extension PaywallComponent { cornerRadiuses: CornerRadiuses? = nil, gradientColors: [ColorHex]? = nil ) { + self.visible = visible self.source = source self.overrideSourceLid = overrideSourceLid self.fitMode = fitMode diff --git a/Sources/Paywalls/Components/PaywallStackComponent.swift b/Sources/Paywalls/Components/PaywallStackComponent.swift index 41684ad5d2..e0f737d664 100644 --- a/Sources/Paywalls/Components/PaywallStackComponent.swift +++ b/Sources/Paywalls/Components/PaywallStackComponent.swift @@ -32,21 +32,20 @@ public extension PaywallComponent { public let border: Border? public let shadow: Shadow? - public let state: ComponentState? - public let conditions: ComponentConditions? + public let overrides: ComponentOverrides? - public init(components: [PaywallComponent], - dimension: Dimension = .vertical(.center), - width: WidthSize? = nil, - spacing: CGFloat? = nil, - backgroundColor: ColorInfo? = nil, - padding: Padding = .zero, - margin: Padding = .zero, - cornerRadiuses: CornerRadiuses? = nil, - border: Border? = nil, - shadow: Shadow? = nil, - state: ComponentState? = nil, - conditions: ComponentConditions? = nil + public init( + components: [PaywallComponent], + dimension: Dimension = .vertical(.center), + width: WidthSize? = nil, + spacing: CGFloat? = nil, + backgroundColor: ColorInfo? = nil, + padding: Padding = .zero, + margin: Padding = .zero, + cornerRadiuses: CornerRadiuses? = nil, + border: Border? = nil, + shadow: Shadow? = nil, + overrides: ComponentOverrides? = nil ) { self.components = components self.width = width @@ -59,14 +58,14 @@ public extension PaywallComponent { self.cornerRadiuses = cornerRadiuses self.border = border self.shadow = shadow - self.state = state - self.conditions = conditions + self.overrides = overrides } } struct PartialStackComponent: PartialComponent { + public let visible: Bool? public let width: WidthSize? public let spacing: CGFloat? public let backgroundColor: ColorInfo? @@ -75,16 +74,21 @@ public extension PaywallComponent { public let margin: Padding? public let cornerRadiuses: CornerRadiuses? public let border: Border? + public let shadow: Shadow? - public init(dimension: Dimension? = nil, - width: WidthSize? = nil, - spacing: CGFloat? = nil, - backgroundColor: ColorInfo? = nil, - padding: Padding? = nil, - margin: Padding? = nil, - cornerRadiuses: CornerRadiuses? = nil, - border: Border? = nil + public init( + visible: Bool? = true, + dimension: Dimension? = nil, + width: WidthSize? = nil, + spacing: CGFloat? = nil, + backgroundColor: ColorInfo? = nil, + padding: Padding? = nil, + margin: Padding? = nil, + cornerRadiuses: CornerRadiuses? = nil, + border: Border? = nil, + shadow: Shadow? = nil ) { + self.visible = visible self.width = width self.spacing = spacing self.backgroundColor = backgroundColor @@ -93,6 +97,7 @@ public extension PaywallComponent { self.margin = margin self.cornerRadiuses = cornerRadiuses self.border = border + self.shadow = shadow } } diff --git a/Sources/Paywalls/Components/PaywallTextComponent.swift b/Sources/Paywalls/Components/PaywallTextComponent.swift index 93323f8a0a..1c6cfad283 100644 --- a/Sources/Paywalls/Components/PaywallTextComponent.swift +++ b/Sources/Paywalls/Components/PaywallTextComponent.swift @@ -25,8 +25,7 @@ public extension PaywallComponent { public let padding: Padding public let margin: Padding - public let state: ComponentState? - public let conditions: ComponentConditions? + public let overrides: ComponentOverrides? public init( text: String, @@ -38,8 +37,7 @@ public extension PaywallComponent { margin: Padding = .zero, textStyle: TextStyle = .body, horizontalAlignment: HorizontalAlignment = .center, - state: ComponentState? = nil, - conditions: ComponentConditions? = nil + overrides: ComponentOverrides? = nil ) { self.type = .text self.text = text @@ -51,13 +49,13 @@ public extension PaywallComponent { self.margin = margin self.textStyle = textStyle self.horizontalAlignment = horizontalAlignment - self.state = state - self.conditions = conditions + self.overrides = overrides } } struct PartialTextComponent: PartialComponent { + public let visible: Bool? public let text: LocalizationKey? public let fontFamily: String? public let fontWeight: FontWeight? @@ -69,6 +67,7 @@ public extension PaywallComponent { public let margin: Padding? public init( + visible: Bool? = true, text: LocalizationKey? = nil, fontFamily: String? = nil, fontWeight: FontWeight? = nil, @@ -79,6 +78,7 @@ public extension PaywallComponent { textStyle: TextStyle? = nil, horizontalAlignment: HorizontalAlignment? = nil ) { + self.visible = visible self.text = text self.fontFamily = fontFamily self.fontWeight = fontWeight @@ -107,8 +107,7 @@ extension PaywallComponent.TextComponent { case padding case margin - case state - case conditions + case overrides } } @@ -116,6 +115,7 @@ extension PaywallComponent.TextComponent { extension PaywallComponent.PartialTextComponent { enum CodingKeys: String, CodingKey { + case visible case text = "text_lid" case fontFamily case fontWeight diff --git a/Tests/TestingApps/PaywallsTester/PaywallsTester/Config/SamplePaywalls.swift b/Tests/TestingApps/PaywallsTester/PaywallsTester/Config/SamplePaywalls.swift index 7910a13efd..daf920511e 100644 --- a/Tests/TestingApps/PaywallsTester/PaywallsTester/Config/SamplePaywalls.swift +++ b/Tests/TestingApps/PaywallsTester/PaywallsTester/Config/SamplePaywalls.swift @@ -748,13 +748,27 @@ private extension SamplePaywallLoader { fontWeight: .bold, color: .init(light: "#000000"), padding: .zero, - margin: .zero + margin: .zero, + overrides: .init( + states: .init( + selected: .init( + color: .init(light: "#ff0000") + ) + ) + ) )), .text(.init( text: detailTextLid, color: .init(light: "#000000"), padding: .zero, - margin: .zero + margin: .zero, + overrides: .init( + states: .init( + selected: .init( + color: .init(light: "#ff0000") + ) + ) + ) )) ], dimension: .vertical(.leading), @@ -768,7 +782,14 @@ private extension SamplePaywallLoader { topTrailing: 8, bottomLeading: 8, bottomTrailing: 8), - border: .init(color: .init(light: "#000000"), width: 1) + border: .init(color: .init(light: "#cccccc"), width: 1), + overrides: .init( + states: .init( + selected: .init( + border: .init(color: .init(light: "#ff0000"), width: 1) + ) + ) + ) ) return .init( @@ -822,12 +843,42 @@ private extension SamplePaywallLoader { )) }() + static var conditionsText1: PaywallComponent = PaywallComponent.text( + .init( + text: "condition_1_default", + color: .init(light: "#000000"), + overrides: .init( + conditions: .init( + medium: .init( + text: "condition_1_medium" + ) + ) + ) + ) + ) + + static var conditionsText2: PaywallComponent = PaywallComponent.text( + .init( + text: "condition_2_default", + color: .init(light: "#000000"), + overrides: .init( + conditions: .init( + medium: .init( + visible: false + ) + ) + ) + ) + ) + static var simpleSix: [PaywallComponent] = { let components: [PaywallComponent] = [ .stack(.init(components: [ fuzzyCat, spacer, + conditionsText1, + conditionsText2, simpleFeatureStack(text: featureText), spacer, simpleSixPackages @@ -1563,7 +1614,11 @@ private extension SamplePaywallLoader { "annual_package_name": .string("Annual"), "annual_package_details": .string("Get now for $19.99/month"), "cta": .string("Purchase now"), - "cta_intro": .string("Claim free trial") + "cta_intro": .string("Claim free trial"), + + "condition_1_default": .string("Showing in portrait"), + "condition_1_medium": .string("Showing in landscape"), + "condition_2_default": .string("Should only show in portrait") ], "fr_FR": [ "welcome_message": .string("Bonjour, Composants Paywall!"), @@ -1578,7 +1633,11 @@ private extension SamplePaywallLoader { "annual_package_name": .string("Annua FRENCH"), "annual_package_details": .string("Get now for $19.99/month FRENCH"), "cta": .string("Purchase now FRENCH"), - "cta_intro": .string("Claim free trial FRENCH") + "cta_intro": .string("Claim free trial FRENCH"), + + "condition_1_default": .string("Showing in portrait IN FRENCH"), + "condition_1_medium": .string("Showing in landscape IN FRENCH"), + "condition_2_default": .string("Should only show in portrait IN FRENCH") ], "es_ES": [ "welcome_message": .string("¡Hola, Componentes Paywall!"), @@ -1593,7 +1652,11 @@ private extension SamplePaywallLoader { "annual_package_name": .string("Annual SPANISH"), "annual_package_details": .string("Get now for $19.99/month SPANISH"), "cta": .string("Purchase now SPANISH"), - "cta_intro": .string("Claim free trial SPANISH") + "cta_intro": .string("Claim free trial SPANISH"), + + "condition_1_default": .string("Showing in portrait IN SPANISH"), + "condition_1_medium": .string("Showing in landscape IN SPANISH"), + "condition_2_default": .string("Should only show in portrait IN SPANISH") ] ] } diff --git a/Tests/UnitTests/Paywalls/Components/PartialComponentTests.swift b/Tests/UnitTests/Paywalls/Components/PartialComponentTests.swift index 28f12003f5..32fd2bda49 100644 --- a/Tests/UnitTests/Paywalls/Components/PartialComponentTests.swift +++ b/Tests/UnitTests/Paywalls/Components/PartialComponentTests.swift @@ -20,7 +20,7 @@ import XCTest final class PartialComponentTests: TestCase { // Properties of full components that partials can't have - static let ignoredProperties = ["type", "components", "state", "conditions"] + static let ignoredProperties = ["type", "components", "overrides"] static let sampleURL = URL(string: "https://revenuecat.com")!