/**
* Copyright (C) 2013 - 2015 the enviroCar community
* <p>
* This file is part of the enviroCar app.
* <p>
* The enviroCar app is free software: you can redistribute it and/or
* modify it under the terms of the GNU General Public License as published
* by the Free Software Foundation, either version 3 of the License, or
* (at your option) any later version.
* <p>
* The enviroCar app 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 General
* Public License for more details.
* <p>
* You should have received a copy of the GNU General Public License along
* with the enviroCar app. If not, see http://www.gnu.org/licenses/.
*/
package org.envirocar.app.view;
import android.animation.Animator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.graphics.Point;
import android.os.Bundle;
import android.support.design.widget.Snackbar;
import android.support.v7.widget.CardView;
import android.support.v7.widget.Toolbar;
import android.text.TextUtils;
import android.view.Display;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.view.animation.Animation;
import android.view.animation.AnimationUtils;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.ListView;
import android.widget.TextView;
import com.afollestad.materialdialogs.MaterialDialog;
import org.envirocar.app.R;
import org.envirocar.app.handler.TermsOfUseManager;
import org.envirocar.app.handler.TrackDAOHandler;
import org.envirocar.app.handler.UserHandler;
import org.envirocar.app.view.utils.ECAnimationUtils;
import org.envirocar.app.views.TypefaceEC;
import org.envirocar.core.dao.TrackDAO;
import org.envirocar.core.entity.TermsOfUse;
import org.envirocar.core.entity.User;
import org.envirocar.core.entity.UserImpl;
import org.envirocar.core.exception.DataUpdateFailureException;
import org.envirocar.core.exception.ResourceConflictException;
import org.envirocar.core.injection.BaseInjectorActivity;
import org.envirocar.core.logging.Logger;
import org.envirocar.remote.DAOProvider;
import java.util.concurrent.TimeUnit;
import javax.inject.Inject;
import butterknife.ButterKnife;
import butterknife.InjectView;
import butterknife.OnClick;
import rx.Scheduler;
import rx.Subscriber;
import rx.Subscription;
import rx.android.schedulers.AndroidSchedulers;
import rx.functions.Action0;
import rx.schedulers.Schedulers;
/**
* TODO JavaDoc
*
* @author dewall
*/
public class LoginActivity extends BaseInjectorActivity {
private static final Logger LOG = Logger.getLogger(LoginActivity.class);
@InjectView(R.id.activity_login_toolbar)
protected Toolbar mToolbar;
@InjectView(R.id.activity_login_exp_toolbar)
protected Toolbar mExpToolbar;
@InjectView(R.id.activity_login_logo_dump)
protected View mLogoView;
@InjectView(R.id.activity_login_exp_toolbar_content)
protected View mExpToolbarContent;
@InjectView(R.id.activity_login_account_image)
protected ImageView mAccountImage;
@InjectView(R.id.activity_login_account_name)
protected TextView mAccountName;
@InjectView(R.id.activity_account_exp_toolbar_tracknumber)
protected TextView mGlobalTrackNumber;
@InjectView(R.id.activity_account_exp_toolbar_local_tracknumber)
protected TextView mLocalTrackNumber;
@InjectView(R.id.activity_account_exp_toolbar_remote_tracknumber)
protected TextView mRemoteTrackNumber;
@InjectView(R.id.activity_login_card)
protected CardView mLoginCard;
@InjectView(R.id.activity_account_login_card_username_text)
protected EditText mLoginUsername;
@InjectView(R.id.activity_account_login_card_password_text)
protected EditText mLoginPassword;
@InjectView(R.id.activity_register_card)
protected CardView mRegisterCard;
@InjectView(R.id.activity_account_register_email_input)
protected EditText mRegisterEmail;
@InjectView(R.id.activity_account_register_username_input)
protected EditText mRegisterUsername;
@InjectView(R.id.activity_account_register_password_input)
protected EditText mRegisterPassword;
@InjectView(R.id.activity_account_register_password2_input)
protected EditText mRegisterPassword2;
@InjectView(R.id.activity_account_statistics_no_statistics_info)
protected View mNoStatisticsInfo;
@InjectView(R.id.activity_account_statistics_listview)
protected ListView mStatisticsListView;
@InjectView(R.id.activity_account_statistics_progress)
protected View mStatisticsProgressView;
@Inject
protected UserHandler mUserManager;
@Inject
protected DAOProvider mDAOProvider;
@Inject
protected TermsOfUseManager mTermsOfUseManager;
@Inject
protected TrackDAOHandler mTrackDAOHandler;
private final Scheduler.Worker mMainThreadWorker = AndroidSchedulers
.mainThread().createWorker();
private final Scheduler.Worker mBackgroundWorker = Schedulers
.newThread().createWorker();
private Subscription mLoginSubscription;
private Subscription mRegisterSubscription;
private Subscription mTermsOfUseSubscription;
private Subscription mStatisticsDownloadSubscription;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
// Inject the Views.
ButterKnife.inject(this);
TypefaceEC.applyCustomFont((ViewGroup) mAccountName.getParent(), TypefaceEC.Raleway(this));
// Initializes the Toolbar.
setSupportActionBar(mToolbar);
getSupportActionBar().setTitle("Account");
getSupportActionBar().setDisplayShowHomeEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
mLoginCard.setVisibility(View.GONE);
mStatisticsListView.setVisibility(View.GONE);
mExpToolbarContent.setVisibility(View.GONE);
mLogoView.setVisibility(View.INVISIBLE);
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
if (item.getItemId() == android.R.id.home) {
if (mRegisterCard.getVisibility() == View.VISIBLE) {
animateViewTransition(mRegisterCard, R.anim.translate_slide_out_right_card, true);
animateViewTransition(mLoginCard, R.anim.translate_slide_in_left_card, false);
} else {
finish();
}
} else if (item.getTitle().equals("Logout")) {
new MaterialDialog.Builder(this)
.title(R.string.activity_login_logout_dialog_title)
.content(R.string.activity_login_logout_dialog_content)
.positiveText(R.string.ok)
.negativeText(R.string.cancel)
.callback(new MaterialDialog.ButtonCallback() {
@Override
public void onPositive(MaterialDialog dialog) {
logOut();
}
})
.build()
.show();
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
menu.add("Logout");
return super.onCreateOptionsMenu(menu);
}
@Override
public void onBackPressed() {
if (mRegisterCard != null && mRegisterCard.getVisibility() == View.VISIBLE) {
animateViewTransition(mRegisterCard, R.anim.translate_slide_out_right_card, true);
animateViewTransition(mLoginCard, R.anim.translate_slide_in_left_card, false);
} else {
super.onBackPressed();
}
}
@Override
protected void onPostResume() {
super.onPostResume();
if (mExpToolbar.getVisibility() == View.GONE)
mMainThreadWorker.schedule(
() -> {
if (!mUserManager.isLoggedIn()) {
slideInLoginCard();
}
expandExpToolbarToHalfScreen();
}, 300, TimeUnit.MILLISECONDS);
}
@Override
protected void onDestroy() {
super.onDestroy();
// If a login process is in progress, then
// unsubscribe the subscription and finish the thread.
if (mLoginSubscription != null && !mLoginSubscription.isUnsubscribed()) {
mLoginSubscription.unsubscribe();
mLoginSubscription = null;
}
// same for the registration process.
if (mRegisterSubscription != null && mRegisterSubscription.isUnsubscribed()) {
mRegisterSubscription.unsubscribe();
mRegisterSubscription = null;
}
}
@OnClick(R.id.activity_account_login_card_login_button)
protected void onLoginButtonClicked() {
// Reset errors.
mLoginUsername.setError(null);
mLoginPassword.setError(null);
// Store values at the time of the login attempt.
String username = mLoginUsername.getText().toString();
String password = mLoginPassword.getText().toString();
View focusView = null;
// Check for a valid password.
if (password == null || password.isEmpty() || password.equals("")) {
mLoginPassword.setError(getString(R.string.error_field_required));
focusView = mLoginPassword;
}
// Check if the password is too short.
else if (password.length() < 6) {
mLoginPassword.setError(getString(R.string.error_invalid_password));
focusView = mLoginPassword;
}
// Check for a valid username.
if (username == null || username.isEmpty() || username.equals("")) {
mLoginUsername.setError(getString(R.string.error_field_required));
focusView = mLoginUsername;
}
if (focusView != null) {
// There was an error; don't attempt login and focus the first
// form field with an error.
focusView.requestFocus();
}
// If the input values are valid, then try to login.
else {
// hide the keyboard
InputMethodManager imm =
(InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mLoginPassword.getWindowToken(), 0);
// Create a dialog indicating the log in process.
final MaterialDialog dialog = new MaterialDialog.Builder(LoginActivity.this)
.title(R.string.activity_login_logging_in_dialog_title)
.progress(true, 0)
.cancelable(false)
.show();
mLoginSubscription = mBackgroundWorker.schedule(() -> {
mUserManager.logIn(username, password, new UserHandler.LoginCallback() {
@Override
public void onSuccess(User user) {
dialog.dismiss();
// Successfully logged in.
mMainThreadWorker.schedule(() -> {
// If any error occurs, then set the focus on the error.
if (user == null) {
if (mLoginUsername.getError() != null)
mLoginUsername.requestFocus();
else
mLoginPassword.requestFocus();
return;
}
// First, show a snackbar.
Snackbar.make(mExpToolbar,
String.format(getResources().getString(
R.string.welcome_message), user.getUsername()),
Snackbar.LENGTH_LONG)
.show();
// TODO: update the UI
updateView(true);
// Then ask for terms of use acceptance.
askForTermsOfUseAcceptance();
});
}
@Override
public void onPasswordIncorrect(String password) {
dialog.dismiss();
mMainThreadWorker.schedule(() ->
mLoginPassword.setError(
getString(R.string.error_incorrect_password)));
}
@Override
public void onUnableToCommunicateServer() {
dialog.dismiss();
mMainThreadWorker.schedule(() ->
mLoginPassword.setError(
getString(R.string.error_host_not_found)));
}
});
});
}
}
private void askForTermsOfUseAcceptance() {
// Unsubscribe before issueing a new request.
if(mTermsOfUseSubscription != null && !mTermsOfUseSubscription.isUnsubscribed())
mTermsOfUseSubscription.unsubscribe();
mTermsOfUseSubscription = mTermsOfUseManager.verifyTermsOfUse(LoginActivity.this)
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(new Subscriber<TermsOfUse>() {
@Override
public void onStart() {
LOG.info("onStart() verifying terms of use");
}
@Override
public void onCompleted() {
LOG.info("onCompleted() verifying terms of use");
}
@Override
public void onError(Throwable e) {
LOG.warn(e.getMessage(), e);
}
@Override
public void onNext(TermsOfUse termsOfUse) {
LOG.info(String.format(
"User has accepted the terms of use -> [%s]",
termsOfUse.getIssuedDate()));
}
});
}
@OnClick(R.id.activity_account_register_button)
protected void onRegisterAccountButtonClicked() {
mRegisterUsername.setError(null);
mRegisterEmail.setError(null);
mRegisterPassword.setError(null);
mRegisterPassword2.setError(null);
// We do not want to have dublicate registration processes.
if (mRegisterSubscription != null && !mRegisterSubscription.isUnsubscribed())
return;
// Get all the values of the edittexts
final String username = mRegisterUsername.getText().toString();
final String email = mRegisterEmail.getText().toString();
final String password = mRegisterPassword.getText().toString();
final String password2 = mRegisterPassword2.getText().toString();
View focusView = null;
// Check for valid passwords.
if (password == null || password.isEmpty() || password.equals("")) {
mRegisterPassword.setError(getString(R.string.error_field_required));
focusView = mRegisterPassword;
} else if (mRegisterPassword.length() < 6) {
mRegisterPassword.setError(getString(R.string.error_invalid_password));
focusView = mRegisterPassword;
}
// check if the password confirm is empty
if (password2 == null || password2.isEmpty() || password2.equals("")) {
mRegisterPassword2.setError(getString(R.string.error_field_required));
focusView = mRegisterPassword2;
}
// Check for a valid email address.
if (TextUtils.isEmpty(email)) {
mRegisterEmail.setError(getString(R.string.error_field_required));
focusView = mRegisterEmail;
} else if (!email.matches("^[_A-Za-z0-9-\\+]+(\\.[_A-Za-z0-9-]+)*@[A-Za-z0-9-]+(\\" +
".[A-Za-z0-9]+)*(\\.[A-Za-z]{2,})$")) {
mRegisterEmail.setError(getString(R.string.error_invalid_email));
focusView = mRegisterEmail;
}
// check for valid username
if (username == null || username.isEmpty() || username.equals("")) {
mRegisterUsername.setError(getString(R.string.error_field_required));
focusView = mRegisterUsername;
} else if (username.length() < 6) {
mRegisterUsername.setError(getString(R.string.error_invalid_username));
focusView = mRegisterUsername;
}
// check if passwords match
if (!password.equals(password2)) {
mRegisterPassword2.setError(getString(R.string.error_passwords_not_matching));
focusView = mRegisterPassword2;
}
// Check if an error occured.
if (focusView != null) {
// There was an error; don't attempt register and focus the first
// form field with an error.
focusView.requestFocus();
} else {
//hide the keyboard
InputMethodManager imm = (InputMethodManager) getSystemService(
Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(mRegisterPassword.getWindowToken(), 0);
// TODO
// mRegisterStatusMessageView.setText(R.string.register_progress_signing_in);
// Show a progress spinner, and kick off a pground task to
// perform the user register attempt.
final MaterialDialog dialog = new MaterialDialog.Builder(LoginActivity.this)
.title(R.string.register_progress_signing_in)
.progress(true, 0)
.cancelable(false)
.show();
mBackgroundWorker.schedule(() -> {
try {
User newUser = new UserImpl(username, password);
newUser.setMail(email);
mDAOProvider.getUserDAO().createUser(newUser);
// Successfully created the user
mMainThreadWorker.schedule(() -> {
// Set the new user as the logged in user.
mUserManager.setUser(newUser);
// Update the view, i.e., hide the registration card and show the profile
// page.
updateView(true);
// Dismiss the progress dialog.
dialog.dismiss();
// Show a snackbar containing a welcome message.
Snackbar.make(mExpToolbar, String.format(
getResources().getString(R.string.welcome_message),
username), Snackbar.LENGTH_LONG).show();
});
askForTermsOfUseAcceptance();
} catch (ResourceConflictException e) {
LOG.warn(e.getMessage(), e);
// Show an error. // TODO show error in a separate error text view.
mMainThreadWorker.schedule(() -> {
mRegisterUsername.setError(getString(
R.string.error_username_already_in_use));
mRegisterEmail.setError(getString(
R.string.error_email_already_in_use));
mRegisterUsername.requestFocus();
});
// Dismuss the progress dialog.
dialog.dismiss();
} catch (DataUpdateFailureException e) {
LOG.warn(e.getMessage(), e);
// Show an error.
mMainThreadWorker.schedule(() -> {
mRegisterUsername.setError(getString(R.string.error_host_not_found));
mRegisterUsername.requestFocus();
});
// Dismuss the progress dialog.
dialog.dismiss();
}
});
}
}
private void logOut() {
if (mUserManager.isLoggedIn()) {
final MaterialDialog dialog = new MaterialDialog.Builder(LoginActivity.this)
.title(R.string.activity_login_logout_progress_dialog_title)
.content(R.string.activity_login_logout_progress_dialog_content)
.progress(true, 0)
.cancelable(false)
.build();
dialog.show();
User user = mUserManager.getUser();
mBackgroundWorker.schedule(() -> {
// Log out the user
mUserManager.logOut();
// Finally, delete all tracks that are associated to the previous user.
mTrackDAOHandler.deleteAllRemoteTracksLocally();
// Close the dialog.
dialog.dismiss();
mMainThreadWorker.schedule(() -> {
// Show a snackbar that indicates the finished logout
Snackbar.make(mExpToolbarContent,
String.format(getString(R.string.goodbye_message), user
.getUsername()),
Snackbar.LENGTH_LONG).show();
// Slide in the login card.
slideInLoginCard();
});
});
// hide the content of the list view and finally delete the adapter.
animateHideView(mStatisticsListView, R.anim.fade_out,
() -> mStatisticsListView.setAdapter(null));
// hide the content of the exp toolbar and finally slide in the login card.
animateHideView(mExpToolbarContent, R.anim.fade_out, null);
// hide the no statistics info if it is visible.
if (mNoStatisticsInfo.getVisibility() == View.VISIBLE) {
animateHideView(mNoStatisticsInfo, R.anim.fade_out, null);
}
ECAnimationUtils.animateHideView(this, mLogoView, R.anim.fade_out);
// hide the no statistics info if it is visible.
if (mStatisticsProgressView.getVisibility() == View.VISIBLE) {
animateHideView(mStatisticsProgressView, R.anim.fade_out, null);
if (mStatisticsDownloadSubscription != null &&
!mStatisticsDownloadSubscription.isUnsubscribed()) {
mStatisticsDownloadSubscription.unsubscribe();
mStatisticsDownloadSubscription = null;
}
}
}
}
/**
* OnClick annotated function that gets invoked when the register button on the login card
* gets clicked.
*/
@OnClick(R.id.activity_account_login_card_register_button)
protected void onRegisterButtonClicked() {
// When the register button was clicked, then replace the login card with the
// registration card.
animateViewTransition(mLoginCard, R.anim.translate_slide_out_left_card, true);
animateViewTransition(mRegisterCard, R.anim.translate_slide_in_right_card, false);
}
private void updateView(boolean isLoggedIn) {
if (isLoggedIn) {
// First, show all user informations.
final User user = mUserManager.getUser();
mAccountName.setText(user.getUsername());
// Animate the fade in progress of the Exp Toolbar content.
if (mExpToolbarContent.getVisibility() != View.VISIBLE)
animateViewTransition(mExpToolbarContent, R.anim
.fade_in, false);
// If the login card is visible, then slide it out.
if (mLoginCard.getVisibility() == View.VISIBLE) {
slideOutLoginCard();
}
// If the register card is visible, then slide it out.
if (mRegisterCard.getVisibility() == View.VISIBLE) {
slideOutRegisterCard();
}
// // If the statistics progess view is not visible, then fade it in.
// if (mStatisticsProgressView.getVisibility() != View.VISIBLE) {
// animateViewTransition(mStatisticsProgressView, R.anim.fade_in, false);
// }
if(mLogoView.getVisibility() != View.VISIBLE){
ECAnimationUtils.animateShowView(this, mLogoView, R.anim.fade_in);
}
// Update the Gravatar image.
mUserManager.getGravatarBitmapObservable()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(bitmap -> {
if (mAccountImage != null && mAccountImage.getVisibility() == View.VISIBLE)
mAccountImage.setImageBitmap(bitmap);
});
// update the local track count.
mTrackDAOHandler.getLocalTrackCount()
.subscribeOn(Schedulers.io())
.observeOn(AndroidSchedulers.mainThread())
.subscribe(integer -> {
mLocalTrackNumber.setText("" + integer);
});
// Update the new values of the exp toolbar content.
mBackgroundWorker.schedule(() -> {
try {
final TrackDAO trackDAO = mDAOProvider.getTrackDAO();
final int totalTrackCount = trackDAO.getTotalTrackCount();
final int userTrackCount = trackDAO.getUserTrackCount();
String.format("%s (%s)", userTrackCount, totalTrackCount);
mMainThreadWorker.schedule(() -> {
mGlobalTrackNumber.setText(Integer.toString(totalTrackCount));
mRemoteTrackNumber.setText(Integer.toString(userTrackCount));
});
} catch (Exception e) {
LOG.warn(e.getMessage(), e);
}
});
// animateHideView(mStatisticsProgressView, R.anim.fade_out,
// () -> animateViewTransition(mNoStatisticsInfo, R
// .anim.fade_in, false)));
// Observable.just(true)
// .map(aBoolean -> {
// try {
// return mDAOProvider
// .getUserStatisticsDAO()
// .getUserStatistics(user)
// .getStatistics();
// } catch (UnauthorizedException e) {
// LOG.warn("The user is unauthorized to access this endpoint.", e);
// } catch (DataRetrievalFailureException e) {
// LOG.warn("Error while trying to retrive user statistics.", e);
// mMainThreadWorker.schedule(() ->
// animateHideView(mStatisticsProgressView, R.anim.fade_out,
// () -> animateViewTransition(mNoStatisticsInfo, R
// .anim.fade_in, false)));
// }
// return null;
// })
// .subscribeOn(Schedulers.io())
// .observeOn(AndroidSchedulers.mainThread())
// .subscribe(statistics -> {
// if (statistics == null || statistics.isEmpty()) {
// animateHideView(mStatisticsProgressView, R.anim.fade_out,
// () -> animateViewTransition(mNoStatisticsInfo,
// R.anim.fade_in, false));
// } else {
// mStatisticsListView.setAdapter(new UserStatisticsAdapter
// (LoginActivity.this,
// new ArrayList<>(statistics.values())));
// animateHideView(mStatisticsProgressView, R.anim.fade_out,
// () -> animateViewTransition(mStatisticsListView,
// R.anim.fade_in, false));
// }
// });
}
}
/**
* Applies an animation on the given view.
*
* @param view the view to apply the animation on.
* @param animResource the animation resource.
* @param hide should the view be hid?
*/
private void animateViewTransition(final View view, int animResource, boolean hide) {
Animation animation = AnimationUtils.loadAnimation(this, animResource);
if (hide) {
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// nothing to do..
}
@Override
public void onAnimationEnd(Animation animation) {
view.setVisibility(View.GONE);
}
@Override
public void onAnimationRepeat(Animation animation) {
// nothing to do..
}
});
view.startAnimation(animation);
} else {
view.setVisibility(View.VISIBLE);
view.startAnimation(animation);
}
}
private void animateHideView(View view, int animResource, Action0 action) {
Animation animation = AnimationUtils.loadAnimation(this, animResource);
animation.setAnimationListener(new Animation.AnimationListener() {
@Override
public void onAnimationStart(Animation animation) {
// nothing to do..
}
@Override
public void onAnimationEnd(Animation animation) {
view.setVisibility(View.GONE);
if (action != null) {
action.call();
}
}
@Override
public void onAnimationRepeat(Animation animation) {
// nothing to do..
}
});
view.startAnimation(animation);
}
private void slideInLoginCard() {
Animation animation = AnimationUtils.loadAnimation(this,
R.anim.translate_in_bottom_login_card);
mLoginCard.setVisibility(View.VISIBLE);
mLoginCard.startAnimation(animation);
}
/**
* Animtes the hiding process by sliding the login card out at the bottom.
*/
private void slideOutLoginCard() {
animateViewTransition(mLoginCard, R.anim.translate_out_bottom_card, true);
}
/**
* Animtes the hiding process by sliding the register card out at the bottom.
*/
private void slideOutRegisterCard() {
animateViewTransition(mRegisterCard, R.anim.translate_out_bottom_card, true);
}
/**
* Expands the expanding toolbar to the a specific amount of the screensize.
*/
private void expandExpToolbarToHalfScreen() {
mExpToolbar.setVisibility(View.VISIBLE);
Display display = getWindowManager().getDefaultDisplay();
Point size = new Point();
display.getSize(size);
int height = size.y;
ValueAnimator animator = createSlideAnimator(0, (int) height / 3);
animator.setDuration(600);
animator.addListener(new Animator.AnimatorListener() {
@Override
public void onAnimationStart(Animator animation) {
// nothing to do..
}
@Override
public void onAnimationEnd(Animator animation) {
updateView(mUserManager.isLoggedIn());
}
@Override
public void onAnimationCancel(Animator animation) {
}
@Override
public void onAnimationRepeat(Animator animation) {
}
});
animator.start();
}
/**
* Constructs and returns a ValueAnimator that animates between int values.
*
* @param start start value
* @param end end value
* @return the ValueAnimator that animates the desired animation.
*/
private ValueAnimator createSlideAnimator(int start, int end) {
ValueAnimator animator = ValueAnimator.ofInt(start, end);
animator.addUpdateListener(animation -> {
int value = (Integer) animation.getAnimatedValue();
ViewGroup.LayoutParams layoutParams = mExpToolbar.getLayoutParams();
layoutParams.height = value;
mExpToolbar.setLayoutParams(layoutParams);
});
return animator;
}
}