diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift index 93a844439a8eb591ee0b8fa6c2361a56402b3d57..611123e0334c6078250bf0e782153975d6891799 100644 --- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift +++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift @@ -70,6 +70,11 @@ extension AppEnvironment { ChatEnvironment( messenger: messenger, db: dbManager.getDB, + sendMessage: .live( + messenger: messenger, + db: dbManager.getDB, + now: Date.init + ), mainQueue: mainQueue, bgQueue: bgQueue ) diff --git a/Examples/xx-messenger/Sources/ChatFeature/ChatFeature.swift b/Examples/xx-messenger/Sources/ChatFeature/ChatFeature.swift index af1554f011b5ea4a94fd7fb975913b046e35fce9..26757d71d5a83318261d52f89ebfad6a2ad7fc9f 100644 --- a/Examples/xx-messenger/Sources/ChatFeature/ChatFeature.swift +++ b/Examples/xx-messenger/Sources/ChatFeature/ChatFeature.swift @@ -1,4 +1,5 @@ import AppCore +import Combine import ComposableArchitecture import Foundation import XCTestDynamicOverlay @@ -34,40 +35,48 @@ public struct ChatState: Equatable, Identifiable { id: ID, myContactId: Data? = nil, messages: IdentifiedArrayOf<Message> = [], - failure: String? = nil + failure: String? = nil, + text: String = "" ) { self.id = id self.myContactId = myContactId self.messages = messages self.failure = failure + self.text = text } public var id: ID public var myContactId: Data? public var messages: IdentifiedArrayOf<Message> public var failure: String? + @BindableState public var text: String } -public enum ChatAction: Equatable { +public enum ChatAction: Equatable, BindableAction { case start case didFetchMessages(IdentifiedArrayOf<ChatState.Message>) + case sendTapped + case binding(BindingAction<ChatState>) } public struct ChatEnvironment { public init( messenger: Messenger, db: DBManagerGetDB, + sendMessage: SendMessage, mainQueue: AnySchedulerOf<DispatchQueue>, bgQueue: AnySchedulerOf<DispatchQueue> ) { self.messenger = messenger self.db = db + self.sendMessage = sendMessage self.mainQueue = mainQueue self.bgQueue = bgQueue } public var messenger: Messenger public var db: DBManagerGetDB + public var sendMessage: SendMessage public var mainQueue: AnySchedulerOf<DispatchQueue> public var bgQueue: AnySchedulerOf<DispatchQueue> } @@ -77,6 +86,7 @@ extension ChatEnvironment { public static let unimplemented = ChatEnvironment( messenger: .unimplemented, db: .unimplemented, + sendMessage: .unimplemented, mainQueue: .unimplemented, bgQueue: .unimplemented ) @@ -126,5 +136,32 @@ public let chatReducer = Reducer<ChatState, ChatAction, ChatEnvironment> case .didFetchMessages(let messages): state.messages = messages return .none + + case .sendTapped: + let text = state.text + let chatId = state.id + state.text = "" + return Effect.run { subscriber in + switch chatId { + case .contact(let recipientId): + env.sendMessage( + text: text, + to: recipientId, + onError: { error in + // TODO: handle error + print("^^^ ERROR: \(error)") + } + ) + } + subscriber.send(completion: .finished) + return AnyCancellable {} + } + .subscribe(on: env.bgQueue) + .receive(on: env.mainQueue) + .eraseToEffect() + + case .binding(_): + return .none } } +.binding() diff --git a/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift b/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift index 74e08c7b6371f86fafbd570020214dedb9e93df7..5905931f698e899efc327805d8946c638ec2f926 100644 --- a/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift +++ b/Examples/xx-messenger/Sources/ChatFeature/ChatView.swift @@ -13,11 +13,13 @@ public struct ChatView: View { var myContactId: Data? var messages: IdentifiedArrayOf<ChatState.Message> var failure: String? + var text: String init(state: ChatState) { myContactId = state.myContactId messages = state.messages failure = state.failure + text = state.text } } @@ -52,11 +54,14 @@ public struct ChatView: View { VStack(spacing: 0) { Divider() HStack { - TextField("Text", text: .constant("")) - .textFieldStyle(.roundedBorder) + TextField("Text", text: viewStore.binding( + get: \.text, + send: { ChatAction.set(\.$text, $0) } + )) + .textFieldStyle(.roundedBorder) Button { - + viewStore.send(.sendTapped) } label: { Image(systemName: "paperplane.fill") } diff --git a/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift b/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift index 3e812db207e3b3d77bae5464280fd4473b5e7dce..89f0658df3b038b2f586f5e34f3477931f7cbfa3 100644 --- a/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift +++ b/Examples/xx-messenger/Tests/ChatFeatureTests/ChatFeatureTests.swift @@ -130,4 +130,36 @@ final class ChatFeatureTests: XCTestCase { $0.failure = error.localizedDescription } } + + func testSend() { + struct SendMessageParams: Equatable { + var text: String + var recipientId: Data + } + var didSendMessageWithParams: [SendMessageParams] = [] + + let store = TestStore( + initialState: ChatState(id: .contact("contact-id".data(using: .utf8)!)), + reducer: chatReducer, + environment: .unimplemented + ) + + store.environment.mainQueue = .immediate + store.environment.bgQueue = .immediate + store.environment.sendMessage.run = { text, recipientId, _ in + didSendMessageWithParams.append(.init(text: text, recipientId: recipientId)) + } + + store.send(.set(\.$text, "Hello")) { + $0.text = "Hello" + } + + store.send(.sendTapped) { + $0.text = "" + } + + XCTAssertNoDifference(didSendMessageWithParams, [ + .init(text: "Hello", recipientId: "contact-id".data(using: .utf8)!) + ]) + } }