package com.kickstarter.viewmodels; import android.support.annotation.NonNull; import com.kickstarter.libs.ActivityViewModel; import com.kickstarter.libs.CurrentUserType; import com.kickstarter.libs.Environment; import com.kickstarter.libs.rx.transformers.Transformers; import com.kickstarter.services.ApiClientType; import com.kickstarter.services.apiresponses.AccessTokenEnvelope; import com.kickstarter.services.apiresponses.ErrorEnvelope; import com.kickstarter.ui.IntentKey; import com.kickstarter.ui.activities.TwoFactorActivity; import com.kickstarter.viewmodels.errors.TwoFactorViewModelErrors; import com.kickstarter.viewmodels.inputs.TwoFactorViewModelInputs; import com.kickstarter.viewmodels.outputs.TwoFactorViewModelOutputs; import rx.Observable; import rx.subjects.PublishSubject; public final class TwoFactorViewModel extends ActivityViewModel<TwoFactorActivity> implements TwoFactorViewModelInputs, TwoFactorViewModelOutputs, TwoFactorViewModelErrors { protected final static class TfaData { final @NonNull String email; final @NonNull String fbAccessToken; final boolean isFacebookLogin; final @NonNull String password; protected TfaData(final @NonNull String email, final @NonNull String fbAccessToken, final boolean isFacebookLogin, final @NonNull String password) { this.email = email; this.fbAccessToken = fbAccessToken; this.isFacebookLogin = isFacebookLogin; this.password = password; } } // INPUTS private final PublishSubject<String> code = PublishSubject.create(); @Override public void code(@NonNull final String s) { code.onNext(s); } private final PublishSubject<Void> loginClick = PublishSubject.create(); @Override public void loginClick() { loginClick.onNext(null); } private final PublishSubject<Void> resendClick = PublishSubject.create(); @Override public void resendClick() { resendClick.onNext(null); } // OUTPUTS private final PublishSubject<Boolean> formSubmitting = PublishSubject.create(); public Observable<Boolean> formSubmitting() { return formSubmitting.asObservable(); } private final PublishSubject<Boolean> formIsValid = PublishSubject.create(); public Observable<Boolean> formIsValid() { return formIsValid.asObservable(); } private final PublishSubject<Void> tfaSuccess = PublishSubject.create(); public Observable<Void> tfaSuccess() { return tfaSuccess.asObservable(); } private final PublishSubject<Void> showResendCodeConfirmation = PublishSubject.create(); public Observable<Void> showResendCodeConfirmation() { return showResendCodeConfirmation.asObservable(); } // ERRORS private final PublishSubject<ErrorEnvelope> tfaError = PublishSubject.create(); public Observable<Void> tfaCodeMismatchError() { return tfaError .filter(ErrorEnvelope::isTfaFailedError) .map(__ -> null); } public Observable<Void> genericTfaError() { return tfaError .filter(env -> !env.isTfaFailedError()) .map(__ -> null); } private final ApiClientType client; private final CurrentUserType currentUser; public final TwoFactorViewModelInputs inputs = this; public final TwoFactorViewModelOutputs outputs = this; public final TwoFactorViewModelErrors errors = this; public TwoFactorViewModel(final @NonNull Environment environment) { super(environment); currentUser = environment.currentUser(); client = environment.apiClient(); final Observable<String> email = intent() .map(i -> i.getStringExtra(IntentKey.EMAIL)); final Observable<String> fbAccessToken = intent() .map(i -> i.getStringExtra(IntentKey.FACEBOOK_TOKEN)); final Observable<Boolean> isFacebookLogin = intent() .map(i -> i.getBooleanExtra(IntentKey.FACEBOOK_LOGIN, false)); final Observable<String> password= intent() .map(i -> i.getStringExtra(IntentKey.PASSWORD)); final Observable<TfaData> tfaData = Observable.combineLatest(email, fbAccessToken, isFacebookLogin, password, TfaData::new); code .map(TwoFactorViewModel::isCodeValid) .compose(bindToLifecycle()) .subscribe(formIsValid); code .compose(Transformers.combineLatestPair(tfaData)) .compose(Transformers.takeWhen(loginClick)) .filter(cd -> !cd.second.isFacebookLogin) .switchMap(cd -> this.login(cd.first, cd.second.email, cd.second.password)) .compose(bindToLifecycle()) .subscribe(this::success); code .compose(Transformers.combineLatestPair(tfaData)) .compose(Transformers.takeWhen(loginClick)) .filter(cd -> cd.second.isFacebookLogin) .switchMap(cd -> this.loginWithFacebook(cd.first, cd.second.fbAccessToken)) .compose(bindToLifecycle()) .subscribe(this::success); tfaData .compose(Transformers.takeWhen(resendClick)) .filter(d -> !d.isFacebookLogin) .flatMap(d -> resendCode(d.email, d.password)) .compose(bindToLifecycle()) .subscribe(); tfaData .compose(Transformers.takeWhen(resendClick)) .filter(d -> d.isFacebookLogin) .flatMap(d -> resendCodeWithFacebook(d.fbAccessToken)) .compose(bindToLifecycle()) .subscribe(); tfaSuccess .compose(bindToLifecycle()) .subscribe(__ -> koala.trackLoginSuccess()); resendClick .compose(bindToLifecycle()) .subscribe(__ -> koala.trackTwoFactorResendCode()); tfaError .compose(bindToLifecycle()) .subscribe(__ -> koala.trackLoginError()); koala.trackTwoFactorAuthView(); } private void success(final @NonNull AccessTokenEnvelope envelope) { currentUser.login(envelope.user(), envelope.accessToken()); tfaSuccess.onNext(null); } private Observable<AccessTokenEnvelope> login(final @NonNull String code, final @NonNull String email, final @NonNull String password) { return client.login(email, password, code) .compose(Transformers.pipeApiErrorsTo(tfaError)) .compose(Transformers.neverError()) .doOnSubscribe(() -> formSubmitting.onNext(true)) .doAfterTerminate(() -> formSubmitting.onNext(false)); } public Observable<AccessTokenEnvelope> loginWithFacebook(final @NonNull String code, final @NonNull String fbAccessToken) { return client.loginWithFacebook(fbAccessToken, code) .compose(Transformers.pipeApiErrorsTo(tfaError)) .compose(Transformers.neverError()) .doOnSubscribe(() -> formSubmitting.onNext(true)) .doAfterTerminate(() -> formSubmitting.onNext(false)); } private Observable<AccessTokenEnvelope> resendCode(final @NonNull String email, final @NonNull String password) { return client.login(email, password) .compose(Transformers.neverError()) .doOnSubscribe(() -> showResendCodeConfirmation.onNext(null)); } private Observable<AccessTokenEnvelope> resendCodeWithFacebook(final @NonNull String fbAccessToken) { return client.loginWithFacebook(fbAccessToken) .compose(Transformers.neverError()) .doOnSubscribe(() -> showResendCodeConfirmation.onNext(null)); } private static boolean isCodeValid(final String code) { return code != null && code.length() > 0; } }