Skip to content

Commit

Permalink
Apply state and conditions ONLY for text component (#4417)
Browse files Browse the repository at this point in the history
* 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
  • Loading branch information
joshdholtz authored Nov 1, 2024
1 parent bcef02a commit 54f4583
Show file tree
Hide file tree
Showing 15 changed files with 664 additions and 132 deletions.
26 changes: 18 additions & 8 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -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 */; };
Expand Down Expand Up @@ -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 */; };
Expand Down Expand Up @@ -1187,10 +1189,13 @@
2C6CC1152B8D2B6800432E4D /* PurchasesSyncAttributesAndOfferingsIfNeededTests.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = PurchasesSyncAttributesAndOfferingsIfNeededTests.swift; sourceTree = "<group>"; };
2C7F0AD22B8EEB4600381179 /* RateLimiter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimiter.swift; sourceTree = "<group>"; };
2C7F0AD42B8EEF0B00381179 /* RateLimiterRests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimiterRests.swift; sourceTree = "<group>"; };
2C8EC6AE2CCBD33E00D6CCF8 /* ButtonComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonComponentTests.swift; sourceTree = "<group>"; };
2C8EC6DA2CCC23B700D6CCF8 /* Template5Preview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Template5Preview.swift; sourceTree = "<group>"; };
2C8EC6DC2CCC7C5500D6CCF8 /* PackageValidator.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageValidator.swift; sourceTree = "<group>"; };
2C8EC6DE2CCD27A100D6CCF8 /* PartialComponentTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialComponentTests.swift; sourceTree = "<group>"; };
2C8EC6E02CCD7BA300D6CCF8 /* PartialComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PartialComponent.swift; sourceTree = "<group>"; };
2C8EC6E02CCD7BA300D6CCF8 /* ComponentOverrides.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentOverrides.swift; sourceTree = "<group>"; };
2C8EC71A2CCDD43500D6CCF8 /* ComponentViewState.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ComponentViewState.swift; sourceTree = "<group>"; };
2C8EC71F2CCF275E00D6CCF8 /* LocalizedPartials.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LocalizedPartials.swift; sourceTree = "<group>"; };
2CAB87F62CAAB13200247013 /* CornerBorder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CornerBorder.swift; sourceTree = "<group>"; };
2CB8CF9227BF538F00C34DE3 /* PlatformInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformInfo.swift; sourceTree = "<group>"; };
2CC7914B2CC0452100FBE120 /* PackageComponentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PackageComponentView.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2062,7 +2067,7 @@
88B1BAE82C813A3C001B7EE5 /* StackComponentView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackComponentView.swift; sourceTree = "<group>"; };
88B1BAE92C813A3C001B7EE5 /* StackComponentViewModel.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StackComponentViewModel.swift; sourceTree = "<group>"; };
88E679462C7503C1007E69D5 /* PaywallStackComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallStackComponent.swift; sourceTree = "<group>"; };
88EA80EC2C8771A7003E6675 /* TemplateComponentsView+extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TemplateComponentsView+extensions.swift"; sourceTree = "<group>"; };
88EA80EC2C8771A7003E6675 /* TemplateComponentsView+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "TemplateComponentsView+Extensions.swift"; sourceTree = "<group>"; };
9A65DFDD258AD60A00DE00B0 /* LogIntent.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = LogIntent.swift; sourceTree = "<group>"; };
9A65E03525918B0500DE00B0 /* ConfigureStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = ConfigureStrings.swift; sourceTree = "<group>"; };
9A65E03A25918B0900DE00B0 /* CustomerInfoStrings.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CustomerInfoStrings.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2324,13 +2329,15 @@
2CC7913E2CC0450900FBE120 /* Recovered References */ = {
isa = PBXGroup;
children = (
2C8EC6AE2CCBD33E00D6CCF8 /* ButtonComponentTests.swift */,
);
name = "Recovered References";
sourceTree = "<group>";
};
2CC7914D2CC0452100FBE120 /* Package */ = {
isa = PBXGroup;
children = (
2C8EC71A2CCDD43500D6CCF8 /* ComponentViewState.swift */,
2CC7914B2CC0452100FBE120 /* PackageComponentView.swift */,
2CC7914C2CC0452100FBE120 /* PackageComponentViewModel.swift */,
);
Expand Down Expand Up @@ -2358,7 +2365,7 @@
2CC791612CC0493600FBE120 /* Common */ = {
isa = PBXGroup;
children = (
2C8EC6E02CCD7BA300D6CCF8 /* PartialComponent.swift */,
2C8EC6E02CCD7BA300D6CCF8 /* ComponentOverrides.swift */,
2CC7915B2CC0493600FBE120 /* Border.swift */,
2CC7915C2CC0493600FBE120 /* Dimension.swift */,
2CC7915D2CC0493600FBE120 /* PaywallComponentBase.swift */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand Down Expand Up @@ -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 */,
Expand All @@ -6155,14 +6164,15 @@
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 */,
2D2AFE8F2C6A9D8700D1B0B4 /* CompatibilityContentUnavailableView.swift in Sources */,
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 */,
Expand Down
45 changes: 45 additions & 0 deletions RevenueCatUI/Templates/Components/LocalizedPartials.swift
Original file line number Diff line number Diff line change
@@ -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<T: LocalizedPartial> {

public let introOffer: LocalizedPartial?
public let states: LocalizedStates<T>?
public let conditions: LocalizedConditions<T>?

}

struct LocalizedStates<T: LocalizedPartial> {

let selected: T?

}

struct LocalizedConditions<T: LocalizedPartial> {

let compact: T?
let medium: T?
let expanded: T?

}

#endif
Original file line number Diff line number Diff line change
@@ -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
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand All @@ -35,6 +44,7 @@ struct PackageComponentView: View {
viewModel: self.viewModel.stackViewModel,
onDismiss: self.onDismiss
)
.environment(\.componentViewState, componentViewState)
}
} else {
EmptyView()
Expand All @@ -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: [
Expand Down Expand Up @@ -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(
Expand All @@ -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")
}
Expand Down
44 changes: 41 additions & 3 deletions RevenueCatUI/Templates/Components/TemplateComponentsView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -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(
Expand Down
Loading

0 comments on commit 54f4583

Please sign in to comment.