From 0e4fd29898cfb9ee605d315cf8eaec3342b2ab11 Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Tue, 14 Jan 2025 12:59:18 -0600 Subject: [PATCH 1/4] I think this is it --- .../Icon/IconComponentViewModel.swift | 33 ++- .../Stack/StackComponentViewModel.swift | 36 ++- .../Components/PaywallButtonComponent.swift | 218 ++++++++++-------- .../Components/PaywallIconComponent.swift | 88 ++++++- .../Components/PaywallImageComponent.swift | 62 ++++- .../Components/PaywallPackageComponent.swift | 17 +- .../PaywallPurchaseButtonComponent.swift | 12 +- .../Components/PaywallStackComponent.swift | 63 ++++- .../PaywallStickyFooterComponent.swift | 23 +- .../Components/PaywallTabsComponent.swift | 129 +++++++++-- .../Components/PaywallTextComponent.swift | 139 +++++++---- 11 files changed, 615 insertions(+), 205 deletions(-) diff --git a/RevenueCatUI/Templates/V2/Components/Icon/IconComponentViewModel.swift b/RevenueCatUI/Templates/V2/Components/Icon/IconComponentViewModel.swift index 4adcee935d..be4be02053 100644 --- a/RevenueCatUI/Templates/V2/Components/Icon/IconComponentViewModel.swift +++ b/RevenueCatUI/Templates/V2/Components/Icon/IconComponentViewModel.swift @@ -73,18 +73,31 @@ class IconComponentViewModel { extension PresentedIconPartial: PresentedPartial { - static func combine(_ base: Self?, with other: Self?) -> Self { + static func combine( + _ base: PaywallComponent.PartialIconComponent?, + with other: PaywallComponent.PartialIconComponent? + ) -> Self { + + let visible = other?.visible ?? base?.visible + let baseUrl = other?.baseUrl ?? base?.baseUrl + let iconName = other?.iconName ?? base?.iconName + let formats = other?.formats ?? base?.formats + let size = other?.size ?? base?.size + let padding = other?.padding ?? base?.padding + let margin = other?.margin ?? base?.margin + let color = other?.color ?? base?.color + let iconBackground = other?.iconBackground ?? base?.iconBackground return .init( - visible: other?.visible ?? base?.visible, - baseUrl: other?.baseUrl ?? base?.baseUrl, - iconName: other?.iconName ?? base?.iconName, - formats: other?.formats ?? base?.formats, - size: other?.size ?? base?.size, - padding: other?.padding ?? base?.padding, - margin: other?.margin ?? base?.margin, - color: other?.color ?? base?.color, - iconBackground: other?.iconBackground ?? base?.iconBackground + visible: visible, + baseUrl: baseUrl, + iconName: iconName, + formats: formats, + size: size, + padding: padding, + margin: margin, + color: color, + iconBackground: iconBackground ) } diff --git a/RevenueCatUI/Templates/V2/Components/Stack/StackComponentViewModel.swift b/RevenueCatUI/Templates/V2/Components/Stack/StackComponentViewModel.swift index 8b663c5813..3da041f9b1 100644 --- a/RevenueCatUI/Templates/V2/Components/Stack/StackComponentViewModel.swift +++ b/RevenueCatUI/Templates/V2/Components/Stack/StackComponentViewModel.swift @@ -79,19 +79,33 @@ class StackComponentViewModel { extension PresentedStackPartial: PresentedPartial { - static func combine(_ base: Self?, with other: Self?) -> Self { + static func combine( + _ base: PaywallComponent.PartialStackComponent?, + with other: PaywallComponent.PartialStackComponent? + ) -> Self { + + let visible = other?.visible ?? base?.visible + let dimension = other?.dimension ?? base?.dimension + let size = other?.size ?? base?.size + let spacing = other?.spacing ?? base?.spacing + let backgroundColor = other?.backgroundColor ?? base?.backgroundColor + let padding = other?.padding ?? base?.padding + let margin = other?.margin ?? base?.margin + let shape = other?.shape ?? base?.shape + let border = other?.border ?? base?.border + let shadow = other?.shadow ?? base?.shadow return .init( - visible: other?.visible ?? base?.visible, - dimension: other?.dimension ?? base?.dimension, - size: other?.size ?? base?.size, - spacing: other?.spacing ?? base?.spacing, - backgroundColor: other?.backgroundColor ?? base?.backgroundColor, - padding: other?.padding ?? base?.padding, - margin: other?.margin ?? base?.margin, - shape: other?.shape ?? base?.shape, - border: other?.border ?? base?.border, - shadow: other?.shadow ?? base?.shadow + visible: visible, + dimension: dimension, + size: size, + spacing: spacing, + backgroundColor: backgroundColor, + padding: padding, + margin: margin, + shape: shape, + border: border, + shadow: shadow ) } diff --git a/Sources/Paywalls/Components/PaywallButtonComponent.swift b/Sources/Paywalls/Components/PaywallButtonComponent.swift index a73f1152b6..9bc1035828 100644 --- a/Sources/Paywalls/Components/PaywallButtonComponent.swift +++ b/Sources/Paywalls/Components/PaywallButtonComponent.swift @@ -11,7 +11,7 @@ // // Created by Jay Shortway on 02/10/2024. // -// swiftlint:disable missing_docs +// swiftlint:disable missing_docs nesting import Foundation @@ -19,9 +19,9 @@ import Foundation public extension PaywallComponent { - struct ButtonComponent: PaywallComponentBase { + final class ButtonComponent: PaywallComponentBase { - let type: ComponentType + public let type: ComponentType public let action: Action public let stack: PaywallComponent.StackComponent @@ -34,119 +34,147 @@ public extension PaywallComponent { self.stack = stack } - } - -} - -public extension PaywallComponent.ButtonComponent { - - enum Action: Codable, Sendable, Hashable, Equatable { - case restorePurchases - case navigateBack - case navigateTo(destination: Destination) - - // swiftlint:disable:next nesting private enum CodingKeys: String, CodingKey { case type - case destination - case url + case action + case stack + } + + required public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + self.type = try container.decode(ComponentType.self, forKey: .type) + self.action = try container.decode(Action.self, forKey: .action) + self.stack = try container.decode(PaywallComponent.StackComponent.self, forKey: .stack) } public func encode(to encoder: Encoder) throws { var container = encoder.container(keyedBy: CodingKeys.self) + try container.encode(type, forKey: .type) + try container.encode(action, forKey: .action) + try container.encode(stack, forKey: .stack) + } - switch self { - case .restorePurchases: - try container.encode("restore_purchases", forKey: .type) - case .navigateBack: - try container.encode("navigate_back", forKey: .type) - case .navigateTo(let destination): - try container.encode("navigate_to", forKey: .type) - try destination.encode(to: encoder) // Encode destination directly under action - } + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + hasher.combine(action) + hasher.combine(stack) } - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let type = try container.decode(String.self, forKey: .type) - - switch type { - case "restore_purchases": - self = .restorePurchases - case "navigate_back": - self = .navigateBack - case "navigate_to": - let destination = try Destination(from: decoder) // Decode destination directly under action - self = .navigateTo(destination: destination) - default: - throw DecodingError.dataCorruptedError(forKey: .type, - in: container, debugDescription: "Invalid action type") - } + public static func == (lhs: ButtonComponent, rhs: ButtonComponent) -> Bool { + return lhs.type == rhs.type && + lhs.action == rhs.action && + lhs.stack == rhs.stack } - } - enum Destination: Codable, Sendable, Hashable, Equatable { - case customerCenter - case privacyPolicy(urlLid: String, method: URLMethod) - case terms(urlLid: String, method: URLMethod) - case url(urlLid: String, method: URLMethod) + public enum Action: Codable, Sendable, Hashable, Equatable { + case restorePurchases + case navigateBack + case navigateTo(destination: Destination) - // swiftlint:disable:next nesting - private enum CodingKeys: String, CodingKey { - case destination - case url - } + private enum CodingKeys: String, CodingKey { + case type + case destination + } - public func encode(to encoder: Encoder) throws { - var container = encoder.container(keyedBy: CodingKeys.self) + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .restorePurchases: + try container.encode("restore_purchases", forKey: .type) + case .navigateBack: + try container.encode("navigate_back", forKey: .type) + case .navigateTo(let destination): + try container.encode("navigate_to", forKey: .type) + try destination.encode(to: encoder) + } + } - switch self { - case .customerCenter: - try container.encode("customer_center", forKey: .destination) - case .terms(let urlLid, let method): - try container.encode("terms", forKey: .destination) - try container.encode(URLPayload(urlLid: urlLid, method: method), forKey: .url) - case .privacyPolicy(let urlLid, let method): - try container.encode("privacy_policy", forKey: .destination) - try container.encode(URLPayload(urlLid: urlLid, method: method), forKey: .url) - case .url(let urlLid, let method): - try container.encode("url", forKey: .destination) - try container.encode(URLPayload(urlLid: urlLid, method: method), forKey: .url) + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let type = try container.decode(String.self, forKey: .type) + + switch type { + case "restore_purchases": + self = .restorePurchases + case "navigate_back": + self = .navigateBack + case "navigate_to": + let destination = try Destination(from: decoder) + self = .navigateTo(destination: destination) + default: + throw DecodingError.dataCorruptedError( + forKey: .type, in: container, + debugDescription: "Invalid action type" + ) + } } } - public init(from decoder: Decoder) throws { - let container = try decoder.container(keyedBy: CodingKeys.self) - let destination = try container.decode(String.self, forKey: .destination) - - switch destination { - case "customer_center": - self = .customerCenter - case "terms": - let urlPayload = try container.decode(URLPayload.self, forKey: .url) - self = .terms(urlLid: urlPayload.urlLid, method: urlPayload.method) - case "privacy_policy": - let urlPayload = try container.decode(URLPayload.self, forKey: .url) - self = .privacyPolicy(urlLid: urlPayload.urlLid, method: urlPayload.method) - case "url": - let urlPayload = try container.decode(URLPayload.self, forKey: .url) - self = .url(urlLid: urlPayload.urlLid, method: urlPayload.method) - default: - throw DecodingError.dataCorruptedError(forKey: .destination, - in: container, debugDescription: "Invalid destination type") + public enum Destination: Codable, Sendable, Hashable, Equatable { + case customerCenter + case privacyPolicy(urlLid: String, method: URLMethod) + case terms(urlLid: String, method: URLMethod) + case url(urlLid: String, method: URLMethod) + + private enum CodingKeys: String, CodingKey { + case destination + case url + } + + public func encode(to encoder: Encoder) throws { + var container = encoder.container(keyedBy: CodingKeys.self) + + switch self { + case .customerCenter: + try container.encode("customer_center", forKey: .destination) + case .terms(let urlLid, let method): + try container.encode("terms", forKey: .destination) + try container.encode(URLPayload(urlLid: urlLid, method: method), forKey: .url) + case .privacyPolicy(let urlLid, let method): + try container.encode("privacy_policy", forKey: .destination) + try container.encode(URLPayload(urlLid: urlLid, method: method), forKey: .url) + case .url(let urlLid, let method): + try container.encode("url", forKey: .destination) + try container.encode(URLPayload(urlLid: urlLid, method: method), forKey: .url) + } + } + + public init(from decoder: Decoder) throws { + let container = try decoder.container(keyedBy: CodingKeys.self) + let destination = try container.decode(String.self, forKey: .destination) + + switch destination { + case "customer_center": + self = .customerCenter + case "terms": + let urlPayload = try container.decode(URLPayload.self, forKey: .url) + self = .terms(urlLid: urlPayload.urlLid, method: urlPayload.method) + case "privacy_policy": + let urlPayload = try container.decode(URLPayload.self, forKey: .url) + self = .privacyPolicy(urlLid: urlPayload.urlLid, method: urlPayload.method) + case "url": + let urlPayload = try container.decode(URLPayload.self, forKey: .url) + self = .url(urlLid: urlPayload.urlLid, method: urlPayload.method) + default: + throw DecodingError.dataCorruptedError( + forKey: .destination, in: container, + debugDescription: "Invalid destination type" + ) + } } } - } - enum URLMethod: String, Codable, Sendable, Hashable, Equatable { - case inAppBrowser = "in_app_browser" - case externalBrowser = "external_browser" - case deepLink = "deep_link" - } + public enum URLMethod: String, Codable, Sendable, Hashable, Equatable { + case inAppBrowser = "in_app_browser" + case externalBrowser = "external_browser" + case deepLink = "deep_link" + } - private struct URLPayload: Codable, Hashable, Sendable { - let urlLid: String - let method: URLMethod + private struct URLPayload: Codable, Hashable, Sendable { + let urlLid: String + let method: URLMethod + } } } diff --git a/Sources/Paywalls/Components/PaywallIconComponent.swift b/Sources/Paywalls/Components/PaywallIconComponent.swift index 89c0ef059f..25b58b0864 100644 --- a/Sources/Paywalls/Components/PaywallIconComponent.swift +++ b/Sources/Paywalls/Components/PaywallIconComponent.swift @@ -4,7 +4,7 @@ // // Created by Josh Holtz on 1/12/24. // -// swiftlint:disable missing_docs +// swiftlint:disable missing_docs nesting import Foundation @@ -12,10 +12,9 @@ import Foundation public extension PaywallComponent { - struct IconComponent: PaywallComponentBase { + final class IconComponent: PaywallComponentBase { - // swiftlint:disable:next nesting - public struct Formats: PaywallComponentBase { + final public class Formats: PaywallComponentBase { public let svg: String public let png: String @@ -32,10 +31,22 @@ public extension PaywallComponent { self.webp = webp } + public func hash(into hasher: inout Hasher) { + hasher.combine(svg) + hasher.combine(png) + hasher.combine(heic) + hasher.combine(webp) + } + + public static func == (lhs: Formats, rhs: Formats) -> Bool { + return lhs.svg == rhs.svg && + lhs.png == rhs.png && + lhs.heic == rhs.heic && + lhs.webp == rhs.webp + } } - // swiftlint:disable:next nesting - public struct IconBackground: PaywallComponentBase { + final public class IconBackground: PaywallComponentBase { public let color: ColorScheme public let shape: IconBackgroundShape @@ -52,9 +63,22 @@ public extension PaywallComponent { self.shadow = shadow } + public func hash(into hasher: inout Hasher) { + hasher.combine(color) + hasher.combine(shape) + hasher.combine(border) + hasher.combine(shadow) + } + + public static func == (lhs: IconBackground, rhs: IconBackground) -> Bool { + return lhs.color == rhs.color && + lhs.shape == rhs.shape && + lhs.border == rhs.border && + lhs.shadow == rhs.shadow + } } - let type: ComponentType + public let type: ComponentType public let baseUrl: String public let iconName: String public let formats: Formats @@ -89,9 +113,34 @@ public extension PaywallComponent { self.overrides = overrides } + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + hasher.combine(baseUrl) + hasher.combine(iconName) + hasher.combine(formats) + hasher.combine(size) + hasher.combine(padding) + hasher.combine(margin) + hasher.combine(color) + hasher.combine(iconBackground) + hasher.combine(overrides) + } + + public static func == (lhs: IconComponent, rhs: IconComponent) -> Bool { + return lhs.type == rhs.type && + lhs.baseUrl == rhs.baseUrl && + lhs.iconName == rhs.iconName && + lhs.formats == rhs.formats && + lhs.size == rhs.size && + lhs.padding == rhs.padding && + lhs.margin == rhs.margin && + lhs.color == rhs.color && + lhs.iconBackground == rhs.iconBackground && + lhs.overrides == rhs.overrides + } } - struct PartialIconComponent: PartialComponent { + final class PartialIconComponent: PartialComponent { public let visible: Bool? public let baseUrl: String? @@ -125,6 +174,29 @@ public extension PaywallComponent { self.iconBackground = iconBackground } + public func hash(into hasher: inout Hasher) { + hasher.combine(visible) + hasher.combine(baseUrl) + hasher.combine(iconName) + hasher.combine(formats) + hasher.combine(size) + hasher.combine(padding) + hasher.combine(margin) + hasher.combine(color) + hasher.combine(iconBackground) + } + + public static func == (lhs: PartialIconComponent, rhs: PartialIconComponent) -> Bool { + return lhs.visible == rhs.visible && + lhs.baseUrl == rhs.baseUrl && + lhs.iconName == rhs.iconName && + lhs.formats == rhs.formats && + lhs.size == rhs.size && + lhs.padding == rhs.padding && + lhs.margin == rhs.margin && + lhs.color == rhs.color && + lhs.iconBackground == rhs.iconBackground + } } } diff --git a/Sources/Paywalls/Components/PaywallImageComponent.swift b/Sources/Paywalls/Components/PaywallImageComponent.swift index d3bb112a33..ed759ddda3 100644 --- a/Sources/Paywalls/Components/PaywallImageComponent.swift +++ b/Sources/Paywalls/Components/PaywallImageComponent.swift @@ -12,9 +12,9 @@ import Foundation public extension PaywallComponent { - struct ImageComponent: PaywallComponentBase { + final class ImageComponent: PaywallComponentBase { - let type: ComponentType + public let type: ComponentType public let source: ThemeImageUrls public let size: Size public let overrideSourceLid: LocalizationKey? @@ -55,9 +55,38 @@ public extension PaywallComponent { self.overrides = overrides } + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + hasher.combine(source) + hasher.combine(size) + hasher.combine(overrideSourceLid) + hasher.combine(fitMode) + hasher.combine(maskShape) + hasher.combine(colorOverlay) + hasher.combine(padding) + hasher.combine(margin) + hasher.combine(border) + hasher.combine(shadow) + hasher.combine(overrides) + } + + public static func == (lhs: ImageComponent, rhs: ImageComponent) -> Bool { + return lhs.type == rhs.type && + lhs.source == rhs.source && + lhs.size == rhs.size && + lhs.overrideSourceLid == rhs.overrideSourceLid && + lhs.fitMode == rhs.fitMode && + lhs.maskShape == rhs.maskShape && + lhs.colorOverlay == rhs.colorOverlay && + lhs.padding == rhs.padding && + lhs.margin == rhs.margin && + lhs.border == rhs.border && + lhs.shadow == rhs.shadow && + lhs.overrides == rhs.overrides + } } - struct PartialImageComponent: PartialComponent { + final class PartialImageComponent: PartialComponent { public let visible: Bool? public let source: ThemeImageUrls? @@ -97,6 +126,33 @@ public extension PaywallComponent { self.shadow = shadow } + public func hash(into hasher: inout Hasher) { + hasher.combine(visible) + hasher.combine(source) + hasher.combine(size) + hasher.combine(overrideSourceLid) + hasher.combine(fitMode) + hasher.combine(maskShape) + hasher.combine(colorOverlay) + hasher.combine(padding) + hasher.combine(margin) + hasher.combine(border) + hasher.combine(shadow) + } + + public static func == (lhs: PartialImageComponent, rhs: PartialImageComponent) -> Bool { + return lhs.visible == rhs.visible && + lhs.source == rhs.source && + lhs.size == rhs.size && + lhs.overrideSourceLid == rhs.overrideSourceLid && + lhs.fitMode == rhs.fitMode && + lhs.maskShape == rhs.maskShape && + lhs.colorOverlay == rhs.colorOverlay && + lhs.padding == rhs.padding && + lhs.margin == rhs.margin && + lhs.border == rhs.border && + lhs.shadow == rhs.shadow + } } } diff --git a/Sources/Paywalls/Components/PaywallPackageComponent.swift b/Sources/Paywalls/Components/PaywallPackageComponent.swift index ed7c52eb60..1f5e5d8984 100644 --- a/Sources/Paywalls/Components/PaywallPackageComponent.swift +++ b/Sources/Paywalls/Components/PaywallPackageComponent.swift @@ -19,9 +19,9 @@ import Foundation public extension PaywallComponent { - struct PackageComponent: PaywallComponentBase { + final class PackageComponent: PaywallComponentBase { - let type: ComponentType + public let type: ComponentType public let packageID: String public let isSelectedByDefault: Bool public let stack: PaywallComponent.StackComponent @@ -36,6 +36,19 @@ public extension PaywallComponent { self.stack = stack } + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + hasher.combine(packageID) + hasher.combine(isSelectedByDefault) + hasher.combine(stack) + } + + public static func == (lhs: PackageComponent, rhs: PackageComponent) -> Bool { + return lhs.type == rhs.type && + lhs.packageID == rhs.packageID && + lhs.isSelectedByDefault == rhs.isSelectedByDefault && + lhs.stack == rhs.stack + } } } diff --git a/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift b/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift index 3cb231da16..57fc2eca83 100644 --- a/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift +++ b/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift @@ -12,9 +12,9 @@ import Foundation public extension PaywallComponent { - struct PurchaseButtonComponent: PaywallComponentBase { + final class PurchaseButtonComponent: PaywallComponentBase { - let type: ComponentType + public let type: ComponentType public let stack: PaywallComponent.StackComponent public init( @@ -24,6 +24,14 @@ public extension PaywallComponent { self.stack = stack } + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + hasher.combine(stack) + } + + public static func == (lhs: PurchaseButtonComponent, rhs: PurchaseButtonComponent) -> Bool { + return lhs.type == rhs.type && lhs.stack == rhs.stack + } } } diff --git a/Sources/Paywalls/Components/PaywallStackComponent.swift b/Sources/Paywalls/Components/PaywallStackComponent.swift index 226434b119..5c99dc81cd 100644 --- a/Sources/Paywalls/Components/PaywallStackComponent.swift +++ b/Sources/Paywalls/Components/PaywallStackComponent.swift @@ -18,9 +18,9 @@ import Foundation public extension PaywallComponent { - struct StackComponent: PaywallComponentBase { + final class StackComponent: PaywallComponentBase { - let type: ComponentType + public let type: ComponentType public let components: [PaywallComponent] public let size: Size public let spacing: CGFloat? @@ -63,10 +63,40 @@ public extension PaywallComponent { self.badge = badge self.overrides = overrides } + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + hasher.combine(components) + hasher.combine(size) + hasher.combine(spacing) + hasher.combine(backgroundColor) + hasher.combine(dimension) + hasher.combine(padding) + hasher.combine(margin) + hasher.combine(shape) + hasher.combine(border) + hasher.combine(shadow) + hasher.combine(badge) + hasher.combine(overrides) + } + public static func == (lhs: StackComponent, rhs: StackComponent) -> Bool { + return lhs.type == rhs.type && + lhs.components == rhs.components && + lhs.size == rhs.size && + lhs.spacing == rhs.spacing && + lhs.backgroundColor == rhs.backgroundColor && + lhs.dimension == rhs.dimension && + lhs.padding == rhs.padding && + lhs.margin == rhs.margin && + lhs.shape == rhs.shape && + lhs.border == rhs.border && + lhs.shadow == rhs.shadow && + lhs.badge == rhs.badge && + lhs.overrides == rhs.overrides + } } - struct PartialStackComponent: PartialComponent { + final class PartialStackComponent: PartialComponent { public let visible: Bool? public let size: Size? @@ -106,6 +136,33 @@ public extension PaywallComponent { self.badge = badge } + public func hash(into hasher: inout Hasher) { + hasher.combine(visible) + hasher.combine(size) + hasher.combine(spacing) + hasher.combine(backgroundColor) + hasher.combine(dimension) + hasher.combine(padding) + hasher.combine(margin) + hasher.combine(shape) + hasher.combine(border) + hasher.combine(shadow) + hasher.combine(badge) + } + + public static func == (lhs: PartialStackComponent, rhs: PartialStackComponent) -> Bool { + return lhs.visible == rhs.visible && + lhs.size == rhs.size && + lhs.spacing == rhs.spacing && + lhs.backgroundColor == rhs.backgroundColor && + lhs.dimension == rhs.dimension && + lhs.padding == rhs.padding && + lhs.margin == rhs.margin && + lhs.shape == rhs.shape && + lhs.border == rhs.border && + lhs.shadow == rhs.shadow && + lhs.badge == rhs.badge + } } } diff --git a/Sources/Paywalls/Components/PaywallStickyFooterComponent.swift b/Sources/Paywalls/Components/PaywallStickyFooterComponent.swift index 42d76e2c1f..3e538aee6f 100644 --- a/Sources/Paywalls/Components/PaywallStickyFooterComponent.swift +++ b/Sources/Paywalls/Components/PaywallStickyFooterComponent.swift @@ -19,17 +19,24 @@ import Foundation public extension PaywallComponent { - struct StickyFooterComponent: PaywallComponentBase { + final class StickyFooterComponent: PaywallComponentBase { - public let stack: PaywallComponent.StackComponent + public let stack: PaywallComponent.StackComponent - public init( - stack: PaywallComponent.StackComponent - ) { - self.stack = stack - } + public init( + stack: PaywallComponent.StackComponent + ) { + self.stack = stack + } + + public func hash(into hasher: inout Hasher) { + hasher.combine(stack) + } - } + public static func == (lhs: StickyFooterComponent, rhs: StickyFooterComponent) -> Bool { + return lhs.stack == rhs.stack + } + } } diff --git a/Sources/Paywalls/Components/PaywallTabsComponent.swift b/Sources/Paywalls/Components/PaywallTabsComponent.swift index 0c970d1acb..f60a9e7bfd 100644 --- a/Sources/Paywalls/Components/PaywallTabsComponent.swift +++ b/Sources/Paywalls/Components/PaywallTabsComponent.swift @@ -10,7 +10,7 @@ // StackComponent.swift // // Created by James Borthwick on 2024-08-20. -// swiftlint:disable missing_docs +// swiftlint:disable missing_docs nesting import Foundation @@ -18,9 +18,9 @@ import Foundation public extension PaywallComponent { - struct TabControlButtonComponent: PaywallComponentBase { - let type: ComponentType + final class TabControlButtonComponent: PaywallComponentBase { + public let type: ComponentType public let tabIndex: Int public let stack: StackComponent @@ -29,11 +29,21 @@ public extension PaywallComponent { self.tabIndex = tabIndex self.stack = stack } + + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + hasher.combine(tabIndex) + hasher.combine(stack) + } + + public static func == (lhs: TabControlButtonComponent, rhs: TabControlButtonComponent) -> Bool { + return lhs.type == rhs.type && lhs.tabIndex == rhs.tabIndex && lhs.stack == rhs.stack + } } - struct TabControlToggleComponent: PaywallComponentBase { - let type: ComponentType + final class TabControlToggleComponent: PaywallComponentBase { + public let type: ComponentType public let defaultValue: Bool public let thumbColorOn: ColorScheme public let thumbColorOff: ColorScheme @@ -52,20 +62,46 @@ public extension PaywallComponent { self.trackColorOn = trackColorOn self.trackColorOff = trackColorOff } + + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + hasher.combine(defaultValue) + hasher.combine(thumbColorOn) + hasher.combine(thumbColorOff) + hasher.combine(trackColorOn) + hasher.combine(trackColorOff) + } + + public static func == (lhs: TabControlToggleComponent, rhs: TabControlToggleComponent) -> Bool { + return lhs.type == rhs.type && + lhs.defaultValue == rhs.defaultValue && + lhs.thumbColorOn == rhs.thumbColorOn && + lhs.thumbColorOff == rhs.thumbColorOff && + lhs.trackColorOn == rhs.trackColorOn && + lhs.trackColorOff == rhs.trackColorOff + } } - struct TabControlComponent: PaywallComponentBase { - let type: ComponentType + final class TabControlComponent: PaywallComponentBase { + + public let type: ComponentType public init() { self.type = .tabControl } + + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + } + + public static func == (lhs: TabControlComponent, rhs: TabControlComponent) -> Bool { + return lhs.type == rhs.type + } } - struct TabsComponent: PaywallComponentBase { + final class TabsComponent: PaywallComponentBase { - // swiftlint:disable:next nesting - public struct Tab: PaywallComponentBase { + final public class Tab: PaywallComponentBase { public let stack: StackComponent @@ -73,13 +109,18 @@ public extension PaywallComponent { self.stack = stack } + public func hash(into hasher: inout Hasher) { + hasher.combine(stack) + } + + public static func == (lhs: Tab, rhs: Tab) -> Bool { + return lhs.stack == rhs.stack + } } - // swiftlint:disable:next nesting - public struct TabControl: Codable, Sendable, Hashable, Equatable { + final public class TabControl: PaywallComponentBase { - // swiftlint:disable:next nesting - public enum TabControlType: Codable, Sendable, Hashable, Equatable { + public enum TabControlType: PaywallComponentBase { case buttons case toggle } @@ -93,9 +134,17 @@ public extension PaywallComponent { self.stack = stack } + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + hasher.combine(stack) + } + + public static func == (lhs: TabControl, rhs: TabControl) -> Bool { + return lhs.type == rhs.type && lhs.stack == rhs.stack + } } - let type: ComponentType + public let type: ComponentType public let size: Size public let padding: Padding public let margin: Padding @@ -138,9 +187,36 @@ public extension PaywallComponent { self.overrides = overrides } + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + hasher.combine(size) + hasher.combine(padding) + hasher.combine(margin) + hasher.combine(backgroundColor) + hasher.combine(shape) + hasher.combine(border) + hasher.combine(shadow) + hasher.combine(control) + hasher.combine(tabs) + hasher.combine(overrides) + } + + public static func == (lhs: TabsComponent, rhs: TabsComponent) -> Bool { + return lhs.type == rhs.type && + lhs.size == rhs.size && + lhs.padding == rhs.padding && + lhs.margin == rhs.margin && + lhs.backgroundColor == rhs.backgroundColor && + lhs.shape == rhs.shape && + lhs.border == rhs.border && + lhs.shadow == rhs.shadow && + lhs.control == rhs.control && + lhs.tabs == rhs.tabs && + lhs.overrides == rhs.overrides + } } - struct PartialTabsComponent: PartialComponent { + final class PartialTabsComponent: PartialComponent { public let visible: Bool? public let size: Size? @@ -171,6 +247,27 @@ public extension PaywallComponent { self.shadow = shadow } + public func hash(into hasher: inout Hasher) { + hasher.combine(visible) + hasher.combine(size) + hasher.combine(padding) + hasher.combine(margin) + hasher.combine(backgroundColor) + hasher.combine(shape) + hasher.combine(border) + hasher.combine(shadow) + } + + public static func == (lhs: PartialTabsComponent, rhs: PartialTabsComponent) -> Bool { + return lhs.visible == rhs.visible && + lhs.size == rhs.size && + lhs.padding == rhs.padding && + lhs.margin == rhs.margin && + lhs.backgroundColor == rhs.backgroundColor && + lhs.shape == rhs.shape && + lhs.border == rhs.border && + lhs.shadow == rhs.shadow + } } } diff --git a/Sources/Paywalls/Components/PaywallTextComponent.swift b/Sources/Paywalls/Components/PaywallTextComponent.swift index a5ff0d7cac..b50fabc718 100644 --- a/Sources/Paywalls/Components/PaywallTextComponent.swift +++ b/Sources/Paywalls/Components/PaywallTextComponent.swift @@ -4,7 +4,7 @@ // // Created by Josh Holtz on 6/11/24. // -// swiftlint:disable missing_docs +// swiftlint:disable missing_docs nesting import Foundation @@ -12,8 +12,8 @@ import Foundation public extension PaywallComponent { - struct TextComponent: PaywallComponentBase { - let type: ComponentType + final class TextComponent: PaywallComponentBase { + public let type: ComponentType public let text: LocalizationKey public let fontName: String? public let fontWeight: FontWeight @@ -54,7 +54,22 @@ public extension PaywallComponent { self.overrides = overrides } - public init(from decoder: Decoder) throws { + private enum CodingKeys: String, CodingKey { + case type + case text = "textLid" + case fontName + case fontWeight + case color + case fontSize + case horizontalAlignment + case backgroundColor + case size + case padding + case margin + case overrides + } + + required public init(from decoder: Decoder) throws { let container = try decoder.container(keyedBy: CodingKeys.self) self.type = try container.decode(ComponentType.self, forKey: .type) @@ -67,11 +82,11 @@ public extension PaywallComponent { self.size = try container.decode(Size.self, forKey: .size) self.padding = try container.decode(Padding.self, forKey: .padding) self.margin = try container.decode(Padding.self, forKey: .margin) - self.overrides = try container.decodeIfPresent(ComponentOverrides.self, - forKey: .overrides) + self.overrides = try container.decodeIfPresent( + ComponentOverrides.self, + forKey: .overrides + ) - // Decode fontSize as CGFloat or fallback to FontSize - // This can be removed after 2025-03-01 when we are sure no more paywalls are using this if let rawFontSize = try? container.decode(CGFloat.self, forKey: .fontSize) { self.fontSize = rawFontSize } else if let fontSizeEnum = try? container.decode(FontSize.self, forKey: .fontSize) { @@ -98,13 +113,41 @@ public extension PaywallComponent { try container.encode(margin, forKey: .margin) try container.encodeIfPresent(overrides, forKey: .overrides) - // Encode fontSize as CGFloat try container.encode(fontSize, forKey: .fontSize) } + public func hash(into hasher: inout Hasher) { + hasher.combine(type) + hasher.combine(text) + hasher.combine(fontName) + hasher.combine(fontWeight) + hasher.combine(color) + hasher.combine(fontSize) + hasher.combine(horizontalAlignment) + hasher.combine(backgroundColor) + hasher.combine(size) + hasher.combine(padding) + hasher.combine(margin) + hasher.combine(overrides) + } + + public static func == (lhs: TextComponent, rhs: TextComponent) -> Bool { + return lhs.type == rhs.type && + lhs.text == rhs.text && + lhs.fontName == rhs.fontName && + lhs.fontWeight == rhs.fontWeight && + lhs.color == rhs.color && + lhs.fontSize == rhs.fontSize && + lhs.horizontalAlignment == rhs.horizontalAlignment && + lhs.backgroundColor == rhs.backgroundColor && + lhs.size == rhs.size && + lhs.padding == rhs.padding && + lhs.margin == rhs.margin && + lhs.overrides == rhs.overrides + } } - struct PartialTextComponent: PartialComponent { + final class PartialTextComponent: PartialComponent { public let visible: Bool? public let text: LocalizationKey? @@ -143,46 +186,48 @@ public extension PaywallComponent { self.fontSize = fontSize self.horizontalAlignment = horizontalAlignment } - } - -} - -extension PaywallComponent.TextComponent { - enum CodingKeys: String, CodingKey { - - case type - case text = "textLid" - case fontName - case fontWeight - case color - case fontSize - case horizontalAlignment - case backgroundColor - case size - case padding - case margin - - case overrides - - } + private enum CodingKeys: String, CodingKey { + case visible + case text = "textLid" + case fontName + case fontWeight + case color + case fontSize + case horizontalAlignment + case backgroundColor + case size + case padding + case margin + } -} + public func hash(into hasher: inout Hasher) { + hasher.combine(visible) + hasher.combine(text) + hasher.combine(fontName) + hasher.combine(fontWeight) + hasher.combine(color) + hasher.combine(fontSize) + hasher.combine(horizontalAlignment) + hasher.combine(backgroundColor) + hasher.combine(size) + hasher.combine(padding) + hasher.combine(margin) + } -extension PaywallComponent.PartialTextComponent { - - enum CodingKeys: String, CodingKey { - case visible - case text = "textLid" - case fontName - case fontWeight - case color - case fontSize - case horizontalAlignment - case backgroundColor - case size - case padding - case margin + public static func == (lhs: PartialTextComponent, rhs: PartialTextComponent) -> Bool { + return lhs.visible == rhs.visible && + lhs.text == rhs.text && + lhs.fontName == rhs.fontName && + lhs.fontWeight == rhs.fontWeight && + lhs.color == rhs.color && + lhs.fontSize == rhs.fontSize && + lhs.horizontalAlignment == rhs.horizontalAlignment && + lhs.backgroundColor == rhs.backgroundColor && + lhs.size == rhs.size && + lhs.padding == rhs.padding && + lhs.margin == rhs.margin + } } } From df201707b0ede5926abdabf0c6f07a8d48e646eb Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Tue, 14 Jan 2025 15:43:01 -0600 Subject: [PATCH 2/4] Retrun after removing a commit From e2e8f2cdf50b55070ed0d8dac5913fb936c9b95f Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Tue, 14 Jan 2025 15:57:19 -0600 Subject: [PATCH 3/4] Remove public from component type --- Sources/Paywalls/Components/PaywallButtonComponent.swift | 2 +- Sources/Paywalls/Components/PaywallIconComponent.swift | 2 +- Sources/Paywalls/Components/PaywallImageComponent.swift | 2 +- Sources/Paywalls/Components/PaywallPackageComponent.swift | 2 +- .../Components/PaywallPurchaseButtonComponent.swift | 2 +- Sources/Paywalls/Components/PaywallStackComponent.swift | 2 +- Sources/Paywalls/Components/PaywallTabsComponent.swift | 8 ++++---- Sources/Paywalls/Components/PaywallTextComponent.swift | 3 ++- 8 files changed, 12 insertions(+), 11 deletions(-) diff --git a/Sources/Paywalls/Components/PaywallButtonComponent.swift b/Sources/Paywalls/Components/PaywallButtonComponent.swift index 9bc1035828..7c4395aac9 100644 --- a/Sources/Paywalls/Components/PaywallButtonComponent.swift +++ b/Sources/Paywalls/Components/PaywallButtonComponent.swift @@ -21,7 +21,7 @@ public extension PaywallComponent { final class ButtonComponent: PaywallComponentBase { - public let type: ComponentType + let type: ComponentType public let action: Action public let stack: PaywallComponent.StackComponent diff --git a/Sources/Paywalls/Components/PaywallIconComponent.swift b/Sources/Paywalls/Components/PaywallIconComponent.swift index 25b58b0864..c493e0656f 100644 --- a/Sources/Paywalls/Components/PaywallIconComponent.swift +++ b/Sources/Paywalls/Components/PaywallIconComponent.swift @@ -78,7 +78,7 @@ public extension PaywallComponent { } } - public let type: ComponentType + let type: ComponentType public let baseUrl: String public let iconName: String public let formats: Formats diff --git a/Sources/Paywalls/Components/PaywallImageComponent.swift b/Sources/Paywalls/Components/PaywallImageComponent.swift index ed759ddda3..0a5aa577aa 100644 --- a/Sources/Paywalls/Components/PaywallImageComponent.swift +++ b/Sources/Paywalls/Components/PaywallImageComponent.swift @@ -14,7 +14,7 @@ public extension PaywallComponent { final class ImageComponent: PaywallComponentBase { - public let type: ComponentType + let type: ComponentType public let source: ThemeImageUrls public let size: Size public let overrideSourceLid: LocalizationKey? diff --git a/Sources/Paywalls/Components/PaywallPackageComponent.swift b/Sources/Paywalls/Components/PaywallPackageComponent.swift index 1f5e5d8984..8c64959747 100644 --- a/Sources/Paywalls/Components/PaywallPackageComponent.swift +++ b/Sources/Paywalls/Components/PaywallPackageComponent.swift @@ -21,7 +21,7 @@ public extension PaywallComponent { final class PackageComponent: PaywallComponentBase { - public let type: ComponentType + let type: ComponentType public let packageID: String public let isSelectedByDefault: Bool public let stack: PaywallComponent.StackComponent diff --git a/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift b/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift index 57fc2eca83..9d2bf330f3 100644 --- a/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift +++ b/Sources/Paywalls/Components/PaywallPurchaseButtonComponent.swift @@ -14,7 +14,7 @@ public extension PaywallComponent { final class PurchaseButtonComponent: PaywallComponentBase { - public let type: ComponentType + let type: ComponentType public let stack: PaywallComponent.StackComponent public init( diff --git a/Sources/Paywalls/Components/PaywallStackComponent.swift b/Sources/Paywalls/Components/PaywallStackComponent.swift index 5c99dc81cd..a04a515322 100644 --- a/Sources/Paywalls/Components/PaywallStackComponent.swift +++ b/Sources/Paywalls/Components/PaywallStackComponent.swift @@ -20,7 +20,7 @@ public extension PaywallComponent { final class StackComponent: PaywallComponentBase { - public let type: ComponentType + let type: ComponentType public let components: [PaywallComponent] public let size: Size public let spacing: CGFloat? diff --git a/Sources/Paywalls/Components/PaywallTabsComponent.swift b/Sources/Paywalls/Components/PaywallTabsComponent.swift index f60a9e7bfd..5c9d0f1074 100644 --- a/Sources/Paywalls/Components/PaywallTabsComponent.swift +++ b/Sources/Paywalls/Components/PaywallTabsComponent.swift @@ -20,7 +20,7 @@ public extension PaywallComponent { final class TabControlButtonComponent: PaywallComponentBase { - public let type: ComponentType + let type: ComponentType public let tabIndex: Int public let stack: StackComponent @@ -43,7 +43,7 @@ public extension PaywallComponent { final class TabControlToggleComponent: PaywallComponentBase { - public let type: ComponentType + let type: ComponentType public let defaultValue: Bool public let thumbColorOn: ColorScheme public let thumbColorOff: ColorScheme @@ -84,7 +84,7 @@ public extension PaywallComponent { final class TabControlComponent: PaywallComponentBase { - public let type: ComponentType + let type: ComponentType public init() { self.type = .tabControl @@ -144,7 +144,7 @@ public extension PaywallComponent { } } - public let type: ComponentType + let type: ComponentType public let size: Size public let padding: Padding public let margin: Padding diff --git a/Sources/Paywalls/Components/PaywallTextComponent.swift b/Sources/Paywalls/Components/PaywallTextComponent.swift index b50fabc718..86669b7f6f 100644 --- a/Sources/Paywalls/Components/PaywallTextComponent.swift +++ b/Sources/Paywalls/Components/PaywallTextComponent.swift @@ -13,7 +13,8 @@ import Foundation public extension PaywallComponent { final class TextComponent: PaywallComponentBase { - public let type: ComponentType + + let type: ComponentType public let text: LocalizationKey public let fontName: String? public let fontWeight: FontWeight From b5948f68f32601085e2cab81a9730c82639e9af8 Mon Sep 17 00:00:00 2001 From: Josh Holtz Date: Tue, 14 Jan 2025 17:33:10 -0600 Subject: [PATCH 4/4] Fix lint --- Sources/Paywalls/Components/PaywallTextComponent.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/Paywalls/Components/PaywallTextComponent.swift b/Sources/Paywalls/Components/PaywallTextComponent.swift index 86669b7f6f..b7ae32fc8f 100644 --- a/Sources/Paywalls/Components/PaywallTextComponent.swift +++ b/Sources/Paywalls/Components/PaywallTextComponent.swift @@ -13,7 +13,7 @@ import Foundation public extension PaywallComponent { final class TextComponent: PaywallComponentBase { - + let type: ComponentType public let text: LocalizationKey public let fontName: String?