diff --git a/RevenueCat.xcodeproj/project.pbxproj b/RevenueCat.xcodeproj/project.pbxproj index 341ef2d6f6..7c3838a857 100644 --- a/RevenueCat.xcodeproj/project.pbxproj +++ b/RevenueCat.xcodeproj/project.pbxproj @@ -29,6 +29,9 @@ 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 */; }; + 2CAB87ED2CA78ED800247013 /* StackableComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAB87EC2CA78ED800247013 /* StackableComponent.swift */; }; + 2CAB87EF2CA78EF600247013 /* Dimension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAB87EE2CA78EF600247013 /* Dimension.swift */; }; + 2CAB87F12CAA3B7800247013 /* Stackable+Extensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CAB87F02CAA3B7800247013 /* Stackable+Extensions.swift */; }; 2CB8CF9327BF538F00C34DE3 /* PlatformInfo.swift in Sources */ = {isa = PBXBuildFile; fileRef = 2CB8CF9227BF538F00C34DE3 /* PlatformInfo.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 */; }; @@ -1173,6 +1176,9 @@ 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 = ""; }; + 2CAB87EC2CA78ED800247013 /* StackableComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StackableComponent.swift; sourceTree = ""; }; + 2CAB87EE2CA78EF600247013 /* Dimension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Dimension.swift; sourceTree = ""; }; + 2CAB87F02CAA3B7800247013 /* Stackable+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Stackable+Extensions.swift"; sourceTree = ""; }; 2CB8CF9227BF538F00C34DE3 /* PlatformInfo.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PlatformInfo.swift; sourceTree = ""; }; 2CD72941268A823900BFC976 /* Data+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data+Extensions.swift"; sourceTree = ""; }; 2CD72943268A826F00BFC976 /* Date+Extensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date+Extensions.swift"; sourceTree = ""; }; @@ -2267,30 +2273,30 @@ path = StoreKit2; sourceTree = ""; }; - 2C2AEB0D2CA64DA900A50F38 /* Previews */ = { + 2C2AEB0D2CA64DA900A50F38 /* TemplateComponentsViewPreviews */ = { isa = PBXGroup; children = ( 2C2AEB0E2CA64E0E00A50F38 /* Template1Preview.swift */, ); - path = Previews; + path = TemplateComponentsViewPreviews; sourceTree = ""; }; - 2C2AEB402CA72A3000A50F38 /* PackageGroup */ = { + 2C2AEB402CA72A3000A50F38 /* Package */ = { isa = PBXGroup; children = ( 2C2AEB4B2CA7339200A50F38 /* PackageComponentView.swift */, 2C2AEB452CA7302400A50F38 /* PackageComponentViewModel.swift */, ); - path = PackageGroup; + path = Package; sourceTree = ""; }; - 2C2AEB412CA72A3600A50F38 /* Package */ = { + 2C2AEB412CA72A3600A50F38 /* PackageGroup */ = { isa = PBXGroup; children = ( 2C2AEB492CA7338A00A50F38 /* PackageGroupComponentView.swift */, 2C2AEB432CA72D9F00A50F38 /* PackageGroupComponentViewModel.swift */, ); - path = Package; + path = PackageGroup; sourceTree = ""; }; 2C2AEB422CA72A3A00A50F38 /* PurchaseButton */ = { @@ -2302,6 +2308,28 @@ path = PurchaseButton; sourceTree = ""; }; + 2C2AEB4F2CA785E200A50F38 /* Packages */ = { + isa = PBXGroup; + children = ( + 2C2AEB412CA72A3600A50F38 /* PackageGroup */, + 2C2AEB402CA72A3000A50F38 /* Package */, + 2C2AEB422CA72A3A00A50F38 /* PurchaseButton */, + ); + path = Packages; + sourceTree = ""; + }; + 2CAB87EB2CA78EA100247013 /* Common */ = { + isa = PBXGroup; + children = ( + 88AD01012C740CF400AA1F2B /* PaywallComponentBase.swift */, + 88EA80EE2C87D68F003E6675 /* PaywallComponentLocalization.swift */, + 88B1BAD72C812D72001B7EE5 /* PaywallComponentPropertyTypes.swift */, + 2CAB87EC2CA78ED800247013 /* StackableComponent.swift */, + 2CAB87EE2CA78EF600247013 /* Dimension.swift */, + ); + path = Common; + sourceTree = ""; + }; 2CD72940268A820E00BFC976 /* FoundationExtensions */ = { isa = PBXGroup; children = ( @@ -4310,9 +4338,7 @@ 88AD010B2C740CF400AA1F2B /* Components */ = { isa = PBXGroup; children = ( - 88AD01012C740CF400AA1F2B /* PaywallComponentBase.swift */, - 88EA80EE2C87D68F003E6675 /* PaywallComponentLocalization.swift */, - 88B1BAD72C812D72001B7EE5 /* PaywallComponentPropertyTypes.swift */, + 2CAB87EB2CA78EA100247013 /* Common */, 88AD01032C740CF400AA1F2B /* PaywallImageComponent.swift */, 88AD01062C740CF400AA1F2B /* PaywallSpacerComponent.swift */, 88E679462C7503C1007E69D5 /* PaywallStackComponent.swift */, @@ -4328,18 +4354,17 @@ 88AD01352C74196600AA1F2B /* Components */ = { isa = PBXGroup; children = ( - 2C2AEB412CA72A3600A50F38 /* Package */, - 2C2AEB402CA72A3000A50F38 /* PackageGroup */, - 2C2AEB422CA72A3A00A50F38 /* PurchaseButton */, - 2C2AEB0D2CA64DA900A50F38 /* Previews */, - 88B1BAE62C813A3C001B7EE5 /* Image */, - 88B1BAE22C813A3C001B7EE5 /* LinkButton */, 88B1BAE72C813A3C001B7EE5 /* PaywallComponentTypeTransformers.swift */, 88B1BAD92C813A3C001B7EE5 /* PaywallComponentViewModel.swift */, - 88B1BADF2C813A3C001B7EE5 /* Spacer */, - 88B1BAEA2C813A3C001B7EE5 /* Stack */, 88B1BAE32C813A3C001B7EE5 /* TemplateComponentsView.swift */, 88EA80EC2C8771A7003E6675 /* TemplateComponentsView+extensions.swift */, + 2CAB87F02CAA3B7800247013 /* Stackable+Extensions.swift */, + 2C2AEB0D2CA64DA900A50F38 /* TemplateComponentsViewPreviews */, + 88B1BAE62C813A3C001B7EE5 /* Image */, + 88B1BAE22C813A3C001B7EE5 /* LinkButton */, + 2C2AEB4F2CA785E200A50F38 /* Packages */, + 88B1BADF2C813A3C001B7EE5 /* Spacer */, + 88B1BAEA2C813A3C001B7EE5 /* Stack */, 88B1BADC2C813A3C001B7EE5 /* Text */, ); path = Components; @@ -5500,6 +5525,7 @@ 4F6ABC782A81595900250E63 /* PaywallCacheWarming.swift in Sources */, F5714EAA26D7A85D00635477 /* PeriodType+Extensions.swift in Sources */, 3592E88A2C2ED54A00D7F91D /* CustomerCenterConfigData.swift in Sources */, + 2CAB87ED2CA78ED800247013 /* StackableComponent.swift in Sources */, 57045B3A29C51751001A5417 /* GetProductEntitlementMappingOperation.swift in Sources */, 4FC083292A4A35FB00A97089 /* Integer+Extensions.swift in Sources */, F5BE447D269E4ADB00254A30 /* ASIdManagerProxy.swift in Sources */, @@ -5566,6 +5592,7 @@ 2DDF41B324F6F387005BC22D /* InAppPurchaseBuilder.swift in Sources */, 4F4FF3E12A3B731A0028018C /* ETagStrings.swift in Sources */, 57D5414227F656D9004CC35C /* NetworkError.swift in Sources */, + 2CAB87EF2CA78EF600247013 /* Dimension.swift in Sources */, B3781568285A79FC000A7B93 /* IdentityAPI.swift in Sources */, B325543C2825C81800DA62EA /* Configuration.swift in Sources */, 4F89A55D2A6ABADF008A411E /* PaywallViewMode.swift in Sources */, @@ -6063,6 +6090,7 @@ 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 */, diff --git a/RevenueCatUI/Templates/Components/Image/ImageComponentViewModel.swift b/RevenueCatUI/Templates/Components/Image/ImageComponentViewModel.swift index 8931101af4..7329e52a73 100644 --- a/RevenueCatUI/Templates/Components/Image/ImageComponentViewModel.swift +++ b/RevenueCatUI/Templates/Components/Image/ImageComponentViewModel.swift @@ -10,7 +10,6 @@ // ImageComponentView.swift // // Created by Josh Holtz on 6/11/24. -// swiftlint:disable missing_docs import Foundation import RevenueCat @@ -19,7 +18,7 @@ import SwiftUI #if PAYWALL_COMPONENTS @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -public class ImageComponentViewModel { +class ImageComponentViewModel { private let localizedStrings: PaywallComponent.LocalizationDictionary private let component: PaywallComponent.ImageComponent @@ -37,19 +36,23 @@ public class ImageComponentViewModel { } } - public var url: URL { + var url: URL { self.imageInfo.light.heic } - public var cornerRadiuses: PaywallComponent.CornerRadiuses { + + var cornerRadiuses: PaywallComponent.CornerRadiuses { component.cornerRadiuses } - public var gradientColors: [Color] { + + var gradientColors: [Color] { component.gradientColors?.compactMap { $0.toColor(fallback: Color.clear) } ?? [] } - public var contentMode: ContentMode { + + var contentMode: ContentMode { component.fitMode.contentMode } - public var maxHeight: CGFloat? { + + var maxHeight: CGFloat? { component.maxHeight } diff --git a/RevenueCatUI/Templates/Components/Package/PackageGroupComponentView.swift b/RevenueCatUI/Templates/Components/Package/PackageGroupComponentView.swift deleted file mode 100644 index 06868768ca..0000000000 --- a/RevenueCatUI/Templates/Components/Package/PackageGroupComponentView.swift +++ /dev/null @@ -1,31 +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 -// -// PackageGroupComponentView.swift -// -// Created by Josh Holtz on 9/27/24. - -import Foundation -import RevenueCat -import SwiftUI - -#if PAYWALL_COMPONENTS - -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -struct PackageGroupComponentView: View { - - let viewModel: PackageGroupComponentViewModel - - var body: some View { - EmptyView() - } - -} - -#endif diff --git a/RevenueCatUI/Templates/Components/PackageGroup/PackageComponentView.swift b/RevenueCatUI/Templates/Components/PackageGroup/PackageComponentView.swift deleted file mode 100644 index d89d7717e5..0000000000 --- a/RevenueCatUI/Templates/Components/PackageGroup/PackageComponentView.swift +++ /dev/null @@ -1,31 +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 -// -// PackageComponentView.swift -// -// Created by Josh Holtz on 9/27/24. - -import Foundation -import RevenueCat -import SwiftUI - -#if PAYWALL_COMPONENTS - -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -struct PackageComponentView: View { - - let viewModel: PackageComponentViewModel - - var body: some View { - EmptyView() - } - -} - -#endif diff --git a/RevenueCatUI/Templates/Components/Packages/Package/PackageComponentView.swift b/RevenueCatUI/Templates/Components/Packages/Package/PackageComponentView.swift new file mode 100644 index 0000000000..bb3a639cbe --- /dev/null +++ b/RevenueCatUI/Templates/Components/Packages/Package/PackageComponentView.swift @@ -0,0 +1,89 @@ +// +// 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 +// +// PackageComponentView.swift +// +// Created by Josh Holtz on 9/27/24. + +import Foundation +import RevenueCat +import SwiftUI + +#if PAYWALL_COMPONENTS + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +struct PackageComponentView: View { + + let viewModel: PackageComponentViewModel + + var body: some View { + // WIP: Do something with package id and selection + StackComponentView(viewModel: self.viewModel.stackComponentViewModel) + } + +} + +#if DEBUG + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +struct PackageComponentView_Previews: PreviewProvider { + + static var components: [PaywallComponent] = [ + .stack(.init( + components: [ + .text(.init( + text: "name", + fontWeight: .bold, + color: .init(light: "#000000"), + padding: .zero, + margin: .zero + )), + .text(.init( + text: "detail", + color: .init(light: "#000000"), + padding: .zero, + margin: .zero + )) + ], + dimension: .vertical(.leading), + spacing: 0, + backgroundColor: nil, + padding: .init(top: 10, + bottom: 10, + leading: 20, + trailing: 20) + )) + ] + + static var previews: some View { + // Package + PackageComponentView( + // swiftlint:disable:next force_try + viewModel: try! .init( + localizedStrings: [ + "name": .string("Weekly"), + "detail": .string("Get for $39.99/wk") + ], + component: .init( + packageID: "$rc_weekly", + components: components + ), + offering: .init(identifier: "", + serverDescription: "", + availablePackages: []) + ) + ) + .previewLayout(.sizeThatFits) + .previewDisplayName("Package") + } +} + +#endif + +#endif diff --git a/RevenueCatUI/Templates/Components/PackageGroup/PackageComponentViewModel.swift b/RevenueCatUI/Templates/Components/Packages/Package/PackageComponentViewModel.swift similarity index 64% rename from RevenueCatUI/Templates/Components/PackageGroup/PackageComponentViewModel.swift rename to RevenueCatUI/Templates/Components/Packages/Package/PackageComponentViewModel.swift index 295a017458..5d962ded20 100644 --- a/RevenueCatUI/Templates/Components/PackageGroup/PackageComponentViewModel.swift +++ b/RevenueCatUI/Templates/Components/Packages/Package/PackageComponentViewModel.swift @@ -14,20 +14,28 @@ import Foundation import RevenueCat -// swiftlint:disable missing_docs - #if PAYWALL_COMPONENTS @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -public class PackageComponentViewModel { +class PackageComponentViewModel { private let localizedStrings: PaywallComponent.LocalizationDictionary private let component: PaywallComponent.PackageComponent + private let offering: Offering + + let stackComponentViewModel: StackComponentViewModel init(localizedStrings: PaywallComponent.LocalizationDictionary, - component: PaywallComponent.PackageComponent) throws { + component: PaywallComponent.PackageComponent, + offering: Offering) throws { self.localizedStrings = localizedStrings self.component = component + self.offering = offering + + self.stackComponentViewModel = try self.component.toStackComponentViewModel( + localizedStrings: localizedStrings, + offering: offering + ) } } diff --git a/RevenueCatUI/Templates/Components/Packages/PackageGroup/PackageGroupComponentView.swift b/RevenueCatUI/Templates/Components/Packages/PackageGroup/PackageGroupComponentView.swift new file mode 100644 index 0000000000..d323d3bd72 --- /dev/null +++ b/RevenueCatUI/Templates/Components/Packages/PackageGroup/PackageGroupComponentView.swift @@ -0,0 +1,107 @@ +// +// 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 +// +// PackageGroupComponentView.swift +// +// Created by Josh Holtz on 9/27/24. + +import Foundation +import RevenueCat +import SwiftUI + +#if PAYWALL_COMPONENTS + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +struct PackageGroupComponentView: View { + + let viewModel: PackageGroupComponentViewModel + + var body: some View { + // WIP: Do something with default package id and selection + StackComponentView(viewModel: self.viewModel.stackComponentViewModel) + } + +} + +#if DEBUG + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +struct PackagesComponentView_Previews: PreviewProvider { + + static let packages: [PaywallComponent] = [ + makePackage(packageID: "weekly", + nameTextLid: "weekly_name", + detailTextLid: "weekly_detail"), + makePackage(packageID: "monthly", + nameTextLid: "monthly_name", + detailTextLid: "monthly_detail") + ] + + static func makePackage(packageID: String, + nameTextLid: String, + detailTextLid: String) -> PaywallComponent { + let stack: PaywallComponent = .stack(.init( + components: [ + .text(.init( + text: nameTextLid, + fontWeight: .bold, + color: .init(light: "#000000"), + padding: .zero, + margin: .zero + )), + .text(.init( + text: detailTextLid, + color: .init(light: "#000000"), + padding: .zero, + margin: .zero + )) + ], + dimension: .vertical(.leading), + spacing: 0, + backgroundColor: nil, + padding: .init(top: 10, + bottom: 10, + leading: 20, + trailing: 20) + )) + + return .package(.init( + packageID: "weekly", + components: [stack] + )) + } + + static var previews: some View { + // Packages + PackageGroupComponentView( + // swiftlint:disable:next force_try + viewModel: try! .init( + localizedStrings: [ + "weekly_name": .string("Weekly"), + "weekly_detail": .string("Get for $39.99/week"), + "monthly_name": .string("Monthly"), + "monthly_detail": .string("Get for $139.99/month") + ], + component: PaywallComponent.PackageGroupComponent( + defaultSelectedPackageID: "weekly", + components: packages + ), + offering: Offering(identifier: "", + serverDescription: "", + availablePackages: []) + ) + ) + .previewLayout(.sizeThatFits) + .previewDisplayName("Packages") + } +} + +#endif + +#endif diff --git a/RevenueCatUI/Templates/Components/Package/PackageGroupComponentViewModel.swift b/RevenueCatUI/Templates/Components/Packages/PackageGroup/PackageGroupComponentViewModel.swift similarity index 64% rename from RevenueCatUI/Templates/Components/Package/PackageGroupComponentViewModel.swift rename to RevenueCatUI/Templates/Components/Packages/PackageGroup/PackageGroupComponentViewModel.swift index e908f372de..12c0f0885a 100644 --- a/RevenueCatUI/Templates/Components/Package/PackageGroupComponentViewModel.swift +++ b/RevenueCatUI/Templates/Components/Packages/PackageGroup/PackageGroupComponentViewModel.swift @@ -14,20 +14,28 @@ import Foundation import RevenueCat -// swiftlint:disable missing_docs - #if PAYWALL_COMPONENTS @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -public class PackageGroupComponentViewModel { +class PackageGroupComponentViewModel { private let localizedStrings: PaywallComponent.LocalizationDictionary private let component: PaywallComponent.PackageGroupComponent + private let offering: Offering + + let stackComponentViewModel: StackComponentViewModel init(localizedStrings: PaywallComponent.LocalizationDictionary, - component: PaywallComponent.PackageGroupComponent) throws { + component: PaywallComponent.PackageGroupComponent, + offering: Offering) throws { self.localizedStrings = localizedStrings self.component = component + self.offering = offering + + self.stackComponentViewModel = try self.component.toStackComponentViewModel( + localizedStrings: localizedStrings, + offering: offering + ) } } diff --git a/RevenueCatUI/Templates/Components/Packages/PurchaseButton/PurchaseButtonComponentView.swift b/RevenueCatUI/Templates/Components/Packages/PurchaseButton/PurchaseButtonComponentView.swift new file mode 100644 index 0000000000..b6152eb87a --- /dev/null +++ b/RevenueCatUI/Templates/Components/Packages/PurchaseButton/PurchaseButtonComponentView.swift @@ -0,0 +1,142 @@ +// +// 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 +// +// PurchaseButtonComponentView.swift +// +// Created by Josh Holtz on 9/27/24. + +import Foundation +import RevenueCat +import SwiftUI + +#if PAYWALL_COMPONENTS + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +struct PurchaseButtonComponentView: View { + + private let viewModel: PurchaseButtonComponentViewModel + + internal init(viewModel: PurchaseButtonComponentViewModel) { + self.viewModel = viewModel + } + + var body: some View { + Button { + // WIP: Need to perform purchase logic + } label: { + // WIP: Need to add logic for intro offer + Text(viewModel.cta) + .font(viewModel.textStyle) + .fontWeight(viewModel.fontWeight) + .fixedSize(horizontal: false, vertical: true) + .multilineTextAlignment(viewModel.horizontalAlignment) + .foregroundStyle(viewModel.color) + .padding(viewModel.padding) + .background(viewModel.backgroundColor) + .shape(viewModel.clipShape) + .applyIfLet(viewModel.cornerRadiuses, apply: { view, value in + view + .roundedCorner(value.topLeading, corners: .topLeft) + .roundedCorner(value.topTrailing, corners: .topRight) + .roundedCorner(value.bottomLeading, corners: .bottomLeft) + .roundedCorner(value.bottomTrailing, corners: .bottomRight) + }) + .padding(viewModel.margin) + } + } + +} + +private struct ShapeModifier: ViewModifier { + var shape: PaywallComponent.Shape + + func body(content: Content) -> some View { + switch shape { + case .pill: + content + .clipShape(Capsule()) + case .rectangle: + content + } + } +} + +private extension View { + + func shape(_ shape: PaywallComponent.Shape) -> some View { + self.modifier(ShapeModifier(shape: shape)) + } + +} + +#if DEBUG + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +struct PurchaseButtonComponentView_Previews: PreviewProvider { + + static var previews: some View { + // Pill + PurchaseButtonComponentView( + // swiftlint:disable:next force_try + viewModel: try! .init( + localizedStrings: [ + "id_1": .string("Hello, world"), + "id_2": .string("Hello, world intro offer") + ], + component: .init( + cta: "id_1", + ctaIntroOffer: "id_2", + fontWeight: .bold, + color: .init(light: "#ffffff"), + backgroundColor: .init(light: "#ff0000"), + padding: .init(top: 10, + bottom: 10, + leading: 30, + trailing: 30), + shape: .pill + ) + ) + ) + .previewLayout(.sizeThatFits) + .previewDisplayName("Pill") + + // Rounded Rectangle + PurchaseButtonComponentView( + // swiftlint:disable:next force_try + viewModel: try! .init( + localizedStrings: [ + "id_1": .string("Hello, world"), + "id_2": .string("Hello, world intro offer") + ], + component: .init( + cta: "id_1", + ctaIntroOffer: "id_2", + fontWeight: .bold, + color: .init(light: "#ffffff"), + backgroundColor: .init(light: "#ff0000"), + padding: .init(top: 10, + bottom: 10, + leading: 30, + trailing: 30), + shape: .rectangle, + cornerRadiuses: .init(topLeading: 8, + topTrailing: 8, + bottomLeading: 8, + bottomTrailing: 8) + ) + ) + ) + .previewLayout(.sizeThatFits) + .previewDisplayName("Rounded Rectangle") + } +} + +#endif + +#endif diff --git a/RevenueCatUI/Templates/Components/Packages/PurchaseButton/PurchaseButtonComponentViewModel.swift b/RevenueCatUI/Templates/Components/Packages/PurchaseButton/PurchaseButtonComponentViewModel.swift new file mode 100644 index 0000000000..fee1d89f49 --- /dev/null +++ b/RevenueCatUI/Templates/Components/Packages/PurchaseButton/PurchaseButtonComponentViewModel.swift @@ -0,0 +1,82 @@ +// +// 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 +// +// PurchaseButtonComponentViewModel.swift +// +// Created by Josh Holtz on 9/27/24. + +import Foundation +import RevenueCat +import SwiftUI + +#if PAYWALL_COMPONENTS + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +class PurchaseButtonComponentViewModel { + + private let localizedStrings: PaywallComponent.LocalizationDictionary + private let component: PaywallComponent.PurchaseButtonComponent + + let cta: String + let ctaIntroOffer: String? + + init(localizedStrings: PaywallComponent.LocalizationDictionary, + component: PaywallComponent.PurchaseButtonComponent) throws { + self.localizedStrings = localizedStrings + self.component = component + + self.cta = try localizedStrings.string(key: component.cta) + self.ctaIntroOffer = try component.ctaIntroOffer.flatMap { + try localizedStrings.string(key: $0) + } + } + + var fontFamily: String? { + component.fontFamily + } + + var fontWeight: Font.Weight { + component.fontWeight.fontWeight + } + + var color: Color { + component.color.toDyanmicColor() + } + + var textStyle: Font { + component.textStyle.font + } + + var horizontalAlignment: TextAlignment { + component.horizontalAlignment.textAlignment + } + + var backgroundColor: Color { + component.backgroundColor?.toDyanmicColor() ?? Color.clear + } + + var padding: EdgeInsets { + component.padding.edgeInsets + } + + var margin: EdgeInsets { + component.margin.edgeInsets + } + + var clipShape: PaywallComponent.Shape { + component.shape + } + + var cornerRadiuses: PaywallComponent.CornerRadiuses? { + component.cornerRadiuses + } + +} + +#endif diff --git a/RevenueCatUI/Templates/Components/PaywallComponentViewModel.swift b/RevenueCatUI/Templates/Components/PaywallComponentViewModel.swift index de043d041f..ae4b7896d3 100644 --- a/RevenueCatUI/Templates/Components/PaywallComponentViewModel.swift +++ b/RevenueCatUI/Templates/Components/PaywallComponentViewModel.swift @@ -30,7 +30,6 @@ extension PaywallComponent { func toViewModel( offering: Offering, - locale: Locale, localizedStrings: LocalizationDictionary ) throws -> PaywallComponentViewModel { switch self { @@ -48,8 +47,7 @@ extension PaywallComponent { ) case .stack(let component): return .stack( - try StackComponentViewModel(locale: locale, - component: component, + try StackComponentViewModel(component: component, localizedStrings: localizedStrings, offering: offering) ) @@ -61,12 +59,14 @@ extension PaywallComponent { case .packageGroup(let component): return .packageGroup( try PackageGroupComponentViewModel(localizedStrings: localizedStrings, - component: component) + component: component, + offering: offering) ) case .package(let component): return .package( try PackageComponentViewModel(localizedStrings: localizedStrings, - component: component) + component: component, + offering: offering) ) case .purchaseButton(let component): return .purchaseButton( diff --git a/RevenueCatUI/Templates/Components/PurchaseButton/PurchaseButtonComponentView.swift b/RevenueCatUI/Templates/Components/PurchaseButton/PurchaseButtonComponentView.swift deleted file mode 100644 index 18afb088a3..0000000000 --- a/RevenueCatUI/Templates/Components/PurchaseButton/PurchaseButtonComponentView.swift +++ /dev/null @@ -1,31 +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 -// -// PurchaseButtonComponentView.swift -// -// Created by Josh Holtz on 9/27/24. - -import Foundation -import RevenueCat -import SwiftUI - -#if PAYWALL_COMPONENTS - -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -struct PurchaseButtonComponentView: View { - - let viewModel: PurchaseButtonComponentViewModel - - var body: some View { - EmptyView() - } - -} - -#endif diff --git a/RevenueCatUI/Templates/Components/PurchaseButton/PurchaseButtonComponentViewModel.swift b/RevenueCatUI/Templates/Components/PurchaseButton/PurchaseButtonComponentViewModel.swift deleted file mode 100644 index 055fa4a881..0000000000 --- a/RevenueCatUI/Templates/Components/PurchaseButton/PurchaseButtonComponentViewModel.swift +++ /dev/null @@ -1,35 +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 -// -// PurchaseButtonComponentViewModel.swift -// -// Created by Josh Holtz on 9/27/24. - -import Foundation -import RevenueCat - -// swiftlint:disable missing_docs - -#if PAYWALL_COMPONENTS - -@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -public class PurchaseButtonComponentViewModel { - - private let localizedStrings: PaywallComponent.LocalizationDictionary - private let component: PaywallComponent.PurchaseButtonComponent - - init(localizedStrings: PaywallComponent.LocalizationDictionary, - component: PaywallComponent.PurchaseButtonComponent) throws { - self.localizedStrings = localizedStrings - self.component = component - } - -} - -#endif diff --git a/RevenueCatUI/Templates/Components/Stack/StackComponentViewModel.swift b/RevenueCatUI/Templates/Components/Stack/StackComponentViewModel.swift index 0655774a9a..64982768fc 100644 --- a/RevenueCatUI/Templates/Components/Stack/StackComponentViewModel.swift +++ b/RevenueCatUI/Templates/Components/Stack/StackComponentViewModel.swift @@ -10,7 +10,6 @@ // StackComponentView.swift // // Created by James Borthwick on 2024-08-20. -// swiftlint:disable missing_docs import RevenueCat import SwiftUI @@ -18,26 +17,23 @@ import SwiftUI #if PAYWALL_COMPONENTS @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -public class StackComponentViewModel { +class StackComponentViewModel { - private let locale: Locale private let component: PaywallComponent.StackComponent let viewModels: [PaywallComponentViewModel] - init(locale: Locale, - component: PaywallComponent.StackComponent, + init(component: PaywallComponent.StackComponent, localizedStrings: PaywallComponent.LocalizationDictionary, offering: Offering ) throws { - self.locale = locale self.component = component self.viewModels = try component.components.map { - try $0.toViewModel(offering: offering, locale: locale, localizedStrings: localizedStrings) + try $0.toViewModel(offering: offering, localizedStrings: localizedStrings) } } - var dimension: PaywallComponent.StackComponent.Dimension { + var dimension: PaywallComponent.Dimension { component.dimension } diff --git a/RevenueCatUI/Templates/Components/Stackable+Extensions.swift b/RevenueCatUI/Templates/Components/Stackable+Extensions.swift new file mode 100644 index 0000000000..2e7c6e181e --- /dev/null +++ b/RevenueCatUI/Templates/Components/Stackable+Extensions.swift @@ -0,0 +1,44 @@ +// +// 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 +// +// Stackable+Extensions.swift +// +// Created by Josh Holtz on 9/29/24. + +import Foundation +import RevenueCat + +#if PAYWALL_COMPONENTS + +@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) +extension PaywallComponent.StackableComponent { + + func toStackComponentViewModel( + localizedStrings: PaywallComponent.LocalizationDictionary, + offering: Offering + ) throws -> StackComponentViewModel { + try StackComponentViewModel( + component: .init( + components: self.components, + dimension: self.dimension, + width: self.width, + spacing: self.spacing, + backgroundColor: self.backgroundColor, + padding: self.padding, + margin: self.margin, + cornerRadiuses: self.cornerRadiuses + ), + localizedStrings: localizedStrings, + offering: offering + ) + } + +} + +#endif diff --git a/RevenueCatUI/Templates/Components/TemplateComponentsView.swift b/RevenueCatUI/Templates/Components/TemplateComponentsView.swift index 344fcdee56..7834fd602c 100644 --- a/RevenueCatUI/Templates/Components/TemplateComponentsView.swift +++ b/RevenueCatUI/Templates/Components/TemplateComponentsView.swift @@ -29,7 +29,6 @@ public struct TemplateComponentsView: View { do { // STEP 3: Make the view models & validate all components have required localization return try component.toViewModel(offering: offering, - locale: localization.locale, localizedStrings: localization.localizedStrings) } catch { diff --git a/RevenueCatUI/Templates/Components/Previews/Template1Preview.swift b/RevenueCatUI/Templates/Components/TemplateComponentsViewPreviews/Template1Preview.swift similarity index 83% rename from RevenueCatUI/Templates/Components/Previews/Template1Preview.swift rename to RevenueCatUI/Templates/Components/TemplateComponentsViewPreviews/Template1Preview.swift index 32d307dfd7..a82f3658a0 100644 --- a/RevenueCatUI/Templates/Components/Previews/Template1Preview.swift +++ b/RevenueCatUI/Templates/Components/TemplateComponentsViewPreviews/Template1Preview.swift @@ -59,10 +59,24 @@ private enum Template1Preview { horizontalAlignment: .center ) + static let purchaseButton = PaywallComponent.PurchaseButtonComponent( + cta: "cta", + ctaIntroOffer: "cta_intro", + fontWeight: .bold, + color: .init(light: "#ffffff"), + backgroundColor: .init(light: "#e89d89"), + padding: .init(top: 10, + bottom: 10, + leading: 30, + trailing: 30), + shape: .pill + ) + static let contentStack = PaywallComponent.StackComponent( components: [ .text(title), - .text(body) + .text(body), + .purchaseButton(purchaseButton) ], width: .init(type: .fill, value: nil), spacing: 30, @@ -93,7 +107,9 @@ private enum Template1Preview { ), componentsLocalizations: ["en_US": [ "title": .string("Ignite your cat's curiosity"), - "body": .string("Get access to all of our educational content trusted by thousands of pet parents.") + "body": .string("Get access to all of our educational content trusted by thousands of pet parents."), + "cta": .string("Get Started"), + "cta_intro": .string("Claim Free Trial") ]], revision: 1, defaultLocaleIdentifier: "en_US" diff --git a/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift b/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift index 7e10316dcc..a4cd3900fc 100644 --- a/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift +++ b/RevenueCatUI/Templates/Components/Text/TextComponentViewModel.swift @@ -10,7 +10,6 @@ // TextComponentView.swift // // Created by Josh Holtz on 6/11/24. -// swiftlint:disable missing_docs import Foundation import RevenueCat @@ -19,7 +18,7 @@ import SwiftUI #if PAYWALL_COMPONENTS @available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *) -public class TextComponentViewModel { +class TextComponentViewModel { private let localizedStrings: PaywallComponent.LocalizationDictionary private let component: PaywallComponent.TextComponent @@ -32,31 +31,31 @@ public class TextComponentViewModel { self.text = try localizedStrings.string(key: component.text) } - public var fontFamily: String? { + var fontFamily: String? { component.fontFamily } - public var fontWeight: Font.Weight { + var fontWeight: Font.Weight { component.fontWeight.fontWeight } - public var color: Color { + var color: Color { component.color.toDyanmicColor() } - public var textStyle: Font { + var textStyle: Font { component.textStyle.font } - public var horizontalAlignment: TextAlignment { + var horizontalAlignment: TextAlignment { component.horizontalAlignment.textAlignment } - public var backgroundColor: Color { + var backgroundColor: Color { component.backgroundColor?.toDyanmicColor() ?? Color.clear } - public var padding: EdgeInsets { + var padding: EdgeInsets { component.padding.edgeInsets } diff --git a/Sources/Paywalls/Components/Common/Dimension.swift b/Sources/Paywalls/Components/Common/Dimension.swift new file mode 100644 index 0000000000..9046713169 --- /dev/null +++ b/Sources/Paywalls/Components/Common/Dimension.swift @@ -0,0 +1,89 @@ +// +// 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 +// +// Dimension.swift +// +// Created by Josh Holtz on 9/27/24. +// swiftlint:disable missing_docs + +import Foundation + +#if PAYWALL_COMPONENTS + +public extension PaywallComponent { + + enum Dimension: Codable, Sendable, Hashable { + + case vertical(HorizontalAlignment) + case horizontal(VerticalAlignment) + case zlayer(TwoDimensionAlignment) + + public func encode(to encoder: any Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .vertical(let alignment): + try container.encode(DimensionType.vertical.rawValue, forKey: .type) + try container.encode(alignment, forKey: .alignment) + case .horizontal(let alignment): + try container.encode(DimensionType.horizontal.rawValue, forKey: .type) + try container.encode(alignment, forKey: .alignment) + case .zlayer(let alignment): + try container.encode(DimensionType.zlayer.rawValue, forKey: .type) + try container.encode(alignment.rawValue, forKey: .alignment) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(DimensionType.self, forKey: .type) + + switch type { + case .vertical: + let alignment = try container.decode(HorizontalAlignment.self, forKey: .alignment) + self = .vertical(alignment) + case .horizontal: + let alignment = try container.decode(VerticalAlignment.self, forKey: .alignment) + self = .horizontal(alignment) + case .zlayer: + let alignment = try container.decode(TwoDimensionAlignment.self, forKey: .alignment) + self = .zlayer(alignment) + } + } + + public static func horizontal() -> Dimension { + return .horizontal(.center) + } + + public static func vertical() -> Dimension { + return .vertical(.center) + } + + // swiftlint:disable:next nesting + private enum CodingKeys: String, CodingKey { + + case type + case alignment + + } + + // swiftlint:disable:next nesting + private enum DimensionType: String, Decodable { + + case vertical + case horizontal + case zlayer + + } + + } + +} + +#endif diff --git a/Sources/Paywalls/Components/PaywallComponentBase.swift b/Sources/Paywalls/Components/Common/PaywallComponentBase.swift similarity index 100% rename from Sources/Paywalls/Components/PaywallComponentBase.swift rename to Sources/Paywalls/Components/Common/PaywallComponentBase.swift diff --git a/Sources/Paywalls/Components/PaywallComponentLocalization.swift b/Sources/Paywalls/Components/Common/PaywallComponentLocalization.swift similarity index 100% rename from Sources/Paywalls/Components/PaywallComponentLocalization.swift rename to Sources/Paywalls/Components/Common/PaywallComponentLocalization.swift diff --git a/Sources/Paywalls/Components/PaywallComponentPropertyTypes.swift b/Sources/Paywalls/Components/Common/PaywallComponentPropertyTypes.swift similarity index 97% rename from Sources/Paywalls/Components/PaywallComponentPropertyTypes.swift rename to Sources/Paywalls/Components/Common/PaywallComponentPropertyTypes.swift index d4cde15b43..25d67bc4df 100644 --- a/Sources/Paywalls/Components/PaywallComponentPropertyTypes.swift +++ b/Sources/Paywalls/Components/Common/PaywallComponentPropertyTypes.swift @@ -55,6 +55,13 @@ public extension PaywallComponent { } + enum Shape: Codable, Sendable, Hashable, Equatable { + + case rectangle + case pill + + } + struct Padding: Codable, Sendable, Hashable, Equatable { public init(top: Double, bottom: Double, leading: Double, trailing: Double) { diff --git a/Sources/Paywalls/Components/Common/StackableComponent.swift b/Sources/Paywalls/Components/Common/StackableComponent.swift new file mode 100644 index 0000000000..7cf572a9f4 --- /dev/null +++ b/Sources/Paywalls/Components/Common/StackableComponent.swift @@ -0,0 +1,36 @@ +// +// 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 +// +// StackableComponent.swift +// +// Created by Josh Holtz on 9/27/24. +// swiftlint:disable missing_docs + +import Foundation + +#if PAYWALL_COMPONENTS + +public extension PaywallComponent { + + protocol StackableComponent { + + var components: [PaywallComponent] { get } + var width: WidthSize? { get } + var spacing: CGFloat? { get } + var backgroundColor: ColorInfo? { get } + var dimension: Dimension { get } + var padding: Padding { get } + var margin: Padding { get } + var cornerRadiuses: CornerRadiuses { get } + + } + +} + +#endif diff --git a/Sources/Paywalls/Components/PaywallPackageComponent.swift b/Sources/Paywalls/Components/PaywallPackageComponent.swift index 062987f93c..81207b5050 100644 --- a/Sources/Paywalls/Components/PaywallPackageComponent.swift +++ b/Sources/Paywalls/Components/PaywallPackageComponent.swift @@ -19,20 +19,40 @@ import Foundation public extension PaywallComponent { - struct PackageComponent: PaywallComponentBase { + struct PackageComponent: PaywallComponentBase, StackableComponent { let type: ComponentType public let packageID: String - public let components: [PaywallComponent] - public init(type: ComponentType, - packageID: String, - isButton: Bool = true, - components: [PaywallComponent] + public let components: [PaywallComponent] + public let width: WidthSize? + public let spacing: CGFloat? + public let backgroundColor: ColorInfo? + public let dimension: Dimension + public let padding: Padding + public let margin: Padding + public let cornerRadiuses: CornerRadiuses + + public init(packageID: String, + components: [PaywallComponent], + dimension: Dimension = .vertical(.center), + width: WidthSize? = nil, + spacing: CGFloat? = 0, + backgroundColor: ColorInfo? = nil, + padding: Padding = .zero, + margin: Padding = .zero, + cornerRadiuses: CornerRadiuses = .zero ) { self.type = .package self.packageID = packageID self.components = components + self.width = width + self.spacing = spacing + self.backgroundColor = backgroundColor + self.dimension = dimension + self.padding = padding + self.margin = margin + self.cornerRadiuses = cornerRadiuses } } diff --git a/Sources/Paywalls/Components/PaywallPackageGroupComponent.swift b/Sources/Paywalls/Components/PaywallPackageGroupComponent.swift index b03e0cd23b..90d6c661d7 100644 --- a/Sources/Paywalls/Components/PaywallPackageGroupComponent.swift +++ b/Sources/Paywalls/Components/PaywallPackageGroupComponent.swift @@ -19,16 +19,40 @@ import Foundation public extension PaywallComponent { - struct PackageGroupComponent: PaywallComponentBase { + struct PackageGroupComponent: PaywallComponentBase, StackableComponent { let type: ComponentType public let defaultSelectedPackageID: String - public let components: [PaywallComponent] - public init(type: ComponentType, defaultSelectedPackageID: String, components: [PaywallComponent]) { + public let components: [PaywallComponent] + public let width: WidthSize? + public let spacing: CGFloat? + public let backgroundColor: ColorInfo? + public let dimension: Dimension + public let padding: Padding + public let margin: Padding + public let cornerRadiuses: CornerRadiuses + + public init(defaultSelectedPackageID: String, + components: [PaywallComponent], + dimension: Dimension = .vertical(.center), + width: WidthSize? = nil, + spacing: CGFloat? = 0, + backgroundColor: ColorInfo? = nil, + padding: Padding = .zero, + margin: Padding = .zero, + cornerRadiuses: CornerRadiuses = .zero + ) { self.type = .packageGroup self.defaultSelectedPackageID = defaultSelectedPackageID self.components = components + self.width = width + self.spacing = spacing + self.backgroundColor = backgroundColor + self.dimension = dimension + self.padding = padding + self.margin = margin + self.cornerRadiuses = cornerRadiuses } } diff --git a/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift b/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift index 72fb9110f3..0bed25ff30 100644 --- a/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift +++ b/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift @@ -25,6 +25,8 @@ public extension PaywallComponent { public let backgroundColor: ColorInfo? public let padding: Padding public let margin: Padding + public let shape: Shape + public let cornerRadiuses: CornerRadiuses? public init( cta: LocalizationKey, @@ -36,7 +38,9 @@ public extension PaywallComponent { padding: Padding = .default, margin: Padding = .default, textStyle: TextStyle = .body, - horizontalAlignment: HorizontalAlignment = .center + horizontalAlignment: HorizontalAlignment = .center, + shape: Shape = .pill, + cornerRadiuses: CornerRadiuses? = nil ) { self.type = .purchaseButton self.cta = cta @@ -49,6 +53,8 @@ public extension PaywallComponent { self.margin = margin self.textStyle = textStyle self.horizontalAlignment = horizontalAlignment + self.shape = shape + self.cornerRadiuses = cornerRadiuses } } @@ -69,6 +75,8 @@ extension PaywallComponent.PurchaseButtonComponent { case backgroundColor case padding case margin + case shape + case cornerRadiuses } } diff --git a/Sources/Paywalls/Components/PaywallStackComponent.swift b/Sources/Paywalls/Components/PaywallStackComponent.swift index c05e98e6ce..cd7df47d8d 100644 --- a/Sources/Paywalls/Components/PaywallStackComponent.swift +++ b/Sources/Paywalls/Components/PaywallStackComponent.swift @@ -10,14 +10,15 @@ // StackComponent.swift // // Created by James Borthwick on 2024-08-20. -// swiftlint:disable missing_docs nesting +// swiftlint:disable missing_docs + import Foundation #if PAYWALL_COMPONENTS public extension PaywallComponent { - struct StackComponent: PaywallComponentBase { + struct StackComponent: PaywallComponentBase, StackableComponent { let type: ComponentType public let components: [PaywallComponent] @@ -33,7 +34,7 @@ public extension PaywallComponent { dimension: Dimension = .vertical(.center), width: WidthSize? = nil, spacing: CGFloat? = 0, - backgroundColor: ColorInfo?, + backgroundColor: ColorInfo? = nil, padding: Padding = .zero, margin: Padding = .zero, cornerRadiuses: CornerRadiuses = .zero @@ -49,70 +50,6 @@ public extension PaywallComponent { self.cornerRadiuses = cornerRadiuses } - public enum Dimension: Codable, Sendable, Hashable { - - case vertical(HorizontalAlignment) - case horizontal(VerticalAlignment) - case zlayer(TwoDimensionAlignment) - - public func encode(to encoder: any Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) - - switch self { - case .vertical(let alignment): - try container.encode(DimensionType.vertical.rawValue, forKey: .type) - try container.encode(alignment, forKey: .alignment) - case .horizontal(let alignment): - try container.encode(DimensionType.horizontal.rawValue, forKey: .type) - try container.encode(alignment, forKey: .alignment) - case .zlayer(let alignment): - try container.encode(DimensionType.zlayer.rawValue, forKey: .type) - try container.encode(alignment.rawValue, forKey: .alignment) - } - } - - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let type = try container.decode(DimensionType.self, forKey: .type) - - switch type { - case .vertical: - let alignment = try container.decode(HorizontalAlignment.self, forKey: .alignment) - self = .vertical(alignment) - case .horizontal: - let alignment = try container.decode(VerticalAlignment.self, forKey: .alignment) - self = .horizontal(alignment) - case .zlayer: - let alignment = try container.decode(TwoDimensionAlignment.self, forKey: .alignment) - self = .zlayer(alignment) - } - } - - public static func horizontal() -> Dimension { - return .horizontal(.center) - } - - public static func vertical() -> Dimension { - return .vertical(.center) - } - - private enum CodingKeys: String, CodingKey { - - case type - case alignment - - } - - private enum DimensionType: String, Decodable { - - case vertical - case horizontal - case zlayer - - } - - } - } }