package com.kickstarter.viewmodels; import android.support.annotation.NonNull; import android.util.Pair; import android.view.View; 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.libs.utils.BooleanUtils; import com.kickstarter.libs.utils.StringUtils; 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.LoginActivity; import com.kickstarter.viewmodels.errors.LoginViewModelErrors; import com.kickstarter.viewmodels.inputs.LoginViewModelInputs; import com.kickstarter.viewmodels.outputs.LoginViewModelOutputs; import rx.Observable; import rx.subjects.BehaviorSubject; import rx.subjects.PublishSubject; import static com.kickstarter.libs.rx.transformers.Transformers.combineLatestPair; import static com.kickstarter.libs.rx.transformers.Transformers.neverError; import static com.kickstarter.libs.rx.transformers.Transformers.takeWhen; public final class LoginViewModel extends ActivityViewModel<LoginActivity> implements LoginViewModelInputs, LoginViewModelOutputs, LoginViewModelErrors { private final ApiClientType client; private final CurrentUserType currentUser; public LoginViewModel(final @NonNull Environment environment) { super(environment); client = environment.apiClient(); currentUser = environment.currentUser(); final Observable<Pair<String, String>> emailAndPassword = email .compose(combineLatestPair(password)); final Observable<Boolean> isValid = emailAndPassword .map(ep -> LoginViewModel.isValid(ep.first, ep.second)); final Observable<String> emailFromIntent = intent() .map(i -> i.getStringExtra(IntentKey.EMAIL)) .ofType(String.class) .compose(bindToLifecycle()); emailFromIntent .compose(bindToLifecycle()) .subscribe(prefillEmailFromPasswordReset); emailFromIntent .map(email -> Pair.create(true, email)) .compose(bindToLifecycle()) .subscribe(showResetPasswordSuccessDialog); resetPasswordConfirmationDialogDismissed .map(BooleanUtils::negate) .compose(combineLatestPair(emailFromIntent)) .compose(bindToLifecycle()) .subscribe(showResetPasswordSuccessDialog); isValid .compose(bindToLifecycle()) .subscribe(setLoginButtonIsEnabled); emailAndPassword .compose(takeWhen(loginClick)) .switchMap(ep -> submit(ep.first, ep.second)) .compose(bindToLifecycle()) .subscribe(this::success); loginSuccess .compose(bindToLifecycle()) .subscribe(__ -> koala.trackLoginSuccess()); invalidLoginError() .mergeWith(genericLoginError()) .compose(bindToLifecycle()) .subscribe(__ -> koala.trackLoginError()); } private static boolean isValid(final @NonNull String email, final @NonNull String password) { return StringUtils.isEmail(email) && password.length() > 0; } private Observable<AccessTokenEnvelope> submit(final @NonNull String email, final @NonNull String password) { return client.login(email, password) .compose(Transformers.pipeApiErrorsTo(loginError)) .compose(neverError()); } private void success(final @NonNull AccessTokenEnvelope envelope) { currentUser.login(envelope.user(), envelope.accessToken()); loginSuccess.onNext(null); } private final PublishSubject<String> email = PublishSubject.create(); private final PublishSubject<View> loginClick = PublishSubject.create(); private final PublishSubject<String> password = PublishSubject.create(); private final PublishSubject<Boolean> resetPasswordConfirmationDialogDismissed = PublishSubject.create(); private final PublishSubject<Void> loginSuccess = PublishSubject.create(); private final BehaviorSubject<String> prefillEmailFromPasswordReset = BehaviorSubject.create(); private final BehaviorSubject<Boolean> setLoginButtonIsEnabled = BehaviorSubject.create(); private final BehaviorSubject<Pair<Boolean, String>> showResetPasswordSuccessDialog = BehaviorSubject.create(); private final PublishSubject<ErrorEnvelope> loginError = PublishSubject.create(); public final LoginViewModelInputs inputs = this; public final LoginViewModelOutputs outputs = this; public final LoginViewModelErrors errors = this; @Override public void email(final @NonNull String s) { email.onNext(s); } @Override public void loginClick() { loginClick.onNext(null); } @Override public void password(final @NonNull String s) { password.onNext(s); } @Override public void resetPasswordConfirmationDialogDismissed() { resetPasswordConfirmationDialogDismissed.onNext(true); } @Override public @NonNull Observable<Void> loginSuccess() { return loginSuccess.asObservable(); } @Override public @NonNull Observable<String> prefillEmailFromPasswordReset() { return prefillEmailFromPasswordReset; } @Override public Observable<Boolean> setLoginButtonIsEnabled() { return setLoginButtonIsEnabled; } @Override public Observable<Pair<Boolean, String>> showResetPasswordSuccessDialog() { return showResetPasswordSuccessDialog; } @Override public Observable<String> invalidLoginError() { return loginError .filter(ErrorEnvelope::isInvalidLoginError) .map(ErrorEnvelope::errorMessage); } @Override public Observable<Void> tfaChallenge() { return loginError .filter(ErrorEnvelope::isTfaRequiredError) .map(__ -> null); } @Override public Observable<String> genericLoginError() { return loginError .filter(ErrorEnvelope::isGenericLoginError) .map(ErrorEnvelope::errorMessage); } }