From 976cf766afe60447f72706e4f721ed7a17599623 Mon Sep 17 00:00:00 2001
From: Dariusz Rybicki <dariusz@elixxir.io>
Date: Wed, 7 Sep 2022 00:47:08 +0200
Subject: [PATCH] Fetch db contact and update UI

---
 Examples/xx-messenger/Package.swift           |  2 +
 .../AppFeature/AppEnvironment+Live.swift      |  6 +-
 .../UserSearchResultFeature.swift             | 45 +++++++++-
 .../UserSearchResultView.swift                | 88 +++++++++++++++++++
 .../UserSearchResultFeatureTests.swift        | 43 +++++++++
 5 files changed, 180 insertions(+), 4 deletions(-)

diff --git a/Examples/xx-messenger/Package.swift b/Examples/xx-messenger/Package.swift
index 565dccc6..8070952e 100644
--- a/Examples/xx-messenger/Package.swift
+++ b/Examples/xx-messenger/Package.swift
@@ -140,10 +140,12 @@ let package = Package(
     .target(
       name: "UserSearchFeature",
       dependencies: [
+        .target(name: "AppCore"),
         .product(name: "ComposableArchitecture", package: "swift-composable-architecture"),
         .product(name: "ComposablePresentation", package: "swift-composable-presentation"),
         .product(name: "XXClient", package: "elixxir-dapps-sdk-swift"),
         .product(name: "XXMessengerClient", package: "elixxir-dapps-sdk-swift"),
+        .product(name: "XXModels", package: "client-ios-db"),
       ],
       swiftSettings: swiftSettings
     ),
diff --git a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
index 00415b43..d8761a65 100644
--- a/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
+++ b/Examples/xx-messenger/Sources/AppFeature/AppEnvironment+Live.swift
@@ -52,7 +52,11 @@ extension AppEnvironment {
               mainQueue: mainQueue,
               bgQueue: bgQueue,
               result: {
-                UserSearchResultEnvironment()
+                UserSearchResultEnvironment(
+                  db: dbManager.getDB,
+                  mainQueue: mainQueue,
+                  bgQueue: bgQueue
+                )
               }
             )
           }
diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultFeature.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultFeature.swift
index f4c0f856..f13c4b10 100644
--- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultFeature.swift
+++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultFeature.swift
@@ -1,18 +1,22 @@
+import AppCore
 import ComposableArchitecture
 import Foundation
 import XCTestDynamicOverlay
 import XXClient
+import XXModels
 
 public struct UserSearchResultState: Equatable, Identifiable {
   public init(
     id: Data,
-    xxContact: Contact,
+    xxContact: XXClient.Contact,
+    dbContact: XXModels.Contact? = nil,
     username: String? = nil,
     email: String? = nil,
     phone: String? = nil
   ) {
     self.id = id
     self.xxContact = xxContact
+    self.dbContact = dbContact
     self.username = username
     self.email = email
     self.phone = phone
@@ -20,6 +24,7 @@ public struct UserSearchResultState: Equatable, Identifiable {
 
   public var id: Data
   public var xxContact: XXClient.Contact
+  public var dbContact: XXModels.Contact?
   public var username: String?
   public var email: String?
   public var phone: String?
@@ -27,26 +32,60 @@ public struct UserSearchResultState: Equatable, Identifiable {
 
 public enum UserSearchResultAction: Equatable {
   case start
+  case didUpdateContact(XXModels.Contact?)
+  case sendRequestButtonTapped
 }
 
 public struct UserSearchResultEnvironment {
-  public init() {}
+  public init(
+    db: DBManagerGetDB,
+    mainQueue: AnySchedulerOf<DispatchQueue>,
+    bgQueue: AnySchedulerOf<DispatchQueue>
+  ) {
+    self.db = db
+    self.mainQueue = mainQueue
+    self.bgQueue = bgQueue
+  }
+
+  public var db: DBManagerGetDB
+  public var mainQueue: AnySchedulerOf<DispatchQueue>
+  public var bgQueue: AnySchedulerOf<DispatchQueue>
 }
 
 #if DEBUG
 extension UserSearchResultEnvironment {
-  public static let unimplemented = UserSearchResultEnvironment()
+  public static let unimplemented = UserSearchResultEnvironment(
+    db: .unimplemented,
+    mainQueue: .unimplemented,
+    bgQueue: .unimplemented
+  )
 }
 #endif
 
 public let userSearchResultReducer = Reducer<UserSearchResultState, UserSearchResultAction, UserSearchResultEnvironment>
 { state, action, env in
+  enum DBFetchEffectID {}
+
   switch action {
   case .start:
     let facts = (try? state.xxContact.getFacts()) ?? []
     state.username = facts.first(where: { $0.type == 0 })?.fact
     state.email = facts.first(where: { $0.type == 1 })?.fact
     state.phone = facts.first(where: { $0.type == 2 })?.fact
+    return try! env.db().fetchContactsPublisher(.init(id: [state.id]))
+      .assertNoFailure()
+      .map(\.first)
+      .map(UserSearchResultAction.didUpdateContact)
+      .subscribe(on: env.bgQueue)
+      .receive(on: env.mainQueue)
+      .eraseToEffect()
+      .cancellable(id: DBFetchEffectID.self, cancelInFlight: true)
+
+  case .didUpdateContact(let contact):
+    state.dbContact = contact
+    return .none
+
+  case .sendRequestButtonTapped:
     return .none
   }
 }
diff --git a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultView.swift b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultView.swift
index 129081fe..4eb4ca8b 100644
--- a/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultView.swift
+++ b/Examples/xx-messenger/Sources/UserSearchFeature/UserSearchResultView.swift
@@ -1,5 +1,6 @@
 import ComposableArchitecture
 import SwiftUI
+import XXModels
 
 public struct UserSearchResultView: View {
   public init(store: Store<UserSearchResultState, UserSearchResultAction>) {
@@ -12,11 +13,13 @@ public struct UserSearchResultView: View {
     var username: String?
     var email: String?
     var phone: String?
+    var dbContactAuth: XXModels.Contact.AuthStatus?
 
     init(state: UserSearchResultState) {
       username = state.username
       email = state.email
       phone = state.phone
+      dbContactAuth = state.dbContact?.authStatus
     }
 
     var isEmpty: Bool {
@@ -41,6 +44,91 @@ public struct UserSearchResultView: View {
             Text(phone)
           }
         }
+        switch viewStore.dbContactAuth {
+        case .none, .stranger:
+          Button {
+            viewStore.send(.sendRequestButtonTapped)
+          } label: {
+            HStack {
+              Text("Send request")
+              Spacer()
+              Image(systemName: "person.badge.plus")
+            }
+          }
+
+        case .requesting:
+          HStack {
+            Text("Sending auth request")
+            Spacer()
+            ProgressView()
+          }
+
+        case .requested:
+          HStack {
+            Text("Request sent")
+            Spacer()
+            Image(systemName: "paperplane")
+          }
+
+        case .requestFailed:
+          HStack {
+            Text("Sending request failed")
+            Spacer()
+            Image(systemName: "xmark.diamond.fill")
+              .foregroundColor(.red)
+          }
+
+        case .verificationInProgress:
+          HStack {
+            Text("Verification is progress")
+            Spacer()
+            ProgressView()
+          }
+
+        case .verified:
+          HStack {
+            Text("Verified")
+            Spacer()
+            Image(systemName: "person.fill.checkmark")
+          }
+
+        case .verificationFailed:
+          HStack {
+            Text("Verification failed")
+            Spacer()
+            Image(systemName: "xmark.diamond.fill")
+              .foregroundColor(.red)
+          }
+
+        case .confirming:
+          HStack {
+            Text("Confirming auth request")
+            Spacer()
+            ProgressView()
+          }
+
+        case .confirmationFailed:
+          HStack {
+            Text("Confirmation failed")
+            Spacer()
+            Image(systemName: "xmark.diamond.fill")
+              .foregroundColor(.red)
+          }
+
+        case .friend:
+          HStack {
+            Text("Friend")
+            Spacer()
+            Image(systemName: "person.fill.checkmark")
+          }
+
+        case .hidden:
+          HStack {
+            Text("Hidden")
+            Spacer()
+            Image(systemName: "eye.slash")
+          }
+        }
       }
       .task { viewStore.send(.start) }
     }
diff --git a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift
index c9efe0c7..e776f803 100644
--- a/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift
+++ b/Examples/xx-messenger/Tests/UserSearchFeatureTests/UserSearchResultFeatureTests.swift
@@ -1,6 +1,9 @@
+import Combine
 import ComposableArchitecture
 import XCTest
+import XCTestDynamicOverlay
 import XXClient
+import XXModels
 @testable import UserSearchFeature
 
 final class UserSearchResultFeatureTests: XCTestCase {
@@ -23,10 +26,50 @@ final class UserSearchResultFeatureTests: XCTestCase {
       environment: .unimplemented
     )
 
+    var dbDidFetchContacts: [XXModels.Contact.Query] = []
+    let dbContactsPublisher = PassthroughSubject<[XXModels.Contact], Error>()
+
+    store.environment.mainQueue = .immediate
+    store.environment.bgQueue = .immediate
+    store.environment.db.run = {
+      var db: Database = .failing
+      db.fetchContactsPublisher.run = { query in
+        dbDidFetchContacts.append(query)
+        return dbContactsPublisher.eraseToAnyPublisher()
+      }
+      return db
+    }
+
     store.send(.start) {
       $0.username = "contact-username"
       $0.email = "contact-email"
       $0.phone = "contact-phone"
     }
+
+    XCTAssertNoDifference(dbDidFetchContacts, [
+      .init(id: ["contact-id".data(using: .utf8)!])
+    ])
+
+    let dbContact = XXModels.Contact(id: "contact-id".data(using: .utf8)!)
+    dbContactsPublisher.send([dbContact])
+
+    store.receive(.didUpdateContact(dbContact)) {
+      $0.dbContact = dbContact
+    }
+
+    dbContactsPublisher.send(completion: .finished)
+  }
+
+  func testSendRequest() {
+    let store = TestStore(
+      initialState: UserSearchResultState(
+        id: "contact-id".data(using: .utf8)!,
+        xxContact: .unimplemented("contact-data".data(using: .utf8)!)
+      ),
+      reducer: userSearchResultReducer,
+      environment: .unimplemented
+    )
+
+    store.send(.sendRequestButtonTapped)
   }
 }
-- 
GitLab