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 0000000000000000000000000000000000000000..17099adba157e5f3e5b943744dbe3cdf5b17706d --- /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 3548cd17ae609a6d59d2722f9bf91556499a09c4..01878fbd634c1e36d0be0f56b53331ba7b8b0d71 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 71af5875f2d25543ac07848467cbd1417bde92cc..85d232415d82af42337d1fceea8eadb26369b000 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 0000000000000000000000000000000000000000..119ff27a2f2a73a772c0e9cf1c97cf7d983e285a --- /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 0000000000000000000000000000000000000000..dd2e7894e256c67f4a1ddcf421877dcbfbac63cc --- /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 0000000000000000000000000000000000000000..1c36e33e9d43509b986c0bce4d9bd4304ef9cd6e --- /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) + } + } +}