package com.kickstarter.viewmodels; import android.support.annotation.NonNull; import android.util.Pair; import com.facebook.CallbackManager; import com.facebook.FacebookAuthorizationException; import com.facebook.FacebookCallback; import com.facebook.FacebookException; import com.facebook.login.LoginManager; import com.facebook.login.LoginResult; import com.kickstarter.libs.ActivityRequestCodes; 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.LoginToutActivity; import com.kickstarter.ui.data.ActivityResult; import com.kickstarter.ui.data.LoginReason; import com.kickstarter.viewmodels.inputs.LoginToutViewModelInputs; import com.kickstarter.viewmodels.outputs.LoginToutViewModelOutputs; import java.util.List; import rx.Observable; import rx.subjects.BehaviorSubject; import rx.subjects.PublishSubject; public final class LoginToutViewModel extends ActivityViewModel<LoginToutActivity> implements LoginToutViewModelInputs, LoginToutViewModelOutputs { private CallbackManager callbackManager; private final CurrentUserType currentUser; private final ApiClientType client; public LoginToutViewModel(final @NonNull Environment environment) { super(environment); client = environment.apiClient(); currentUser = environment.currentUser(); registerFacebookCallback(); final Observable<AccessTokenEnvelope> facebookSuccessTokenEnvelope = facebookAccessToken .switchMap(this::loginWithFacebookAccessToken) .share(); intent() .map(i -> i.getSerializableExtra(IntentKey.LOGIN_REASON)) .ofType(LoginReason.class) .compose(bindToLifecycle()) .subscribe(loginReason::onNext); activityResult() .compose(bindToLifecycle()) .subscribe(r -> callbackManager.onActivityResult(r.requestCode(), r.resultCode(), r.intent())); activityResult() .filter(r -> r.isRequestCode(ActivityRequestCodes.LOGIN_FLOW)) .filter(ActivityResult::isOk) .compose(bindToLifecycle()) .subscribe(__ -> finishWithSuccessfulResult.onNext(null)); facebookAuthorizationError .compose(bindToLifecycle()) .subscribe(this::clearFacebookSession); facebookSuccessTokenEnvelope .compose(bindToLifecycle()) .subscribe(envelope -> { currentUser.login(envelope.user(), envelope.accessToken()); finishWithSuccessfulResult.onNext(null); }); startFacebookConfirmationActivity = loginError .filter(ErrorEnvelope::isConfirmFacebookSignupError) .map(ErrorEnvelope::facebookUser) .compose(Transformers.combineLatestPair(facebookAccessToken)); startLoginActivity = loginClick; startSignupActivity = signupClick; loginReason.take(1) .compose(bindToLifecycle()) .subscribe(koala::trackLoginRegisterTout); loginError .compose(bindToLifecycle()) .subscribe(__ -> koala.trackLoginError()); showMissingFacebookEmailErrorToast() .mergeWith(showFacebookInvalidAccessTokenErrorToast()) .mergeWith(showFacebookAuthorizationErrorDialog()) .compose(bindToLifecycle()) .subscribe(__ -> koala.trackFacebookLoginError()); } private void clearFacebookSession(final @NonNull FacebookException e) { LoginManager.getInstance().logOut(); } private @NonNull Observable<AccessTokenEnvelope> loginWithFacebookAccessToken(final @NonNull String fbAccessToken) { return client.loginWithFacebook(fbAccessToken) .compose(Transformers.pipeApiErrorsTo(loginError)) .compose(Transformers.neverError()); } private void registerFacebookCallback() { callbackManager = CallbackManager.Factory.create(); LoginManager.getInstance().registerCallback(callbackManager, new FacebookCallback<LoginResult>() { @Override public void onSuccess(final @NonNull LoginResult result) { facebookAccessToken.onNext(result.getAccessToken().getToken()); } @Override public void onCancel() { // continue } @Override public void onError(final @NonNull FacebookException error) { if (error instanceof FacebookAuthorizationException) { facebookAuthorizationError.onNext(error); } } }); } private final PublishSubject<String> facebookAccessToken = PublishSubject.create(); private final PublishSubject<Void> loginClick = PublishSubject.create(); private final PublishSubject<ErrorEnvelope> loginError = PublishSubject.create(); private final PublishSubject<LoginReason> loginReason = PublishSubject.create(); private final PublishSubject<Void> signupClick = PublishSubject.create(); private final BehaviorSubject<FacebookException> facebookAuthorizationError = BehaviorSubject.create(); private final BehaviorSubject<Void> finishWithSuccessfulResult = BehaviorSubject.create(); private final Observable<Pair<ErrorEnvelope.FacebookUser, String>> startFacebookConfirmationActivity; private final Observable<Void> startLoginActivity; private final Observable<Void> startSignupActivity; public final LoginToutViewModelInputs inputs = this; public final LoginToutViewModelOutputs outputs = this; @Override public void facebookLoginClick(final @NonNull LoginToutActivity activity, final @NonNull List<String> facebookPermissions) { LoginManager.getInstance().logInWithReadPermissions(activity, facebookPermissions); } @Override public void loginClick() { loginClick.onNext(null); } @Override public void signupClick() { signupClick.onNext(null); } @Override public @NonNull Observable<Void> finishWithSuccessfulResult() { return finishWithSuccessfulResult; } @Override public @NonNull Observable<String> showFacebookAuthorizationErrorDialog() { return facebookAuthorizationError .map(FacebookException::getLocalizedMessage); } @Override public @NonNull Observable<String> showFacebookInvalidAccessTokenErrorToast() { return loginError .filter(ErrorEnvelope::isFacebookInvalidAccessTokenError) .map(ErrorEnvelope::errorMessage); } @Override public @NonNull Observable<String> showMissingFacebookEmailErrorToast() { return loginError .filter(ErrorEnvelope::isMissingFacebookEmailError) .map(ErrorEnvelope::errorMessage); } @Override public @NonNull Observable<String> showUnauthorizedErrorDialog() { return loginError .filter(ErrorEnvelope::isUnauthorizedError) .map(ErrorEnvelope::errorMessage); } @Override public @NonNull Observable<Pair<ErrorEnvelope.FacebookUser, String>> startFacebookConfirmationActivity() { return startFacebookConfirmationActivity; } @Override public @NonNull Observable<Void> startLoginActivity() { return startLoginActivity; } @Override public @NonNull Observable<Void> startSignupActivity() { return startSignupActivity; } @Override public @NonNull Observable<Void> startTwoFactorChallenge() { return loginError .filter(ErrorEnvelope::isTfaRequiredError) .map(__ -> null); } }