Not a member of Pastebin yet?
Sign Up,
it unlocks many cool features!
- enum SignInNavigationStep {
- case signIn
- }
- final class SignInViewModel: BaseViewModel<
- SignInNavigationStep,
- SignInViewModel.InputFromView,
- SignInViewModel.Output
- > {
- private let useCase: AuthenticationUseCase
- public init(useCase: AuthenticationUseCase) {
- self.useCase = useCase
- }
- override func transform(input: Input) -> Output {
- func userDid(_ step: SignInNavigationStep) {
- navigator.navigate(to: .signIn)
- }
- let activityTracker = ActivityTracker()
- let errorTracker = ErrorTracker()
- let isSigningIn = activityTracker.asDriver()
- // Validate input
- let validator = InputValidator()
- let usernameValidation: Driver<Validation<String>> = input.fromView.username.map { validator.validate(email: $0) }
- let validUsername: Driver<String> = usernameValidation.map { $0.asValidated }.filterNil()
- let passwordsValidation: Driver<Validation<String>> = Driver.combineLatest(input.fromView.password, input.fromView.confirmPassword) {
- validator.validate(password: $0, confirmedBy: $1)
- }
- let validPassword: Driver<String> = passwordsValidation.map { $0.asValidated }.filterNil()
- // Make button enabled/disabled based on validation
- let isSignInButtonEnabled = Driver.combineLatest(
- usernameValidation.map { $0.isValid },
- passwordsValidation.map { $0.isValid }
- ) { $0 && $1 }
- // Create request from valid input
- let signInRequest: Driver<SignInRequest> = Driver.combineLatest(validUsername, validPassword) {
- SignInRequest(username: $0, password: $1)
- }
- // Create and dispose of `Disposable`s
- [
- // Make request
- input.fromView.signInTrigger.withLatestFrom(signInRequest) { $1 }
- .flatMapLatest { [unowned useCase] in
- useCase.signIn(request: $1)
- .trackError(errorTracker)
- .trackActivity(activityTracker)
- .asDriverOnErrorReturnEmpty()
- }.do(onNext: { userDid(.signIn) }) // when successful, notify coordinator
- .drive(),
- // Let Controller display alert for error message (could also be moved to Coordinator)
- errorTracker.asDriver()
- .do(onNext: {
- // Remember I said ControllerInput was a bit simplified? It has an `alertSubject`
- // On which we can notify of an `Alert` type, to display errors or short messages.
- input.fromController.alertSubject.onNext(Alert(error: $0))
- }).drive()
- ].forEach { $0.disposed(by: bag) }
- // Return output to drives UI
- return Output(
- isSignInButtonEnabled: isSignInButtonEnabled,
- isSignInButtonEnabled: isSigningIn,
- usernameValidation: usernameValidation,
- passwordValidation: passwordValidation
- )
- }
- }
- extension SignInViewModel {
- struct InputFromView {
- let username: Driver<String>
- let password: Driver<String>
- let confirmPassword: Driver<String>
- let signInTrigger: Driver<Void>
- }
- struct Output {
- let isSignInButtonEnabled: Driver<Bool>
- let isSigningIn: Driver<Bool>
- let usernameValidation: Driver<Validation<String>>
- let passwordValidation: Driver<Validation<String>>
- }
- }
- // MARK: - Field Validation
- extension SignInViewModel {
- struct InputValidator {
- // Some simple validator types, implementation omitted.
- private let emailValidator = EmailValidator()
- private let passwordValidator = PasswordValidator()
- func validate(email: String) -> Validation<String> {
- return emailValidator.validate(input: email)
- }
- func validate(password: String, confirmedBy confirmingPassword: String) -> Validation<String> {
- return passwordValidator.validate(password: password, passwordConfirmation: confirmingPassword)
- }
- }
- }
Add Comment
Please, Sign In to add comment