Skip to content

Commit

Permalink
Paywall component containers are all stacks (#4380)
Browse files Browse the repository at this point in the history
* Root paywalls is/has a stack

* Stacks on stacks

* Cleaned up some left over

* Removed commented out thing
  • Loading branch information
joshdholtz authored Oct 18, 2024
1 parent 62d0035 commit 17529fa
Show file tree
Hide file tree
Showing 17 changed files with 177 additions and 270 deletions.
10 changes: 1 addition & 9 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@
2C6CC1162B8D2B6900432E4D /* PurchasesSyncAttributesAndOfferingsIfNeededTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C6CC1152B8D2B6800432E4D /* PurchasesSyncAttributesAndOfferingsIfNeededTests.swift */; };
2C7F0AD32B8EEB4600381179 /* RateLimiter.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7F0AD22B8EEB4600381179 /* RateLimiter.swift */; };
2C7F0AD62B8EEF7B00381179 /* RateLimiterRests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2C7F0AD42B8EEF0B00381179 /* RateLimiterRests.swift */; };
2CAB87F12CAA3B7800247013 /* Stackable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAB87F02CAA3B7800247013 /* Stackable+Extensions.swift */; };
2CAB87F72CAAB13200247013 /* CornerBorder.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAB87F62CAAB13200247013 /* CornerBorder.swift */; };
2CB8CF9327BF538F00C34DE3 /* PlatformInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CB8CF9227BF538F00C34DE3 /* PlatformInfo.swift */; };
2CC790CA2CC00C5900FBE120 /* PackageComponentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC790B12CC00C5900FBE120 /* PackageComponentView.swift */; };
Expand All @@ -54,7 +53,6 @@
2CC791632CC0493600FBE120 /* PaywallComponentLocalization.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC7915E2CC0493600FBE120 /* PaywallComponentLocalization.swift */; };
2CC791642CC0493600FBE120 /* Border.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC7915B2CC0493600FBE120 /* Border.swift */; };
2CC791652CC0493600FBE120 /* Dimension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC7915C2CC0493600FBE120 /* Dimension.swift */; };
2CC791662CC0493600FBE120 /* StackableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC791602CC0493600FBE120 /* StackableComponent.swift */; };
2CC791672CC0493600FBE120 /* PaywallComponentBase.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CC7915D2CC0493600FBE120 /* PaywallComponentBase.swift */; };
2CD72942268A823900BFC976 /* Data+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD72941268A823900BFC976 /* Data+Extensions.swift */; };
2CD72944268A826F00BFC976 /* Date+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CD72943268A826F00BFC976 /* Date+Extensions.swift */; };
Expand Down Expand Up @@ -1201,7 +1199,6 @@
2C7F0AD42B8EEF0B00381179 /* RateLimiterRests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RateLimiterRests.swift; sourceTree = "<group>"; };
2CAB87EC2CA78ED800247013 /* StackableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackableComponent.swift; sourceTree = "<group>"; };
2CAB87EE2CA78EF600247013 /* Dimension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dimension.swift; sourceTree = "<group>"; };
2CAB87F02CAA3B7800247013 /* Stackable+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stackable+Extensions.swift"; sourceTree = "<group>"; };
2CAB87F32CAAAF9300247013 /* Border.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Border.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>"; };
Expand Down Expand Up @@ -1234,7 +1231,6 @@
2CC7915D2CC0493600FBE120 /* PaywallComponentBase.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallComponentBase.swift; sourceTree = "<group>"; };
2CC7915E2CC0493600FBE120 /* PaywallComponentLocalization.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallComponentLocalization.swift; sourceTree = "<group>"; };
2CC7915F2CC0493600FBE120 /* PaywallComponentPropertyTypes.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallComponentPropertyTypes.swift; sourceTree = "<group>"; };
2CC791602CC0493600FBE120 /* StackableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackableComponent.swift; sourceTree = "<group>"; };
2CD72941268A823900BFC976 /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = "<group>"; };
2CD72943268A826F00BFC976 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = "<group>"; };
2D00A41C2767C08300FC3DD8 /* ManageSubscriptionsStrings.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ManageSubscriptionsStrings.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -2417,7 +2413,6 @@
2CC7915D2CC0493600FBE120 /* PaywallComponentBase.swift */,
2CC7915E2CC0493600FBE120 /* PaywallComponentLocalization.swift */,
2CC7915F2CC0493600FBE120 /* PaywallComponentPropertyTypes.swift */,
2CC791602CC0493600FBE120 /* StackableComponent.swift */,
);
path = Common;
sourceTree = "<group>";
Expand Down Expand Up @@ -4081,8 +4076,8 @@
7707A94A2CAD936A006E0313 /* Button */ = {
isa = PBXGroup;
children = (
7707A94D2CAD94D2006E0313 /* ButtonComponentViewModel.swift */,
7707A94F2CAD9775006E0313 /* ButtonComponentView.swift */,
7707A94D2CAD94D2006E0313 /* ButtonComponentViewModel.swift */,
);
path = Button;
sourceTree = "<group>";
Expand Down Expand Up @@ -4463,7 +4458,6 @@
88B1BAD92C813A3C001B7EE5 /* PaywallComponentViewModel.swift */,
88B1BAE32C813A3C001B7EE5 /* TemplateComponentsView.swift */,
88EA80EC2C8771A7003E6675 /* TemplateComponentsView+extensions.swift */,
2CAB87F02CAA3B7800247013 /* Stackable+Extensions.swift */,
2CAB87F62CAAB13200247013 /* CornerBorder.swift */,
2C2AEB0D2CA64DA900A50F38 /* TemplateComponentsViewPreviews */,
7707A94A2CAD936A006E0313 /* Button */,
Expand Down Expand Up @@ -5728,7 +5722,6 @@
2CC791632CC0493600FBE120 /* PaywallComponentLocalization.swift in Sources */,
2CC791642CC0493600FBE120 /* Border.swift in Sources */,
2CC791652CC0493600FBE120 /* Dimension.swift in Sources */,
2CC791662CC0493600FBE120 /* StackableComponent.swift in Sources */,
2CC791672CC0493600FBE120 /* PaywallComponentBase.swift in Sources */,
4F6BED592A26A14400CD9322 /* DebugView.swift in Sources */,
5733B18E27FF586A00EC2045 /* BackendError.swift in Sources */,
Expand Down Expand Up @@ -6207,7 +6200,6 @@
887A607B2C1D037000E1A461 /* Bundle+Extensions.swift in Sources */,
353756662C382C2800A1B8D6 /* CustomerCenterError.swift in Sources */,
887A60CF2C1D037000E1A461 /* View+PresentPaywallFooter.swift in Sources */,
2CAB87F12CAA3B7800247013 /* Stackable+Extensions.swift in Sources */,
3537566B2C382C2800A1B8D6 /* CustomerCenterView.swift in Sources */,
887A60BB2C1D037000E1A461 /* Template4View.swift in Sources */,
887A607E2C1D037000E1A461 /* Logger.swift in Sources */,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ struct PackageComponentView: View {

var body: some View {
// WIP: Do something with package id and selection
StackComponentView(viewModel: self.viewModel.stackComponentViewModel,
StackComponentView(viewModel: self.viewModel.stackViewModel,
onDismiss: self.onDismiss)
}

Expand All @@ -39,8 +39,8 @@ struct PackageComponentView: View {
@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
struct PackageComponentView_Previews: PreviewProvider {

static var components: [PaywallComponent] = [
.stack(.init(
static var stack: PaywallComponent.StackComponent {
return .init(
components: [
.text(.init(
text: "name",
Expand All @@ -63,8 +63,15 @@ struct PackageComponentView_Previews: PreviewProvider {
bottom: 10,
leading: 20,
trailing: 20)
))
]
)
}

static var package: Package {
return .init(identifier: "weekly",
packageType: .weekly,
storeProduct: .init(sk1Product: .init()),
offeringIdentifier: "default")
}

static var previews: some View {
// Package
Expand All @@ -77,15 +84,12 @@ struct PackageComponentView_Previews: PreviewProvider {
],
component: .init(
packageID: "weekly",
components: components
stack: stack
),
offering: .init(identifier: "default",
serverDescription: "",
availablePackages: [
.init(identifier: "weekly",
packageType: .weekly,
storeProduct: .init(sk1Product: .init()),
offeringIdentifier: "default")])
availablePackages: [package]),
package: package
), onDismiss: {}
)
.previewLayout(.sizeThatFits)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,41 +24,24 @@ class PackageComponentViewModel {
private let offering: Offering

let package: Package
let stackComponentViewModel: StackComponentViewModel
let stackViewModel: StackComponentViewModel

init(localizedStrings: PaywallComponent.LocalizationDictionary,
component: PaywallComponent.PackageComponent,
offering: Offering) throws {
offering: Offering,
package: Package) throws {
self.localizedStrings = localizedStrings
self.component = component
self.offering = offering
self.package = package

self.package = try Self.findPackage(identifier: component.packageID, offering: offering)

self.stackComponentViewModel = try self.component.toStackComponentViewModel(
components: self.component.components,
self.stackViewModel = try StackComponentViewModel(
component: component.stack,
localizedStrings: localizedStrings,
offering: offering
)
}

static func findPackage(identifier: String, offering: Offering) throws -> Package {
guard let package = offering.package(identifier: identifier) else {
Logger.error(Strings.paywall_could_not_find_package(identifier))
throw PackageValidationError.missingPackage(
"Missing package from offering: \"\(identifier)\""
)
}

return package
}

enum PackageValidationError: Error {

case missingPackage(String)

}

}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -28,8 +28,7 @@ struct PackageGroupComponentView: View {

var body: some View {
// WIP: Do something with default package id and selection
StackComponentView(viewModel: self.viewModel.stackComponentViewModel,
onDismiss: self.onDismiss)
StackComponentView(viewModel: viewModel.stackViewModel, onDismiss: self.onDismiss)
.onAppear {
self.paywallState.select(package: self.viewModel.defaultPackage)
}
Expand Down Expand Up @@ -59,7 +58,7 @@ struct PackagesComponentView_Previews: PreviewProvider {
static func makePackage(packageID: String,
nameTextLid: String,
detailTextLid: String) -> PaywallComponent.PackageComponent {
let stack: PaywallComponent = .stack(.init(
let stack: PaywallComponent.StackComponent = .init(
components: [
.text(.init(
text: nameTextLid,
Expand All @@ -82,11 +81,11 @@ struct PackagesComponentView_Previews: PreviewProvider {
bottom: 10,
leading: 20,
trailing: 20)
))
)

return PaywallComponent.PackageComponent(
packageID: packageID,
components: [stack]
stack: stack
)
}

Expand All @@ -106,7 +105,7 @@ struct PackagesComponentView_Previews: PreviewProvider {
],
component: PaywallComponent.PackageGroupComponent(
defaultSelectedPackageID: "weekly",
packages: packages
stack: .init(components: packages)
),
offering: Offering(identifier: "default",
serverDescription: "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ class PackageGroupComponentViewModel {
private let offering: Offering

let defaultPackage: Package
let stackComponentViewModel: StackComponentViewModel
let stackViewModel: StackComponentViewModel

init(localizedStrings: PaywallComponent.LocalizationDictionary,
component: PaywallComponent.PackageGroupComponent,
Expand All @@ -36,20 +36,42 @@ class PackageGroupComponentViewModel {
let info = try Self.getPackages(component: component, offering: offering)
self.defaultPackage = info.defaultPackage

self.stackComponentViewModel = try self.component.toStackComponentViewModel(
components: info.availablePackageComponents.map { .package($0) },
localizedStrings: localizedStrings,
offering: offering
let componentViewModels = try info.availablePackageComponents.map { info in
try PackageComponentViewModel(
localizedStrings: localizedStrings,
component: info.component,
offering: offering,
package: info.package
).stackViewModel
}.map(PaywallComponentViewModel.stack)

self.stackViewModel = StackComponentViewModel(
component: .init(
components: [], // Empty on purpose because we are feeding this view models already
dimension: component.stack.dimension,
width: component.stack.width,
spacing: component.stack.spacing,
backgroundColor: component.stack.backgroundColor,
padding: component.stack.padding,
margin: component.stack.margin,
cornerRadiuses: component.stack.cornerRadiuses,
border: component.stack.border
),
viewModels: componentViewModels
)
}

static func getPackages(
component: PaywallComponent.PackageGroupComponent,
offering: Offering
) throws -> (defaultPackage: Package, availablePackageComponents: [PaywallComponent.PackageComponent]) {
) throws -> (defaultPackage: Package,
availablePackageComponents: [(component: PaywallComponent.PackageComponent, package: Package)]) {

// Stack of packages
let packages = component.stack.components

// Get list of available package components and their packages
let availablePackageInfos = component.packages.compactMap { packageComponent in
let availablePackageInfos = packages.compactMap { packageComponent in
let pkg = offering.availablePackages.first(where: { $0.packageIdentifier == packageComponent.packageID })
if let pkg {
return (component: packageComponent, package: pkg)
Expand All @@ -69,12 +91,11 @@ class PackageGroupComponentViewModel {
return packageInfo.package.id == component.defaultSelectedPackageID
}

let availablePackageComponents = availablePackageInfos.map { $0.component }
if let defaultPackage {
return (defaultPackage.package, availablePackageComponents)
return (defaultPackage.package, availablePackageInfos)
} else {
Logger.warning(Strings.paywall_could_not_find_default_package(component.defaultSelectedPackageID))
return (firstPackage.package, availablePackageComponents)
return (firstPackage.package, availablePackageInfos)
}
}

Expand Down
17 changes: 10 additions & 7 deletions RevenueCatUI/Templates/Components/PaywallComponentViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ enum PaywallComponentViewModel {
case linkButton(LinkButtonComponentViewModel)
case button(ButtonComponentViewModel)
case packageGroup(PackageGroupComponentViewModel)
case package(PackageComponentViewModel)
// Purposely leaving out a `case package` since `PackageGroupComponentViewModel` creates this model
case purchaseButton(PurchaseButtonComponentViewModel)

}
Expand Down Expand Up @@ -70,12 +70,9 @@ extension PaywallComponent {
component: component,
offering: offering)
)
case .package(let component):
return .package(
try PackageComponentViewModel(localizedStrings: localizedStrings,
component: component,
offering: offering)
)
case .package:
// PackageGroupViewModel makes the PackageViewModel since it needs a Package
throw PaywallComponentViewModelError.invalidAttemptToCreatePackage
case .purchaseButton(let component):
return .purchaseButton(
try PurchaseButtonComponentViewModel(localizedStrings: localizedStrings,
Expand All @@ -84,6 +81,12 @@ extension PaywallComponent {
}
}

enum PaywallComponentViewModelError: Error {

case invalidAttemptToCreatePackage

}

}

#endif
Original file line number Diff line number Diff line change
Expand Up @@ -23,14 +23,25 @@ class StackComponentViewModel {

let viewModels: [PaywallComponentViewModel]

init(component: PaywallComponent.StackComponent,
localizedStrings: PaywallComponent.LocalizationDictionary,
offering: Offering
convenience init(
component: PaywallComponent.StackComponent,
localizedStrings: PaywallComponent.LocalizationDictionary,
offering: Offering
) throws {
self.init(
component: component,
viewModels: try component.components.map {
try $0.toViewModel(offering: offering, localizedStrings: localizedStrings)
}
)
}

init(
component: PaywallComponent.StackComponent,
viewModels: [PaywallComponentViewModel]
) {
self.component = component
self.viewModels = try component.components.map {
try $0.toViewModel(offering: offering, localizedStrings: localizedStrings)
}
self.viewModels = viewModels
}

var dimension: PaywallComponent.Dimension {
Expand Down
Loading

0 comments on commit 17529fa

Please sign in to comment.