Skip to content

Commit

Permalink
Merge pull request #6 from primer-io/jn/model-service-rework
Browse files Browse the repository at this point in the history
Rework model-service interactions
  • Loading branch information
jnewc authored Feb 27, 2024
2 parents 7790ccf + 4f3aaf2 commit 1cfdcb4
Show file tree
Hide file tree
Showing 13 changed files with 132 additions and 85 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@
04345A442B209A30006B40CC /* ButtonProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04345A432B209A30006B40CC /* ButtonProgressView.swift */; };
04345A472B20DD0C006B40CC /* Formatters.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04345A462B20DD0C006B40CC /* Formatters.swift */; };
04345A4A2B21D6F1006B40CC /* Appearance.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04345A492B21D6F1006B40CC /* Appearance.swift */; };
043701CF2B88A9330019696B /* Constants.swift in Sources */ = {isa = PBXBuildFile; fileRef = 043701CE2B88A9330019696B /* Constants.swift */; };
04420E752B11005000EA8790 /* TextDivider.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04420E742B11005000EA8790 /* TextDivider.swift */; };
04420E772B110DD100EA8790 /* Localizable.xcstrings in Resources */ = {isa = PBXBuildFile; fileRef = 04420E762B110DD100EA8790 /* Localizable.xcstrings */; };
04420E792B14A84500EA8790 /* ExampleAppLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = 04420E782B14A84500EA8790 /* ExampleAppLogger.swift */; };
Expand Down Expand Up @@ -79,6 +80,7 @@
04345A432B209A30006B40CC /* ButtonProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ButtonProgressView.swift; sourceTree = "<group>"; };
04345A462B20DD0C006B40CC /* Formatters.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Formatters.swift; sourceTree = "<group>"; };
04345A492B21D6F1006B40CC /* Appearance.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Appearance.swift; sourceTree = "<group>"; };
043701CE2B88A9330019696B /* Constants.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Constants.swift; sourceTree = "<group>"; };
04420E742B11005000EA8790 /* TextDivider.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TextDivider.swift; sourceTree = "<group>"; };
04420E762B110DD100EA8790 /* Localizable.xcstrings */ = {isa = PBXFileReference; lastKnownFileType = text.json.xcstrings; path = Localizable.xcstrings; sourceTree = "<group>"; };
04420E782B14A84500EA8790 /* ExampleAppLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ExampleAppLogger.swift; sourceTree = "<group>"; };
Expand Down Expand Up @@ -149,6 +151,7 @@
children = (
04345A462B20DD0C006B40CC /* Formatters.swift */,
04345A492B21D6F1006B40CC /* Appearance.swift */,
043701CE2B88A9330019696B /* Constants.swift */,
);
path = Utilities;
sourceTree = "<group>";
Expand Down Expand Up @@ -463,6 +466,7 @@
042391112AF3B17F00833025 /* LaunchScreenViewController.swift in Sources */,
04D781562AF517BD00A3B29B /* ImageColorInverterModifier.swift in Sources */,
04FAF9FA2AE81563002E4BAE /* ExampleApp.swift in Sources */,
043701CF2B88A9330019696B /* Constants.swift in Sources */,
);
runOnlyForDeploymentPostprocessing = 0;
};
Expand Down
17 changes: 7 additions & 10 deletions Co-Badged Cards/SwiftUI/Co-Badged Cards Example/ContentView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -9,18 +9,15 @@ import SwiftUI

struct ContentView: View {

let service: PrimerDataService

@StateObject var settingsModel: SettingsModel = .init()

@StateObject var settingsModel: SettingsModel

init(service: PrimerDataService) {
_settingsModel = StateObject(wrappedValue: SettingsModel(service: service))
}

var body: some View {
StartPage(service: service, settingsModel: settingsModel)
StartPage(settingsModel: settingsModel)
.navigationTitle("App.Title")
.navigationBarTitleDisplayMode(.inline)
.onReceive(settingsModel.$clientToken, perform: onReceive(clientToken:))
}

func onReceive(clientToken: String) {
service.clientToken = clientToken
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ struct ExampleApp: App {
static var clientToken = ""

// 👇 You can point to a server that provides the client token here
static var clientTokenUrl = ""
static var clientTokenUrl = "https://my.glitch.server/"

let service = PrimerDataService(clientToken: Self.clientToken)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@ import PrimerSDK

class PrimerCardDataErrorsModel: PrimerBaseCardDataModel {

override init() {
init(service: PrimerDataService) {
super.init()
logger.info("[PrimerCardDataErrorsModel.init]")
service.errorsDelegate = self
}

fileprivate func clearErrors() {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -47,31 +47,38 @@ class PrimerCardDataModel: PrimerBaseCardDataModel {
var shouldDisplayCardSelectionView: Bool {
return !cardNumber.isEmpty && !cardNetworksModel.cardNetworks.isEmpty
}

let service: PrimerDataService

init(service: PrimerDataService) {
self.service = service
super.init()
objectWillChange.sink {
DispatchQueue.main.async {
self.service.update(withModel: self)
}
}.store(in: &cancellables)
service.modelsDelegate = self
}

weak var service: PrimerDataService?

func makePayment(_ completion: @escaping (PaymentResultModel) -> Void) {
service.makePayment { result in
completion(result)
}
}

func updateCardNetworks(with networks: [CardDisplayModel]) {
cardNetworksModel.cardNetworks = networks
if !networks.isEmpty {
selectCardNetwork(at: 0)
}
}

func selectCardNetwork(at index: Int) {
selectedCardNetwork = cardNetworksModel.cardNetworks[index].value
objectWillChange.send()
}

override init() {
super.init()
logger.info("[PrimerCardDataModel.init]")
objectWillChange.sink {
DispatchQueue.main.async {
self.service?.update(withModel: self)
}
}.store(in: &cancellables)
}


var isEmpty: Bool {
[cardNumber, expiryDate, cvvNumber, cardholderName].allSatisfy { $0.isEmpty }
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ class SettingsModel: ObservableObject {
case clientTokenUrl = "CLIENT_TOKEN_URL"
}

let service: PrimerDataService

@Published var clientToken: String = ""

@Published var clientTokenUrl: String = "" {
Expand All @@ -27,7 +29,9 @@ class SettingsModel: ObservableObject {

@Published var fetchErrorMessage: String? = nil

init() {
init(service: PrimerDataService) {
self.service = service

if let clientToken = UserDefaults.standard.string(forKey: Key.clientToken.rawValue) {
self.clientToken = clientToken
} else if !ExampleApp.clientToken.isEmpty {
Expand All @@ -38,13 +42,51 @@ class SettingsModel: ObservableObject {
} else {
self.clientTokenUrl = ExampleApp.clientTokenUrl
}

}

var isClientTokenValid: Bool {
return !clientToken.isEmpty && isValidJWT(clientToken)
}


func updateClientToken() async throws {
DispatchQueue.main.sync {
fetchErrorMessage = nil
}
do {
let clientToken = try await service.fetchClientToken(from: clientTokenUrl)
DispatchQueue.main.sync {
self.clientToken = clientToken
}
} catch {
DispatchQueue.main.sync {
fetchErrorMessage = ErrorMessages.clientTokenFetch(clientTokenUrl: clientTokenUrl)
clientToken = ""
}
throw error
}
}

func setup() async throws {
if !isClientTokenValid {
try await updateClientToken()
}

do {
try await service.start()
service.configureForPayments()
} catch {
logger.error(error.localizedDescription)
fetchErrorMessage = ErrorMessages.sdkStart
clientToken = ""

if let error = error as? PrimerDataService.Error {
logger.error(error.message)
}

throw error
}
}

// MARK: Helpers

var isConfiguredForMakingPayment: Bool {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,15 @@ class PrimerDataService: NSObject {
case failedToFetchClientToken(error: Swift.Error)
case failedToInitialiseSDK(error: Swift.Error)
case paymentFailed(error: Swift.Error)

var message: String {
switch self {
case .failedToInitialiseSDK(let error),
.failedToFetchClientToken(let error),
.paymentFailed(let error):
return error.localizedDescription
}
}
}

var clientToken: String
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,6 @@
import UIKit

final class Appearance {

static func setup() {
UINavigationBar.appearance().titleTextAttributes = [.foregroundColor: UIColor.white]
UINavigationBar.appearance().tintColor = .white
Expand All @@ -17,5 +16,4 @@ final class Appearance {
UINavigationBar.appearance().isTranslucent = false
UIBarButtonItem.appearance().tintColor = .white
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
//
// Constants.swift
// Co-Badged Cards Example
//
// Created by Jack Newcombe on 23/02/2024.
//

import Foundation

struct ErrorMessages {
private init() {}

static let sdkStart = "There was an error starting the SDK - check your configuration."

static func clientTokenFetch(clientTokenUrl: String) -> String {
"""
Could not fetch a client token from:
POST \(clientTokenUrl)
Make sure the server is running and that your network connection is working.
"""
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -10,16 +10,19 @@ import Combine

struct CardFormFullPageView: View {

let service: PrimerDataService
let model: PrimerCardDataModel

var model: PrimerCardDataModel = .init()

@StateObject var errorsModel: PrimerCardDataErrorsModel = .init()
@StateObject var errorsModel: PrimerCardDataErrorsModel

var cancellables: Set<AnyCancellable> = []

@State var paymentModel: PaymentResultModel?


init(model: PrimerCardDataModel, errorsModel: PrimerCardDataErrorsModel) {
self.model = model
self._errorsModel = StateObject(wrappedValue: errorsModel)
}

var body: some View {
VStack(spacing: 12) {
Image("primer-icon")
Expand All @@ -36,13 +39,10 @@ struct CardFormFullPageView: View {
}

func onAppear() {
self.model.service = service
self.service.errorsDelegate = errorsModel
self.service.modelsDelegate = model
}

func onSubmit(_ completion: @escaping () -> Void) {
service.makePayment { result in
model.makePayment { result in
self.paymentModel = result
completion()
}
Expand All @@ -51,7 +51,6 @@ struct CardFormFullPageView: View {
}

#Preview {
CardFormFullPageView(service: .init(clientToken: ""),
model: .init(),
errorsModel: .init())
CardFormFullPageView(model: .init(service: .init(clientToken: "")),
errorsModel: .init(service: .init(clientToken: "")))
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,6 @@ import SwiftUI

struct SettingsView: View {

let service: PrimerDataService

@StateObject var settingsModel: SettingsModel

@State var isFetchingClientToken: Bool = false
Expand Down Expand Up @@ -74,24 +72,10 @@ struct SettingsView: View {

private func onFetchClientToken() {
Task {
settingsModel.fetchErrorMessage = nil
isFetchingClientToken = true
defer { isFetchingClientToken = false }

defer {
isFetchingClientToken = false
}

do {
settingsModel.clientToken = try await service.fetchClientToken(from: settingsModel.clientTokenUrl)
} catch {
settingsModel.fetchErrorMessage = """
Could not fetch a client token from:
POST \(settingsModel.clientTokenUrl)
Make sure the server is running and that your network connection is working.
"""
settingsModel.clientToken = ""
throw error
}
try await settingsModel.updateClientToken()
}
}

Expand All @@ -104,6 +88,6 @@ Make sure the server is running and that your network connection is working.

#Preview {
Form {
SettingsView(service: .init(clientToken: ""), settingsModel: .init())
SettingsView(settingsModel: .init(service: .init(clientToken: "")))
}
}
Loading

0 comments on commit 1cfdcb4

Please sign in to comment.