From 946ef9d0bae4d0065b971b56d556cc7893cba7da Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Sun, 11 Sep 2022 22:29:59 +0200 Subject: [PATCH 01/12] Add VerifyContactFeature library --- .../xcschemes/VerifyContactFeature.xcscheme | 78 +++++++++++++++++++ Examples/xx-messenger/Package.swift | 13 ++++ .../xcschemes/XXMessenger.xcscheme | 10 +++ .../VerifyContactFeature.swift | 28 +++++++ .../VerifyContactView.swift | 36 +++++++++ .../VerifyContactFeatureTests.swift | 15 ++++ 6 files changed, 180 insertions(+) create mode 100644 Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/VerifyContactFeature.xcscheme create mode 100644 Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift create mode 100644 Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift create mode 100644 Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/VerifyContactFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/VerifyContactFeature.xcscheme new file mode 100644 index 00000000..54cf617e --- /dev/null +++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/VerifyContactFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1400" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "VerifyContactFeature" + BuildableName = "VerifyContactFeature" + BlueprintName = "VerifyContactFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "VerifyContactFeatureTests" + BuildableName = "VerifyContactFeatureTests" + BlueprintName = "VerifyContactFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "VerifyContactFeature" + BuildableName = "VerifyContactFeature" + BlueprintName = "VerifyContactFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 57081f3e..8c7a1c90 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -27,6 +27,7 @@ let package = Package( .library(name: "RestoreFeature", targets: ["RestoreFeature"]), .library(name: "SendRequestFeature", targets: ["SendRequestFeature"]), .library(name: "UserSearchFeature", targets: ["UserSearchFeature"]), + .library(name: "VerifyContactFeature", targets: ["VerifyContactFeature"]), .library(name: "WelcomeFeature", targets: ["WelcomeFeature"]), ], dependencies: [ @@ -223,6 +224,18 @@ let package = Package( ], swiftSettings: swiftSettings ), + .target( + name: "VerifyContactFeature", + dependencies: [ + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + ] + ), + .testTarget( + name: "VerifyContactFeatureTests", + dependencies: [ + .target(name: "VerifyContactFeature"), + ] + ), .target( name: "WelcomeFeature", dependencies: [ diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme index 9d31f6f7..71af5875 100644 --- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme +++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme @@ -119,6 +119,16 @@ ReferencedContainer = "container:.."> </BuildableReference> </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "VerifyContactFeatureTests" + BuildableName = "VerifyContactFeatureTests" + BlueprintName = "VerifyContactFeatureTests" + ReferencedContainer = "container:.."> + </BuildableReference> + </TestableReference> <TestableReference skipped = "NO"> <BuildableReference diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift new file mode 100644 index 00000000..c657dd72 --- /dev/null +++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift @@ -0,0 +1,28 @@ +import ComposableArchitecture +import XCTestDynamicOverlay + +public struct VerifyContactState: Equatable { + public init() {} +} + +public enum VerifyContactAction: Equatable { + case start +} + +public struct VerifyContactEnvironment { + public init() {} +} + +#if DEBUG +extension VerifyContactEnvironment { + public static let unimplemented = VerifyContactEnvironment() +} +#endif + +public let verifyContactReducer = Reducer<VerifyContactState, VerifyContactAction, VerifyContactEnvironment> +{ state, action, env in + switch action { + case .start: + return .none + } +} diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift new file mode 100644 index 00000000..ff2d76b2 --- /dev/null +++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift @@ -0,0 +1,36 @@ +import ComposableArchitecture +import SwiftUI + +public struct VerifyContactView: View { + public init(store: Store<VerifyContactState, VerifyContactAction>) { + self.store = store + } + + let store: Store<VerifyContactState, VerifyContactAction> + + struct ViewState: Equatable { + init(state: VerifyContactState) {} + } + + public var body: some View { + WithViewStore(store, observe: ViewState.init) { viewStore in + Form { + + } + .navigationTitle("Verify Contact") + .task { viewStore.send(.start) } + } + } +} + +#if DEBUG +public struct VerifyContactView_Previews: PreviewProvider { + public static var previews: some View { + VerifyContactView(store: Store( + initialState: VerifyContactState(), + reducer: .empty, + environment: () + )) + } +} +#endif diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift new file mode 100644 index 00000000..b0f41108 --- /dev/null +++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift @@ -0,0 +1,15 @@ +import ComposableArchitecture +import XCTest +@testable import VerifyContactFeature + +final class VerifyContactFeatureTests: XCTestCase { + func testStart() { + let store = TestStore( + initialState: VerifyContactState(), + reducer: verifyContactReducer, + environment: .unimplemented + ) + + store.send(.start) + } +} -- GitLab From 7735cfe7b022c0ad283ed14299c522be29fc5e3c Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Sun, 11 Sep 2022 22:53:20 +0200 Subject: [PATCH 02/12] Present VerifyContact from Contact --- Examples/xx-messenger/Package.swift | 3 ++ .../AppFeature/AppEnvironment+Live.swift | 4 ++ .../ContactFeature/ContactFeature.swift | 37 +++++++++++++++--- .../Sources/ContactFeature/ContactView.swift | 18 +++++++++ .../VerifyContactFeature.swift | 9 ++++- .../VerifyContactView.swift | 4 +- .../ContactFeatureTests.swift | 39 +++++++++++++++++++ .../VerifyContactFeatureTests.swift | 4 +- 8 files changed, 110 insertions(+), 8 deletions(-) diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 8c7a1c90..3951efb5 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -81,6 +81,7 @@ let package = Package( .target(name: "RestoreFeature"), .target(name: "SendRequestFeature"), .target(name: "UserSearchFeature"), + .target(name: "VerifyContactFeature"), .target(name: "WelcomeFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "ComposablePresentation", package: "swift-composable-presentation"), @@ -101,6 +102,7 @@ let package = Package( dependencies: [ .target(name: "AppCore"), .target(name: "SendRequestFeature"), + .target(name: "VerifyContactFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "ComposablePresentation", package: "swift-composable-presentation"), .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), @@ -228,6 +230,7 @@ let package = Package( name: "VerifyContactFeature", dependencies: [ .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + .product(name: "XXClient", 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 5267fcc3..beb07300 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -7,6 +7,7 @@ import RegisterFeature import RestoreFeature import SendRequestFeature import UserSearchFeature +import VerifyContactFeature import WelcomeFeature import XXMessengerClient import XXModels @@ -41,6 +42,9 @@ extension AppEnvironment { mainQueue: mainQueue, bgQueue: bgQueue ) + }, + verifyContact: { + VerifyContactEnvironment() } ) diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift index 9f6ef294..9d7a03d8 100644 --- a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift +++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift @@ -3,6 +3,7 @@ import ComposableArchitecture import ComposablePresentation import Foundation import SendRequestFeature +import VerifyContactFeature import XCTestDynamicOverlay import XXClient import XXMessengerClient @@ -16,7 +17,8 @@ public struct ContactState: Equatable { importUsername: Bool = true, importEmail: Bool = true, importPhone: Bool = true, - sendRequest: SendRequestState? = nil + sendRequest: SendRequestState? = nil, + verifyContact: VerifyContactState? = nil ) { self.id = id self.dbContact = dbContact @@ -25,6 +27,7 @@ public struct ContactState: Equatable { self.importEmail = importEmail self.importPhone = importPhone self.sendRequest = sendRequest + self.verifyContact = verifyContact } public var id: Data @@ -34,6 +37,7 @@ public struct ContactState: Equatable { @BindableState public var importEmail: Bool @BindableState public var importPhone: Bool public var sendRequest: SendRequestState? + public var verifyContact: VerifyContactState? } public enum ContactAction: Equatable, BindableAction { @@ -43,6 +47,9 @@ public enum ContactAction: Equatable, BindableAction { case sendRequestTapped case sendRequestDismissed case sendRequest(SendRequestAction) + case verifyContactTapped + case verifyContactDismissed + case verifyContact(VerifyContactAction) case binding(BindingAction<ContactState>) } @@ -52,13 +59,15 @@ public struct ContactEnvironment { db: DBManagerGetDB, mainQueue: AnySchedulerOf<DispatchQueue>, bgQueue: AnySchedulerOf<DispatchQueue>, - sendRequest: @escaping () -> SendRequestEnvironment + sendRequest: @escaping () -> SendRequestEnvironment, + verifyContact: @escaping () -> VerifyContactEnvironment ) { self.messenger = messenger self.db = db self.mainQueue = mainQueue self.bgQueue = bgQueue self.sendRequest = sendRequest + self.verifyContact = verifyContact } public var messenger: Messenger @@ -66,6 +75,7 @@ public struct ContactEnvironment { public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> public var sendRequest: () -> SendRequestEnvironment + public var verifyContact: () -> VerifyContactEnvironment } #if DEBUG @@ -75,7 +85,8 @@ extension ContactEnvironment { db: .unimplemented, mainQueue: .unimplemented, bgQueue: .unimplemented, - sendRequest: { .unimplemented } + sendRequest: { .unimplemented }, + verifyContact: { .unimplemented } ) } #endif @@ -135,10 +146,19 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm state.sendRequest = nil return .none - case .sendRequest(_): + case .verifyContactTapped: + if let marshaled = state.dbContact?.marshaled { + state.verifyContact = VerifyContactState( + xxContact: .live(marshaled) + ) + } + return .none + + case .verifyContactDismissed: + state.verifyContact = nil return .none - case .binding(_): + case .binding(_), .sendRequest(_), .verifyContact(_): return .none } } @@ -150,3 +170,10 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm action: /ContactAction.sendRequest, environment: { $0.sendRequest() } ) +.presenting( + verifyContactReducer, + state: .keyPath(\.verifyContact), + id: .notNil(), + action: /ContactAction.verifyContact, + environment: { $0.verifyContact() } +) diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift index 1cd8fa2e..86202f15 100644 --- a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift +++ b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift @@ -3,6 +3,7 @@ import ComposableArchitecture import ComposablePresentation import SendRequestFeature import SwiftUI +import VerifyContactFeature import XXClient import XXModels @@ -114,6 +115,15 @@ public struct ContactView: View { Image(systemName: "chevron.forward") } } + Button { + viewStore.send(.verifyContactTapped) + } label: { + HStack { + Text("Verify contact") + Spacer() + Image(systemName: "chevron.forward") + } + } } header: { Text("Auth") } @@ -131,6 +141,14 @@ public struct ContactView: View { onDeactivate: { viewStore.send(.sendRequestDismissed) }, destination: SendRequestView.init(store:) )) + .background(NavigationLinkWithStore( + store.scope( + state: \.verifyContact, + action: ContactAction.verifyContact + ), + onDeactivate: { viewStore.send(.verifyContactDismissed) }, + destination: VerifyContactView.init(store:) + )) } } } diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift index c657dd72..a64c43c4 100644 --- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift +++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift @@ -1,8 +1,15 @@ import ComposableArchitecture import XCTestDynamicOverlay +import XXClient public struct VerifyContactState: Equatable { - public init() {} + public init( + xxContact: XXClient.Contact + ) { + self.xxContact = xxContact + } + + public var xxContact: XXClient.Contact } public enum VerifyContactAction: Equatable { diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift index ff2d76b2..fff1ad51 100644 --- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift +++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactView.swift @@ -27,7 +27,9 @@ public struct VerifyContactView: View { public struct VerifyContactView_Previews: PreviewProvider { public static var previews: some View { VerifyContactView(store: Store( - initialState: VerifyContactState(), + initialState: VerifyContactState( + xxContact: .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 247abb49..260ed279 100644 --- a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift @@ -2,6 +2,7 @@ import Combine import ComposableArchitecture import CustomDump import SendRequestFeature +import VerifyContactFeature import XCTest import XXClient import XXModels @@ -163,4 +164,42 @@ final class ContactFeatureTests: XCTestCase { $0.sendRequest = nil } } + + func testVerifyContactTapped() { + let contactData = "contact-data".data(using: .utf8)! + let store = TestStore( + initialState: ContactState( + id: Data(), + dbContact: XXModels.Contact( + id: Data(), + marshaled: contactData + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.verifyContactTapped) { + $0.verifyContact = VerifyContactState( + xxContact: .live(contactData) + ) + } + } + + func testVerifyContactDismissed() { + let store = TestStore( + initialState: ContactState( + id: "contact-id".data(using: .utf8)!, + verifyContact: VerifyContactState( + xxContact: .unimplemented("contact-data".data(using: .utf8)!) + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.verifyContactDismissed) { + $0.verifyContact = nil + } + } } diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift index b0f41108..212b3d0a 100644 --- a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift @@ -5,7 +5,9 @@ import XCTest final class VerifyContactFeatureTests: XCTestCase { func testStart() { let store = TestStore( - initialState: VerifyContactState(), + initialState: VerifyContactState( + xxContact: .unimplemented("contact-data".data(using: .utf8)!) + ), reducer: verifyContactReducer, environment: .unimplemented ) -- GitLab From 0b255adcdde3b4fc145dd7e079ef115c4803dff6 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Sun, 11 Sep 2022 23:36:33 +0200 Subject: [PATCH 03/12] Implement contact verification --- Examples/xx-messenger/Package.swift | 1 + .../AppFeature/AppEnvironment+Live.swift | 6 +- .../ContactFeature/ContactFeature.swift | 2 +- .../VerifyContactFeature.swift | 61 +++++++++++++-- .../VerifyContactView.swift | 68 ++++++++++++++++- .../ContactFeatureTests.swift | 4 +- .../VerifyContactFeatureTests.swift | 75 ++++++++++++++++++- 7 files changed, 200 insertions(+), 17 deletions(-) diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 3951efb5..3548cd17 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 beb07300..ad921825 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 9d7a03d8..15f32bed 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 a64c43c4..4a38f3ad 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 fff1ad51..32b81ab7 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 260ed279..9a24cab9 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 212b3d0a..3721bc46 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) + } } } -- GitLab From 33256ca4d895d4fcc2ff71905e816b717cbf8c92 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 12 Sep 2022 00:07:51 +0200 Subject: [PATCH 04/12] Add CheckContactAuthFeature library --- .../CheckContactAuthFeature.xcscheme | 78 +++++++++++++ Examples/xx-messenger/Package.swift | 16 +++ .../xcschemes/XXMessenger.xcscheme | 10 ++ .../CheckContactAuthFeature.swift | 84 +++++++++++++ .../CheckContactAuthView.swift | 101 ++++++++++++++++ .../CheckContactAuthFeatureTests.swift | 110 ++++++++++++++++++ 6 files changed, 399 insertions(+) create mode 100644 Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/CheckContactAuthFeature.xcscheme create mode 100644 Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift create mode 100644 Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthView.swift create mode 100644 Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/CheckContactAuthFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/CheckContactAuthFeature.xcscheme new file mode 100644 index 00000000..17099adb --- /dev/null +++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/CheckContactAuthFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1400" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "CheckContactAuthFeature" + BuildableName = "CheckContactAuthFeature" + BlueprintName = "CheckContactAuthFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "CheckContactAuthFeatureTests" + BuildableName = "CheckContactAuthFeatureTests" + BlueprintName = "CheckContactAuthFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "CheckContactAuthFeature" + BuildableName = "CheckContactAuthFeature" + BlueprintName = "CheckContactAuthFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 3548cd17..01878fbd 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -20,6 +20,7 @@ let package = Package( products: [ .library(name: "AppCore", targets: ["AppCore"]), .library(name: "AppFeature", targets: ["AppFeature"]), + .library(name: "CheckContactAuthFeature", targets: ["CheckContactAuthFeature"]), .library(name: "ContactFeature", targets: ["ContactFeature"]), .library(name: "ContactsFeature", targets: ["ContactsFeature"]), .library(name: "HomeFeature", targets: ["HomeFeature"]), @@ -97,6 +98,21 @@ let package = Package( ], swiftSettings: swiftSettings ), + .target( + name: "CheckContactAuthFeature", + dependencies: [ + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXModels", package: "client-ios-db"), + ] + ), + .testTarget( + name: "CheckContactAuthFeatureTests", + dependencies: [ + .target(name: "CheckContactAuthFeature"), + ] + ), .target( name: "ContactFeature", dependencies: [ diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme index 71af5875..85d23241 100644 --- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme +++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme @@ -49,6 +49,16 @@ ReferencedContainer = "container:.."> </BuildableReference> </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "CheckContactAuthFeatureTests" + BuildableName = "CheckContactAuthFeatureTests" + BlueprintName = "CheckContactAuthFeatureTests" + ReferencedContainer = "container:.."> + </BuildableReference> + </TestableReference> <TestableReference skipped = "NO"> <BuildableReference diff --git a/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift new file mode 100644 index 00000000..119ff27a --- /dev/null +++ b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift @@ -0,0 +1,84 @@ +import ComposableArchitecture +import Foundation +import XCTestDynamicOverlay +import XXClient +import XXMessengerClient + +public struct CheckContactAuthState: Equatable { + public enum Result: Equatable { + case success(Bool) + case failure(String) + } + + public init( + contact: Contact, + isChecking: Bool = false, + result: Result? = nil + ) { + self.contact = contact + self.isChecking = isChecking + self.result = result + } + + public var contact: Contact + public var isChecking: Bool + public var result: Result? +} + +public enum CheckContactAuthAction: Equatable { + case checkTapped + case didCheck(CheckContactAuthState.Result) +} + +public struct CheckContactAuthEnvironment { + 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 CheckContactAuthEnvironment { + public static let unimplemented = CheckContactAuthEnvironment( + messenger: .unimplemented, + mainQueue: .unimplemented, + bgQueue: .unimplemented + ) +} +#endif + +public let checkContactAuthReducer = Reducer<CheckContactAuthState, CheckContactAuthAction, CheckContactAuthEnvironment> +{ state, action, env in + switch action { + case .checkTapped: + state.isChecking = true + state.result = nil + return Effect.result { [state] in + do { + let e2e = try env.messenger.e2e.tryGet() + let contactId = try state.contact.getId() + let result = try e2e.hasAuthenticatedChannel(partnerId: contactId) + return .success(.didCheck(.success(result))) + } catch { + return .success(.didCheck(.failure(error.localizedDescription))) + } + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .didCheck(let result): + state.isChecking = false + state.result = result + return .none + } +} diff --git a/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthView.swift b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthView.swift new file mode 100644 index 00000000..dd2e7894 --- /dev/null +++ b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthView.swift @@ -0,0 +1,101 @@ +import ComposableArchitecture +import SwiftUI +import XXClient + +public struct CheckContactAuthView: View { + public init(store: Store<CheckContactAuthState, CheckContactAuthAction>) { + self.store = store + } + + let store: Store<CheckContactAuthState, CheckContactAuthAction> + + struct ViewState: Equatable { + var username: String? + var email: String? + var phone: String? + var isChecking: Bool + var result: CheckContactAuthState.Result? + + init(state: CheckContactAuthState) { + username = try? state.contact.getFact(.username)?.value + email = try? state.contact.getFact(.email)?.value + phone = try? state.contact.getFact(.phone)?.value + isChecking = state.isChecking + result = state.result + } + } + + public var body: some View { + WithViewStore(store.scope(state: 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(.checkTapped) + } label: { + HStack { + Text("Check") + Spacer() + if viewStore.isChecking { + ProgressView() + } else { + Image(systemName: "play") + } + } + } + .disabled(viewStore.isChecking) + } + + if let result = viewStore.result { + Section { + HStack { + switch result { + case .success(true): + Text("Authorized") + Spacer() + Image(systemName: "person.fill.checkmark") + + case .success(false): + Text("Not authorized") + Spacer() + Image(systemName: "person.fill.xmark") + + case .failure(_): + Text("Checking status failed") + Spacer() + Image(systemName: "xmark") + } + } + if case .failure(let failure) = result { + Text(failure) + } + } header: { + Text("Result") + } + } + } + .navigationTitle("Check connection") + } + } +} + +#if DEBUG +public struct CheckContactAuthView_Previews: PreviewProvider { + public static var previews: some View { + CheckContactAuthView(store: Store( + initialState: CheckContactAuthState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ), + reducer: .empty, + environment: () + )) + } +} +#endif diff --git a/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift new file mode 100644 index 00000000..1c36e33e --- /dev/null +++ b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift @@ -0,0 +1,110 @@ +import ComposableArchitecture +import XCTest +import XXClient +@testable import CheckContactAuthFeature + +final class CheckContactAuthFeatureTests: XCTestCase { + func testCheck() { + var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: CheckContactAuthState( + contact: contact + ), + reducer: checkContactAuthReducer, + environment: .unimplemented + ) + + var didCheckPartnerId: [Data] = [] + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.hasAuthenticatedChannel.run = { partnerId in + didCheckPartnerId.append(partnerId) + return true + } + return e2e + } + + store.send(.checkTapped) { + $0.isChecking = true + $0.result = nil + } + + store.receive(.didCheck(.success(true))) { + $0.isChecking = false + $0.result = .success(true) + } + } + + func testCheckNoConnection() { + var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: CheckContactAuthState( + contact: contact + ), + reducer: checkContactAuthReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.hasAuthenticatedChannel.run = { _ in false } + return e2e + } + + store.send(.checkTapped) { + $0.isChecking = true + $0.result = nil + } + + store.receive(.didCheck(.success(false))) { + $0.isChecking = false + $0.result = .success(false) + } + } + + func testCheckFailure() { + var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: CheckContactAuthState( + contact: contact + ), + reducer: checkContactAuthReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.hasAuthenticatedChannel.run = { _ in throw error } + return e2e + } + + store.send(.checkTapped) { + $0.isChecking = true + $0.result = nil + } + + store.receive(.didCheck(.failure(error.localizedDescription))) { + $0.isChecking = false + $0.result = .failure(error.localizedDescription) + } + } +} -- GitLab From 5152e13d66b4dd4c91b98b657f8e3ff9b634230e Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 12 Sep 2022 00:17:08 +0200 Subject: [PATCH 05/12] Present CheckContactAuth from Contact --- Examples/xx-messenger/Package.swift | 2 + .../AppFeature/AppEnvironment+Live.swift | 8 ++++ .../ContactFeature/ContactFeature.swift | 38 ++++++++++++++++-- .../Sources/ContactFeature/ContactView.swift | 18 +++++++++ .../ContactFeatureTests.swift | 39 +++++++++++++++++++ 5 files changed, 101 insertions(+), 4 deletions(-) diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 01878fbd..c2da9978 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -75,6 +75,7 @@ let package = Package( name: "AppFeature", dependencies: [ .target(name: "AppCore"), + .target(name: "CheckContactAuthFeature"), .target(name: "ContactFeature"), .target(name: "ContactsFeature"), .target(name: "HomeFeature"), @@ -117,6 +118,7 @@ let package = Package( name: "ContactFeature", dependencies: [ .target(name: "AppCore"), + .target(name: "CheckContactAuthFeature"), .target(name: "SendRequestFeature"), .target(name: "VerifyContactFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index ad921825..ae1da8b9 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -1,4 +1,5 @@ import AppCore +import CheckContactAuthFeature import ContactFeature import ContactsFeature import Foundation @@ -49,6 +50,13 @@ extension AppEnvironment { mainQueue: mainQueue, bgQueue: bgQueue ) + }, + checkAuth: { + CheckContactAuthEnvironment( + 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 15f32bed..8bee1b75 100644 --- a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift +++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift @@ -1,4 +1,5 @@ import AppCore +import CheckContactAuthFeature import ComposableArchitecture import ComposablePresentation import Foundation @@ -18,7 +19,8 @@ public struct ContactState: Equatable { importEmail: Bool = true, importPhone: Bool = true, sendRequest: SendRequestState? = nil, - verifyContact: VerifyContactState? = nil + verifyContact: VerifyContactState? = nil, + checkAuth: CheckContactAuthState? = nil ) { self.id = id self.dbContact = dbContact @@ -28,6 +30,7 @@ public struct ContactState: Equatable { self.importPhone = importPhone self.sendRequest = sendRequest self.verifyContact = verifyContact + self.checkAuth = checkAuth } public var id: Data @@ -38,6 +41,7 @@ public struct ContactState: Equatable { @BindableState public var importPhone: Bool public var sendRequest: SendRequestState? public var verifyContact: VerifyContactState? + public var checkAuth: CheckContactAuthState? } public enum ContactAction: Equatable, BindableAction { @@ -50,6 +54,9 @@ public enum ContactAction: Equatable, BindableAction { case verifyContactTapped case verifyContactDismissed case verifyContact(VerifyContactAction) + case checkAuthTapped + case checkAuthDismissed + case checkAuth(CheckContactAuthAction) case binding(BindingAction<ContactState>) } @@ -60,7 +67,8 @@ public struct ContactEnvironment { mainQueue: AnySchedulerOf<DispatchQueue>, bgQueue: AnySchedulerOf<DispatchQueue>, sendRequest: @escaping () -> SendRequestEnvironment, - verifyContact: @escaping () -> VerifyContactEnvironment + verifyContact: @escaping () -> VerifyContactEnvironment, + checkAuth: @escaping () -> CheckContactAuthEnvironment ) { self.messenger = messenger self.db = db @@ -68,6 +76,7 @@ public struct ContactEnvironment { self.bgQueue = bgQueue self.sendRequest = sendRequest self.verifyContact = verifyContact + self.checkAuth = checkAuth } public var messenger: Messenger @@ -76,6 +85,7 @@ public struct ContactEnvironment { public var bgQueue: AnySchedulerOf<DispatchQueue> public var sendRequest: () -> SendRequestEnvironment public var verifyContact: () -> VerifyContactEnvironment + public var checkAuth: () -> CheckContactAuthEnvironment } #if DEBUG @@ -86,7 +96,8 @@ extension ContactEnvironment { mainQueue: .unimplemented, bgQueue: .unimplemented, sendRequest: { .unimplemented }, - verifyContact: { .unimplemented } + verifyContact: { .unimplemented }, + checkAuth: { .unimplemented } ) } #endif @@ -158,7 +169,19 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm state.verifyContact = nil return .none - case .binding(_), .sendRequest(_), .verifyContact(_): + case .checkAuthTapped: + if let marshaled = state.dbContact?.marshaled { + state.checkAuth = CheckContactAuthState( + contact: .live(marshaled) + ) + } + return .none + + case .checkAuthDismissed: + state.checkAuth = nil + return .none + + case .binding(_), .sendRequest(_), .verifyContact(_), .checkAuth(_): return .none } } @@ -177,3 +200,10 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm action: /ContactAction.verifyContact, environment: { $0.verifyContact() } ) +.presenting( + checkContactAuthReducer, + state: .keyPath(\.checkAuth), + id: .notNil(), + action: /ContactAction.checkAuth, + environment: { $0.checkAuth() } +) diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift index 86202f15..07942841 100644 --- a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift +++ b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift @@ -1,4 +1,5 @@ import AppCore +import CheckContactAuthFeature import ComposableArchitecture import ComposablePresentation import SendRequestFeature @@ -124,6 +125,15 @@ public struct ContactView: View { Image(systemName: "chevron.forward") } } + Button { + viewStore.send(.checkAuthTapped) + } label: { + HStack { + Text("Check authorization") + Spacer() + Image(systemName: "chevron.forward") + } + } } header: { Text("Auth") } @@ -149,6 +159,14 @@ public struct ContactView: View { onDeactivate: { viewStore.send(.verifyContactDismissed) }, destination: VerifyContactView.init(store:) )) + .background(NavigationLinkWithStore( + store.scope( + state: \.checkAuth, + action: ContactAction.checkAuth + ), + onDeactivate: { viewStore.send(.checkAuthDismissed) }, + destination: CheckContactAuthView.init(store:) + )) } } } diff --git a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift index 9a24cab9..250d802c 100644 --- a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift @@ -1,3 +1,4 @@ +import CheckContactAuthFeature import Combine import ComposableArchitecture import CustomDump @@ -202,4 +203,42 @@ final class ContactFeatureTests: XCTestCase { $0.verifyContact = nil } } + + func testCheckAuthTapped() { + let contactData = "contact-data".data(using: .utf8)! + let store = TestStore( + initialState: ContactState( + id: Data(), + dbContact: XXModels.Contact( + id: Data(), + marshaled: contactData + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.checkAuthTapped) { + $0.checkAuth = CheckContactAuthState( + contact: .unimplemented(contactData) + ) + } + } + + func testCheckAuthDismissed() { + let store = TestStore( + initialState: ContactState( + id: "contact-id".data(using: .utf8)!, + checkAuth: CheckContactAuthState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.checkAuthDismissed) { + $0.checkAuth = nil + } + } } -- GitLab From d7a739086cfbf2dfd260474907f566e854b3ce6a Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 12 Sep 2022 00:34:07 +0200 Subject: [PATCH 06/12] Update db contact after checking auth status --- Examples/xx-messenger/Package.swift | 1 + .../AppFeature/AppEnvironment+Live.swift | 1 + .../CheckContactAuthFeature.swift | 14 +++++- .../CheckContactAuthFeatureTests.swift | 45 +++++++++++++++++-- 4 files changed, 55 insertions(+), 6 deletions(-) diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index c2da9978..9c44e441 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -102,6 +102,7 @@ let package = Package( .target( name: "CheckContactAuthFeature", dependencies: [ + .target(name: "AppCore"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index ae1da8b9..fa3774e8 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -54,6 +54,7 @@ extension AppEnvironment { checkAuth: { CheckContactAuthEnvironment( messenger: messenger, + db: dbManager.getDB, mainQueue: mainQueue, bgQueue: bgQueue ) diff --git a/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift index 119ff27a..1f768be8 100644 --- a/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift +++ b/Examples/xx-messenger/Sources/CheckContactAuthFeature/CheckContactAuthFeature.swift @@ -1,8 +1,10 @@ +import AppCore import ComposableArchitecture import Foundation import XCTestDynamicOverlay import XXClient import XXMessengerClient +import XXModels public struct CheckContactAuthState: Equatable { public enum Result: Equatable { @@ -11,7 +13,7 @@ public struct CheckContactAuthState: Equatable { } public init( - contact: Contact, + contact: XXClient.Contact, isChecking: Bool = false, result: Result? = nil ) { @@ -20,7 +22,7 @@ public struct CheckContactAuthState: Equatable { self.result = result } - public var contact: Contact + public var contact: XXClient.Contact public var isChecking: Bool public var result: Result? } @@ -33,15 +35,18 @@ public enum CheckContactAuthAction: Equatable { public struct CheckContactAuthEnvironment { public init( messenger: Messenger, + db: DBManagerGetDB, mainQueue: AnySchedulerOf<DispatchQueue>, bgQueue: AnySchedulerOf<DispatchQueue> ) { self.messenger = messenger + self.db = db self.mainQueue = mainQueue self.bgQueue = bgQueue } public var messenger: Messenger + public var db: DBManagerGetDB public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> } @@ -50,6 +55,7 @@ public struct CheckContactAuthEnvironment { extension CheckContactAuthEnvironment { public static let unimplemented = CheckContactAuthEnvironment( messenger: .unimplemented, + db: .unimplemented, mainQueue: .unimplemented, bgQueue: .unimplemented ) @@ -67,6 +73,10 @@ public let checkContactAuthReducer = Reducer<CheckContactAuthState, CheckContact let e2e = try env.messenger.e2e.tryGet() let contactId = try state.contact.getId() let result = try e2e.hasAuthenticatedChannel(partnerId: contactId) + try env.db().bulkUpdateContacts.callAsFunction( + .init(id: [contactId]), + .init(authStatus: result ? .friend : .stranger) + ) return .success(.didCheck(.success(result))) } catch { return .success(.didCheck(.failure(error.localizedDescription))) diff --git a/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift index 1c36e33e..95f5a807 100644 --- a/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift +++ b/Examples/xx-messenger/Tests/CheckContactAuthFeatureTests/CheckContactAuthFeatureTests.swift @@ -1,11 +1,13 @@ import ComposableArchitecture +import CustomDump import XCTest import XXClient +import XXModels @testable import CheckContactAuthFeature final class CheckContactAuthFeatureTests: XCTestCase { func testCheck() { - var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) let contactId = "contact-id".data(using: .utf8)! contact.getIdFromContact.run = { _ in contactId } @@ -18,6 +20,8 @@ final class CheckContactAuthFeatureTests: XCTestCase { ) var didCheckPartnerId: [Data] = [] + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate @@ -29,12 +33,25 @@ final class CheckContactAuthFeatureTests: XCTestCase { } return e2e } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } store.send(.checkTapped) { $0.isChecking = true $0.result = nil } + XCTAssertNoDifference(didCheckPartnerId, [contactId]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .friend)]) + store.receive(.didCheck(.success(true))) { $0.isChecking = false $0.result = .success(true) @@ -42,7 +59,7 @@ final class CheckContactAuthFeatureTests: XCTestCase { } func testCheckNoConnection() { - var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) let contactId = "contact-id".data(using: .utf8)! contact.getIdFromContact.run = { _ in contactId } @@ -54,19 +71,39 @@ final class CheckContactAuthFeatureTests: XCTestCase { environment: .unimplemented ) + var didCheckPartnerId: [Data] = [] + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.e2e.get = { var e2e: E2E = .unimplemented - e2e.hasAuthenticatedChannel.run = { _ in false } + e2e.hasAuthenticatedChannel.run = { partnerId in + didCheckPartnerId.append(partnerId) + return false + } return e2e } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } store.send(.checkTapped) { $0.isChecking = true $0.result = nil } + XCTAssertNoDifference(didCheckPartnerId, [contactId]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .stranger)]) + store.receive(.didCheck(.success(false))) { $0.isChecking = false $0.result = .success(false) @@ -74,7 +111,7 @@ final class CheckContactAuthFeatureTests: XCTestCase { } func testCheckFailure() { - var contact = Contact.unimplemented("contact-data".data(using: .utf8)!) + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) let contactId = "contact-id".data(using: .utf8)! contact.getIdFromContact.run = { _ in contactId } -- GitLab From 71cc4baaf89e8f1070e97da33f14a25b6b506ae9 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 12 Sep 2022 00:42:32 +0200 Subject: [PATCH 07/12] Update db contact after verification --- Examples/xx-messenger/Package.swift | 2 + .../AppFeature/AppEnvironment+Live.swift | 1 + .../VerifyContactFeature.swift | 15 +++++- .../VerifyContactFeatureTests.swift | 53 +++++++++++++++++-- 4 files changed, 65 insertions(+), 6 deletions(-) diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 9c44e441..2736c69c 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -248,9 +248,11 @@ let package = Package( .target( name: "VerifyContactFeature", dependencies: [ + .target(name: "AppCore"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXModels", package: "client-ios-db"), ] ), .testTarget( diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index fa3774e8..906e9992 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -47,6 +47,7 @@ extension AppEnvironment { verifyContact: { VerifyContactEnvironment( messenger: messenger, + db: dbManager.getDB, mainQueue: mainQueue, bgQueue: bgQueue ) diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift index 4a38f3ad..8a69a072 100644 --- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift +++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift @@ -1,8 +1,10 @@ +import AppCore import ComposableArchitecture import Foundation import XCTestDynamicOverlay import XXClient import XXMessengerClient +import XXModels public struct VerifyContactState: Equatable { public enum Result: Equatable { @@ -11,7 +13,7 @@ public struct VerifyContactState: Equatable { } public init( - contact: Contact, + contact: XXClient.Contact, isVerifying: Bool = false, result: Result? = nil ) { @@ -20,7 +22,7 @@ public struct VerifyContactState: Equatable { self.result = result } - public var contact: Contact + public var contact: XXClient.Contact public var isVerifying: Bool public var result: Result? } @@ -33,15 +35,18 @@ public enum VerifyContactAction: Equatable { public struct VerifyContactEnvironment { public init( messenger: Messenger, + db: DBManagerGetDB, mainQueue: AnySchedulerOf<DispatchQueue>, bgQueue: AnySchedulerOf<DispatchQueue> ) { self.messenger = messenger + self.db = db self.mainQueue = mainQueue self.bgQueue = bgQueue } public var messenger: Messenger + public var db: DBManagerGetDB public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> } @@ -50,6 +55,7 @@ public struct VerifyContactEnvironment { extension VerifyContactEnvironment { public static let unimplemented = VerifyContactEnvironment( messenger: .unimplemented, + db: .unimplemented, mainQueue: .unimplemented, bgQueue: .unimplemented ) @@ -65,6 +71,11 @@ public let verifyContactReducer = Reducer<VerifyContactState, VerifyContactActio return Effect.result { [state] in do { let result = try env.messenger.verifyContact(state.contact) + let contactId = try state.contact.getId() + try env.db().bulkUpdateContacts.callAsFunction( + .init(id: [contactId]), + .init(authStatus: result ? .verified : .verificationFailed) + ) return .success(.didVerify(.success(result))) } catch { return .success(.didVerify(.failure(error.localizedDescription))) diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift index 3721bc46..6ca82b1e 100644 --- a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift @@ -1,19 +1,27 @@ import ComposableArchitecture +import CustomDump import XCTest import XXClient +import XXModels @testable import VerifyContactFeature final class VerifyContactFeatureTests: XCTestCase { func testVerify() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + let store = TestStore( initialState: VerifyContactState( - contact: .unimplemented("contact-data".data(using: .utf8)!) + contact: contact ), reducer: verifyContactReducer, environment: .unimplemented ) - var didVerifyContact: [Contact] = [] + var didVerifyContact: [XXClient.Contact] = [] + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate @@ -21,12 +29,25 @@ final class VerifyContactFeatureTests: XCTestCase { didVerifyContact.append(contact) return true } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } store.send(.verifyTapped) { $0.isVerifying = true $0.result = nil } + XCTAssertNoDifference(didVerifyContact, [contact]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .verified)]) + store.receive(.didVerify(.success(true))) { $0.isVerifying = false $0.result = .success(true) @@ -34,23 +55,47 @@ final class VerifyContactFeatureTests: XCTestCase { } func testVerifyNotVerified() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + let store = TestStore( initialState: VerifyContactState( - contact: .unimplemented("contact-data".data(using: .utf8)!) + contact: contact ), reducer: verifyContactReducer, environment: .unimplemented ) + var didVerifyContact: [XXClient.Contact] = [] + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate - store.environment.messenger.verifyContact.run = { _ in false } + store.environment.messenger.verifyContact.run = { contact in + didVerifyContact.append(contact) + return false + } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } store.send(.verifyTapped) { $0.isVerifying = true $0.result = nil } + XCTAssertNoDifference(didVerifyContact, [contact]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .verificationFailed)]) + store.receive(.didVerify(.success(false))) { $0.isVerifying = false $0.result = .success(false) -- GitLab From a13e6f066c71d55a1aaa570e3924a548acae6aef Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 12 Sep 2022 00:50:43 +0200 Subject: [PATCH 08/12] Do not verify contact when request received --- .../AuthCallbackHandlerRequest.swift | 15 +- .../AppFeature/AppEnvironment+Live.swift | 6 +- .../AuthCallbackHandlerRequestTests.swift | 132 ++---------------- 3 files changed, 11 insertions(+), 142 deletions(-) diff --git a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift index d8ac3d18..6d1943e9 100644 --- a/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift +++ b/Examples/xx-messenger/Sources/AppCore/AuthCallbackHandler/AuthCallbackHandlerRequest.swift @@ -15,7 +15,6 @@ public struct AuthCallbackHandlerRequest { extension AuthCallbackHandlerRequest { public static func live( db: DBManagerGetDB, - messenger: Messenger, now: @escaping () -> Date ) -> AuthCallbackHandlerRequest { AuthCallbackHandlerRequest { xxContact in @@ -28,21 +27,9 @@ extension AuthCallbackHandlerRequest { dbContact.username = try xxContact.getFact(.username)?.value dbContact.email = try xxContact.getFact(.email)?.value dbContact.phone = try xxContact.getFact(.phone)?.value - dbContact.authStatus = .verificationInProgress + dbContact.authStatus = .stranger dbContact.createdAt = now() dbContact = try db().saveContact(dbContact) - - do { - try messenger.waitForNetwork() - try messenger.waitForNodes() - let verified = try messenger.verifyContact(xxContact) - dbContact.authStatus = verified ? .verified : .verificationFailed - dbContact = try db().saveContact(dbContact) - } catch { - dbContact.authStatus = .verificationFailed - dbContact = try db().saveContact(dbContact) - throw error - } } } } diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index 906e9992..4cbcef24 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -20,11 +20,7 @@ extension AppEnvironment { let messenger = Messenger.live(messengerEnv) let authHandler = AuthCallbackHandler.live( messenger: messenger, - handleRequest: .live( - db: dbManager.getDB, - messenger: messenger, - now: Date.init - ), + handleRequest: .live(db: dbManager.getDB, now: Date.init), handleConfirm: .live(db: dbManager.getDB), handleReset: .live(db: dbManager.getDB) ) diff --git a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift index 65786884..3a7cb6fb 100644 --- a/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift +++ b/Examples/xx-messenger/Tests/AppCoreTests/AuthCallbackHandler/AuthCallbackHandlerRequestTests.swift @@ -10,17 +10,8 @@ final class AuthCallbackHandlerRequestTests: XCTestCase { func testRequestFromNewContact() throws { let now = Date() var didFetchContacts: [XXModels.Contact.Query] = [] - var didVerifyContact: [XXClient.Contact] = [] var didSaveContact: [XXModels.Contact] = [] - var messenger: Messenger = .unimplemented - messenger.waitForNetwork.run = { _ in } - messenger.waitForNodes.run = { _, _, _, _ in } - messenger.verifyContact.run = { contact in - didVerifyContact.append(contact) - return true - } - let request = AuthCallbackHandlerRequest.live( db: .init { var db: Database = .failing @@ -34,7 +25,6 @@ final class AuthCallbackHandlerRequestTests: XCTestCase { } return db }, - messenger: messenger, now: { now } ) var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!) @@ -50,26 +40,15 @@ final class AuthCallbackHandlerRequestTests: XCTestCase { try request(xxContact) XCTAssertNoDifference(didFetchContacts, [.init(id: ["id".data(using: .utf8)!])]) - XCTAssertNoDifference(didSaveContact, [ - .init( - id: "id".data(using: .utf8)!, - marshaled: "contact".data(using: .utf8)!, - username: "username", - email: "email", - phone: "phone", - authStatus: .verificationInProgress, - createdAt: now - ), - .init( - id: "id".data(using: .utf8)!, - marshaled: "contact".data(using: .utf8)!, - username: "username", - email: "email", - phone: "phone", - authStatus: .verified, - createdAt: now - ) - ]) + XCTAssertNoDifference(didSaveContact, [.init( + id: "id".data(using: .utf8)!, + marshaled: "contact".data(using: .utf8)!, + username: "username", + email: "email", + phone: "phone", + authStatus: .stranger, + createdAt: now + )]) } func testRequestWhenContactInDatabase() throws { @@ -79,7 +58,6 @@ final class AuthCallbackHandlerRequestTests: XCTestCase { db.fetchContacts.run = { _ in [.init(id: "id".data(using: .utf8)!)] } return db }, - messenger: .unimplemented, now: XCTUnimplemented("now", placeholder: Date()) ) var contact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!) @@ -87,96 +65,4 @@ final class AuthCallbackHandlerRequestTests: XCTestCase { try request(contact) } - - func testRequestFromNewContactVerificationFalse() throws { - let now = Date() - var didSaveContact: [XXModels.Contact] = [] - - var messenger: Messenger = .unimplemented - messenger.waitForNetwork.run = { _ in } - messenger.waitForNodes.run = { _, _, _, _ in } - messenger.verifyContact.run = { _ in false } - - let request = AuthCallbackHandlerRequest.live( - db: .init { - var db: Database = .failing - db.fetchContacts.run = { query in return [] } - db.saveContact.run = { contact in - didSaveContact.append(contact) - return contact - } - return db - }, - messenger: messenger, - now: { now } - ) - var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!) - xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! } - xxContact.getFactsFromContact.run = { _ in [] } - - try request(xxContact) - - XCTAssertNoDifference(didSaveContact, [ - .init( - id: "id".data(using: .utf8)!, - marshaled: "contact".data(using: .utf8)!, - authStatus: .verificationInProgress, - createdAt: now - ), - .init( - id: "id".data(using: .utf8)!, - marshaled: "contact".data(using: .utf8)!, - authStatus: .verificationFailed, - createdAt: now - ) - ]) - } - - func testRequestFromNewContactVerificationFailure() throws { - struct Failure: Error, Equatable {} - let failure = Failure() - let now = Date() - var didSaveContact: [XXModels.Contact] = [] - - var messenger: Messenger = .unimplemented - messenger.waitForNetwork.run = { _ in } - messenger.waitForNodes.run = { _, _, _, _ in } - messenger.verifyContact.run = { _ in throw failure } - - let request = AuthCallbackHandlerRequest.live( - db: .init { - var db: Database = .failing - db.fetchContacts.run = { query in return [] } - db.saveContact.run = { contact in - didSaveContact.append(contact) - return contact - } - return db - }, - messenger: messenger, - now: { now } - ) - var xxContact = XXClient.Contact.unimplemented("contact".data(using: .utf8)!) - xxContact.getIdFromContact.run = { _ in "id".data(using: .utf8)! } - xxContact.getFactsFromContact.run = { _ in [] } - - XCTAssertThrowsError(try request(xxContact)) { error in - XCTAssertNoDifference(error as? Failure, failure) - } - - XCTAssertNoDifference(didSaveContact, [ - .init( - id: "id".data(using: .utf8)!, - marshaled: "contact".data(using: .utf8)!, - authStatus: .verificationInProgress, - createdAt: now - ), - .init( - id: "id".data(using: .utf8)!, - marshaled: "contact".data(using: .utf8)!, - authStatus: .verificationFailed, - createdAt: now - ) - ]) - } } -- GitLab From c36c1d81eadf36934e4cabfbbd6b9174a4f6e93a Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 12 Sep 2022 01:10:28 +0200 Subject: [PATCH 09/12] Add ConfirmRequestFeature library --- .../xcschemes/ConfirmRequestFeature.xcscheme | 78 +++++++++++++++ Examples/xx-messenger/Package.swift | 17 ++++ .../xcschemes/XXMessenger.xcscheme | 10 ++ .../ConfirmRequestFeature.swift | 94 ++++++++++++++++++ .../ConfirmRequestView.swift | 95 +++++++++++++++++++ .../ConfirmRequestFeatureTests.swift | 95 +++++++++++++++++++ 6 files changed, 389 insertions(+) create mode 100644 Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ConfirmRequestFeature.xcscheme create mode 100644 Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift create mode 100644 Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestView.swift create mode 100644 Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift diff --git a/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ConfirmRequestFeature.xcscheme b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ConfirmRequestFeature.xcscheme new file mode 100644 index 00000000..a3026c70 --- /dev/null +++ b/Examples/xx-messenger/.swiftpm/xcode/xcshareddata/xcschemes/ConfirmRequestFeature.xcscheme @@ -0,0 +1,78 @@ +<?xml version="1.0" encoding="UTF-8"?> +<Scheme + LastUpgradeVersion = "1400" + version = "1.3"> + <BuildAction + parallelizeBuildables = "YES" + buildImplicitDependencies = "YES"> + <BuildActionEntries> + <BuildActionEntry + buildForTesting = "YES" + buildForRunning = "YES" + buildForProfiling = "YES" + buildForArchiving = "YES" + buildForAnalyzing = "YES"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "ConfirmRequestFeature" + BuildableName = "ConfirmRequestFeature" + BlueprintName = "ConfirmRequestFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </BuildActionEntry> + </BuildActionEntries> + </BuildAction> + <TestAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + shouldUseLaunchSchemeArgsEnv = "YES" + codeCoverageEnabled = "YES"> + <Testables> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "ConfirmRequestFeatureTests" + BuildableName = "ConfirmRequestFeatureTests" + BlueprintName = "ConfirmRequestFeatureTests" + ReferencedContainer = "container:"> + </BuildableReference> + </TestableReference> + </Testables> + </TestAction> + <LaunchAction + buildConfiguration = "Debug" + selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB" + selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB" + launchStyle = "0" + useCustomWorkingDirectory = "NO" + ignoresPersistentStateOnLaunch = "NO" + debugDocumentVersioning = "YES" + debugServiceExtension = "internal" + allowLocationSimulation = "YES"> + </LaunchAction> + <ProfileAction + buildConfiguration = "Release" + shouldUseLaunchSchemeArgsEnv = "YES" + savedToolIdentifier = "" + useCustomWorkingDirectory = "NO" + debugDocumentVersioning = "YES"> + <MacroExpansion> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "ConfirmRequestFeature" + BuildableName = "ConfirmRequestFeature" + BlueprintName = "ConfirmRequestFeature" + ReferencedContainer = "container:"> + </BuildableReference> + </MacroExpansion> + </ProfileAction> + <AnalyzeAction + buildConfiguration = "Debug"> + </AnalyzeAction> + <ArchiveAction + buildConfiguration = "Release" + revealArchiveInOrganizer = "YES"> + </ArchiveAction> +</Scheme> diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 2736c69c..716b1482 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -21,6 +21,7 @@ let package = Package( .library(name: "AppCore", targets: ["AppCore"]), .library(name: "AppFeature", targets: ["AppFeature"]), .library(name: "CheckContactAuthFeature", targets: ["CheckContactAuthFeature"]), + .library(name: "ConfirmRequestFeature", targets: ["ConfirmRequestFeature"]), .library(name: "ContactFeature", targets: ["ContactFeature"]), .library(name: "ContactsFeature", targets: ["ContactsFeature"]), .library(name: "HomeFeature", targets: ["HomeFeature"]), @@ -115,6 +116,22 @@ let package = Package( .target(name: "CheckContactAuthFeature"), ] ), + .target( + name: "ConfirmRequestFeature", + dependencies: [ + .target(name: "AppCore"), + .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), + .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"), + .product(name: "XXModels", package: "client-ios-db"), + ] + ), + .testTarget( + name: "ConfirmRequestFeatureTests", + dependencies: [ + .target(name: "ConfirmRequestFeature"), + ] + ), .target( name: "ContactFeature", dependencies: [ diff --git a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme index 85d23241..0e5e54ad 100644 --- a/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme +++ b/Examples/xx-messenger/Project/XXMessenger.xcodeproj/xcshareddata/xcschemes/XXMessenger.xcscheme @@ -59,6 +59,16 @@ ReferencedContainer = "container:.."> </BuildableReference> </TestableReference> + <TestableReference + skipped = "NO"> + <BuildableReference + BuildableIdentifier = "primary" + BlueprintIdentifier = "ConfirmRequestFeatureTests" + BuildableName = "ConfirmRequestFeatureTests" + BlueprintName = "ConfirmRequestFeatureTests" + ReferencedContainer = "container:.."> + </BuildableReference> + </TestableReference> <TestableReference skipped = "NO"> <BuildableReference diff --git a/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift new file mode 100644 index 00000000..b18f9da9 --- /dev/null +++ b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift @@ -0,0 +1,94 @@ +import AppCore +import ComposableArchitecture +import Foundation +import XCTestDynamicOverlay +import XXClient +import XXMessengerClient +import XXModels + +public struct ConfirmRequestState: Equatable { + public enum Result: Equatable { + case success + case failure(String) + } + + public init( + contact: XXClient.Contact, + isConfirming: Bool = false, + result: Result? = nil + ) { + self.contact = contact + self.isConfirming = isConfirming + self.result = result + } + + public var contact: XXClient.Contact + public var isConfirming: Bool + public var result: Result? +} + +public enum ConfirmRequestAction: Equatable { + case confirmTapped + case didConfirm(ConfirmRequestState.Result) +} + +public struct ConfirmRequestEnvironment { + public init( + messenger: Messenger, + db: DBManagerGetDB, + mainQueue: AnySchedulerOf<DispatchQueue>, + bgQueue: AnySchedulerOf<DispatchQueue> + ) { + self.messenger = messenger + self.db = db + self.mainQueue = mainQueue + self.bgQueue = bgQueue + } + + public var messenger: Messenger + public var db: DBManagerGetDB + public var mainQueue: AnySchedulerOf<DispatchQueue> + public var bgQueue: AnySchedulerOf<DispatchQueue> +} + +#if DEBUG +extension ConfirmRequestEnvironment { + public static let unimplemented = ConfirmRequestEnvironment( + messenger: .unimplemented, + db: .unimplemented, + mainQueue: .unimplemented, + bgQueue: .unimplemented + ) +} +#endif + +public let confirmRequestReducer = Reducer<ConfirmRequestState, ConfirmRequestAction, ConfirmRequestEnvironment> +{ state, action, env in + switch action { + case .confirmTapped: + state.isConfirming = true + state.result = nil + return Effect.result { [state] in + do { + let e2e = try env.messenger.e2e.tryGet() + _ = try e2e.confirmReceivedRequest(partner: state.contact) + let contactId = try state.contact.getId() + try env.db().bulkUpdateContacts.callAsFunction( + .init(id: [contactId]), + .init(authStatus: .friend) + ) + return .success(.didConfirm(.success)) + } catch { + return .success(.didConfirm(.failure(error.localizedDescription))) + } + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .didConfirm(let result): + state.isConfirming = false + state.result = result + return .none + } +} diff --git a/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestView.swift b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestView.swift new file mode 100644 index 00000000..90ebc70e --- /dev/null +++ b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestView.swift @@ -0,0 +1,95 @@ +import ComposableArchitecture +import SwiftUI + +public struct ConfirmRequestView: View { + public init(store: Store<ConfirmRequestState, ConfirmRequestAction>) { + self.store = store + } + + let store: Store<ConfirmRequestState, ConfirmRequestAction> + + struct ViewState: Equatable { + var username: String? + var email: String? + var phone: String? + var isConfirming: Bool + var result: ConfirmRequestState.Result? + + init(state: ConfirmRequestState) { + username = try? state.contact.getFact(.username)?.value + email = try? state.contact.getFact(.email)?.value + phone = try? state.contact.getFact(.phone)?.value + isConfirming = state.isConfirming + 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(.confirmTapped) + } label: { + HStack { + Text("Confirm") + Spacer() + if viewStore.isConfirming { + ProgressView() + } else { + Image(systemName: "checkmark") + } + } + } + .disabled(viewStore.isConfirming) + } + + if let result = viewStore.result { + Section { + HStack { + switch result { + case .success: + Text("Request confirmed") + Spacer() + Image(systemName: "person.fill.checkmark") + + case .failure(_): + Text("Confirming request failed") + Spacer() + Image(systemName: "xmark") + } + } + if case .failure(let failure) = result { + Text(failure) + } + } header: { + Text("Result") + } + } + } + .navigationTitle("Confirm request") + } + } +} + +#if DEBUG +public struct ConfirmRequestView_Previews: PreviewProvider { + public static var previews: some View { + ConfirmRequestView(store: Store( + initialState: ConfirmRequestState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ), + reducer: .empty, + environment: () + )) + } +} +#endif diff --git a/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift new file mode 100644 index 00000000..2686d645 --- /dev/null +++ b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift @@ -0,0 +1,95 @@ +import ComposableArchitecture +import CustomDump +import XCTest +import XXClient +import XXModels +@testable import ConfirmRequestFeature + +final class ConfirmRequestFeatureTests: XCTestCase { + func testConfirm() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: ConfirmRequestState( + contact: contact + ), + reducer: confirmRequestReducer, + environment: .unimplemented + ) + + var didConfirmRequestFromContact: [XXClient.Contact] = [] + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.confirmReceivedRequest.run = { contact in + didConfirmRequestFromContact.append(contact) + return 0 + } + return e2e + } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } + + store.send(.confirmTapped) { + $0.isConfirming = true + $0.result = nil + } + + XCTAssertNoDifference(didConfirmRequestFromContact, [contact]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .friend)]) + + store.receive(.didConfirm(.success)) { + $0.isConfirming = false + $0.result = .success + } + } + + func testConfirmFailure() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + + let store = TestStore( + initialState: ConfirmRequestState( + contact: contact + ), + reducer: confirmRequestReducer, + environment: .unimplemented + ) + + struct Failure: Error {} + let error = Failure() + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.messenger.e2e.get = { + var e2e: E2E = .unimplemented + e2e.confirmReceivedRequest.run = { _ in throw error } + return e2e + } + + store.send(.confirmTapped) { + $0.isConfirming = true + $0.result = nil + } + + store.receive(.didConfirm(.failure(error.localizedDescription))) { + $0.isConfirming = false + $0.result = .failure(error.localizedDescription) + } + } +} -- GitLab From 6b5720f017b91d930a074989e97440c85ea83a3a Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 12 Sep 2022 01:20:25 +0200 Subject: [PATCH 10/12] Present ConfirmRequest from Contact --- Examples/xx-messenger/Package.swift | 2 + .../AppFeature/AppEnvironment+Live.swift | 9 +++++ .../ContactFeature/ContactFeature.swift | 32 ++++++++++++++- .../Sources/ContactFeature/ContactView.swift | 18 +++++++++ .../ContactFeatureTests.swift | 39 +++++++++++++++++++ 5 files changed, 99 insertions(+), 1 deletion(-) diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift index 716b1482..5e9a48c3 100644 --- a/Examples/xx-messenger/Package.swift +++ b/Examples/xx-messenger/Package.swift @@ -77,6 +77,7 @@ let package = Package( dependencies: [ .target(name: "AppCore"), .target(name: "CheckContactAuthFeature"), + .target(name: "ConfirmRequestFeature"), .target(name: "ContactFeature"), .target(name: "ContactsFeature"), .target(name: "HomeFeature"), @@ -137,6 +138,7 @@ let package = Package( dependencies: [ .target(name: "AppCore"), .target(name: "CheckContactAuthFeature"), + .target(name: "ConfirmRequestFeature"), .target(name: "SendRequestFeature"), .target(name: "VerifyContactFeature"), .product(name: "ComposableArchitecture", package: "swift-composable-architecture"), diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index 4cbcef24..7adbcae8 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -1,5 +1,6 @@ import AppCore import CheckContactAuthFeature +import ConfirmRequestFeature import ContactFeature import ContactsFeature import Foundation @@ -48,6 +49,14 @@ extension AppEnvironment { bgQueue: bgQueue ) }, + confirmRequest: { + ConfirmRequestEnvironment( + messenger: messenger, + db: dbManager.getDB, + mainQueue: mainQueue, + bgQueue: bgQueue + ) + }, checkAuth: { CheckContactAuthEnvironment( messenger: messenger, diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift index 8bee1b75..545cb8fb 100644 --- a/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift +++ b/Examples/xx-messenger/Sources/ContactFeature/ContactFeature.swift @@ -2,6 +2,7 @@ import AppCore import CheckContactAuthFeature import ComposableArchitecture import ComposablePresentation +import ConfirmRequestFeature import Foundation import SendRequestFeature import VerifyContactFeature @@ -20,6 +21,7 @@ public struct ContactState: Equatable { importPhone: Bool = true, sendRequest: SendRequestState? = nil, verifyContact: VerifyContactState? = nil, + confirmRequest: ConfirmRequestState? = nil, checkAuth: CheckContactAuthState? = nil ) { self.id = id @@ -30,6 +32,7 @@ public struct ContactState: Equatable { self.importPhone = importPhone self.sendRequest = sendRequest self.verifyContact = verifyContact + self.confirmRequest = confirmRequest self.checkAuth = checkAuth } @@ -41,6 +44,7 @@ public struct ContactState: Equatable { @BindableState public var importPhone: Bool public var sendRequest: SendRequestState? public var verifyContact: VerifyContactState? + public var confirmRequest: ConfirmRequestState? public var checkAuth: CheckContactAuthState? } @@ -57,6 +61,9 @@ public enum ContactAction: Equatable, BindableAction { case checkAuthTapped case checkAuthDismissed case checkAuth(CheckContactAuthAction) + case confirmRequestTapped + case confirmRequestDismissed + case confirmRequest(ConfirmRequestAction) case binding(BindingAction<ContactState>) } @@ -68,6 +75,7 @@ public struct ContactEnvironment { bgQueue: AnySchedulerOf<DispatchQueue>, sendRequest: @escaping () -> SendRequestEnvironment, verifyContact: @escaping () -> VerifyContactEnvironment, + confirmRequest: @escaping () -> ConfirmRequestEnvironment, checkAuth: @escaping () -> CheckContactAuthEnvironment ) { self.messenger = messenger @@ -76,6 +84,7 @@ public struct ContactEnvironment { self.bgQueue = bgQueue self.sendRequest = sendRequest self.verifyContact = verifyContact + self.confirmRequest = confirmRequest self.checkAuth = checkAuth } @@ -85,6 +94,7 @@ public struct ContactEnvironment { public var bgQueue: AnySchedulerOf<DispatchQueue> public var sendRequest: () -> SendRequestEnvironment public var verifyContact: () -> VerifyContactEnvironment + public var confirmRequest: () -> ConfirmRequestEnvironment public var checkAuth: () -> CheckContactAuthEnvironment } @@ -97,6 +107,7 @@ extension ContactEnvironment { bgQueue: .unimplemented, sendRequest: { .unimplemented }, verifyContact: { .unimplemented }, + confirmRequest: { .unimplemented }, checkAuth: { .unimplemented } ) } @@ -181,7 +192,19 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm state.checkAuth = nil return .none - case .binding(_), .sendRequest(_), .verifyContact(_), .checkAuth(_): + case .confirmRequestTapped: + if let marshaled = state.dbContact?.marshaled { + state.confirmRequest = ConfirmRequestState( + contact: .live(marshaled) + ) + } + return .none + + case .confirmRequestDismissed: + state.confirmRequest = nil + return .none + + case .binding(_), .sendRequest(_), .verifyContact(_), .confirmRequest(_), .checkAuth(_): return .none } } @@ -200,6 +223,13 @@ public let contactReducer = Reducer<ContactState, ContactAction, ContactEnvironm action: /ContactAction.verifyContact, environment: { $0.verifyContact() } ) +.presenting( + confirmRequestReducer, + state: .keyPath(\.confirmRequest), + id: .notNil(), + action: /ContactAction.confirmRequest, + environment: { $0.confirmRequest() } +) .presenting( checkContactAuthReducer, state: .keyPath(\.checkAuth), diff --git a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift index 07942841..08ae7eb8 100644 --- a/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift +++ b/Examples/xx-messenger/Sources/ContactFeature/ContactView.swift @@ -2,6 +2,7 @@ import AppCore import CheckContactAuthFeature import ComposableArchitecture import ComposablePresentation +import ConfirmRequestFeature import SendRequestFeature import SwiftUI import VerifyContactFeature @@ -125,6 +126,15 @@ public struct ContactView: View { Image(systemName: "chevron.forward") } } + Button { + viewStore.send(.confirmRequestTapped) + } label: { + HStack { + Text("Confirm request") + Spacer() + Image(systemName: "chevron.forward") + } + } Button { viewStore.send(.checkAuthTapped) } label: { @@ -159,6 +169,14 @@ public struct ContactView: View { onDeactivate: { viewStore.send(.verifyContactDismissed) }, destination: VerifyContactView.init(store:) )) + .background(NavigationLinkWithStore( + store.scope( + state: \.confirmRequest, + action: ContactAction.confirmRequest + ), + onDeactivate: { viewStore.send(.confirmRequestDismissed) }, + destination: ConfirmRequestView.init(store:) + )) .background(NavigationLinkWithStore( store.scope( state: \.checkAuth, diff --git a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift index 250d802c..afc146b1 100644 --- a/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ContactFeatureTests/ContactFeatureTests.swift @@ -1,6 +1,7 @@ import CheckContactAuthFeature import Combine import ComposableArchitecture +import ConfirmRequestFeature import CustomDump import SendRequestFeature import VerifyContactFeature @@ -241,4 +242,42 @@ final class ContactFeatureTests: XCTestCase { $0.checkAuth = nil } } + + func testConfirmRequestTapped() { + let contactData = "contact-data".data(using: .utf8)! + let store = TestStore( + initialState: ContactState( + id: Data(), + dbContact: XXModels.Contact( + id: Data(), + marshaled: contactData + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.confirmRequestTapped) { + $0.confirmRequest = ConfirmRequestState( + contact: .unimplemented(contactData) + ) + } + } + + func testConfirmRequestDismissed() { + let store = TestStore( + initialState: ContactState( + id: "contact-id".data(using: .utf8)!, + confirmRequest: ConfirmRequestState( + contact: .unimplemented("contact-data".data(using: .utf8)!) + ) + ), + reducer: contactReducer, + environment: .unimplemented + ) + + store.send(.confirmRequestDismissed) { + $0.confirmRequest = nil + } + } } -- GitLab From 64847678745b17b433ae399172cb15dd15d0ff14 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 12 Sep 2022 12:58:16 +0200 Subject: [PATCH 11/12] Update auth status when verifying contact --- .../VerifyContactFeature.swift | 14 ++++-- .../VerifyContactFeatureTests.swift | 47 +++++++++++++++++-- 2 files changed, 51 insertions(+), 10 deletions(-) diff --git a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift index 8a69a072..1663d155 100644 --- a/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift +++ b/Examples/xx-messenger/Sources/VerifyContactFeature/VerifyContactFeature.swift @@ -69,15 +69,19 @@ public let verifyContactReducer = Reducer<VerifyContactState, VerifyContactActio state.isVerifying = true state.result = nil return Effect.result { [state] in - do { - let result = try env.messenger.verifyContact(state.contact) - let contactId = try state.contact.getId() + func updateStatus(_ status: XXModels.Contact.AuthStatus) throws { try env.db().bulkUpdateContacts.callAsFunction( - .init(id: [contactId]), - .init(authStatus: result ? .verified : .verificationFailed) + .init(id: [try state.contact.getId()]), + .init(authStatus: status) ) + } + do { + try updateStatus(.verificationInProgress) + let result = try env.messenger.verifyContact(state.contact) + try updateStatus(result ? .verified : .verificationFailed) return .success(.didVerify(.success(result))) } catch { + try? updateStatus(.verificationFailed) return .success(.didVerify(.failure(error.localizedDescription))) } } diff --git a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift index 6ca82b1e..97f8b428 100644 --- a/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift +++ b/Examples/xx-messenger/Tests/VerifyContactFeatureTests/VerifyContactFeatureTests.swift @@ -45,8 +45,14 @@ final class VerifyContactFeatureTests: XCTestCase { } XCTAssertNoDifference(didVerifyContact, [contact]) - XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])]) - XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .verified)]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [ + .init(id: [contactId]), + .init(id: [contactId]), + ]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [ + .init(authStatus: .verificationInProgress), + .init(authStatus: .verified) + ]) store.receive(.didVerify(.success(true))) { $0.isVerifying = false @@ -93,8 +99,14 @@ final class VerifyContactFeatureTests: XCTestCase { } XCTAssertNoDifference(didVerifyContact, [contact]) - XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])]) - XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .verificationFailed)]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [ + .init(id: [contactId]), + .init(id: [contactId]), + ]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [ + .init(authStatus: .verificationInProgress), + .init(authStatus: .verificationFailed), + ]) store.receive(.didVerify(.success(false))) { $0.isVerifying = false @@ -103,9 +115,13 @@ final class VerifyContactFeatureTests: XCTestCase { } func testVerifyFailure() { + var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) + let contactId = "contact-id".data(using: .utf8)! + contact.getIdFromContact.run = { _ in contactId } + let store = TestStore( initialState: VerifyContactState( - contact: .unimplemented("contact-data".data(using: .utf8)!) + contact: contact ), reducer: verifyContactReducer, environment: .unimplemented @@ -114,15 +130,36 @@ final class VerifyContactFeatureTests: XCTestCase { struct Failure: Error {} let error = Failure() + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.verifyContact.run = { _ in throw error } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } store.send(.verifyTapped) { $0.isVerifying = true $0.result = nil } + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [ + .init(id: [contactId]), + .init(id: [contactId]), + ]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [ + .init(authStatus: .verificationInProgress), + .init(authStatus: .verificationFailed), + ]) + store.receive(.didVerify(.failure(error.localizedDescription))) { $0.isVerifying = false $0.result = .failure(error.localizedDescription) -- GitLab From 145805a662581c519f93ef27a730e31d5f796fe9 Mon Sep 17 00:00:00 2001 From: Dariusz Rybicki <dariusz@elixxir.io> Date: Mon, 12 Sep 2022 13:01:57 +0200 Subject: [PATCH 12/12] Update auth status when confirming request --- .../ConfirmRequestFeature.swift | 14 ++++++--- .../ConfirmRequestFeatureTests.swift | 31 +++++++++++++++++-- 2 files changed, 38 insertions(+), 7 deletions(-) diff --git a/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift index b18f9da9..7cc40da0 100644 --- a/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift +++ b/Examples/xx-messenger/Sources/ConfirmRequestFeature/ConfirmRequestFeature.swift @@ -69,16 +69,20 @@ public let confirmRequestReducer = Reducer<ConfirmRequestState, ConfirmRequestAc state.isConfirming = true state.result = nil return Effect.result { [state] in + func updateStatus(_ status: XXModels.Contact.AuthStatus) throws { + try env.db().bulkUpdateContacts.callAsFunction( + .init(id: [try state.contact.getId()]), + .init(authStatus: status) + ) + } do { + try updateStatus(.confirming) let e2e = try env.messenger.e2e.tryGet() _ = try e2e.confirmReceivedRequest(partner: state.contact) - let contactId = try state.contact.getId() - try env.db().bulkUpdateContacts.callAsFunction( - .init(id: [contactId]), - .init(authStatus: .friend) - ) + try updateStatus(.friend) return .success(.didConfirm(.success)) } catch { + try? updateStatus(.confirmationFailed) return .success(.didConfirm(.failure(error.localizedDescription))) } } diff --git a/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift index 2686d645..8dea06e1 100644 --- a/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ConfirmRequestFeatureTests/ConfirmRequestFeatureTests.swift @@ -49,8 +49,14 @@ final class ConfirmRequestFeatureTests: XCTestCase { } XCTAssertNoDifference(didConfirmRequestFromContact, [contact]) - XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [.init(id: [contactId])]) - XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [.init(authStatus: .friend)]) + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [ + .init(id: [contactId]), + .init(id: [contactId]), + ]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [ + .init(authStatus: .confirming), + .init(authStatus: .friend), + ]) store.receive(.didConfirm(.success)) { $0.isConfirming = false @@ -74,6 +80,9 @@ final class ConfirmRequestFeatureTests: XCTestCase { struct Failure: Error {} let error = Failure() + var didBulkUpdateContactsWithQuery: [XXModels.Contact.Query] = [] + var didBulkUpdateContactsWithAssignments: [XXModels.Contact.Assignments] = [] + store.environment.mainQueue = .immediate store.environment.bgQueue = .immediate store.environment.messenger.e2e.get = { @@ -81,12 +90,30 @@ final class ConfirmRequestFeatureTests: XCTestCase { e2e.confirmReceivedRequest.run = { _ in throw error } return e2e } + store.environment.db.run = { + var db: Database = .failing + db.bulkUpdateContacts.run = { query, assignments in + didBulkUpdateContactsWithQuery.append(query) + didBulkUpdateContactsWithAssignments.append(assignments) + return 0 + } + return db + } store.send(.confirmTapped) { $0.isConfirming = true $0.result = nil } + XCTAssertNoDifference(didBulkUpdateContactsWithQuery, [ + .init(id: [contactId]), + .init(id: [contactId]), + ]) + XCTAssertNoDifference(didBulkUpdateContactsWithAssignments, [ + .init(authStatus: .confirming), + .init(authStatus: .confirmationFailed), + ]) + store.receive(.didConfirm(.failure(error.localizedDescription))) { $0.isConfirming = false $0.result = .failure(error.localizedDescription) -- GitLab