Skip to content

Commit

Permalink
[Paywalls V2] Add UIConfig to OfferingsResponse (#4628)
Browse files Browse the repository at this point in the history
* Added tests

* Removed compiler flags because extra work to handle this on CI unit tests when we are releasing this soon and this isn't really that dangerous

* Fix lint

* Some naming improvements

* Passing UIConfig into Offering

* Reorged some data structures

* Fixed some lint

* This
  • Loading branch information
joshdholtz authored Jan 6, 2025
1 parent b02262e commit 9ef833b
Show file tree
Hide file tree
Showing 21 changed files with 516 additions and 104 deletions.
22 changes: 21 additions & 1 deletion RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,9 @@
0313FD41268A506400168386 /* DateProvider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 0313FD40268A506400168386 /* DateProvider.swift */; };
03A98CEF2D1EE048009BCA61 /* FallbackComponentTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A98CEE2D1EE040009BCA61 /* FallbackComponentTests.swift */; };
03A98CF12D222F5F009BCA61 /* FallbackComponentPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A98CF02D222F53009BCA61 /* FallbackComponentPreview.swift */; };
03A98D302D240C41009BCA61 /* UIConfig.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A98D2F2D240C3E009BCA61 /* UIConfig.swift */; };
03A98D322D2441B8009BCA61 /* PaywallDataDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A98D312D2441B2009BCA61 /* PaywallDataDecodingTests.swift */; };
03A98D362D244329009BCA61 /* UIConfigDecodingTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 03A98D352D244321009BCA61 /* UIConfigDecodingTests.swift */; };
1E2F910B2CC8FE5600BDB016 /* ContactSupportUtilities.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F910A2CC8FE5600BDB016 /* ContactSupportUtilities.swift */; };
1E2F911B2CC918ED00BDB016 /* ContactSupportUtilitiesTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F911A2CC918ED00BDB016 /* ContactSupportUtilitiesTests.swift */; };
1E2F91722CCFA98C00BDB016 /* WebRedemptionStrings.swift in Sources */ = {isa = PBXBuildFile; fileRef = 1E2F91712CCFA98C00BDB016 /* WebRedemptionStrings.swift */; };
Expand Down Expand Up @@ -1242,6 +1245,9 @@
0313FD40268A506400168386 /* DateProvider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateProvider.swift; sourceTree = "<group>"; };
03A98CEE2D1EE040009BCA61 /* FallbackComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FallbackComponentTests.swift; sourceTree = "<group>"; };
03A98CF02D222F53009BCA61 /* FallbackComponentPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FallbackComponentPreview.swift; sourceTree = "<group>"; };
03A98D2F2D240C3E009BCA61 /* UIConfig.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConfig.swift; sourceTree = "<group>"; };
03A98D312D2441B2009BCA61 /* PaywallDataDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallDataDecodingTests.swift; sourceTree = "<group>"; };
03A98D352D244321009BCA61 /* UIConfigDecodingTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = UIConfigDecodingTests.swift; sourceTree = "<group>"; };
1E2F910A2CC8FE5600BDB016 /* ContactSupportUtilities.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSupportUtilities.swift; sourceTree = "<group>"; };
1E2F911A2CC918ED00BDB016 /* ContactSupportUtilitiesTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ContactSupportUtilitiesTests.swift; sourceTree = "<group>"; };
1E2F91712CCFA98C00BDB016 /* WebRedemptionStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WebRedemptionStrings.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2446,6 +2452,15 @@
path = StoreKit2;
sourceTree = "<group>";
};
03A98D2E2D240C2D009BCA61 /* RevenueCatUI */ = {
isa = PBXGroup;
children = (
03A98D2F2D240C3E009BCA61 /* UIConfig.swift */,
2C9107EF2CE2ED8300189565 /* PaywallComponentsData.swift */,
);
path = RevenueCatUI;
sourceTree = "<group>";
};
1E8A60402CDBD73E0034ACF3 /* WebPurchaseRedemption */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4210,6 +4225,8 @@
5774F9B92805E6E200997128 /* CustomerInfoDecodingTests.swift */,
5774F9C02805EA3000997128 /* BaseHTTPResponseTest.swift */,
574A2F3E282D75E300150D40 /* OfferingsDecodingTests.swift */,
03A98D312D2441B2009BCA61 /* PaywallDataDecodingTests.swift */,
03A98D352D244321009BCA61 /* UIConfigDecodingTests.swift */,
574A2F4E282D7B9E00150D40 /* PostOfferDecodingTests.swift */,
5766C61F282DA3D50067D886 /* GetIntroEligibilityDecodingTests.swift */,
57045B3729C514A8001A5417 /* ProductEntitlementMappingDecodingTests.swift */,
Expand Down Expand Up @@ -4273,11 +4290,11 @@
57D5412C27F63108004CC35C /* Responses */ = {
isa = PBXGroup;
children = (
03A98D2E2D240C2D009BCA61 /* RevenueCatUI */,
3592E88D2C2ED5B200D7F91D /* CustomerCenterConfigResponse.swift */,
5774F9B52805E6CC00997128 /* CustomerInfoResponse.swift */,
5766C621282DAA700067D886 /* GetIntroEligibilityResponse.swift */,
57D5412D27F6311C004CC35C /* OfferingsResponse.swift */,
2C9107EF2CE2ED8300189565 /* PaywallComponentsData.swift */,
574A2F4A282D7AEA00150D40 /* PostOfferResponse.swift */,
57488A7E29CA145B0000EE7E /* ProductEntitlementMappingResponse.swift */,
);
Expand Down Expand Up @@ -6017,6 +6034,7 @@
4D2C7D0B2CC40A35002562BC /* PurchaseParams.swift in Sources */,
9A65E0A02591A23200DE00B0 /* OfferingStrings.swift in Sources */,
5774F9B62805E6CC00997128 /* CustomerInfoResponse.swift in Sources */,
03A98D302D240C41009BCA61 /* UIConfig.swift in Sources */,
B34605D1279A6E600031CA74 /* CustomerAPI.swift in Sources */,
35D159D12BC492E4004D8061 /* DiagnosticsHTTPRequestPath.swift in Sources */,
2DDF41A224F6F331005BC22D /* ProductsManager.swift in Sources */,
Expand Down Expand Up @@ -6253,6 +6271,7 @@
57E6C27E29723F9E001AFE98 /* SigningTests.swift in Sources */,
574A2F3F282D75E300150D40 /* OfferingsDecodingTests.swift in Sources */,
35E840CE2710E2EB00899AE2 /* MockManageSubscriptionsHelper.swift in Sources */,
03A98D322D2441B8009BCA61 /* PaywallDataDecodingTests.swift in Sources */,
4F8DDB692AAA9189000188F2 /* OperationDispatcherTests.swift in Sources */,
57FFD2512922DBED00A9A878 /* MockStoreTransaction.swift in Sources */,
57CB2A7A29CCC61600C91439 /* CustomerInfoResponseHandlerTests.swift in Sources */,
Expand Down Expand Up @@ -6283,6 +6302,7 @@
351B51A726D450D400BD2BD7 /* SystemInfoTests.swift in Sources */,
5733B1A827FFBCC800EC2045 /* BackendErrorTests.swift in Sources */,
4FFFE6C82AA9467800B2955C /* PaywallEventsManagerTests.swift in Sources */,
03A98D362D244329009BCA61 /* UIConfigDecodingTests.swift in Sources */,
351B515626D44B2300BD2BD7 /* MockNotificationCenter.swift in Sources */,
351B515226D44AF000BD2BD7 /* MockReceiptFetcher.swift in Sources */,
351B51C226D450E800BD2BD7 /* ProductRequestDataTests.swift in Sources */,
Expand Down
4 changes: 2 additions & 2 deletions RevenueCatUI/PaywallView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -253,9 +253,9 @@ public struct PaywallView: View {
)

#if PAYWALL_COMPONENTS
if let componentData = offering.paywallComponentsData {
if let paywallComponents = offering.paywallComponents {
PaywallsV2View(
paywallComponentsData: componentData,
paywallComponents: paywallComponents,
offering: offering,
introEligibilityChecker: .default(),
showZeroDecimalPlacePrices: showZeroDecimalPlacePrices,
Expand Down
10 changes: 6 additions & 4 deletions RevenueCatUI/Templates/V2/PaywallsV2View.swift
Original file line number Diff line number Diff line change
Expand Up @@ -50,17 +50,19 @@ struct PaywallsV2View: View {
private var paywallStateManager: PaywallStateManager

private let paywallComponentsData: PaywallComponentsData
private let uiConfig: UIConfig
private let offering: Offering
private let onDismiss: () -> Void

public init(
paywallComponentsData: PaywallComponentsData,
paywallComponents: Offering.PaywallComponents,
offering: Offering,
introEligibilityChecker: TrialOrIntroEligibilityChecker,
showZeroDecimalPlacePrices: Bool,
onDismiss: @escaping () -> Void
) {
self.paywallComponentsData = paywallComponentsData
self.paywallComponentsData = paywallComponents.data
self.uiConfig = paywallComponents.uiConfig
self.offering = offering
self.onDismiss = onDismiss
self._introOfferEligibilityContext = .init(
Expand All @@ -76,8 +78,8 @@ struct PaywallsV2View: View {
self._paywallStateManager = .init(
wrappedValue: .init(state: Self.createPaywallState(
componentsConfig: componentsConfig,
componentsLocalizations: paywallComponentsData.componentsLocalizations,
defaultLocale: paywallComponentsData.defaultLocale,
componentsLocalizations: paywallComponents.data.componentsLocalizations,
defaultLocale: paywallComponents.data.defaultLocale,
offering: offering,
introEligibilityChecker: introEligibilityChecker,
showZeroDecimalPlacePrices: showZeroDecimalPlacePrices
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -137,6 +137,21 @@ private enum Template1Preview {
backgroundColor: nil
)

static let paywallComponents: Offering.PaywallComponents = .init(
uiConfig: .init(
app: .init(
colors: [:],
fonts: [:]
),
localizations: [:],
variableConfig: .init(
variableCompatibilityMap: [:],
functionCompatibilityMap: [:]
)
),
data: data
)

static let data: PaywallComponentsData = .init(
templateName: "components",
assetBaseURL: URL(string: "https://assets.pawwalls.com")!,
Expand Down Expand Up @@ -181,7 +196,7 @@ struct Template1Preview_Previews: PreviewProvider {

// Template 1
PaywallsV2View(
paywallComponentsData: Template1Preview.data,
paywallComponents: Template1Preview.paywallComponents,
offering: .init(identifier: "default",
serverDescription: "",
availablePackages: [package]),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -212,6 +212,21 @@ private enum Template5Preview {
backgroundColor: nil
)

static let paywallComponents: Offering.PaywallComponents = .init(
uiConfig: .init(
app: .init(
colors: [:],
fonts: [:]
),
localizations: [:],
variableConfig: .init(
variableCompatibilityMap: [:],
functionCompatibilityMap: [:]
)
),
data: data
)

static let data: PaywallComponentsData = .init(
templateName: "components",
assetBaseURL: URL(string: "https://assets.pawwalls.com")!,
Expand Down Expand Up @@ -255,7 +270,7 @@ struct Template5Preview_Previews: PreviewProvider {

// Template 5
PaywallsV2View(
paywallComponentsData: Template5Preview.data,
paywallComponents: Template5Preview.paywallComponents,
offering: Offering(identifier: "default",
serverDescription: "",
availablePackages: [
Expand Down
3 changes: 2 additions & 1 deletion Sources/Networking/Responses/OfferingsResponse.swift
Original file line number Diff line number Diff line change
Expand Up @@ -34,7 +34,6 @@ struct OfferingsResponse {
var metadata: [String: AnyDecodable]

#if PAYWALL_COMPONENTS
// components
var paywallComponents: PaywallComponentsData?
#endif

Expand All @@ -55,6 +54,8 @@ struct OfferingsResponse {
let offerings: [Offering]
let placements: Placements?
let targeting: Targeting?
let uiConfig: UIConfig?

}

extension OfferingsResponse {
Expand Down
129 changes: 129 additions & 0 deletions Sources/Networking/Responses/RevenueCatUI/UIConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
//
// 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
//
// UIConfig.swift
//
// Created by Josh Holtz on 12/31/24.
// swiftlint:disable missing_docs

import Foundation

#if PAYWALL_COMPONENTS

public struct UIConfig: Codable, Equatable, Sendable {

public struct AppConfig: Codable, Equatable, Sendable {

public var colors: [String: PaywallComponent.ColorInfo]
public var fonts: [String: FontsConfig]

public init(colors: [String: PaywallComponent.ColorInfo],
fonts: [String: FontsConfig]) {
self.colors = colors
self.fonts = fonts
}

}

public struct FontsConfig: Codable, Equatable, Sendable {

public var ios: FontInfo

public init(ios: FontInfo) {
self.ios = ios
}

}

public enum FontInfo: Codable, Sendable, Hashable {

case name(String)
case googleFonts(String)

public func encode(to encoder: any Encoder) throws {
var container = encoder.container(keyedBy: CodingKeys.self)

switch self {
case .name(let name):
try container.encode(FontInfoTypes.name.rawValue, forKey: .type)
try container.encode(name, forKey: .value)
case .googleFonts(let name):
try container.encode(FontInfoTypes.googleFonts.rawValue, forKey: .type)
try container.encode(name, forKey: .value)
}
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
let type = try container.decode(FontInfoTypes.self, forKey: .type)

switch type {
case .name:
let value = try container.decode(String.self, forKey: .value)
self = .name(value)
case .googleFonts:
let value = try container.decode(String.self, forKey: .value)
self = .googleFonts(value)
}
}

// swiftlint:disable:next nesting
private enum CodingKeys: String, CodingKey {

case type
case value

}

// swiftlint:disable:next nesting
private enum FontInfoTypes: String, Decodable {

case name
case googleFonts

}

}

public struct VariableConfig: Codable, Equatable, Sendable {

public var variableCompatibilityMap: [String: String]
public var functionCompatibilityMap: [String: String]

public init(
variableCompatibilityMap: [String: String],
functionCompatibilityMap: [String: String]
) {
self.variableCompatibilityMap = variableCompatibilityMap
self.functionCompatibilityMap = functionCompatibilityMap
}

}

public var app: AppConfig
public var localizations: [String: [String: String]]
public var variableConfig: VariableConfig

public init(app: AppConfig,
localizations: [String: [String: String]],
variableConfig: VariableConfig) {
self.app = app
self.localizations = localizations
self.variableConfig = variableConfig
}

}

#else

public struct UIConfig: Codable, Equatable, Sendable {

}

#endif
Loading

0 comments on commit 9ef833b

Please sign in to comment.