Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Adds scaffolding for the ButtonComponent. #4348

Open
wants to merge 10 commits into
base: main
Choose a base branch
from
20 changes: 20 additions & 0 deletions RevenueCat.xcodeproj/project.pbxproj
Original file line number Diff line number Diff line change
Expand Up @@ -670,6 +670,9 @@
57FFD2522922DBED00A9A878 /* MockStoreTransaction.swift in Sources */ = {isa = PBXBuildFile; fileRef = 57FFD2502922DBED00A9A878 /* MockStoreTransaction.swift */; };
6E38843A0CAFD551013D0A3F /* StoreProduct.swift in Sources */ = {isa = PBXBuildFile; fileRef = FECF627761D375C8431EB866 /* StoreProduct.swift */; };
7706ED3E2C6E374D0004B9F9 /* ButtonStyles.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7706ED3D2C6E374D0004B9F9 /* ButtonStyles.swift */; };
7707A94C2CAD93AC006E0313 /* PaywallButtonComponent.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7707A94B2CAD93AC006E0313 /* PaywallButtonComponent.swift */; };
7707A94E2CAD94D2006E0313 /* ButtonComponentViewModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7707A94D2CAD94D2006E0313 /* ButtonComponentViewModel.swift */; };
7707A9502CAD9775006E0313 /* ButtonComponentView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 7707A94F2CAD9775006E0313 /* ButtonComponentView.swift */; };
77372D992C6F8C7B008E59D3 /* AppUpdateWarningView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77372D982C6F8C7B008E59D3 /* AppUpdateWarningView.swift */; };
77791ECF2C6B852000BCEF03 /* SemanticVersion.swift in Sources */ = {isa = PBXBuildFile; fileRef = 77791ECE2C6B851F00BCEF03 /* SemanticVersion.swift */; };
777FB4882C661C0600CD4749 /* SemanticVersionTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 777FB4872C661C0600CD4749 /* SemanticVersionTests.swift */; };
Expand Down Expand Up @@ -1820,6 +1823,9 @@
57FFD2502922DBED00A9A878 /* MockStoreTransaction.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MockStoreTransaction.swift; sourceTree = "<group>"; };
7706ED3D2C6E374D0004B9F9 /* ButtonStyles.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonStyles.swift; sourceTree = "<group>"; };
7707A93B2CAD8AA2006E0313 /* Local.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Local.xcconfig; sourceTree = "<group>"; };
7707A94B2CAD93AC006E0313 /* PaywallButtonComponent.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PaywallButtonComponent.swift; sourceTree = "<group>"; };
7707A94D2CAD94D2006E0313 /* ButtonComponentViewModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonComponentViewModel.swift; sourceTree = "<group>"; };
7707A94F2CAD9775006E0313 /* ButtonComponentView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonComponentView.swift; sourceTree = "<group>"; };
77372D982C6F8C7B008E59D3 /* AppUpdateWarningView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppUpdateWarningView.swift; sourceTree = "<group>"; };
77607FD72CAD87D60066C23C /* Global.xcconfig */ = {isa = PBXFileReference; lastKnownFileType = text.xcconfig; path = Global.xcconfig; sourceTree = "<group>"; };
77791ECE2C6B851F00BCEF03 /* SemanticVersion.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SemanticVersion.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -3914,6 +3920,15 @@
path = Security;
sourceTree = "<group>";
};
7707A94A2CAD936A006E0313 /* Button */ = {
isa = PBXGroup;
children = (
7707A94D2CAD94D2006E0313 /* ButtonComponentViewModel.swift */,
7707A94F2CAD9775006E0313 /* ButtonComponentView.swift */,
);
path = Button;
sourceTree = "<group>";
};
887A5FBB2C1D036200E1A461 /* RevenueCatUIDev */ = {
isa = PBXGroup;
children = (
Expand Down Expand Up @@ -4270,6 +4285,7 @@
isa = PBXGroup;
children = (
88AD01012C740CF400AA1F2B /* PaywallComponentBase.swift */,
7707A94B2CAD93AC006E0313 /* PaywallButtonComponent.swift */,
88EA80EE2C87D68F003E6675 /* PaywallComponentLocalization.swift */,
88B1BAD72C812D72001B7EE5 /* PaywallComponentPropertyTypes.swift */,
88AD01032C740CF400AA1F2B /* PaywallImageComponent.swift */,
Expand All @@ -4284,6 +4300,7 @@
88AD01352C74196600AA1F2B /* Components */ = {
isa = PBXGroup;
children = (
7707A94A2CAD936A006E0313 /* Button */,
2C2AEB0D2CA64DA900A50F38 /* Previews */,
88B1BAE62C813A3C001B7EE5 /* Image */,
88B1BAE22C813A3C001B7EE5 /* LinkButton */,
Expand Down Expand Up @@ -5512,6 +5529,7 @@
B32B74FF26868AEB005647BF /* Package.swift in Sources */,
353756522C382BC700A1B8D6 /* PreferredLocalesProvider.swift in Sources */,
35D159CF2BC43B89004D8061 /* DiagnosticsSynchronizer.swift in Sources */,
7707A94C2CAD93AC006E0313 /* PaywallButtonComponent.swift in Sources */,
578DAA482948EEAD001700FD /* Clock.swift in Sources */,
2DDF41B324F6F387005BC22D /* InAppPurchaseBuilder.swift in Sources */,
4F4FF3E12A3B731A0028018C /* ETagStrings.swift in Sources */,
Expand Down Expand Up @@ -5975,6 +5993,7 @@
887A60702C1D037000E1A461 /* PaywallTemplate.swift in Sources */,
887A60882C1D037000E1A461 /* MockPurchases.swift in Sources */,
887A60BC2C1D037000E1A461 /* Template5View.swift in Sources */,
7707A9502CAD9775006E0313 /* ButtonComponentView.swift in Sources */,
887A60BE2C1D037000E1A461 /* PaywallFooterViewController.swift in Sources */,
887A608A2C1D037000E1A461 /* PurchaseHandler.swift in Sources */,
2D2AFE8D2C6A834D00D1B0B4 /* TestData.swift in Sources */,
Expand Down Expand Up @@ -6065,6 +6084,7 @@
887A60BF2C1D037000E1A461 /* PaywallViewController.swift in Sources */,
887A60772C1D037000E1A461 /* TemplateViewConfiguration+Images.swift in Sources */,
3537566A2C382C2800A1B8D6 /* ManageSubscriptionsViewModel.swift in Sources */,
7707A94E2CAD94D2006E0313 /* ButtonComponentViewModel.swift in Sources */,
887A60842C1D037000E1A461 /* ConsistentPackageContentView.swift in Sources */,
887A60C42C1D037000E1A461 /* IconView.swift in Sources */,
887A60732C1D037000E1A461 /* ProcessedLocalizedConfiguration.swift in Sources */,
Expand Down
73 changes: 73 additions & 0 deletions RevenueCatUI/Templates/Components/Button/ButtonComponentView.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
//
// 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
//
// ButtonComponentView.swift
//
// Created by Jay Shortway on 02/10/2024.

import Foundation
import RevenueCat
import SwiftUI

#if PAYWALL_COMPONENTS

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
struct ButtonComponentView: View {

private let viewModel: ButtonComponentViewModel

internal init(viewModel: ButtonComponentViewModel) {
self.viewModel = viewModel
}

var body: some View {
StackComponentView(viewModel: viewModel.stackViewModel)
}

}

#if DEBUG

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
struct ButtonComponentView_Previews: PreviewProvider {

static var previews: some View {
VStack {
ButtonComponentView(
// swiftlint:disable:next force_try
viewModel: try! .init(
component: .init(
stack: .init(
components: [
PaywallComponent.text(
PaywallComponent.TextComponent(
textLid: "buttonText",
color: .init(light: "#000000")
)
)
],
backgroundColor: nil
)
),
locale: Locale(identifier: "en_US"),
localizedStrings: [
"buttonText": PaywallComponentsData.LocalizationData.string("Do something")
],
offering: Offering(identifier: "", serverDescription: "", availablePackages: [])
)
)
}
.previewLayout(.fixed(width: 400, height: 400))
.previewDisplayName("Default")
}
}

#endif

#endif
Original file line number Diff line number Diff line change
@@ -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
//
// ButtonComponentViewModel.swift
//
// Created by Jay Shortway on 02/10/2024.
//
// swiftlint:disable missing_docs

import Foundation
import RevenueCat

#if PAYWALL_COMPONENTS

@available(iOS 15.0, macOS 12.0, tvOS 15.0, watchOS 8.0, *)
public class ButtonComponentViewModel {

private let component: PaywallComponent.ButtonComponent
let stackViewModel: StackComponentViewModel

init(
component: PaywallComponent.ButtonComponent,
locale: Locale,
localizedStrings: PaywallComponent.LocalizationDictionary,
offering: Offering
) throws {
self.component = component
self.stackViewModel = try StackComponentViewModel(
locale: locale,
component: component.stack,
localizedStrings: localizedStrings,
offering: offering
)
}

}

#endif
10 changes: 10 additions & 0 deletions RevenueCatUI/Templates/Components/PaywallComponentViewModel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@ enum PaywallComponentViewModel {
case spacer(SpacerComponentViewModel)
case stack(StackComponentViewModel)
case linkButton(LinkButtonComponentViewModel)
case button(ButtonComponentViewModel)

}

Expand Down Expand Up @@ -55,6 +56,15 @@ extension PaywallComponent {
try LinkButtonComponentViewModel(component: component,
localizedStrings: localizedStrings)
)
case .button(let component):
return .button(
try ButtonComponentViewModel(
component: component,
locale: locale,
localizedStrings: localizedStrings,
offering: offering
)
)
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,8 @@ struct ComponentsView: View {
StackComponentView(viewModel: viewModel)
case .linkButton(let viewModel):
LinkButtonComponentView(viewModel: viewModel)
case .button(let viewModel):
ButtonComponentView(viewModel: viewModel)
}
}
}
Expand Down
38 changes: 38 additions & 0 deletions Sources/Paywalls/Components/PaywallButtonComponent.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,38 @@
//
// 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
//
// PaywallButtonComponent.swift
//
// Created by Jay Shortway on 02/10/2024.
//
// swiftlint:disable missing_docs

import Foundation

#if PAYWALL_COMPONENTS

public extension PaywallComponent {

struct ButtonComponent: PaywallComponentBase {

let type: ComponentType
public let stack: PaywallComponent.StackComponent

public init(
stack: PaywallComponent.StackComponent
) {
self.type = .button
self.stack = stack
}

}
Comment on lines +22 to +34
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

So in my packages PRs, I have that StackableComponent protocol (see here) that does a similar thing to this 🤔

But instead of the ButtonComponent having a stack, it would make the ButtonComponent behave like a stack.

I know we don't StackableComponent in this branch yet but we should probably make sure we do the same approach for all the things 😇

I do think this having a stack looks a lot cleaner in the code! 🫶 However, it does make the JSON data structure of the ButtonComponent more nesty...

{
  "type": "button",
  # button properties
  "stack": {
    "type": "stack",
    "components": [
      # children components
    ]
    # all the stack properties
  }
}

vs

{
  "type": "button",
  "components": [
      # children components
    ],
   # button properties
   # all the stack properties
}

Any thoughts? 😇

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh yes, making it "have" a stack was a bit WIPpy to just get the idea out of my head and onto "paper" haha. Can definitely be changed still! If we want it to be a StackableComponent, I'll wait until your branch is merged.

I don't have a very strong opinion on this, but I do have some counter arguments we might wanna consider:

  • Composition is how both SwiftUI and Jetpack Compose build up view hierarchies. It might be good for us to stay close to this philosophy, to easily translate our components into views without a risk of having to "fight" the system.
  • JSON is primarily meant for machines to read, and code is mostly read by humans. If a change makes the code more readable and maintainable, but it makes the JSON more nested, that could be worth it?

What do you think?


}

#endif
7 changes: 7 additions & 0 deletions Sources/Paywalls/Components/PaywallComponentBase.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ public enum PaywallComponent: PaywallComponentBase {
case spacer(SpacerComponent)
case stack(StackComponent)
case linkButton(LinkButtonComponent)
case button(ButtonComponent)

public enum ComponentType: String, Codable, Sendable {

Expand All @@ -26,6 +27,7 @@ public enum PaywallComponent: PaywallComponentBase {
case spacer
case stack
case linkButton = "link_button"
case button

}

Expand Down Expand Up @@ -65,6 +67,9 @@ extension PaywallComponent: Codable {
case .linkButton(let component):
try container.encode(ComponentType.linkButton, forKey: .type)
try component.encode(to: encoder)
case .button(let component):
try container.encode(ComponentType.button, forKey: .type)
try component.encode(to: encoder)
}
}

Expand All @@ -83,6 +88,8 @@ extension PaywallComponent: Codable {
self = .stack(try StackComponent(from: decoder))
case .linkButton:
self = .linkButton(try LinkButtonComponent(from: decoder))
case .button:
self = .button(try ButtonComponent(from: decoder))
}
}

Expand Down