Skip to content
Snippets Groups Projects
RegisterFeature.swift 3.18 KiB
Newer Older
import AppCore
import ComposableArchitecture
Dariusz Rybicki's avatar
Dariusz Rybicki committed
import Foundation
import XCTestDynamicOverlay
import XXClient
import XXMessengerClient
import XXModels

public struct RegisterState: Equatable {
  public enum Error: Swift.Error, Equatable {
    case usernameMismatch(registering: String, registered: String?)
  }

  public enum Field: String, Hashable {
    case username
  }

  public init(
    focusedField: Field? = nil,
    username: String = "",
Dariusz Rybicki's avatar
Dariusz Rybicki committed
    isRegistering: Bool = false,
    failure: String? = nil
  ) {
    self.focusedField = focusedField
    self.username = username
    self.isRegistering = isRegistering
Dariusz Rybicki's avatar
Dariusz Rybicki committed
    self.failure = failure
  }

  @BindableState public var focusedField: Field?
  @BindableState public var username: String
  public var isRegistering: Bool
  public var failure: String?
}

public enum RegisterAction: Equatable, BindableAction {
  case registerTapped
  case failed(String)
  case finished
  case binding(BindingAction<RegisterState>)
}

public struct RegisterEnvironment {
  public init(
    messenger: Messenger,
    db: DBManagerGetDB,
    now: @escaping () -> Date,
    mainQueue: AnySchedulerOf<DispatchQueue>,
    bgQueue: AnySchedulerOf<DispatchQueue>
  ) {
    self.messenger = messenger
    self.db = db
    self.now = now
    self.mainQueue = mainQueue
    self.bgQueue = bgQueue
  }

  public var messenger: Messenger
  public var db: DBManagerGetDB
  public var now: () -> Date
  public var mainQueue: AnySchedulerOf<DispatchQueue>
  public var bgQueue: AnySchedulerOf<DispatchQueue>
}

extension RegisterEnvironment {
  public static let unimplemented = RegisterEnvironment(
    messenger: .unimplemented,
    db: .unimplemented,
    now: XCTUnimplemented("\(Self.self).now"),
    mainQueue: .unimplemented,
    bgQueue: .unimplemented
  )
}

public let registerReducer = Reducer<RegisterState, RegisterAction, RegisterEnvironment>
{ state, action, env in
  switch action {
  case .binding(_):
    return .none

  case .registerTapped:
    state.focusedField = nil
    state.isRegistering = true
    state.failure = nil
    return .future { [username = state.username] fulfill in
      do {
        let db = try env.db()
        try env.messenger.register(username: username)
        let contact = try env.messenger.myContact()
        let facts = try contact.getFacts()
        try db.saveContact(Contact(
          id: try contact.getId(),
          marshaled: contact.data,
          username: facts.get(.username)?.value,
          email: facts.get(.email)?.value,
          phone: facts.get(.phone)?.value,
          createdAt: env.now()
        ))
        guard facts.get(.username)?.value == username else {
          throw RegisterState.Error.usernameMismatch(
            registering: username,
            registered: facts.get(.username)?.value
          )
        }
        fulfill(.success(.finished))
      }
      catch {
        fulfill(.success(.failed(error.localizedDescription)))
      }
    }
    .subscribe(on: env.bgQueue)
    .receive(on: env.mainQueue)
    .eraseToEffect()

  case .failed(let failure):
    state.isRegistering = false
    state.failure = failure
    return .none

  case .finished:
    state.isRegistering = false
    return .none
  }
}
.binding()