From 9f8fef65dbf0f2c5b2b32cd9b30cae2496fd8f1e Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 6 Jun 2022 11:47:22 +0200 Subject: [PATCH 01/16] Add client getter to SessionEnvironment --- Example/example-app/Package.swift | 4 ++++ Example/example-app/Sources/AppFeature/App.swift | 4 +++- .../Sources/SessionFeature/SessionFeature.swift | 13 +++++++++++-- 3 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Example/example-app/Package.swift b/Example/example-app/Package.swift index 3b02bf97..6277f619 100644 --- a/Example/example-app/Package.swift +++ b/Example/example-app/Package.swift @@ -139,6 +139,10 @@ let package = Package( name: "ComposableArchitecture", package: "swift-composable-architecture" ), + .product( + name: "ElixxirDAppsSDK", + package: "elixxir-dapps-sdk-swift" + ), ], swiftSettings: swiftSettings ), diff --git a/Example/example-app/Sources/AppFeature/App.swift b/Example/example-app/Sources/AppFeature/App.swift index 1353882a..1af9cbbe 100644 --- a/Example/example-app/Sources/AppFeature/App.swift +++ b/Example/example-app/Sources/AppFeature/App.swift @@ -40,7 +40,9 @@ extension AppEnvironment { mainScheduler: mainScheduler, error: ErrorEnvironment() ), - session: SessionEnvironment() + session: SessionEnvironment( + getClient: { clientSubject.value } + ) ) } } diff --git a/Example/example-app/Sources/SessionFeature/SessionFeature.swift b/Example/example-app/Sources/SessionFeature/SessionFeature.swift index 3b7dd16f..c98f2d4d 100644 --- a/Example/example-app/Sources/SessionFeature/SessionFeature.swift +++ b/Example/example-app/Sources/SessionFeature/SessionFeature.swift @@ -1,4 +1,5 @@ import ComposableArchitecture +import ElixxirDAppsSDK public struct SessionState: Equatable { public init() {} @@ -9,7 +10,13 @@ public enum SessionAction: Equatable { } public struct SessionEnvironment { - public init() {} + public init( + getClient: @escaping () -> Client? + ) { + self.getClient = getClient + } + + public var getClient: () -> Client? } public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironment> @@ -22,6 +29,8 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm #if DEBUG extension SessionEnvironment { - public static let failing = SessionEnvironment() + public static let failing = SessionEnvironment( + getClient: { .failing } + ) } #endif -- GitLab From 44bd113210cb0ee246b96cf1cf73d58d7269a7f2 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 6 Jun 2022 12:01:43 +0200 Subject: [PATCH 02/16] Add id to feature states --- Example/example-app/Sources/AppFeature/App.swift | 1 + .../Sources/AppFeature/AppFeature.swift | 15 ++++++++++----- .../Sources/LandingFeature/LandingFeature.swift | 3 +++ .../Sources/LandingFeature/LandingView.swift | 2 +- .../Sources/SessionFeature/SessionFeature.swift | 8 +++++++- .../Sources/SessionFeature/SessionView.swift | 2 +- .../Tests/AppFeatureTests/AppFeatureTests.swift | 6 ++++-- .../LandingFeatureTests/LandingFeatureTests.swift | 12 ++++++------ .../SessionFeatureTests/SessionFeatureTests.swift | 2 +- 9 files changed, 34 insertions(+), 17 deletions(-) diff --git a/Example/example-app/Sources/AppFeature/App.swift b/Example/example-app/Sources/AppFeature/App.swift index 1af9cbbe..ed136fb1 100644 --- a/Example/example-app/Sources/AppFeature/App.swift +++ b/Example/example-app/Sources/AppFeature/App.swift @@ -29,6 +29,7 @@ extension AppEnvironment { ).eraseToAnyScheduler() return AppEnvironment( + makeId: UUID.init, hasClient: clientSubject.map { $0 != nil }.eraseToAnyPublisher(), mainScheduler: mainScheduler, landing: LandingEnvironment( diff --git a/Example/example-app/Sources/AppFeature/AppFeature.swift b/Example/example-app/Sources/AppFeature/AppFeature.swift index b99904b0..347c13b1 100644 --- a/Example/example-app/Sources/AppFeature/AppFeature.swift +++ b/Example/example-app/Sources/AppFeature/AppFeature.swift @@ -10,7 +10,8 @@ struct AppState: Equatable { case session(SessionState) } - var scene: Scene = .landing(LandingState()) + var id: UUID = UUID() + var scene: Scene = .landing(LandingState(id: UUID())) } extension AppState.Scene { @@ -45,6 +46,7 @@ enum AppAction: Equatable { } struct AppEnvironment { + var makeId: () -> UUID var hasClient: AnyPublisher<Bool, Never> var mainScheduler: AnySchedulerOf<DispatchQueue> var landing: LandingEnvironment @@ -55,20 +57,22 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment> { state, action, env in switch action { case .viewDidLoad: - struct HasClientEffectId: Hashable {} + struct HasClientEffectId: Hashable { + var id: UUID + } return env.hasClient .removeDuplicates() .map(AppAction.clientDidChange(hasClient:)) .receive(on: env.mainScheduler) .eraseToEffect() - .cancellable(id: HasClientEffectId(), cancelInFlight: true) + .cancellable(id: HasClientEffectId(id: state.id), cancelInFlight: true) case .clientDidChange(let hasClient): if hasClient { - let sessionState = state.scene.asSession ?? SessionState() + let sessionState = state.scene.asSession ?? SessionState(id: env.makeId()) state.scene = .session(sessionState) } else { - let landingState = state.scene.asLanding ?? LandingState() + let landingState = state.scene.asLanding ?? LandingState(id: env.makeId()) state.scene = .landing(landingState) } return .none @@ -95,6 +99,7 @@ let appReducer = Reducer<AppState, AppAction, AppEnvironment> #if DEBUG extension AppEnvironment { static let failing = AppEnvironment( + makeId: { fatalError() }, hasClient: Empty().eraseToAnyPublisher(), mainScheduler: .failing, landing: .failing, diff --git a/Example/example-app/Sources/LandingFeature/LandingFeature.swift b/Example/example-app/Sources/LandingFeature/LandingFeature.swift index dfca1d37..511b1107 100644 --- a/Example/example-app/Sources/LandingFeature/LandingFeature.swift +++ b/Example/example-app/Sources/LandingFeature/LandingFeature.swift @@ -5,17 +5,20 @@ import ErrorFeature public struct LandingState: Equatable { public init( + id: UUID, hasStoredClient: Bool = false, isMakingClient: Bool = false, isRemovingClient: Bool = false, error: ErrorState? = nil ) { + self.id = id self.hasStoredClient = hasStoredClient self.isMakingClient = isMakingClient self.isRemovingClient = isRemovingClient self.error = error } + var id: UUID var hasStoredClient: Bool var isMakingClient: Bool var isRemovingClient: Bool diff --git a/Example/example-app/Sources/LandingFeature/LandingView.swift b/Example/example-app/Sources/LandingFeature/LandingView.swift index a4dd2b73..4a45669a 100644 --- a/Example/example-app/Sources/LandingFeature/LandingView.swift +++ b/Example/example-app/Sources/LandingFeature/LandingView.swift @@ -80,7 +80,7 @@ public struct LandingView_Previews: PreviewProvider { public static var previews: some View { NavigationView { LandingView(store: .init( - initialState: .init(), + initialState: .init(id: UUID()), reducer: .empty, environment: () )) diff --git a/Example/example-app/Sources/SessionFeature/SessionFeature.swift b/Example/example-app/Sources/SessionFeature/SessionFeature.swift index c98f2d4d..2e0d9c96 100644 --- a/Example/example-app/Sources/SessionFeature/SessionFeature.swift +++ b/Example/example-app/Sources/SessionFeature/SessionFeature.swift @@ -2,7 +2,13 @@ import ComposableArchitecture import ElixxirDAppsSDK public struct SessionState: Equatable { - public init() {} + public init( + id: UUID + ) { + self.id = id + } + + public var id: UUID } public enum SessionAction: Equatable { diff --git a/Example/example-app/Sources/SessionFeature/SessionView.swift b/Example/example-app/Sources/SessionFeature/SessionView.swift index eb1fcf00..cbf8934e 100644 --- a/Example/example-app/Sources/SessionFeature/SessionView.swift +++ b/Example/example-app/Sources/SessionFeature/SessionView.swift @@ -27,7 +27,7 @@ public struct SessionView: View { public struct SessionView_Previews: PreviewProvider { public static var previews: some View { SessionView(store: .init( - initialState: .init(), + initialState: .init(id: UUID()), reducer: .empty, environment: () )) diff --git a/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift b/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift index 96a0b077..ce5891ed 100644 --- a/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift +++ b/Example/example-app/Tests/AppFeatureTests/AppFeatureTests.swift @@ -7,10 +7,12 @@ import XCTest final class AppFeatureTests: XCTestCase { func testViewDidLoad() throws { + let newId = UUID() let hasClient = PassthroughSubject<Bool, Never>() let mainScheduler = DispatchQueue.test var env = AppEnvironment.failing + env.makeId = { newId } env.hasClient = hasClient.eraseToAnyPublisher() env.mainScheduler = mainScheduler.eraseToAnyScheduler() @@ -31,7 +33,7 @@ final class AppFeatureTests: XCTestCase { mainScheduler.advance() store.receive(.clientDidChange(hasClient: true)) { - $0.scene = .session(SessionState()) + $0.scene = .session(SessionState(id: newId)) } hasClient.send(true) @@ -41,7 +43,7 @@ final class AppFeatureTests: XCTestCase { mainScheduler.advance() store.receive(.clientDidChange(hasClient: false)) { - $0.scene = .landing(LandingState()) + $0.scene = .landing(LandingState(id: newId)) } hasClient.send(completion: .finished) diff --git a/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift b/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift index df9791e7..c5b56f98 100644 --- a/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift +++ b/Example/example-app/Tests/LandingFeatureTests/LandingFeatureTests.swift @@ -9,7 +9,7 @@ final class LandingFeatureTests: XCTestCase { env.clientStorage.hasStoredClient = { true } let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) @@ -33,7 +33,7 @@ final class LandingFeatureTests: XCTestCase { env.mainScheduler = mainScheduler.eraseToAnyScheduler() let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) @@ -68,7 +68,7 @@ final class LandingFeatureTests: XCTestCase { env.mainScheduler = mainScheduler.eraseToAnyScheduler() let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) @@ -101,7 +101,7 @@ final class LandingFeatureTests: XCTestCase { env.mainScheduler = mainScheduler.eraseToAnyScheduler() let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) @@ -133,7 +133,7 @@ final class LandingFeatureTests: XCTestCase { env.mainScheduler = mainScheduler.eraseToAnyScheduler() let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) @@ -167,7 +167,7 @@ final class LandingFeatureTests: XCTestCase { env.mainScheduler = mainScheduler.eraseToAnyScheduler() let store = TestStore( - initialState: LandingState(), + initialState: LandingState(id: UUID()), reducer: landingReducer, environment: env ) diff --git a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift index 5ed11e32..1e13e0d1 100644 --- a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift +++ b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift @@ -5,7 +5,7 @@ import XCTest final class SessionFeatureTests: XCTestCase { func testViewDidLoad() throws { let store = TestStore( - initialState: SessionState(), + initialState: SessionState(id: UUID()), reducer: sessionReducer, environment: .failing ) -- GitLab From 503cba3faf1b8f13da57b8946633fbc14f0d2a82 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 6 Jun 2022 12:55:54 +0200 Subject: [PATCH 03/16] Add NetworkFollowerStatusView --- .../NetworkFollowerStatusView.swift | 44 +++++++++++++++++++ 1 file changed, 44 insertions(+) create mode 100644 Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift diff --git a/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift b/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift new file mode 100644 index 00000000..5d1ba1de --- /dev/null +++ b/Example/example-app/Sources/SessionFeature/NetworkFollowerStatusView.swift @@ -0,0 +1,44 @@ +import ElixxirDAppsSDK +import SwiftUI + +struct NetworkFollowerStatusView: View { + var status: NetworkFollowerStatus? + + var body: some View { + switch status { + case .stopped: + Label("Stopped", systemImage: "stop.fill") + + case .starting: + Label("Starting...", systemImage: "play") + + case .running: + Label("Running", systemImage: "play.fill") + + case .stopping: + Label("Stopping...", systemImage: "stop") + + case .unknown(let code): + Label("Status \(code)", systemImage: "questionmark") + + case .none: + Label("Unknown", systemImage: "questionmark") + } + } +} + +#if DEBUG +struct NetworkFollowerStatusView_Previews: PreviewProvider { + static var previews: some View { + Group { + NetworkFollowerStatusView(status: .stopped) + NetworkFollowerStatusView(status: .starting) + NetworkFollowerStatusView(status: .running) + NetworkFollowerStatusView(status: .stopping) + NetworkFollowerStatusView(status: .unknown(code: -1)) + NetworkFollowerStatusView(status: nil) + } + .previewLayout(.sizeThatFits) + } +} +#endif -- GitLab From 8ab5b3b57abe9de0d03bc6b655cd4b85f636e14c Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 6 Jun 2022 13:14:46 +0200 Subject: [PATCH 04/16] Start & stop network follower from SessionView --- Example/example-app/Package.swift | 5 + .../example-app/Sources/AppFeature/App.swift | 4 +- .../SessionFeature/SessionFeature.swift | 72 ++++++++++- .../Sources/SessionFeature/SessionView.swift | 47 ++++++- .../SessionFeatureTests.swift | 118 +++++++++++++++++- 5 files changed, 235 insertions(+), 11 deletions(-) diff --git a/Example/example-app/Package.swift b/Example/example-app/Package.swift index 6277f619..55d54626 100644 --- a/Example/example-app/Package.swift +++ b/Example/example-app/Package.swift @@ -135,10 +135,15 @@ let package = Package( .target( name: "SessionFeature", dependencies: [ + .target(name: "ErrorFeature"), .product( name: "ComposableArchitecture", package: "swift-composable-architecture" ), + .product( + name: "ComposablePresentation", + package: "swift-composable-presentation" + ), .product( name: "ElixxirDAppsSDK", package: "elixxir-dapps-sdk-swift" diff --git a/Example/example-app/Sources/AppFeature/App.swift b/Example/example-app/Sources/AppFeature/App.swift index ed136fb1..341cb46c 100644 --- a/Example/example-app/Sources/AppFeature/App.swift +++ b/Example/example-app/Sources/AppFeature/App.swift @@ -42,7 +42,9 @@ extension AppEnvironment { error: ErrorEnvironment() ), session: SessionEnvironment( - getClient: { clientSubject.value } + getClient: { clientSubject.value }, + bgScheduler: bgScheduler, + mainScheduler: mainScheduler ) ) } diff --git a/Example/example-app/Sources/SessionFeature/SessionFeature.swift b/Example/example-app/Sources/SessionFeature/SessionFeature.swift index 2e0d9c96..b07a14cc 100644 --- a/Example/example-app/Sources/SessionFeature/SessionFeature.swift +++ b/Example/example-app/Sources/SessionFeature/SessionFeature.swift @@ -1,34 +1,98 @@ +import Combine import ComposableArchitecture import ElixxirDAppsSDK +import ErrorFeature public struct SessionState: Equatable { public init( - id: UUID + id: UUID, + networkFollowerStatus: NetworkFollowerStatus? = nil, + error: ErrorState? = nil ) { self.id = id + self.networkFollowerStatus = networkFollowerStatus + self.error = error } public var id: UUID + public var networkFollowerStatus: NetworkFollowerStatus? + public var error: ErrorState? } public enum SessionAction: Equatable { case viewDidLoad + case updateNetworkFollowerStatus + case didUpdateNetworkFollowerStatus(NetworkFollowerStatus?) + case runNetworkFollower(Bool) + case networkFollowerDidFail(NSError) + case error(ErrorAction) + case didDismissError } public struct SessionEnvironment { public init( - getClient: @escaping () -> Client? + getClient: @escaping () -> Client?, + bgScheduler: AnySchedulerOf<DispatchQueue>, + mainScheduler: AnySchedulerOf<DispatchQueue> ) { self.getClient = getClient + self.bgScheduler = bgScheduler + self.mainScheduler = mainScheduler } public var getClient: () -> Client? + public var bgScheduler: AnySchedulerOf<DispatchQueue> + public var mainScheduler: AnySchedulerOf<DispatchQueue> } public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironment> { state, action, env in switch action { case .viewDidLoad: + return .merge([ + .init(value: .updateNetworkFollowerStatus), + ]) + + case .updateNetworkFollowerStatus: + return Effect.future { fulfill in + let status = env.getClient()?.networkFollower.status() + fulfill(.success(.didUpdateNetworkFollowerStatus(status))) + } + .subscribe(on: env.bgScheduler) + .receive(on: env.mainScheduler) + .eraseToEffect() + + case .didUpdateNetworkFollowerStatus(let status): + state.networkFollowerStatus = status + return .none + + case .runNetworkFollower(let start): + state.networkFollowerStatus = start ? .starting : .stopping + return Effect.run { subscriber in + do { + if start { + try env.getClient()?.networkFollower.start(timeoutMS: 30_000) + } else { + try env.getClient()?.networkFollower.stop() + } + } catch { + subscriber.send(.networkFollowerDidFail(error as NSError)) + } + let status = env.getClient()?.networkFollower.status() + subscriber.send(.didUpdateNetworkFollowerStatus(status)) + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgScheduler) + .receive(on: env.mainScheduler) + .eraseToEffect() + + case .networkFollowerDidFail(let error): + state.error = ErrorState(error: error) + return .none + + case .didDismissError: + state.error = nil return .none } } @@ -36,7 +100,9 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm #if DEBUG extension SessionEnvironment { public static let failing = SessionEnvironment( - getClient: { .failing } + getClient: { .failing }, + bgScheduler: .failing, + mainScheduler: .failing ) } #endif diff --git a/Example/example-app/Sources/SessionFeature/SessionView.swift b/Example/example-app/Sources/SessionFeature/SessionView.swift index cbf8934e..74b4d130 100644 --- a/Example/example-app/Sources/SessionFeature/SessionView.swift +++ b/Example/example-app/Sources/SessionFeature/SessionView.swift @@ -1,4 +1,7 @@ import ComposableArchitecture +import ComposablePresentation +import ElixxirDAppsSDK +import ErrorFeature import SwiftUI public struct SessionView: View { @@ -9,16 +12,50 @@ public struct SessionView: View { let store: Store<SessionState, SessionAction> struct ViewState: Equatable { - init(state: SessionState) {} + let networkFollowerStatus: NetworkFollowerStatus? + + init(state: SessionState) { + networkFollowerStatus = state.networkFollowerStatus + } } public var body: some View { WithViewStore(store.scope(state: ViewState.init)) { viewStore in - Text("SessionView") - .navigationTitle("Session") - .task { - viewStore.send(.viewDidLoad) + Form { + Section { + NetworkFollowerStatusView(status: viewStore.networkFollowerStatus) + + Button { + viewStore.send(.runNetworkFollower(true)) + } label: { + Text("Start") + } + .disabled(viewStore.networkFollowerStatus != .stopped) + + Button { + viewStore.send(.runNetworkFollower(false)) + } label: { + Text("Stop") + } + .disabled(viewStore.networkFollowerStatus != .running) + } header: { + Text("Network follower") } + } + .navigationTitle("Session") + .task { + viewStore.send(.viewDidLoad) + } + .sheet( + store.scope( + state: \.error, + action: SessionAction.error + ), + onDismiss: { + viewStore.send(.didDismissError) + }, + content: ErrorView.init(store:) + ) } } } diff --git a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift index 1e13e0d1..092573ed 100644 --- a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift +++ b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift @@ -1,15 +1,129 @@ import ComposableArchitecture +import ElixxirDAppsSDK +import ErrorFeature import XCTest @testable import SessionFeature final class SessionFeatureTests: XCTestCase { - func testViewDidLoad() throws { + func testViewDidLoad() { + var networkFollowerStatus: NetworkFollowerStatus! + let bgScheduler = DispatchQueue.test + let mainScheduler = DispatchQueue.test + + var env = SessionEnvironment.failing + env.getClient = { + var client = Client.failing + client.networkFollower.status.status = { networkFollowerStatus } + return client + } + env.bgScheduler = bgScheduler.eraseToAnyScheduler() + env.mainScheduler = mainScheduler.eraseToAnyScheduler() + let store = TestStore( initialState: SessionState(id: UUID()), reducer: sessionReducer, - environment: .failing + environment: env ) store.send(.viewDidLoad) + + store.receive(.updateNetworkFollowerStatus) + + networkFollowerStatus = .stopped + bgScheduler.advance() + mainScheduler.advance() + + store.receive(.didUpdateNetworkFollowerStatus(.stopped)) { + $0.networkFollowerStatus = .stopped + } + } + + func testStartStopNetworkFollower() { + var networkFollowerStatus: NetworkFollowerStatus! + var didStartNetworkFollowerWithTimeout = [Int]() + var didStopNetworkFollower = 0 + var networkFollowerStartError: NSError? + let bgScheduler = DispatchQueue.test + let mainScheduler = DispatchQueue.test + + var env = SessionEnvironment.failing + env.getClient = { + var client = Client.failing + client.networkFollower.status.status = { + networkFollowerStatus + } + client.networkFollower.start.start = { + didStartNetworkFollowerWithTimeout.append($0) + if let error = networkFollowerStartError { + throw error + } + } + client.networkFollower.stop.stop = { + didStopNetworkFollower += 1 + } + return client + } + env.bgScheduler = bgScheduler.eraseToAnyScheduler() + env.mainScheduler = mainScheduler.eraseToAnyScheduler() + + let store = TestStore( + initialState: SessionState(id: UUID()), + reducer: sessionReducer, + environment: env + ) + + store.send(.runNetworkFollower(true)) { + $0.networkFollowerStatus = .starting + } + + networkFollowerStatus = .running + bgScheduler.advance() + mainScheduler.advance() + + XCTAssertEqual(didStartNetworkFollowerWithTimeout, [30_000]) + XCTAssertEqual(didStopNetworkFollower, 0) + + store.receive(.didUpdateNetworkFollowerStatus(.running)) { + $0.networkFollowerStatus = .running + } + + store.send(.runNetworkFollower(false)) { + $0.networkFollowerStatus = .stopping + } + + networkFollowerStatus = .stopped + bgScheduler.advance() + mainScheduler.advance() + + XCTAssertEqual(didStartNetworkFollowerWithTimeout, [30_000]) + XCTAssertEqual(didStopNetworkFollower, 1) + + store.receive(.didUpdateNetworkFollowerStatus(.stopped)) { + $0.networkFollowerStatus = .stopped + } + + store.send(.runNetworkFollower(true)) { + $0.networkFollowerStatus = .starting + } + + networkFollowerStartError = NSError(domain: "test", code: 1234) + networkFollowerStatus = .stopped + bgScheduler.advance() + mainScheduler.advance() + + XCTAssertEqual(didStartNetworkFollowerWithTimeout, [30_000, 30_000]) + XCTAssertEqual(didStopNetworkFollower, 1) + + store.receive(.networkFollowerDidFail(networkFollowerStartError!)) { + $0.error = ErrorState(error: networkFollowerStartError!) + } + + store.receive(.didUpdateNetworkFollowerStatus(.stopped)) { + $0.networkFollowerStatus = .stopped + } + + store.send(.didDismissError) { + $0.error = nil + } } } -- GitLab From 52611a24e52bde6e201ff5ccd3c53f5d0fa1880f Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 6 Jun 2022 13:44:50 +0200 Subject: [PATCH 05/16] Add NetworkHealthStatusView --- .../NetworkHealthStatusView.swift | 33 +++++++++++++++++++ 1 file changed, 33 insertions(+) create mode 100644 Example/example-app/Sources/SessionFeature/NetworkHealthStatusView.swift diff --git a/Example/example-app/Sources/SessionFeature/NetworkHealthStatusView.swift b/Example/example-app/Sources/SessionFeature/NetworkHealthStatusView.swift new file mode 100644 index 00000000..e13cb6fc --- /dev/null +++ b/Example/example-app/Sources/SessionFeature/NetworkHealthStatusView.swift @@ -0,0 +1,33 @@ +import SwiftUI + +struct NetworkHealthStatusView: View { + var status: Bool? + + var body: some View { + switch status { + case .some(true): + Label("Healthy", systemImage: "wifi") + .foregroundColor(.green) + + case .some(false): + Label("Unhealthy", systemImage: "bolt.horizontal.fill") + .foregroundColor(.red) + + case .none: + Label("Unknown", systemImage: "questionmark") + } + } +} + +#if DEBUG +struct NetworkHealthStatusView_Previews: PreviewProvider { + static var previews: some View { + Group { + NetworkHealthStatusView(status: true) + NetworkHealthStatusView(status: false) + NetworkHealthStatusView(status: nil) + } + .previewLayout(.sizeThatFits) + } +} +#endif -- GitLab From 47d1a2b33c59807bc67e90357c7d44914d08854f Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 6 Jun 2022 13:55:14 +0200 Subject: [PATCH 06/16] Monitor network health in SessionFeature --- .../SessionFeature/SessionFeature.swift | 39 ++++++++++++++++++- .../Sources/SessionFeature/SessionView.swift | 8 ++++ .../SessionFeatureTests.swift | 29 ++++++++++++++ 3 files changed, 75 insertions(+), 1 deletion(-) diff --git a/Example/example-app/Sources/SessionFeature/SessionFeature.swift b/Example/example-app/Sources/SessionFeature/SessionFeature.swift index b07a14cc..0756f738 100644 --- a/Example/example-app/Sources/SessionFeature/SessionFeature.swift +++ b/Example/example-app/Sources/SessionFeature/SessionFeature.swift @@ -7,15 +7,18 @@ public struct SessionState: Equatable { public init( id: UUID, networkFollowerStatus: NetworkFollowerStatus? = nil, + isNetworkHealthy: Bool? = nil, error: ErrorState? = nil ) { self.id = id self.networkFollowerStatus = networkFollowerStatus + self.isNetworkHealthy = isNetworkHealthy self.error = error } public var id: UUID public var networkFollowerStatus: NetworkFollowerStatus? + public var isNetworkHealthy: Bool? public var error: ErrorState? } @@ -25,8 +28,10 @@ public enum SessionAction: Equatable { case didUpdateNetworkFollowerStatus(NetworkFollowerStatus?) case runNetworkFollower(Bool) case networkFollowerDidFail(NSError) - case error(ErrorAction) + case monitorNetworkHealth(Bool) + case didUpdateNetworkHealth(Bool?) case didDismissError + case error(ErrorAction) } public struct SessionEnvironment { @@ -51,6 +56,7 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm case .viewDidLoad: return .merge([ .init(value: .updateNetworkFollowerStatus), + .init(value: .monitorNetworkHealth(true)), ]) case .updateNetworkFollowerStatus: @@ -91,9 +97,40 @@ public let sessionReducer = Reducer<SessionState, SessionAction, SessionEnvironm state.error = ErrorState(error: error) return .none + case .monitorNetworkHealth(let start): + struct MonitorEffectId: Hashable { + var id: UUID + } + let effectId = MonitorEffectId(id: state.id) + if start { + return Effect.run { subscriber in + var cancellable = env.getClient()?.monitorNetworkHealth { isHealthy in + subscriber.send(.didUpdateNetworkHealth(isHealthy)) + } + return AnyCancellable { + cancellable?.cancel() + } + } + .subscribe(on: env.bgScheduler) + .receive(on: env.mainScheduler) + .eraseToEffect() + .cancellable(id: effectId, cancelInFlight: true) + } else { + return Effect.cancel(id: effectId) + .subscribe(on: env.bgScheduler) + .eraseToEffect() + } + + case .didUpdateNetworkHealth(let isHealthy): + state.isNetworkHealthy = isHealthy + return .none + case .didDismissError: state.error = nil return .none + + case .error(_): + return .none } } diff --git a/Example/example-app/Sources/SessionFeature/SessionView.swift b/Example/example-app/Sources/SessionFeature/SessionView.swift index 74b4d130..395cfcb1 100644 --- a/Example/example-app/Sources/SessionFeature/SessionView.swift +++ b/Example/example-app/Sources/SessionFeature/SessionView.swift @@ -13,9 +13,11 @@ public struct SessionView: View { struct ViewState: Equatable { let networkFollowerStatus: NetworkFollowerStatus? + let isNetworkHealthy: Bool? init(state: SessionState) { networkFollowerStatus = state.networkFollowerStatus + isNetworkHealthy = state.isNetworkHealthy } } @@ -41,6 +43,12 @@ public struct SessionView: View { } header: { Text("Network follower") } + + Section { + NetworkHealthStatusView(status: viewStore.isNetworkHealthy) + } header: { + Text("Network health") + } } .navigationTitle("Session") .task { diff --git a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift index 092573ed..2ada840d 100644 --- a/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift +++ b/Example/example-app/Tests/SessionFeatureTests/SessionFeatureTests.swift @@ -7,6 +7,9 @@ import XCTest final class SessionFeatureTests: XCTestCase { func testViewDidLoad() { var networkFollowerStatus: NetworkFollowerStatus! + var didStartMonitoringNetworkHealth = 0 + var didStopMonitoringNetworkHealth = 0 + var networkHealthCallback: ((Bool) -> Void)! let bgScheduler = DispatchQueue.test let mainScheduler = DispatchQueue.test @@ -14,6 +17,13 @@ final class SessionFeatureTests: XCTestCase { env.getClient = { var client = Client.failing client.networkFollower.status.status = { networkFollowerStatus } + client.monitorNetworkHealth.listen = { callback in + networkHealthCallback = callback + didStartMonitoringNetworkHealth += 1 + return Cancellable { + didStopMonitoringNetworkHealth += 1 + } + } return client } env.bgScheduler = bgScheduler.eraseToAnyScheduler() @@ -28,6 +38,7 @@ final class SessionFeatureTests: XCTestCase { store.send(.viewDidLoad) store.receive(.updateNetworkFollowerStatus) + store.receive(.monitorNetworkHealth(true)) networkFollowerStatus = .stopped bgScheduler.advance() @@ -36,6 +47,24 @@ final class SessionFeatureTests: XCTestCase { store.receive(.didUpdateNetworkFollowerStatus(.stopped)) { $0.networkFollowerStatus = .stopped } + + XCTAssertEqual(didStartMonitoringNetworkHealth, 1) + XCTAssertEqual(didStopMonitoringNetworkHealth, 0) + + networkHealthCallback(true) + bgScheduler.advance() + mainScheduler.advance() + + store.receive(.didUpdateNetworkHealth(true)) { + $0.isNetworkHealthy = true + } + + store.send(.monitorNetworkHealth(false)) + + bgScheduler.advance() + + XCTAssertEqual(didStartMonitoringNetworkHealth, 1) + XCTAssertEqual(didStopMonitoringNetworkHealth, 1) } func testStartStopNetworkFollower() { -- GitLab From fd86cea6245d58fc31e9befd3f76d2af66720223 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 7 Jun 2022 11:04:34 +0200 Subject: [PATCH 07/16] Update IdentityMaker to return Identity --- Sources/ElixxirDAppsSDK/IdentityMaker.swift | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Sources/ElixxirDAppsSDK/IdentityMaker.swift b/Sources/ElixxirDAppsSDK/IdentityMaker.swift index 91e7eeed..863e180e 100644 --- a/Sources/ElixxirDAppsSDK/IdentityMaker.swift +++ b/Sources/ElixxirDAppsSDK/IdentityMaker.swift @@ -1,9 +1,9 @@ import Bindings public struct IdentityMaker { - public var make: () throws -> Data + public var make: () throws -> Identity - public func callAsFunction() throws -> Data { + public func callAsFunction() throws -> Identity { try make() } } @@ -11,7 +11,9 @@ public struct IdentityMaker { extension IdentityMaker { public static func live(bindingsClient: BindingsClient) -> IdentityMaker { IdentityMaker { - try bindingsClient.makeIdentity() + let data = try bindingsClient.makeIdentity() + let decoder = JSONDecoder() + return try decoder.decode(Identity.self, from: data) } } } -- GitLab From c6042614b2865361ee8a272dd26097528c1691b5 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 7 Jun 2022 11:27:38 +0200 Subject: [PATCH 08/16] Update ConnectionMaker to accept Identity model --- Sources/ElixxirDAppsSDK/ConnectionMaker.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/ElixxirDAppsSDK/ConnectionMaker.swift b/Sources/ElixxirDAppsSDK/ConnectionMaker.swift index 4baa7618..34d7c14b 100644 --- a/Sources/ElixxirDAppsSDK/ConnectionMaker.swift +++ b/Sources/ElixxirDAppsSDK/ConnectionMaker.swift @@ -1,12 +1,12 @@ import Bindings public struct ConnectionMaker { - public var connect: (Bool, Data, Data) throws -> Connection + public var connect: (Bool, Data, Identity) throws -> Connection public func callAsFunction( withAuthentication: Bool, recipientContact: Data, - myIdentity: Data + myIdentity: Identity ) throws -> Connection { try connect(withAuthentication, recipientContact, myIdentity) } @@ -15,18 +15,20 @@ public struct ConnectionMaker { extension ConnectionMaker { public static func live(bindingsClient: BindingsClient) -> ConnectionMaker { ConnectionMaker { withAuthentication, recipientContact, myIdentity in + let encoder = JSONEncoder() + let myIdentityData = try encoder.encode(myIdentity) if withAuthentication { return Connection.live( bindingsAuthenticatedConnection: try bindingsClient.connect( withAuthentication: recipientContact, - myIdentity: myIdentity + myIdentity: myIdentityData ) ) } else { return Connection.live( bindingsConnection: try bindingsClient.connect( recipientContact, - myIdentity: myIdentity + myIdentity: myIdentityData ) ) } -- GitLab From 04d241f266bb39a5b65e54d9924887571d625b96 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 7 Jun 2022 11:35:30 +0200 Subject: [PATCH 09/16] Use Message model in MessageListener --- Sources/ElixxirDAppsSDK/MessageListener.swift | 19 +++++++++++++------ 1 file changed, 13 insertions(+), 6 deletions(-) diff --git a/Sources/ElixxirDAppsSDK/MessageListener.swift b/Sources/ElixxirDAppsSDK/MessageListener.swift index bed6b8c1..dc2bfd1c 100644 --- a/Sources/ElixxirDAppsSDK/MessageListener.swift +++ b/Sources/ElixxirDAppsSDK/MessageListener.swift @@ -1,12 +1,12 @@ import Bindings public struct MessageListener { - public var listen: (Int, String, @escaping (Data) -> Void) -> Void + public var listen: (Int, String, @escaping (Message) -> Void) -> Void public func callAsFunction( messageType: Int, listenerName: String = "MessageListener", - callback: @escaping (Data) -> Void + callback: @escaping (Message) -> Void ) { listen(messageType, listenerName, callback) } @@ -39,18 +39,25 @@ extension MessageListener { } private class Listener: NSObject, BindingsListenerProtocol { - init(listenerName: String, onHear: @escaping (Data) -> Void) { + init(listenerName: String, onHear: @escaping (Message) -> Void) { self.listenerName = listenerName self.onHear = onHear super.init() } let listenerName: String - let onHear: (Data) -> Void + let onHear: (Message) -> Void + let decoder = JSONDecoder() func hear(_ item: Data?) { - guard let item = item else { return } - onHear(item) + guard let item = item else { + fatalError("BindingsListenerProtocol.hear received `nil`") + } + do { + onHear(try decoder.decode(Message.self, from: item)) + } catch { + fatalError("Message decoding failed with error: \(error)") + } } func name() -> String { -- GitLab From 5c954babb949fcb08e8343daedce402cadfa9758 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 7 Jun 2022 11:39:05 +0200 Subject: [PATCH 10/16] Use RestlikeMessage model in RestlikeRequestSender --- .../RestlikeRequestSender.swift | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/Sources/ElixxirDAppsSDK/RestlikeRequestSender.swift b/Sources/ElixxirDAppsSDK/RestlikeRequestSender.swift index fda472aa..efe42bc5 100644 --- a/Sources/ElixxirDAppsSDK/RestlikeRequestSender.swift +++ b/Sources/ElixxirDAppsSDK/RestlikeRequestSender.swift @@ -1,13 +1,13 @@ import Bindings public struct RestlikeRequestSender { - public var send: (Int, Int, Data) throws -> Data + public var send: (Int, Int, RestlikeMessage) throws -> RestlikeMessage public func callAsFunction( clientId: Int, connectionId: Int, - request: Data - ) throws -> Data { + request: RestlikeMessage + ) throws -> RestlikeMessage { try send(clientId, connectionId, request) } } @@ -15,20 +15,24 @@ public struct RestlikeRequestSender { extension RestlikeRequestSender { public static func live(authenticated: Bool) -> RestlikeRequestSender { RestlikeRequestSender { clientId, connectionId, request in + let encoder = JSONEncoder() + let requestData = try encoder.encode(request) var error: NSError? - let response: Data? + let responseData: Data? if authenticated { - response = BindingsRestlikeRequestAuth(clientId, connectionId, request, &error) + responseData = BindingsRestlikeRequestAuth(clientId, connectionId, requestData, &error) } else { - response = BindingsRestlikeRequest(clientId, connectionId, request, &error) + responseData = BindingsRestlikeRequest(clientId, connectionId, requestData, &error) } if let error = error { throw error } - guard let response = response else { + guard let responseData = responseData else { let functionName = "BindingsRestlikeRequest\(authenticated ? "Auth" : "")" fatalError("\(functionName) returned `nil` without providing error") } + let decoder = JSONDecoder() + let response = try decoder.decode(RestlikeMessage.self, from: responseData) return response } } -- GitLab From 3e1a515f6214542866d5de49cf66206c377f62ed Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 7 Jun 2022 11:53:13 +0200 Subject: [PATCH 11/16] Use MessageSendReport in MessageDeliveryWaiter --- .../ElixxirDAppsSDK/MessageDeliveryWaiter.swift | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift b/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift index 92b6ab96..38283a47 100644 --- a/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift +++ b/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift @@ -6,22 +6,24 @@ public struct MessageDeliveryWaiter { case notDelivered(timedOut: Bool) } - public var wait: (Data, Int, @escaping (Result) -> Void) throws -> Void + public var wait: (MessageSendReport, Int, @escaping (Result) -> Void) throws -> Void public func callAsFunction( - roundList: Data, + report: MessageSendReport, timeoutMS: Int, callback: @escaping (Result) -> Void - ) throws -> Void { - try wait(roundList, timeoutMS, callback) + ) throws { + try wait(report, timeoutMS, callback) } } extension MessageDeliveryWaiter { public static func live(bindingsClient: BindingsClient) -> MessageDeliveryWaiter { - MessageDeliveryWaiter { roundList, timeoutMS, callback in + MessageDeliveryWaiter { report, timeoutMS, callback in + let encoder = JSONEncoder() + let reportData = try encoder.encode(report) try bindingsClient.wait( - forMessageDelivery: roundList, + forMessageDelivery: reportData, mdc: Callback(onCallback: callback), timeoutMS: timeoutMS ) -- GitLab From 37629e49f1a9a0980071ebffd408a3e04c5ed762 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 7 Jun 2022 11:55:06 +0200 Subject: [PATCH 12/16] Use MessageSendReport in MessageSender --- Sources/ElixxirDAppsSDK/MessageSender.swift | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/Sources/ElixxirDAppsSDK/MessageSender.swift b/Sources/ElixxirDAppsSDK/MessageSender.swift index 888159b4..4e493b13 100644 --- a/Sources/ElixxirDAppsSDK/MessageSender.swift +++ b/Sources/ElixxirDAppsSDK/MessageSender.swift @@ -1,12 +1,12 @@ import Bindings public struct MessageSender { - public var send: (Int, Data) throws -> Data + public var send: (Int, Data) throws -> MessageSendReport public func callAsFunction( messageType: Int, payload: Data - ) throws -> Data { + ) throws -> MessageSendReport { try send(messageType, payload) } } @@ -28,7 +28,10 @@ extension MessageSender { sendE2E: @escaping (Int, Data) throws -> Data ) -> MessageSender { MessageSender { messageType, payload in - try sendE2E(messageType, payload) + let reportData = try sendE2E(messageType, payload) + let decoder = JSONDecoder() + let report = try decoder.decode(MessageSendReport.self, from: reportData) + return report } } } -- GitLab From d331d62826073682917001898c2a4592c39722af Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 7 Jun 2022 12:02:15 +0200 Subject: [PATCH 13/16] Use [Int] in MessageDeliveryWaiter result --- Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift b/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift index 38283a47..84c19ce7 100644 --- a/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift +++ b/Sources/ElixxirDAppsSDK/MessageDeliveryWaiter.swift @@ -2,7 +2,7 @@ import Bindings public struct MessageDeliveryWaiter { public enum Result: Equatable { - case delivered(roundResults: Data) + case delivered(roundResults: [Int]) case notDelivered(timedOut: Bool) } @@ -40,8 +40,14 @@ private final class Callback: NSObject, BindingsMessageDeliveryCallbackProtocol let onCallback: (MessageDeliveryWaiter.Result) -> Void func eventCallback(_ delivered: Bool, timedOut: Bool, roundResults: Data?) { - if delivered, !timedOut, let roundResults = roundResults { - return onCallback(.delivered(roundResults: roundResults)) + if delivered, !timedOut, let roundResultsData = roundResults { + let decoder = JSONDecoder() + do { + let roundResults = try decoder.decode([Int].self, from: roundResultsData) + return onCallback(.delivered(roundResults: roundResults)) + } catch { + fatalError("BindingsMessageDeliveryCallback roundResults decoding error: \(error)") + } } if !delivered, roundResults == nil { return onCallback(.notDelivered(timedOut: timedOut)) -- GitLab From 18bc435a56b876e4f5535909d43777bc8b2f7ec4 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 7 Jun 2022 12:04:20 +0200 Subject: [PATCH 14/16] Use Identity model in ContactFromIdentityProvider --- .../ElixxirDAppsSDK/ContactFromIdentityProvider.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/ElixxirDAppsSDK/ContactFromIdentityProvider.swift b/Sources/ElixxirDAppsSDK/ContactFromIdentityProvider.swift index 1643a0cd..292f217d 100644 --- a/Sources/ElixxirDAppsSDK/ContactFromIdentityProvider.swift +++ b/Sources/ElixxirDAppsSDK/ContactFromIdentityProvider.swift @@ -1,16 +1,21 @@ import Bindings public struct ContactFromIdentityProvider { - public var get: (Data) throws -> Data + public var get: (Identity) throws -> Data - public func callAsFunction(identity: Data) throws -> Data { + public func callAsFunction(identity: Identity) throws -> Data { try get(identity) } } extension ContactFromIdentityProvider { public static func live(bindingsClient: BindingsClient) -> ContactFromIdentityProvider { - ContactFromIdentityProvider(get: bindingsClient.getContactFromIdentity(_:)) + ContactFromIdentityProvider { identity in + let encoder = JSONEncoder() + let identityData = try encoder.encode(identity) + let contactData = try bindingsClient.getContactFromIdentity(identityData) + return contactData + } } } -- GitLab From e8194482e842365f3d2abbf6bdf0437815386cc9 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 7 Jun 2022 12:10:01 +0200 Subject: [PATCH 15/16] Use Fact model in ContactFactsProvider --- Sources/ElixxirDAppsSDK/ContactFactsProvider.swift | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/Sources/ElixxirDAppsSDK/ContactFactsProvider.swift b/Sources/ElixxirDAppsSDK/ContactFactsProvider.swift index e2adebfb..2029dc6f 100644 --- a/Sources/ElixxirDAppsSDK/ContactFactsProvider.swift +++ b/Sources/ElixxirDAppsSDK/ContactFactsProvider.swift @@ -1,9 +1,9 @@ import Bindings public struct ContactFactsProvider { - public var get: (Data) throws -> Data + public var get: (Data) throws -> [Fact] - public func callAsFunction(contact: Data) throws -> Data { + public func callAsFunction(contact: Data) throws -> [Fact] { try get(contact) } } @@ -11,13 +11,15 @@ public struct ContactFactsProvider { extension ContactFactsProvider { public static let live = ContactFactsProvider { contact in var error: NSError? - let facts = BindingsGetFactsFromContact(contact, &error) + let factsData = BindingsGetFactsFromContact(contact, &error) if let error = error { throw error } - guard let facts = facts else { + guard let factsData = factsData else { fatalError("BindingsGetFactsFromContact returned `nil` without providing error") } + let decoder = JSONDecoder() + let facts = try decoder.decode([Fact].self, from: factsData) return facts } } -- GitLab From 8bdd389799cc895ff9ac2cd95105d7ff29acd59a Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Tue, 7 Jun 2022 12:10:12 +0200 Subject: [PATCH 16/16] Use Fact model in ContactFactsSetter --- Sources/ElixxirDAppsSDK/ContactFactsSetter.swift | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/Sources/ElixxirDAppsSDK/ContactFactsSetter.swift b/Sources/ElixxirDAppsSDK/ContactFactsSetter.swift index 0fca1df3..8dd0ed98 100644 --- a/Sources/ElixxirDAppsSDK/ContactFactsSetter.swift +++ b/Sources/ElixxirDAppsSDK/ContactFactsSetter.swift @@ -1,17 +1,22 @@ import Bindings public struct ContactFactsSetter { - public var set: (Data, Data) throws -> Data + public var set: (Data, [Fact]) throws -> Data - public func callAsFunction(contact: Data, facts: Data) throws -> Data { + public func callAsFunction( + contact: Data, + facts: [Fact] + ) throws -> Data { try set(contact, facts) } } extension ContactFactsSetter { public static let live = ContactFactsSetter { contact, facts in + let encoder = JSONEncoder() + let factsData = try encoder.encode(facts) var error: NSError? - let updatedContact = BindingsSetFactsOnContact(contact, facts, &error) + let updatedContact = BindingsSetFactsOnContact(contact, factsData, &error) if let error = error { throw error } -- GitLab