package org.wikipedia.createaccount; import android.app.ProgressDialog; import android.content.Intent; import android.os.Bundle; import android.support.annotation.NonNull; import android.support.design.widget.PasswordTextInput; import android.view.KeyEvent; import android.view.View; import android.view.View.OnKeyListener; import android.widget.EditText; import android.widget.TextView; import com.mobsandgeeks.saripaar.ValidationError; import com.mobsandgeeks.saripaar.Validator; import com.mobsandgeeks.saripaar.annotation.ConfirmPassword; import com.mobsandgeeks.saripaar.annotation.Password; import com.mobsandgeeks.saripaar.annotation.Pattern; import org.wikipedia.R; import org.wikipedia.WikipediaApp; import org.wikipedia.activity.ThemedActionBarActivity; import org.wikipedia.analytics.CreateAccountFunnel; import org.wikipedia.captcha.CaptchaHandler; import org.wikipedia.captcha.CaptchaResult; import org.wikipedia.dataclient.WikiSite; import org.wikipedia.dataclient.mwapi.MwQueryResponse; import org.wikipedia.util.FeedbackUtil; import org.wikipedia.util.log.L; import org.wikipedia.views.NonEmptyValidator; import org.wikipedia.views.WikiErrorView; import java.util.List; import retrofit2.Call; import static org.wikipedia.util.DeviceUtil.hideSoftKeyboard; import static org.wikipedia.util.FeedbackUtil.setErrorPopup; public class CreateAccountActivity extends ThemedActionBarActivity { public static final int RESULT_ACCOUNT_CREATED = 1; public static final int RESULT_ACCOUNT_NOT_CREATED = 2; public static final int ACTION_CREATE_ACCOUNT = 1; public static final String LOGIN_REQUEST_SOURCE = "login_request_source"; public static final String LOGIN_SESSION_TOKEN = "login_session_token"; public static final String CREATE_ACCOUNT_RESULT_USERNAME = "username"; public static final String CREATE_ACCOUNT_RESULT_PASSWORD = "password"; private CreateAccountInfoClient createAccountInfoClient; private CreateAccountClient createAccountClient; @Pattern(regex = "[^#<>\\[\\]|{}\\/@]*", messageResId = R.string.create_account_username_error) private EditText usernameEdit; private PasswordTextInput passwordInput; @Password() private EditText passwordEdit; private PasswordTextInput passwordRepeatInput; @ConfirmPassword(messageResId = R.string.create_account_passwords_mismatch_error) private EditText passwordRepeatEdit; // TODO: remove and replace with @Optional annotation once it's available in the library // https://github.com/ragunathjawahar/android-saripaar/issues/102 @OptionalEmail(messageResId = R.string.create_account_email_error) private EditText emailEdit; private TextView createAccountButton; private TextView createAccountButtonCaptcha; private ProgressDialog progressDialog; private WikiErrorView errorView; private CaptchaHandler captchaHandler; private CreateAccountResult createAccountResult; private Validator validator; private CreateAccountFunnel funnel; private WikiSite wiki; @Override public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); setContentView(R.layout.activity_create_account); usernameEdit = (EditText) findViewById(R.id.create_account_username); passwordRepeatInput = ((PasswordTextInput) findViewById(R.id.create_account_password_repeat)); passwordRepeatEdit = passwordRepeatInput.getEditText(); emailEdit = (EditText) findViewById(R.id.create_account_email); createAccountButton = (TextView) findViewById(R.id.create_account_submit_button); createAccountButtonCaptcha = (TextView) findViewById(R.id.captcha_submit_button); EditText captchaText = (EditText) findViewById(R.id.captcha_text); View primaryContainer = findViewById(R.id.create_account_primary_container); passwordInput = (PasswordTextInput) findViewById(R.id.create_account_password_input); passwordEdit = passwordInput.getEditText(); progressDialog = new ProgressDialog(this); progressDialog.setIndeterminate(true); progressDialog.setCancelable(false); progressDialog.setMessage(getString(R.string.dialog_create_account_checking_progress)); errorView = (WikiErrorView) findViewById(R.id.view_create_account_error); errorView.setBackClickListener(new View.OnClickListener() { @Override public void onClick(View v) { onBackPressed(); } }); errorView.setRetryClickListener(new View.OnClickListener() { @Override public void onClick(View v) { errorView.setVisibility(View.GONE); } }); wiki = WikipediaApp.getInstance().getWikiSite(); createAccountInfoClient = new CreateAccountInfoClient(); createAccountClient = new CreateAccountClient(); captchaHandler = new CaptchaHandler(this, WikipediaApp.getInstance().getWikiSite(), progressDialog, primaryContainer, getString(R.string.create_account_activity_title), getString(R.string.create_account_button)); // We enable the menu item as soon as the username and password fields are filled // Tapping does further validation validator = new Validator(this); Validator.registerAnnotation(OptionalEmail.class); validator.setValidationListener(new Validator.ValidationListener() { @Override public void onValidationSucceeded() { if (captchaHandler.isActive() && captchaHandler.token() != null) { doCreateAccount(captchaHandler.token()); } else { getCreateAccountInfo(); } } @Override public void onValidationFailed(List<ValidationError> errors) { for (ValidationError error : errors) { View view = error.getView(); String message = error.getCollatedErrorMessage(view.getContext()); if (view instanceof EditText) { //Request focus on the EditText before setting error, so that error is visible view.requestFocus(); setErrorPopup((EditText) view, message); } else { throw new RuntimeException("This should not be happening"); } } } }); passwordInput.setOnShowPasswordListener(new PasswordTextInput.OnShowPasswordClickListener() { @Override public void onShowPasswordClick(boolean visible) { passwordRepeatInput.setVisibility(visible ? View.GONE : View.VISIBLE); } }); // Don't allow user to submit registration unless they've put in a username and password new NonEmptyValidator(new NonEmptyValidator.ValidationChangedCallback() { @Override public void onValidationChanged(boolean isValid) { createAccountButton.setEnabled(isValid); } }, usernameEdit, passwordEdit); // Don't allow user to continue when they're shown a captcha until they fill it in new NonEmptyValidator(new NonEmptyValidator.ValidationChangedCallback() { @Override public void onValidationChanged(boolean isValid) { createAccountButtonCaptcha.setEnabled(isValid); } }, captchaText); createAccountButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { validator.validate(); } }); createAccountButtonCaptcha.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View v) { validator.validate(); } }); // Add listener so that when the user taps enter, it submits the captcha captchaText.setOnKeyListener(new OnKeyListener() { @Override public boolean onKey(View v, int keyCode, KeyEvent event) { if ((event.getAction() == KeyEvent.ACTION_UP) && (keyCode == KeyEvent.KEYCODE_ENTER)) { validator.validate(); return true; } return false; } }); if (savedInstanceState != null && savedInstanceState.containsKey("result")) { createAccountResult = savedInstanceState.getParcelable("result"); } findViewById(R.id.create_account_login_link).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { // already coming from LoginActivity finish(); } }); funnel = new CreateAccountFunnel(WikipediaApp.getInstance(), getIntent().getStringExtra(LOGIN_REQUEST_SOURCE)); // Only send the editing start log event if the activity is created for the first time if (savedInstanceState == null) { funnel.logStart(getIntent().getStringExtra(LOGIN_SESSION_TOKEN)); } // Set default result to failed, so we can override if it did not setResult(RESULT_ACCOUNT_NOT_CREATED); } @Override protected void setTheme() { setActionBarTheme(); } @Override protected void onSaveInstanceState(Bundle outState) { super.onSaveInstanceState(outState); outState.putParcelable("result", createAccountResult); } public void showPrivacyPolicy(View v) { FeedbackUtil.showPrivacyPolicy(this); } public void handleAccountCreationError(@NonNull String message) { FeedbackUtil.showMessage(this, message); L.w("Account creation failed with result " + message); } public void getCreateAccountInfo() { createAccountInfoClient.request(wiki, new CreateAccountInfoClient.Callback() { @Override public void success(@NonNull Call<MwQueryResponse<CreateAccountInfo>> call, @NonNull CreateAccountInfoResult result) { if (result.token() == null) { handleAccountCreationError(getString(R.string.create_account_generic_error)); } else if (result.hasCaptcha()) { captchaHandler.handleCaptcha(result.token(), new CaptchaResult(result.captchaId())); } else { doCreateAccount(result.token()); } } @Override public void failure(@NonNull Call<MwQueryResponse<CreateAccountInfo>> call, @NonNull Throwable caught) { showError(caught); L.e(caught); } }); } public void doCreateAccount(@NonNull String token) { progressDialog.show(); String email = null; if (emailEdit.getText().length() != 0) { email = emailEdit.getText().toString(); } String password = passwordEdit.getText().toString(); String repeat = passwordInput.isPasswordVisible() ? password : passwordRepeatEdit.getText().toString(); createAccountClient.request(wiki, usernameEdit.getText().toString(), password, repeat, token, email, captchaHandler.isActive() ? captchaHandler.captchaId() : "null", captchaHandler.isActive() ? captchaHandler.captchaWord() : "null", new CreateAccountClient.Callback() { @Override public void success(@NonNull Call<CreateAccountResponse> call, @NonNull final CreateAccountSuccessResult result) { if (!progressDialog.isShowing()) { // no longer attached to activity! return; } finishWithUserResult(result); } @Override public void failure(@NonNull Call<CreateAccountResponse> call, @NonNull Throwable caught) { L.e(caught.toString()); if (!progressDialog.isShowing()) { // no longer attached to activity! return; } progressDialog.dismiss(); if (caught instanceof CreateAccountException) { handleAccountCreationError(caught.getMessage()); } else { showError(caught); } } }); } @Override public void onBackPressed() { hideSoftKeyboard(this); super.onBackPressed(); } @Override public void onStop() { if (progressDialog.isShowing()) { progressDialog.dismiss(); } super.onStop(); } private void finishWithUserResult(@NonNull CreateAccountSuccessResult result) { Intent resultIntent = new Intent(); resultIntent.putExtra(CREATE_ACCOUNT_RESULT_USERNAME, result.getUsername()); resultIntent.putExtra(CREATE_ACCOUNT_RESULT_PASSWORD, passwordEdit.getText().toString()); setResult(RESULT_ACCOUNT_CREATED, resultIntent); createAccountResult = result; progressDialog.dismiss(); captchaHandler.cancelCaptcha(); funnel.logSuccess(); hideSoftKeyboard(CreateAccountActivity.this); finish(); } private void showError(@NonNull Throwable caught) { errorView.setError(caught); errorView.setVisibility(View.VISIBLE); } }