package im.actor.sdk.controllers.auth;
import android.app.AlertDialog;
import android.app.ProgressDialog;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.MenuItem;
import im.actor.core.AuthState;
import im.actor.core.entity.AuthCodeRes;
import im.actor.core.entity.AuthRes;
import im.actor.core.entity.AuthStartRes;
import im.actor.core.entity.Sex;
import im.actor.core.network.RpcException;
import im.actor.core.network.RpcInternalException;
import im.actor.core.network.RpcTimeoutException;
import im.actor.runtime.actors.Actor;
import im.actor.runtime.actors.ActorCreator;
import im.actor.runtime.actors.ActorRef;
import im.actor.runtime.actors.ActorSystem;
import im.actor.runtime.actors.Props;
import im.actor.runtime.function.Consumer;
import im.actor.runtime.promise.Promise;
import im.actor.runtime.storage.PreferencesStorage;
import im.actor.sdk.ActorSDK;
import im.actor.sdk.R;
import im.actor.sdk.controllers.root.RootActivity;
import im.actor.sdk.controllers.activity.BaseFragmentActivity;
import static im.actor.sdk.util.ActorSDKMessenger.messenger;
public class AuthActivity extends BaseFragmentActivity {
public static final String AUTH_TYPE_KEY = "auth_type";
public static final String SIGN_TYPE_KEY = "sign_type";
public static final int AUTH_TYPE_PHONE = 1;
public static final int AUTH_TYPE_EMAIL = 2;
public static final int SIGN_TYPE_IN = 3;
public static final int SIGN_TYPE_UP = 4;
private static final int OAUTH_DIALOG = 1;
private ProgressDialog progressDialog;
private AlertDialog alertDialog;
private AuthState state;
private int availableAuthType = AUTH_TYPE_PHONE;
private int currentAuthType = AUTH_TYPE_PHONE;
private int signType;
private long currentPhone;
private String currentEmail;
private String transactionHash;
private String currentCode;
private boolean isRegistered = false;
private String currentName;
private Sex currentSex;
private ActorRef authActor;
private boolean codeValidated = false;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
authActor = ActorSystem.system().actorOf(Props.create(new ActorCreator() {
@Override
public Actor create() {
return new Actor();
}
}), "actor/auth_promises_actor");
signType = getIntent().getIntExtra(SIGN_TYPE_KEY, SIGN_TYPE_IN);
PreferencesStorage preferences = messenger().getPreferences();
currentPhone = preferences.getLong("currentPhone", 0);
currentEmail = preferences.getString("currentEmail");
transactionHash = preferences.getString("transactionHash");
isRegistered = preferences.getBool("isRegistered", false);
codeValidated = preferences.getBool("codeValidated", false);
currentName = preferences.getString("currentName");
signType = preferences.getInt("signType", signType);
String savedState = preferences.getString("auth_state");
state = Enum.valueOf(AuthState.class, savedState != null ? savedState : "AUTH_START");
updateState(state, true);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
}
return super.onOptionsItemSelected(item);
}
private void updateState(AuthState state) {
updateState(state, false);
}
private void updateState(AuthState state, boolean force) {
if (this.state != null && (this.state == state && !force)) {
return;
}
PreferencesStorage preferences = messenger().getPreferences();
preferences.putLong("currentPhone", currentPhone);
preferences.putString("currentEmail", currentEmail);
preferences.putString("transactionHash", transactionHash);
preferences.putBool("isRegistered", isRegistered);
preferences.putBool("codeValidated", codeValidated);
preferences.putString("currentName", currentName);
preferences.putInt("signType", signType);
preferences.putString("auth_state", state.toString());
// if we show the next fragment when app is in background and not visible , app crashes!
// e.g when the GSM data is off and after trying to send code we go to settings to turn on, app is going invisible and ...
if (state != AuthState.LOGGED_IN && getIsResumed() == false) {
return;
}
this.state = state;
switch (state) {
case AUTH_START:
if (signType == SIGN_TYPE_UP) {
updateState(AuthState.SIGN_UP);
} else if (signType == SIGN_TYPE_IN) {
showFragment(new SignInFragment(), false);
}
break;
case SIGN_UP:
if (currentName != null && !currentName.isEmpty()) {
startAuth(currentName);
} else {
showFragment(new SignUpFragment(), false);
}
break;
case AUTH_PHONE:
currentAuthType = AUTH_TYPE_PHONE;
currentCode = "";
showFragment(ActorSDK.sharedActor().getDelegatedFragment(ActorSDK.sharedActor().getDelegate().getAuthStartIntent(), new SignPhoneFragment(), BaseAuthFragment.class), false);
break;
case AUTH_EMAIL:
currentCode = "";
currentAuthType = AUTH_TYPE_EMAIL;
showFragment(ActorSDK.sharedActor().getDelegatedFragment(ActorSDK.sharedActor().getDelegate().getAuthStartIntent(), new SignEmailFragment(), BaseAuthFragment.class), false);
break;
case CODE_VALIDATION_PHONE:
case CODE_VALIDATION_EMAIL:
Fragment signInFragment = new ValidateCodeFragment();
Bundle args = new Bundle();
args.putString("authType", state == AuthState.CODE_VALIDATION_EMAIL ? ValidateCodeFragment.AUTH_TYPE_EMAIL : ValidateCodeFragment.AUTH_TYPE_PHONE);
args.putBoolean(ValidateCodeFragment.AUTH_TYPE_SIGN, signType == SIGN_TYPE_IN);
args.putString("authId", state == AuthState.CODE_VALIDATION_EMAIL ? currentEmail : Long.toString(currentPhone));
signInFragment.setArguments(args);
showFragment(signInFragment, false);
break;
case LOGGED_IN:
finish();
ActorSDK.sharedActor().startAfterLoginActivity(this);
break;
}
}
public void startAuth(String name) {
currentName = name;
currentSex = Sex.UNKNOWN;
availableAuthType = ActorSDK.sharedActor().getAuthType();
AuthState authState;
if (!codeValidated) {
if ((availableAuthType & AUTH_TYPE_PHONE) == AUTH_TYPE_PHONE) {
authState = AuthState.AUTH_PHONE;
} else if ((availableAuthType & AUTH_TYPE_EMAIL) == AUTH_TYPE_EMAIL) {
authState = AuthState.AUTH_EMAIL;
} else {
// none of valid auth types selected - force crash?
return;
}
updateState(authState);
} else {
signUp(messenger().doSignup(currentName, currentSex != null ? currentSex : Sex.UNKNOWN, transactionHash), currentName, currentSex);
}
}
public void startPhoneAuth(Promise<AuthStartRes> promise, long phone) {
currentAuthType = AUTH_TYPE_PHONE;
currentPhone = phone;
startAuth(promise);
}
public void startEmailAuth(Promise<AuthStartRes> promise, String email) {
currentAuthType = AUTH_TYPE_EMAIL;
currentEmail = email;
startAuth(promise);
}
private void startAuth(Promise<AuthStartRes> res) {
showProgress();
res.then(new Consumer<AuthStartRes>() {
@Override
public void apply(AuthStartRes authStartRes) {
if (dismissProgress()) {
transactionHash = authStartRes.getTransactionHash();
isRegistered = authStartRes.isRegistered();
switch (authStartRes.getAuthMode()) {
case OTP:
switch (currentAuthType) {
case AUTH_TYPE_PHONE:
updateState(AuthState.CODE_VALIDATION_PHONE);
break;
case AUTH_TYPE_EMAIL:
updateState(AuthState.CODE_VALIDATION_EMAIL);
break;
}
break;
default:
//not supported AuthMode - force crash?
}
}
}
}).failure(new Consumer<Exception>() {
@Override
public void apply(Exception e) {
handleAuthError(e);
}
});
}
public void validateCode(Promise<AuthCodeRes> promise, String code) {
currentCode = code;
showProgress();
promise.then(new Consumer<AuthCodeRes>() {
@Override
public void apply(AuthCodeRes authCodeRes) {
if (dismissProgress()) {
codeValidated = true;
transactionHash = authCodeRes.getTransactionHash();
if (!authCodeRes.isNeedToSignup()) {
messenger().doCompleteAuth(authCodeRes.getResult()).then(new Consumer<Boolean>() {
@Override
public void apply(Boolean aBoolean) {
updateState(AuthState.LOGGED_IN);
}
}).failure(new Consumer<Exception>() {
@Override
public void apply(Exception e) {
handleAuthError(e);
}
});
} else {
if (currentName == null || currentName.isEmpty()) {
updateState(AuthState.SIGN_UP, true);
} else {
signUp(messenger().doSignup(currentName, currentSex != null ? currentSex : Sex.UNKNOWN, transactionHash), currentName, currentSex);
}
}
}
}
}).failure(new Consumer<Exception>() {
@Override
public void apply(Exception e) {
handleAuthError(e);
}
});
}
public void signUp(Promise<AuthRes> promise, String name, Sex sex) {
currentName = name;
currentSex = sex;
promise.then(new Consumer<AuthRes>() {
@Override
public void apply(AuthRes authRes) {
dismissProgress();
messenger().doCompleteAuth(authRes).then(new Consumer<Boolean>() {
@Override
public void apply(Boolean aBoolean) {
updateState(AuthState.LOGGED_IN);
}
}).failure(new Consumer<Exception>() {
@Override
public void apply(Exception e) {
handleAuthError(e);
}
});
}
}).failure(new Consumer<Exception>() {
@Override
public void apply(Exception e) {
handleAuthError(e);
}
});
}
public void handleAuthError(final Exception e) {
runOnUiThread(new Runnable() {
@Override
public void run() {
if (dismissProgress()) {
boolean canTryAgain = false;
boolean keepState = false;
String message = getString(R.string.error_unknown);
String tag = "UNKNOWN";
if (e instanceof RpcException) {
RpcException re = (RpcException) e;
if (re instanceof RpcInternalException) {
message = getString(R.string.error_unknown);
canTryAgain = true;
} else if (re instanceof RpcTimeoutException) {
message = getString(R.string.error_connection);
canTryAgain = true;
} else {
if ("PHONE_CODE_EXPIRED".equals(re.getTag()) || "EMAIL_CODE_EXPIRED".equals(re.getTag())) {
currentCode = "";
message = getString(R.string.auth_error_code_expired);
canTryAgain = false;
} else if ("PHONE_CODE_INVALID".equals(re.getTag()) || "EMAIL_CODE_INVALID".equals(re.getTag())) {
message = getString(R.string.auth_error_code_invalid);
canTryAgain = false;
keepState = true;
} else if ("FAILED_GET_OAUTH2_TOKEN".equals(re.getTag())) {
message = getString(R.string.auth_error_failed_get_oauth2_token);
canTryAgain = false;
} else {
message = re.getMessage();
canTryAgain = re.isCanTryAgain();
}
}
}
try {
if (canTryAgain) {
new AlertDialog.Builder(AuthActivity.this)
.setMessage(message)
.setPositiveButton(R.string.dialog_try_again, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dismissAlert();
switch (state) {
case AUTH_EMAIL:
case AUTH_PHONE:
switch (currentAuthType) {
case AUTH_TYPE_PHONE:
startAuth(messenger().doStartPhoneAuth(currentPhone));
break;
case AUTH_TYPE_EMAIL:
startAuth(messenger().doStartEmailAuth(currentEmail));
break;
}
break;
case CODE_VALIDATION_EMAIL:
case CODE_VALIDATION_PHONE:
validateCode(messenger().doValidateCode(currentCode, transactionHash), currentCode);
break;
case SIGN_UP:
signUp(messenger().doSignup(currentName, currentSex!=null?currentSex:Sex.UNKNOWN, transactionHash), currentName, currentSex);
break;
}
}
})
.setNegativeButton(R.string.dialog_cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dismissAlert();
updateState(AuthState.AUTH_START);
}
}).setCancelable(false)
.show()
.setCanceledOnTouchOutside(false);
} else {
final boolean finalKeepState = keepState;
new AlertDialog.Builder(AuthActivity.this)
.setMessage(message)
.setPositiveButton(R.string.dialog_ok, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dismissAlert();
if (finalKeepState) {
updateState(state, true);
} else if (signType == SIGN_TYPE_UP) {
if (currentAuthType == AUTH_TYPE_EMAIL) {
switchToEmailAuth();
} else if (currentAuthType == AUTH_TYPE_PHONE) {
switchToPhoneAuth();
} else {
updateState(AuthState.AUTH_START);
}
} else if (signType == SIGN_TYPE_IN) {
startSignIn();
} else {
updateState(AuthState.AUTH_START);
}
}
})
.setCancelable(false)
.show()
.setCanceledOnTouchOutside(false);
}
} catch (Exception ex) {
ex.printStackTrace();
}
}
}
});
}
public void switchToEmailAuth() {
updateState(AuthState.AUTH_EMAIL);
}
public void switchToPhoneAuth() {
updateState(AuthState.AUTH_PHONE);
}
public void startSignIn() {
signType = SIGN_TYPE_IN;
updateState(AuthState.AUTH_START, true);
}
public void startSignUp() {
signType = SIGN_TYPE_UP;
updateState(AuthState.AUTH_START, true);
}
public void showProgress() {
dismissProgress();
progressDialog = new ProgressDialog(this);
progressDialog.setCanceledOnTouchOutside(false);
progressDialog.setCancelable(false);
progressDialog.setTitle(getString(R.string.progress_common));
progressDialog.show();
}
@Override
protected void onPause() {
super.onPause();
dismissProgress();
dismissAlert();
}
private boolean dismissProgress() {
if (progressDialog != null) {
progressDialog.dismiss();
progressDialog = null;
return true;
}
return false;
}
private void dismissAlert() {
if (alertDialog != null) {
alertDialog.dismiss();
alertDialog = null;
}
}
public String getCurrentCode() {
return currentCode;
}
public String getTransactionHash() {
return transactionHash;
}
}