Skip to content
Snippets Groups Projects
SendRequestComponent.swift 4.06 KiB
Newer Older
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(\.app.messenger) var messenger: Messenger
  @Dependency(\.app.dbManager.getDB) var db: DBManagerGetDB
  @Dependency(\.app.mainQueue) var mainQueue: AnySchedulerOf<DispatchQueue>
  @Dependency(\.app.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
      }
    }
  }
}