/*
* Copyright (c) 2016 Zhang Hai <Dreaming.in.Code.ZH@Gmail.com>
* All Rights Reserved.
*/
package me.zhanghai.android.douya.account.ui;
import android.accounts.Account;
import android.accounts.AccountManager;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.support.annotation.Nullable;
import android.support.design.widget.TextInputLayout;
import android.support.v4.app.Fragment;
import android.text.TextUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ProgressBar;
import android.widget.TextView;
import com.android.volley.ParseError;
import com.android.volley.VolleyError;
import butterknife.BindView;
import butterknife.ButterKnife;
import me.zhanghai.android.douya.R;
import me.zhanghai.android.douya.account.content.AuthenticateRequest;
import me.zhanghai.android.douya.account.info.AccountContract;
import me.zhanghai.android.douya.account.util.AccountUtils;
import me.zhanghai.android.douya.account.util.AuthenticatorUtils;
import me.zhanghai.android.douya.link.NotImplementedManager;
import me.zhanghai.android.douya.network.api.ApiContract.Response.Error.Codes.Token;
import me.zhanghai.android.douya.network.api.TokenRequest;
import me.zhanghai.android.douya.util.FragmentUtils;
import me.zhanghai.android.douya.util.LogUtils;
import me.zhanghai.android.douya.util.ToastUtils;
import me.zhanghai.android.douya.util.ViewUtils;
import static me.zhanghai.android.douya.account.ui.AuthenticatorActivity.AUTH_MODE_ADD;
import static me.zhanghai.android.douya.account.ui.AuthenticatorActivity.AUTH_MODE_CONFIRM;
import static me.zhanghai.android.douya.account.ui.AuthenticatorActivity.AUTH_MODE_NEW;
import static me.zhanghai.android.douya.account.ui.AuthenticatorActivity.AUTH_MODE_UPDATE;
import static me.zhanghai.android.douya.account.ui.AuthenticatorActivity.AuthMode;
public class AuthenticatorFragment extends Fragment implements AuthenticateRequest.Listener {
private static final String KEY_PREFIX = AuthenticatorFragment.class.getName() + '.';
private static final String EXTRA_AUTH_MODE = KEY_PREFIX + "auth_mode";
private static final String EXTRA_USERNAME = KEY_PREFIX + "username";
private static final String AUTH_TOKEN_TYPE = AccountContract.AUTH_TOKEN_TYPE_FRODO;
@BindView(R.id.form)
View mFormLayout;
@BindView(R.id.username_layout)
TextInputLayout mUsernameLayout;
@BindView(R.id.username)
EditText mUsernameEdit;
@BindView(R.id.password_layout)
TextInputLayout mPasswordLayout;
@BindView(R.id.password)
EditText mPasswordEdit;
@BindView(R.id.sign_in)
Button mSignInButton;
@BindView(R.id.sign_up)
Button mSignUpButton;
@BindView(R.id.progress)
ProgressBar mProgress;
private AuthenticateRequest mAuthenticateRequest;
@AuthMode
private String mAuthMode;
private String mExtraUsername;
public static AuthenticatorFragment newInstance(String authMode, String username) {
//noinspection deprecation
AuthenticatorFragment fragment = new AuthenticatorFragment();
Bundle arguments = FragmentUtils.ensureArguments(fragment);
arguments.putString(EXTRA_AUTH_MODE, authMode);
arguments.putString(EXTRA_USERNAME, username);
return fragment;
}
/**
* @deprecated Use {@link #newInstance(String, String)} instead.
*/
public AuthenticatorFragment() {}
@Override
public void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
//noinspection WrongConstant
mAuthMode = getArguments().getString(EXTRA_AUTH_MODE);
mExtraUsername = getArguments().getString(EXTRA_USERNAME);
}
@Nullable
@Override
public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container,
@Nullable Bundle savedInstanceState) {
return inflater.inflate(R.layout.authenticator_fragment, container, false);
}
@Override
public void onViewCreated(View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState);
ButterKnife.bind(this, view);
}
@Override
public void onActivityCreated(@Nullable Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
mAuthenticateRequest = AuthenticateRequest.attachTo(this);
// TODO: Make the card slide in from bottom.
updateViews(false);
if (savedInstanceState == null && !TextUtils.isEmpty(mExtraUsername)) {
mUsernameEdit.setText(mExtraUsername);
}
ViewUtils.hideTextInputLayoutErrorOnTextChange(mUsernameEdit, mUsernameLayout);
ViewUtils.hideTextInputLayoutErrorOnTextChange(mPasswordEdit, mPasswordLayout);
mPasswordEdit.setOnEditorActionListener(new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int id, KeyEvent keyEvent) {
if (id == R.id.ime_login || id == EditorInfo.IME_ACTION_DONE
|| id == EditorInfo.IME_NULL) {
authenticate();
return true;
}
return false;
}
});
mSignInButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
authenticate();
}
});
mSignUpButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
NotImplementedManager.signUp(getActivity());
}
});
Activity activity = getActivity();
switch (mAuthMode) {
case AUTH_MODE_NEW:
activity.setTitle(R.string.auth_title_new);
break;
case AUTH_MODE_ADD:
activity.setTitle(R.string.auth_title_add);
break;
case AUTH_MODE_UPDATE:
activity.setTitle(R.string.auth_title_update);
// See the source of setKeyListener(null), it just satisfies our need.
mUsernameEdit.setKeyListener(null);
mPasswordEdit.requestFocus();
break;
case AUTH_MODE_CONFIRM:
activity.setTitle(R.string.auth_title_confirm);
mUsernameEdit.setKeyListener(null);
mPasswordEdit.requestFocus();
break;
default:
throw new IllegalArgumentException();
}
}
private void authenticate() {
// Store values at the time of login attempt.
String username = mUsernameEdit.getText().toString();
String password = mPasswordEdit.getText().toString();
boolean cancel = false;
View errorView = null;
if (TextUtils.isEmpty(username)) {
mUsernameLayout.setError(getString(R.string.auth_error_empty_username));
errorView = mUsernameEdit;
cancel = true;
}
if (TextUtils.isEmpty(password)) {
mPasswordLayout.setError(getString(R.string.auth_error_empty_password));
if (errorView == null) {
errorView = mPasswordEdit;
}
cancel = true;
}
if (cancel) {
errorView.requestFocus();
} else {
mAuthenticateRequest.start(AUTH_TOKEN_TYPE, username, password);
}
}
@Override
public void onAuthenticateStarted(int requestCode) {
// FIXME
mUsernameLayout.setError(null);
mPasswordLayout.setError(null);
updateViews(true);
}
@Override
public void onAuthenticateFinished(int requestCode) {
// DISABLED: Do so only when failed, otherwise leave the progress bar running till we
// finish.
//updateSignInUpViews(true);
}
@Override
public void onAuthenticateSuccess(int requestCode,
AuthenticateRequest.RequestState requestState,
TokenRequest.Response response) {
Account account = new Account(requestState.username, AccountContract.ACCOUNT_TYPE);
switch (mAuthMode) {
case AUTH_MODE_NEW:
AccountUtils.addAccountExplicitly(account, requestState.password);
AccountUtils.setActiveAccount(account);
break;
case AUTH_MODE_ADD:
AccountUtils.addAccountExplicitly(account, requestState.password);
break;
case AUTH_MODE_UPDATE:
case AUTH_MODE_CONFIRM:
AccountUtils.setPassword(account, requestState.password);
break;
}
AccountUtils.setUserName(account, response.userName);
AccountUtils.setUserId(account, response.userId);
AccountUtils.setAuthToken(account, AUTH_TOKEN_TYPE, response.accessToken);
AccountUtils.setRefreshToken(account, AUTH_TOKEN_TYPE, response.refreshToken);
Intent intent;
switch (mAuthMode) {
case AUTH_MODE_NEW:
case AUTH_MODE_ADD:
case AUTH_MODE_UPDATE:
intent = makeSuccessIntent(requestState.username);
break;
case AUTH_MODE_CONFIRM:
intent = makeBooleanIntent(true);
break;
default:
throw new IllegalArgumentException();
}
AppCompatAccountAuthenticatorActivity activity =
(AppCompatAccountAuthenticatorActivity) getActivity();
activity.setAccountAuthenticatorResult(intent.getExtras());
activity.setResult(Activity.RESULT_OK, intent);
activity.finish();
}
@Override
public void onAuthenticateError(int requestCode, AuthenticateRequest.RequestState requestState,
VolleyError error) {
updateViews(true);
LogUtils.e(error.toString());
if (error instanceof ParseError) {
mPasswordLayout.setError(getString(R.string.auth_error_invalid_response));
} else if (error instanceof TokenRequest.Error) {
TokenRequest.Error apiError = (TokenRequest.Error) error;
String errorString = getString(apiError.getErrorStringRes());
Activity activity = getActivity();
switch (apiError.code) {
case Token.INVALID_APIKEY:
case Token.APIKEY_IS_BLOCKED:
case Token.INVALID_REQUEST_URI:
case Token.INVALID_CREDENCIAL2:
case Token.REQUIRED_PARAMETER_IS_MISSING:
case Token.CLIENT_SECRET_MISMATCH:
ToastUtils.show(errorString, activity);
startActivity(AuthenticatorUtils.makeSetApiKeyIntent((activity)));
break;
case Token.USER_HAS_BLOCKED:
case Token.USER_LOCKED:
ToastUtils.show(errorString, activity);
startActivity(AuthenticatorUtils.makeWebsiteIntent(activity));
break;
case Token.NOT_TRIAL_USER:
case Token.INVALID_USER:
mUsernameLayout.setError(errorString);
break;
default:
mPasswordLayout.setError(errorString);
break;
}
} else {
mPasswordLayout.setError(getString(R.string.auth_error_unknown));
}
}
private void updateViews(boolean animate) {
boolean authenticating = mAuthenticateRequest.isRequesting();
mFormLayout.setEnabled(!authenticating);
if (animate) {
if (authenticating) {
ViewUtils.fadeOutThenFadeIn(mFormLayout, mProgress);
} else {
ViewUtils.fadeOutThenFadeIn(mProgress, mFormLayout);
}
} else {
ViewUtils.setVisibleOrInvisible(mFormLayout, !authenticating);
ViewUtils.setVisibleOrInvisible(mProgress, authenticating);
}
}
private Intent makeSuccessIntent(String accountName) {
Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_ACCOUNT_NAME, accountName);
intent.putExtra(AccountManager.KEY_ACCOUNT_TYPE, AccountContract.ACCOUNT_TYPE);
return intent;
}
private Intent makeBooleanIntent(boolean result) {
Intent intent = new Intent();
intent.putExtra(AccountManager.KEY_BOOLEAN_RESULT, result);
return intent;
}
}