/* * The CroudTrip! application aims at revolutionizing the car-ride-sharing market with its easy, * user-friendly and highly automated way of organizing shared Trips. Copyright (C) 2015 Nazeeh Ammari, * Philipp Eichhorn, Ricarda Hohn, Vanessa Lange, Alexander Popp, Frederik Simon, Michael Weber * This program is free software: you can redistribute it and/or modify it under the terms of the GNU * Affero General Public License as published by the Free Software Foundation, either version 3 of the * License, or (at your option) any later version. * This program is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Affero General Public License for more details. * You should have received a copy of the GNU Affero General Public License along with this program. * If not, see http://www.gnu.org/licenses/. */ package org.croudtrip.activities; import android.content.Context; import android.content.Intent; import android.content.res.Resources; import android.graphics.PorterDuff; import android.os.Build; import android.os.Bundle; import android.text.TextUtils; import android.view.View; import android.view.Window; import android.view.WindowManager; import android.widget.Button; import android.widget.EditText; import android.widget.TextView; import android.widget.Toast; import com.pnikosis.materialishprogress.ProgressWheel; import org.croudtrip.R; import org.croudtrip.account.AccountManager; import org.croudtrip.api.UsersResource; import org.croudtrip.api.account.User; import org.croudtrip.api.account.UserDescription; import org.croudtrip.utils.CrashCallback; import org.croudtrip.utils.DefaultTransformer; import java.util.regex.Matcher; import java.util.regex.Pattern; import javax.inject.Inject; import retrofit.RequestInterceptor; import retrofit.RestAdapter; import retrofit.RetrofitError; import retrofit.client.Response; import retrofit.converter.JacksonConverter; import roboguice.activity.RoboActionBarActivity; import roboguice.inject.ContentView; import roboguice.inject.InjectView; import rx.functions.Action1; import timber.log.Timber; /** * Login and registration activity. * Start this activity via {@link android.app.Activity#startActivityForResult(Intent, int)}. * Returns {@link LoginActivity#RESULT_AUTHENTICATED} on logged in user. * Returns {@link LoginActivity#RESULT_SKIPPED} when login was skipped. * * @author Frederik Simon, Vanessa Lange */ @ContentView(R.layout.activity_login) public class LoginActivity extends RoboActionBarActivity { public static final int RESULT_AUTHENTICATED = 1, RESULT_SKIPPED = 2; @Inject private UsersResource usersResource; @InjectView(R.id.layout_choose) private View loginChoiceView; @InjectView(R.id.btn_register_email) private Button chooseRegisterButton; @InjectView(R.id.btn_login_with_email) private Button chooseLoginButton; @InjectView(R.id.layout_register) private View registerView; @InjectView(R.id.btn_register) private Button registerButton; @InjectView(R.id.pb_register) private ProgressWheel registerProgressBar; @InjectView(R.id.et_firstName) private EditText registerFirstName; @InjectView(R.id.et_lastName) private EditText registerLastName; @InjectView(R.id.et_password) private EditText registerPassword; @InjectView(R.id.et_email) private EditText registerEmail; @InjectView(R.id.layout_login) private View loginView; @InjectView(R.id.btn_login) private Button loginButton; @InjectView(R.id.pb_login) private ProgressWheel loginProgressBar; @InjectView(R.id.tv_invalid_login) private TextView loginErrorTextView; @InjectView(R.id.et_login_email) private EditText loginEmail; @InjectView(R.id.et_login_password) private EditText loginPassword; private int animationDuration; private View activeView; private static final Pattern VALID_EMAIL_ADDRESS_REGEX = Pattern.compile("^[A-Z0-9._%+-]+@[A-Z0-9.-]+\\.[A-Z]{2,6}$", Pattern.CASE_INSENSITIVE); //************************** Methods ******************************// @Override protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); //Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar); //setSupportActionBar(toolbar); if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) { Window window = getWindow(); window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS); window.setStatusBarColor(getResources().getColor(R.color.primary_dark)); } Resources res = getResources(); registerFirstName.getBackground().setColorFilter(res.getColor(R.color.primary_light), PorterDuff.Mode.SRC_ATOP); registerLastName.getBackground().setColorFilter(res.getColor(R.color.primary_light), PorterDuff.Mode.SRC_ATOP); registerPassword.getBackground().setColorFilter(res.getColor(R.color.primary_light), PorterDuff.Mode.SRC_ATOP); registerEmail.getBackground().setColorFilter(res.getColor(R.color.primary_light), PorterDuff.Mode.SRC_ATOP); loginEmail.getBackground().setColorFilter(res.getColor(R.color.primary_light), PorterDuff.Mode.SRC_ATOP); loginPassword.getBackground().setColorFilter(res.getColor(R.color.primary_light), PorterDuff.Mode.SRC_ATOP); // Animation to blend over from the login choice view to registerButton or login view animationDuration = res.getInteger(android.R.integer.config_shortAnimTime); // Remember the currently shown layout/view activeView = loginChoiceView; // The user can decide to login (with registerEmail and password)... chooseLoginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { showView(loginView); } }); // ... or to registerButton with a new account chooseRegisterButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { showView(registerView); } }); // REGISTER LAYOUT registerButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { //Check if first and last name are entered if (TextUtils.isEmpty(registerFirstName.getText().toString()) || TextUtils.isEmpty(registerLastName.getText().toString())) { Toast.makeText(getApplication().getApplicationContext(), getResources().getString(R.string.first_last_mandatory), Toast.LENGTH_SHORT).show(); } //Check the validity of the entered email address using Regex else if (!validate(registerEmail.getText().toString())) { Toast.makeText(getApplication().getApplicationContext(), getResources().getString(R.string.email_invalid), Toast.LENGTH_SHORT).show(); } else registerUser( registerFirstName.getText().toString(), registerLastName.getText().toString(), registerEmail.getText().toString(), registerPassword.getText().toString() ); } }); // LOGIN LAYOUT loginButton.setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { loginUser(loginEmail.getText().toString(), loginPassword.getText().toString()); } }); // SKIP login findViewById(R.id.skip).setOnClickListener(new View.OnClickListener() { @Override public void onClick(View view) { setResult(RESULT_SKIPPED); finish(); } }); } @Override public void onBackPressed() { if (activeView.equals(loginChoiceView)) { // Perform a normal back super.onBackPressed(); } else { // If the user decided to register or login before, animate now back to // the choice screen showView(loginChoiceView); } } private void showView(final View view) { activeView.setVisibility(View.GONE); view.setVisibility(View.VISIBLE); activeView = view; /* FIXME: translation is not working // Make the new view visible view.setAlpha(0f); view.setVisibility(View.VISIBLE); // Put views slightly below screen to let them "pop up" afterwards (not the choice screen) float animatedTranslationY = (view.equals(loginChoiceView)) ? 0 : 100; view.setTranslationY(-animatedTranslationY); // Show the new view view.animate() .setStartDelay(animationDuration / 2) .alpha(1f) .translationY(animatedTranslationY) .setDuration(animationDuration) .setListener(null); // Hide the current view activeView.animate() .alpha(0f) .setDuration(animationDuration) .translationY((activeView.equals(loginChoiceView)) ? 200 : 0) .setListener(new AnimatorListenerAdapter() { @Override public void onAnimationEnd(Animator animation) { activeView.setVisibility(View.GONE); activeView = view; } }); */ } /** * Registers the user with the given data at the server * * @param firstName the user's first name * @param lastName the user's last name * @param email the user's registerEmail address * @param password the user's password */ private void registerUser(final String firstName, final String lastName, final String email, final String password) { // UI: Disable register button and show progress bar registerButton.setEnabled(false); registerProgressBar.setVisibility(View.VISIBLE); // Create a new user on the server UserDescription userDescription = new UserDescription(email, firstName, lastName, password); usersResource.registerUser(userDescription) .compose(new DefaultTransformer<User>()) .subscribe(new Action1<User>() { @Override public void call(User user) { // REGISTER SUCCESS // UI: Hide progress bar registerProgressBar.setVisibility(View.GONE); Timber.i("Successfully registered user with registerEmail " + user.getEmail()); // Finally log the user in locally and start the main app loginAndRedirect(user, password); } }, new CrashCallback(this, "failed to register user", new Action1<Throwable>() { @Override public void call(Throwable throwable) { // UI: Re-enable register button and hide progress bar registerButton.setEnabled(true); registerProgressBar.setVisibility(View.GONE); Response response = ((RetrofitError) throwable).getResponse(); String message; if (response != null && response.getStatus() == 409) { // Conflict message = getString(R.string.registration_error_conflict); } else { message = getString(R.string.registration_error_general); } // Show an error message Toast.makeText(LoginActivity.this, message, Toast.LENGTH_LONG).show(); Timber.e("Registration failed with error:\n" + throwable.getMessage()); } })); } /** * Logs a user in by connecting to the server and checking the given registerEmail and password * * @param email the user's registerEmail address * @param password the user's password */ private void loginUser(final String email, final String password) { // UI: Show progress bar, disable login button loginButton.setEnabled(false); loginProgressBar.setVisibility(View.VISIBLE); loginErrorTextView.setVisibility(View.GONE); // Create new adapter to set "custom" auth header (user not officially logged in yet) UsersResource usersResource = new RestAdapter.Builder() .setEndpoint(getString(R.string.server_address)) .setRequestInterceptor(new RequestInterceptor() { @Override public void intercept(RequestFacade request) { AccountManager.addAuthorizationHeader(email, password, request); } }) .setConverter(new JacksonConverter()) .build() .create(UsersResource.class); // Make the actual call to the server to receive the correct user object usersResource.getUser() .compose(new DefaultTransformer<User>()) .subscribe(new Action1<User>() { @Override public void call(User user) { // LOGIN SUCCESS Timber.d("Successful Login as " + user.getFirstName() + " " + user.getLastName()); // UI: Hide progress bar loginProgressBar.setVisibility(View.GONE); // Finally log the user in locally and start the main app loginAndRedirect(user, password); } }, new CrashCallback(this, "failed to get user") { @Override public void call(Throwable throwable) { super.call(throwable); // ERROR // UI: Hide progress bar, enable login button again loginProgressBar.setVisibility(View.GONE); loginButton.setEnabled(true); Response response = ((RetrofitError) throwable).getResponse(); if (response != null && response.getStatus() == 401) { // Not Authorized // Show error message for invalid login loginErrorTextView.setVisibility(View.VISIBLE); } else { // Show an error for general errors e.g. connection issues String errorMessage = LoginActivity.this.getString(R.string.login_error_general); Toast.makeText(LoginActivity.this, errorMessage, Toast.LENGTH_LONG).show(); } Timber.e("Logging the user in failed with error:\n" + throwable.getMessage()); } }); } /** * After having been authorized by the server, this method logs the user in locally and * redirects him to the main app. * * @param user the user to log in * @param password the user's password */ private void loginAndRedirect(User user, String password) { Context appContext = LoginActivity.this.getApplicationContext(); AccountManager.login(appContext, user, password); Intent returnIntent = getIntent(); if (getParent() == null) { setResult(RESULT_OK, returnIntent); } else { getParent().setResult(RESULT_AUTHENTICATED, returnIntent); } finish(); } //This method is used to check for email addres validity public static boolean validate(String emailStr) { Matcher matcher = VALID_EMAIL_ADDRESS_REGEX .matcher(emailStr); return matcher.find(); } }