diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 3951efb54d0a9dbad1b6b975850bb97c39516589..3548cd17ae609a6d59d2722f9bf91556499a09c4 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -231,6 +231,7 @@ let package = Package( dependencies: [ .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), ] ), .testTarget( diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index beb0730000cf3567195ec9fbb8f76ff143b20dc1..ad9218250dfe2f9d2e8464da32a1c015948ed35d 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -44,7 +44,11 @@ extension AppEnvironment { ) }, verifyContact: { - VerifyContactEnvironment() + VerifyContactEnvironment( + messenger: messenger, + mainQueue: mainQueue, + bgQueue: bgQueue + ) } ) diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift index 9d7a03d86c71b5e24c6c3b9bcbe965c04c102a4c..15f32bed5e442d9ec305f3e842607bfe8786cdf2 100644 --- a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift +++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift @@ -149,7 +149,7 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm case .verifyContactTapped: if let marshaled = state.dbContact?.marshaled { state.verifyContact = VerifyContactState( - xxContact: .live(marshaled) + contact: .live(marshaled) ) } return .none diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift index a64c43c4dbb1e0cb97a2fdef8a0d9776b1e80823..4a38f3ade560742fef925667c5d158b64894a1cf 100644 --- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift +++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift @@ -1,35 +1,82 @@ import ComposableArchitecture +import Foundation import XCTestDynamicOverlay import XXClient +import XXMessengerClient public struct VerifyContactState: Equatable { + public enum Result: Equatable { + case success(Bool) + case failure(String) + } + public init( - xxContact: XXClient.Contact + contact: Contact, + isVerifying: Bool = false, + result: Result? = nil ) { - self.xxContact = xxContact + self.contact = contact + self.isVerifying = isVerifying + self.result = result } - public var xxContact: XXClient.Contact + public var contact: Contact + public var isVerifying: Bool + public var result: Result? } public enum VerifyContactAction: Equatable { - case start + case verifyTapped + case didVerify(VerifyContactState.Result) } public struct VerifyContactEnvironment { - public init() {} + public init( + messenger: Messenger, + mainQueue: AnySchedulerOf<DispatchQueue>, + bgQueue: AnySchedulerOf<DispatchQueue> + ) { + self.messenger = messenger + self.mainQueue = mainQueue + self.bgQueue = bgQueue + } + + public var messenger: Messenger + public var mainQueue: AnySchedulerOf<DispatchQueue> + public var bgQueue: AnySchedulerOf<DispatchQueue> } #if DEBUG extension VerifyContactEnvironment { - public static let unimplemented = VerifyContactEnvironment() + public static let unimplemented = VerifyContactEnvironment( + messenger: .unimplemented, + mainQueue: .unimplemented, + bgQueue: .unimplemented + ) } #endif public let verifyContactReducer = Reducer<VerifyContactState, VerifyContactAction, VerifyContactEnvironment> { state, action, env in switch action { - case .start: + case .verifyTapped: + state.isVerifying = true + state.result = nil + return Effect.result { [state] in + do { + let result = try env.messenger.verifyContact(state.contact) + return .success(.didVerify(.success(result))) + } catch { + return .success(.didVerify(.failure(error.localizedDescription))) + } + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .didVerify(let result): + state.isVerifying = false + state.result = result return .none } } diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift index fff1ad51f53b1a238295c1f323035dcb42ec6ab7..32b81ab789a5408cb46f34574cca6bf7e12144c0 100644 --- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift +++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift @@ -9,16 +9,78 @@ public struct VerifyContactView: View { let store: Store<VerifyContactState, VerifyContactAction> struct ViewState: Equatable { - init(state: VerifyContactState) {} + var username: String? + var email: String? + var phone: String? + var isVerifying: Bool + var result: VerifyContactState.Result? + + init(state: VerifyContactState) { + username = try? state.contact.getFact(.username)?.value + email = try? state.contact.getFact(.email)?.value + phone = try? state.contact.getFact(.phone)?.value + isVerifying = state.isVerifying + result = state.result + } } public var body: some View { WithViewStore(store, observe: ViewState.init) { viewStore in Form { + Section { + Label(viewStore.username ?? "", systemImage: "person") + Label(viewStore.email ?? "", systemImage: "envelope") + Label(viewStore.phone ?? "", systemImage: "phone") + } header: { + Text("Facts") + } + + Section { + Button { + viewStore.send(.verifyTapped) + } label: { + HStack { + Text("Verify") + Spacer() + if viewStore.isVerifying { + ProgressView() + } else { + Image(systemName: "play") + } + } + } + .disabled(viewStore.isVerifying) + } + + if let result = viewStore.result { + Section { + HStack { + switch result { + case .success(true): + Text("Contact verified") + Spacer() + Image(systemName: "person.fill.checkmark") + + case .success(false): + Text("Contact not verified") + Spacer() + Image(systemName: "person.fill.xmark") + case .failure(_): + Text("Verification failed") + Spacer() + Image(systemName: "xmark") + } + } + if case .failure(let failure) = result { + Text(failure) + } + } header: { + Text("Result") + } + } } .navigationTitle("Verify Contact") - .task { viewStore.send(.start) } } } } @@ -28,7 +90,7 @@ public struct VerifyContactView_Previews: PreviewProvider { public static var previews: some View { VerifyContactView(store: Store( initialState: VerifyContactState( - xxContact: .unimplemented("contact-data".data(using: .utf8)!) + contact: .unimplemented("contact-data".data(using: .utf8)!) ), reducer: .empty, environment: () diff --git a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift index 260ed2790e6387c2c3bfbdfd466483ca33816afc..9a24cab9517dddb2f9f2df66548e80da438fdbd4 100644 --- a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift @@ -181,7 +181,7 @@ final class ContactFeatureTests: XCTestCase { store.send(.verifyContactTapped) { $0.verifyContact = VerifyContactState( - xxContact: .live(contactData) + contact: .unimplemented(contactData) ) } } @@ -191,7 +191,7 @@ final class ContactFeatureTests: XCTestCase { initialState: ContactState( id: "contact-id".data(using: .utf8)!, verifyContact: VerifyContactState( - xxContact: .unimplemented("contact-data".data(using: .utf8)!) + contact: .unimplemented("contact-data".data(using: .utf8)!) ) ), reducer: contactReducer, diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift index 212b3d0a9d047841a01a40b26e2bf1ced5d6287b..3721bc46e99c93747fd3a5b3341e8a53770996e9 100644 --- a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift @@ -1,17 +1,86 @@ import ComposableArchitecture import XCTest +import XXClient @testable import VerifyContactFeature final class VerifyContactFeatureTests: XCTestCase { - func testStart() { + func testVerify() { let store = TestStore( initialState: VerifyContactState( - xxContact: .unimplemented("contact-data".data(using: .utf8)!) + contact: .unimplemented("contact-data".data(using: .utf8)!) ), reducer: verifyContactReducer, environment: .unimplemented ) - store.send(.start) + var didVerifyContact: [Contact] = [] + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.verifyContact.run = { contact in + didVerifyContact.append(contact) + return true + } + + store.send(.verifyTapped) { + $0.isVerifying = true + $0.result = nil + } + + store.receive(.didVerify(.success(true))) { + $0.isVerifying = false + $0.result = .success(true) + } + } + + func testVerifyNotVerified() { + let store = TestStore( + initialState: VerifyContactState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ), + reducer: verifyContactReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.verifyContact.run = { _ in false } + + store.send(.verifyTapped) { + $0.isVerifying = true + $0.result = nil + } + + store.receive(.didVerify(.success(false))) { + $0.isVerifying = false + $0.result = .success(false) + } + } + + func testVerifyFailure() { + let store = TestStore( + initialState: VerifyContactState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ), + reducer: verifyContactReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.verifyContact.run = { _ in throw error } + + store.send(.verifyTapped) { + $0.isVerifying = true + $0.result = nil + } + + store.receive(.didVerify(.failure(error.localizedDescription))) { + $0.isVerifying = false + $0.result = .failure(error.localizedDescription) + } } }