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

feature: Components - breaking the ice #1109

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions PrimerSDK.xcworkspace/contents.xcworkspacedata

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
//
// DefaultPaymentMethodContentScope.swift
//
//
// Created by Boris on 6.2.25..
//

import Foundation
#if canImport(SwiftUI)
import SwiftUI
#endif

/// The default implementation of PaymentMethodContentScope for any payment method.
struct DefaultPaymentMethodContentScope: PaymentMethodContentScope {
let method: PaymentMethod

// Simulated state; in a real system, this state would be derived from backend/network updates.
var simulatedState = PaymentMethodState(
isLoading: false,
validationState: PaymentValidationState(isValid: true)
)

/// Returns the current simulated state.
func getState() async -> PaymentMethodState {
// TODO: Replace simulated state with actual state mapping from your processing logic.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ TODOs should be resolved (Replace simulated state with a...). (todo)

return simulatedState
}

/// Simulate payment submission.
func submit() async -> Result<PaymentResult, Error> {
// TODO: Replace with real payment submission logic.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ TODOs should be resolved (Replace with real payment subm...). (todo)

try? await Task.sleep(nanoseconds: 2 * 1_000_000_000) // simulate a 2-second delay
return .success(PaymentResult(success: true, message: "Payment processed successfully"))
}

#if canImport(SwiftUI)
Copy link
Contributor

Choose a reason for hiding this comment

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

Do we require #canImport(SwiftUI) given our current iOS target?

/// Provides default SwiftUI UI for the payment method.
@ViewBuilder
func defaultContent() -> AnyView {
// Wrap your view in AnyView to satisfy the return type.
AnyView(
VStack {
Text("Default UI for \(method.name)")
.font(.headline)
.padding()
// TODO: Add form fields, validations, and error messaging as needed.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ TODOs should be resolved (Add form fields, validations, ...). (todo)

}
)
}
#endif
}
82 changes: 82 additions & 0 deletions Sources/PrimerSDK/Classes/Components/Core/PaymentFlow.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,82 @@
//
// PaymentFlow.swift
//
//
// Created by Boris on 6.2.25..
//

import Foundation
#if canImport(SwiftUI)
import SwiftUI
#endif

/// The headless core component that encapsulates payment processing logic and state.
/// This actor leverages Swift Concurrency (async/await) as per Apple's guidelines.
actor PaymentFlow: PaymentFlowScope {

// MARK: Internal State

private var _paymentMethods: [PaymentMethod] = []
private var _selectedMethod: PaymentMethod?

// AsyncStream continuations to emit updates.
private var paymentMethodsContinuation: AsyncStream<[PaymentMethod]>.Continuation?
private var selectedMethodContinuation: AsyncStream<PaymentMethod?>.Continuation?

/// Initialize with default payment methods.
init() {
// TODO: In a production system, fetch or update available payment methods dynamically.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ TODOs should be resolved (In a production system, fetch ...). (todo)

_paymentMethods = [
CardPaymentMethod(id: "card", name: "Credit Card"),
PayPalPaymentMethod(id: "paypal", name: "PayPal"),
ApplePayPaymentMethod(id: "applePay", name: "Apple Pay")
]
}

// MARK: AsyncStream Providers

/// Provides an `AsyncStream` for payment methods.
func getPaymentMethods() async -> AsyncStream<[PaymentMethod]> {
return AsyncStream { continuation in
self.paymentMethodsContinuation = continuation
continuation.yield(self._paymentMethods)
// TODO: Implement dynamic updates if payment methods change.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ TODOs should be resolved (Implement dynamic updates if p...). (todo)

}
}

/// Provides an `AsyncStream` for the currently selected payment method.
func getSelectedMethod() async -> AsyncStream<PaymentMethod?> {
return AsyncStream { continuation in
self.selectedMethodContinuation = continuation
continuation.yield(self._selectedMethod)
// TODO: Implement dynamic updates if the selection changes.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ TODOs should be resolved (Implement dynamic updates if t...). (todo)

}
}

// MARK: Internal Update Helpers

private func updatePaymentMethods() {
paymentMethodsContinuation?.yield(_paymentMethods)
}

private func updateSelectedMethod() {
selectedMethodContinuation?.yield(_selectedMethod)
}

// MARK: PaymentFlowScope Implementation

func selectPaymentMethod(_ method: PaymentMethod?) async {
_selectedMethod = method
updateSelectedMethod()
}

#if canImport(SwiftUI)
nonisolated func paymentMethodContent<Content: View>(
for method: PaymentMethod,
@ViewBuilder content: @escaping (any PaymentMethodContentScope) -> Content
) -> AnyView {
// Wrap the helper view in AnyView for type erasure.
AnyView(PaymentMethodContentView(method: method, content: content))
}
#endif
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
//
// PaymentFlowScope.swift
//
//
// Created by Boris on 6.2.25..
//

import Foundation
#if canImport(SwiftUI)
import SwiftUI
#endif

/// The main integration interface (similar to PaymentFlowScope on Android).
protocol PaymentFlowScope {
/// Retrieves the list of available payment methods as an `AsyncStream`.
func getPaymentMethods() async -> AsyncStream<[PaymentMethod]>

/// Retrieves the currently selected payment method as an `AsyncStream`.
func getSelectedMethod() async -> AsyncStream<PaymentMethod?>

/// Asynchronously select a payment method.
func selectPaymentMethod(_ method: PaymentMethod?) async

#if canImport(SwiftUI)
/// SwiftUI helper to render payment method content.
@ViewBuilder
func paymentMethodContent<Content: View>(
for method: PaymentMethod,
@ViewBuilder content: @escaping (any PaymentMethodContentScope) -> Content
) -> AnyView
#endif
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
//
// PaymentFlowViewModel.swift
//
//
// Created by Boris on 6.2.25..
//

import SwiftUI

/// A view model that bridges the PaymentFlow actor with SwiftUI state.
@MainActor
class PaymentFlowViewModel: ObservableObject {
/// The underlying PaymentFlow actor.
let paymentFlow = PaymentFlow()

@Published var paymentMethods: [PaymentMethod] = []
@Published var selectedMethod: PaymentMethod?

/// Load payment methods from the PaymentFlow actor.
func loadPaymentMethods() async {
for await methods in await paymentFlow.getPaymentMethods() {
self.paymentMethods = methods
}
}

func loadSelectedMethod() async {
for await method in await paymentFlow.getSelectedMethod() {
self.selectedMethod = method
}
}

/// Select a payment method.
func selectMethod(_ method: PaymentMethod) async {
await paymentFlow.selectPaymentMethod(method)
self.selectedMethod = method
}
}
35 changes: 35 additions & 0 deletions Sources/PrimerSDK/Classes/Components/Core/PaymentMethod.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
//
// PaymentMethod.swift
//
//
// Created by Boris on 6.2.25..
//

/// Protocol representing a payment method.
protocol PaymentMethod {
var id: String { get }
var name: String { get }
var methodType: PaymentMethodType { get }
// TODO: Add additional properties (e.g., icon, configuration) if needed.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ TODOs should be resolved (Add additional properties (e.g...). (todo)

}

/// Enumeration for different payment method types.
enum PaymentMethodType {
case card
case paypal
case applePay
// TODO: Add more types as needed.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ TODOs should be resolved (Add more types as needed.). (todo)

}

/// Represents the validation state of a payment method.
struct PaymentValidationState {
let isValid: Bool
// TODO: Add more validation info if needed (e.g., error messages).
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ TODOs should be resolved (Add more validation info if ne...). (todo)

}

/// Represents the overall state for a payment method.
struct PaymentMethodState {
let isLoading: Bool
let validationState: PaymentValidationState
// TODO: Extend with additional state properties as needed.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ TODOs should be resolved (Extend with additional state p...). (todo)

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
//
// PaymentMethodContentScope.swift
//
//
// Created by Boris on 6.2.25..
//

import Foundation
#if canImport(SwiftUI)
import SwiftUI
#endif

/// Scope that handles behavior and UI for an individual payment method.
protocol PaymentMethodContentScope {
/// The payment method for which this scope applies.
var method: PaymentMethod { get }

/// Retrieve the current state asynchronously.
func getState() async -> PaymentMethodState

/// Asynchronously submit the payment.
func submit() async -> Result<PaymentResult, Error>

#if canImport(SwiftUI)
/// Provides default SwiftUI UI for this payment method.
@ViewBuilder
func defaultContent() -> AnyView
#endif
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
//
// PaymentMethodContentView.swift
//
//
// Created by Boris on 6.2.25..
//

import SwiftUI

// PaymentMethodContentView.swift

/// A helper view that instantiates the appropriate PaymentMethodContentScope and passes it to the provided content builder.
struct PaymentMethodContentView<Content: View>: View {
let method: PaymentMethod
let content: (any PaymentMethodContentScope) -> Content

var body: some View {
// Decide which scope to use based on the payment method type.
let scope: any PaymentMethodContentScope
switch method.methodType {
case .card:
scope = DefaultPaymentMethodContentScope(method: method)
case .paypal:
scope = PayPalPaymentContentScope(method: method)
case .applePay:
scope = ApplePayPaymentContentScope(method: method)
}
return content(scope)
}
}
13 changes: 13 additions & 0 deletions Sources/PrimerSDK/Classes/Components/Core/PaymentResult.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
//
// PaymentResult.swift
//
//
// Created by Boris on 6.2.25..
//

/// The result returned when a payment is processed.
struct PaymentResult {
let success: Bool
let message: String?
// TODO: Add error codes or more detailed response if required.
Copy link
Contributor

Choose a reason for hiding this comment

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

  • ⚠️ TODOs should be resolved (Add error codes or more detail...). (todo)

}
Loading
Loading