diff --git a/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestComponent.swift b/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestComponent.swift new file mode 100644 index 0000000000000000000000000000000000000000..6bac71a26ca831b2a149cf9a43be3e35e4264f2e --- /dev/null +++ b/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestComponent.swift @@ -0,0 +1,138 @@ +import AppCore +import Combine +import ComposableArchitecture +import Foundation +import XCTestDynamicOverlay +import XXClient +import XXMessengerClient +import XXModels + +public struct SendRequestComponent: ReducerProtocol { + public struct State: Equatable { + public init( + contact: XXClient.Contact, + myContact: XXClient.Contact? = nil, + sendUsername: Bool = true, + sendEmail: Bool = true, + sendPhone: Bool = true, + isSending: Bool = false, + failure: String? = nil + ) { + self.contact = contact + self.myContact = myContact + self.sendUsername = sendUsername + self.sendEmail = sendEmail + self.sendPhone = sendPhone + self.isSending = isSending + self.failure = failure + } + + public var contact: XXClient.Contact + public var myContact: XXClient.Contact? + @BindableState public var sendUsername: Bool + @BindableState public var sendEmail: Bool + @BindableState public var sendPhone: Bool + public var isSending: Bool + public var failure: String? + } + + public enum Action: Equatable, BindableAction { + case start + case sendTapped + case sendSucceeded + case sendFailed(String) + case binding(BindingAction<State>) + case myContactFetched(XXClient.Contact) + case myContactFetchFailed(NSError) + } + + public init() {} + + @Dependency(\.appDependencies.messenger) var messenger: Messenger + @Dependency(\.appDependencies.dbManager.getDB) var db: DBManagerGetDB + @Dependency(\.appDependencies.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue> + @Dependency(\.appDependencies.bgQueue) var bgQueue: AnySchedulerOf<DispatchQueue> + + public var body: some ReducerProtocol<State, Action> { + BindingReducer() + Reduce { state, action in + switch action { + case .start: + return Effect.run { subscriber in + do { + let contact = try messenger.myContact() + subscriber.send(.myContactFetched(contact)) + } catch { + subscriber.send(.myContactFetchFailed(error as NSError)) + } + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .receive(on: mainQueue) + .subscribe(on: bgQueue) + .eraseToEffect() + + case .myContactFetched(let contact): + state.myContact = contact + state.failure = nil + return .none + + case .myContactFetchFailed(let failure): + state.myContact = nil + state.failure = failure.localizedDescription + return .none + + case .sendTapped: + state.isSending = true + state.failure = nil + return .result { [state] in + func updateAuthStatus(_ authStatus: XXModels.Contact.AuthStatus) throws { + try db().bulkUpdateContacts( + .init(id: [try state.contact.getId()]), + .init(authStatus: authStatus) + ) + } + do { + try updateAuthStatus(.requesting) + let myFacts = try state.myContact?.getFacts() ?? [] + var includedFacts: [Fact] = [] + if state.sendUsername, let fact = myFacts.get(.username) { + includedFacts.append(fact) + } + if state.sendEmail, let fact = myFacts.get(.email) { + includedFacts.append(fact) + } + if state.sendPhone, let fact = myFacts.get(.phone) { + includedFacts.append(fact) + } + _ = try messenger.e2e.tryGet().requestAuthenticatedChannel( + partner: state.contact, + myFacts: includedFacts + ) + try updateAuthStatus(.requested) + return .success(.sendSucceeded) + } catch { + try? updateAuthStatus(.requestFailed) + return .success(.sendFailed(error.localizedDescription)) + } + } + .subscribe(on: bgQueue) + .receive(on: mainQueue) + .eraseToEffect() + + case .sendSucceeded: + state.isSending = false + state.failure = nil + return .none + + case .sendFailed(let failure): + state.isSending = false + state.failure = failure + return .none + + case .binding(_): + return .none + } + } + } +} diff --git a/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestFeature.swift b/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestFeature.swift deleted file mode 100644 index 18075179aedc62daff46847052e2bc0071f76b0a..0000000000000000000000000000000000000000 --- a/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestFeature.swift +++ /dev/null @@ -1,158 +0,0 @@ -import AppCore -import Combine -import ComposableArchitecture -import Foundation -import XCTestDynamicOverlay -import XXClient -import XXMessengerClient -import XXModels - -public struct SendRequestState: Equatable { - public init( - contact: XXClient.Contact, - myContact: XXClient.Contact? = nil, - sendUsername: Bool = true, - sendEmail: Bool = true, - sendPhone: Bool = true, - isSending: Bool = false, - failure: String? = nil - ) { - self.contact = contact - self.myContact = myContact - self.sendUsername = sendUsername - self.sendEmail = sendEmail - self.sendPhone = sendPhone - self.isSending = isSending - self.failure = failure - } - - public var contact: XXClient.Contact - public var myContact: XXClient.Contact? - @BindableState public var sendUsername: Bool - @BindableState public var sendEmail: Bool - @BindableState public var sendPhone: Bool - public var isSending: Bool - public var failure: String? -} - -public enum SendRequestAction: Equatable, BindableAction { - case start - case sendTapped - case sendSucceeded - case sendFailed(String) - case binding(BindingAction<SendRequestState>) - case myContactFetched(XXClient.Contact) - case myContactFetchFailed(NSError) -} - -public struct SendRequestEnvironment { - 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 SendRequestEnvironment { - public static let unimplemented = SendRequestEnvironment( - messenger: .unimplemented, - db: .unimplemented, - mainQueue: .unimplemented, - bgQueue: .unimplemented - ) -} -#endif - -public let sendRequestReducer = Reducer<SendRequestState, SendRequestAction, SendRequestEnvironment> -{ state, action, env in - switch action { - case .start: - return Effect.run { subscriber in - do { - let contact = try env.messenger.myContact() - subscriber.send(.myContactFetched(contact)) - } catch { - subscriber.send(.myContactFetchFailed(error as NSError)) - } - subscriber.send(completion: .finished) - return AnyCancellable {} - } - .receive(on: env.mainQueue) - .subscribe(on: env.bgQueue) - .eraseToEffect() - - case .myContactFetched(let contact): - state.myContact = contact - state.failure = nil - return .none - - case .myContactFetchFailed(let failure): - state.myContact = nil - state.failure = failure.localizedDescription - return .none - - case .sendTapped: - state.isSending = true - state.failure = nil - return .result { [state] in - func updateAuthStatus(_ authStatus: XXModels.Contact.AuthStatus) throws { - try env.db().bulkUpdateContacts( - .init(id: [try state.contact.getId()]), - .init(authStatus: authStatus) - ) - } - do { - try updateAuthStatus(.requesting) - let myFacts = try state.myContact?.getFacts() ?? [] - var includedFacts: [Fact] = [] - if state.sendUsername, let fact = myFacts.get(.username) { - includedFacts.append(fact) - } - if state.sendEmail, let fact = myFacts.get(.email) { - includedFacts.append(fact) - } - if state.sendPhone, let fact = myFacts.get(.phone) { - includedFacts.append(fact) - } - _ = try env.messenger.e2e.tryGet().requestAuthenticatedChannel( - partner: state.contact, - myFacts: includedFacts - ) - try updateAuthStatus(.requested) - return .success(.sendSucceeded) - } catch { - try? updateAuthStatus(.requestFailed) - return .success(.sendFailed(error.localizedDescription)) - } - } - .subscribe(on: env.bgQueue) - .receive(on: env.mainQueue) - .eraseToEffect() - - case .sendSucceeded: - state.isSending = false - state.failure = nil - return .none - - case .sendFailed(let failure): - state.isSending = false - state.failure = failure - return .none - - case .binding(_): - return .none - } -} -.binding() diff --git a/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestView.swift b/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestView.swift index 809d45be72436067d4e04c35bdef1995f7ed5a9d..90294a80303f1746d659aa8e3789e1b09e696275 100644 --- a/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestView.swift +++ b/Examples/xx-messenger/Sources/SendRequestFeature/SendRequestView.swift @@ -4,11 +4,11 @@ import SwiftUI import XXClient public struct SendRequestView: View { - public init(store: Store<SendRequestState, SendRequestAction>) { + public init(store: StoreOf<SendRequestComponent>) { self.store = store } - let store: Store<SendRequestState, SendRequestAction> + let store: StoreOf<SendRequestComponent> struct ViewState: Equatable { var contactUsername: String? @@ -23,7 +23,7 @@ public struct SendRequestView: View { var isSending: Bool var failure: String? - init(state: SendRequestState) { + init(state: SendRequestComponent.State) { contactUsername = try? state.contact.getFact(.username)?.value contactEmail = try? state.contact.getFact(.email)?.value contactPhone = try? state.contact.getFact(.phone)?.value @@ -129,7 +129,7 @@ public struct SendRequestView_Previews: PreviewProvider { public static var previews: some View { NavigationView { SendRequestView(store: Store( - initialState: SendRequestState( + initialState: SendRequestComponent.State( contact: { var contact = XXClient.Contact.unimplemented("contact-data".data(using: .utf8)!) contact.getFactsFromContact.run = { _ in @@ -158,8 +158,7 @@ public struct SendRequestView_Previews: PreviewProvider { isSending: false, failure: "Something went wrong" ), - reducer: .empty, - environment: () + reducer: EmptyReducer() )) } } diff --git a/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift b/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestComponentTests.swift similarity index 76% rename from Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift rename to Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestComponentTests.swift index 4c68abab06a609346b47b4fe472d5f377417c2f5..0de0596628ec74f3d0c5263510d8e54c08a95ea4 100644 --- a/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestFeatureTests.swift +++ b/Examples/xx-messenger/Tests/SendRequestFeatureTests/SendRequestComponentTests.swift @@ -7,22 +7,21 @@ import XXMessengerClient import XXModels @testable import SendRequestFeature -final class SendRequestFeatureTests: XCTestCase { +final class SendRequestComponentTests: XCTestCase { func testStart() { let myContact = XXClient.Contact.unimplemented("my-contact-data".data(using: .utf8)!) var didGetMyContact: [MessengerMyContact.IncludeFacts?] = [] let store = TestStore( - initialState: SendRequestState( + initialState: SendRequestComponent.State( contact: .unimplemented("contact-data".data(using: .utf8)!) ), - reducer: sendRequestReducer, - environment: .unimplemented + reducer: SendRequestComponent() ) - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.messenger.myContact.run = { includeFacts in + store.dependencies.appDependencies.mainQueue = .immediate + store.dependencies.appDependencies.bgQueue = .immediate + store.dependencies.appDependencies.messenger.myContact.run = { includeFacts in didGetMyContact.append(includeFacts) return myContact } @@ -39,15 +38,14 @@ final class SendRequestFeatureTests: XCTestCase { let failure = Failure() let store = TestStore( - initialState: SendRequestState( + initialState: SendRequestComponent.State( contact: .unimplemented("contact-data".data(using: .utf8)!) ), - reducer: sendRequestReducer, - environment: .unimplemented + reducer: SendRequestComponent() ) - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.messenger.myContact.run = { _ in throw failure } + store.dependencies.appDependencies.mainQueue = .immediate + store.dependencies.appDependencies.bgQueue = .immediate + store.dependencies.appDependencies.messenger.myContact.run = { _ in throw failure } store.send(.start) @@ -70,12 +68,11 @@ final class SendRequestFeatureTests: XCTestCase { myContact.getFactsFromContact.run = { _ in myFacts } let store = TestStore( - initialState: SendRequestState( + initialState: SendRequestComponent.State( contact: contact, myContact: myContact ), - reducer: sendRequestReducer, - environment: .unimplemented + reducer: SendRequestComponent() ) struct DidBulkUpdateContacts: Equatable { @@ -90,9 +87,9 @@ final class SendRequestFeatureTests: XCTestCase { var didBulkUpdateContacts: [DidBulkUpdateContacts] = [] var didRequestAuthChannel: [DidRequestAuthChannel] = [] - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.db.run = { + store.dependencies.appDependencies.mainQueue = .immediate + store.dependencies.appDependencies.bgQueue = .immediate + store.dependencies.appDependencies.dbManager.getDB.run = { var db: Database = .unimplemented db.bulkUpdateContacts.run = { query, assignments in didBulkUpdateContacts.append(.init(query: query, assignments: assignments)) @@ -100,7 +97,7 @@ final class SendRequestFeatureTests: XCTestCase { } return db } - store.environment.messenger.e2e.get = { + store.dependencies.appDependencies.messenger.e2e.get = { var e2e: E2E = .unimplemented e2e.requestAuthenticatedChannel.run = { partner, myFacts in didRequestAuthChannel.append(.init(partner: partner, myFacts: myFacts)) @@ -149,25 +146,24 @@ final class SendRequestFeatureTests: XCTestCase { myContact.getFactsFromContact.run = { _ in myFacts } let store = TestStore( - initialState: SendRequestState( + initialState: SendRequestComponent.State( contact: contact, myContact: myContact ), - reducer: sendRequestReducer, - environment: .unimplemented + reducer: SendRequestComponent() ) struct Failure: Error {} let failure = Failure() - store.environment.mainQueue = .immediate - store.environment.bgQueue = .immediate - store.environment.db.run = { + store.dependencies.appDependencies.mainQueue = .immediate + store.dependencies.appDependencies.bgQueue = .immediate + store.dependencies.appDependencies.dbManager.getDB.run = { var db: Database = .unimplemented db.bulkUpdateContacts.run = { _, _ in return 0 } return db } - store.environment.messenger.e2e.get = { + store.dependencies.appDependencies.messenger.e2e.get = { var e2e: E2E = .unimplemented e2e.requestAuthenticatedChannel.run = { _, _ in throw failure } return e2e