Skip to content
Snippets Groups Projects
RegisterFeature.swift 3.18 KiB
import AppCore
import ComposableArchitecture
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 = "",
    isRegistering: Bool = false,
    failure: String? = nil
  ) {
    self.focusedField = focusedField
    self.username = username
    self.isRegistering = isRegistering
    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>
}

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

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()