From 2d70964223f9323858d11e72674fc66cf98e361f Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 14 Sep 2022 14:02:52 +0200
Subject: [PATCH] Implement message sending

---
 .../AppFeature/AppEnvironment+Live.swift      |  5 +++
 .../Sources/ChatFeature/ChatFeature.swift     | 41 ++++++++++++++++++-
 .../Sources/ChatFeature/ChatView.swift        | 11 +++--
 .../ChatFeatureTests/ChatFeatureTests.swift   | 32 +++++++++++++++
 4 files changed, 84 insertions(+), 5 deletions(-)

diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 93a84443..611123e0 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 af1554f0..26757d71 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 74e08c7b..5905931f 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 3e812db2..89f0658d 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)!)
+    ])
+  }
 }
-- 
GitLab