Android sign up

This commit is contained in:
Gabriel Calero 2018-09-21 14:58:11 -03:00
parent 32da2cbef6
commit f4be767d22
10 changed files with 536 additions and 1 deletions

View file

@ -26,6 +26,7 @@
QAndroidJniObject __interfaceActivity;
QAndroidJniObject __loginCompletedListener;
QAndroidJniObject __signupCompletedListener;
QAndroidJniObject __loadCompleteListener;
QAndroidJniObject __usernameChangedListener;
void tempMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
@ -306,6 +307,58 @@ Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *en
Q_ARG(const QString&, username), Q_ARG(const QString&, password));
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeInitAfterAppLoaded(JNIEnv* env, jobject obj) {
AndroidHelper::instance().moveToThread(qApp->thread());
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_nativeSignup(JNIEnv *env, jobject instance,
jstring email_, jstring username_,
jstring password_) {
const char *c_email = env->GetStringUTFChars(email_, 0);
const char *c_username = env->GetStringUTFChars(username_, 0);
const char *c_password = env->GetStringUTFChars(password_, 0);
QString email = QString(c_email);
QString username = QString(c_username);
QString password = QString(c_password);
env->ReleaseStringUTFChars(email_, c_email);
env->ReleaseStringUTFChars(username_, c_username);
env->ReleaseStringUTFChars(password_, c_password);
__signupCompletedListener = QAndroidJniObject(instance);
// disconnect any previous callback
QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::handleSignupCompleted, nullptr, nullptr);
QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::handleSignupFailed, nullptr, nullptr);
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::handleSignupCompleted, []() {
jboolean jSuccess = (jboolean) true;
if (__signupCompletedListener.isValid()) {
__signupCompletedListener.callMethod<void>("handleSignupCompleted", "()V", jSuccess);
}
});
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::handleSignupFailed, [](QString errorString) {
jboolean jSuccess = (jboolean) false;
jstring jError = QAndroidJniObject::fromString(errorString).object<jstring>();
if (__signupCompletedListener.isValid()) {
QAndroidJniObject string = QAndroidJniObject::fromString(errorString);
__signupCompletedListener.callMethod<void>("handleSignupFailed", "(Ljava/lang/String;)V", string.object<jstring>());
}
});
AndroidHelper::instance().signup(email, username, password);
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_nativeCancelSignup(JNIEnv *env, jobject instance) {
QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::handleSignupCompleted, nullptr, nullptr);
QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::handleSignupFailed, nullptr, nullptr);
__signupCompletedListener = nullptr;
}
JNIEXPORT jboolean JNICALL
Java_io_highfidelity_hifiinterface_fragment_FriendsFragment_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
auto accountManager = DependencyManager::get<AccountManager>();

View file

@ -65,6 +65,7 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
private native void nativeEnterBackground();
private native void nativeEnterForeground();
private native long nativeOnExitVr();
private native void nativeInitAfterAppLoaded();
private AssetManager assetManager;
@ -335,6 +336,9 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
if (nativeEnterBackgroundCallEnqueued) {
nativeEnterBackground();
}
runOnUiThread(() -> {
nativeInitAfterAppLoaded();
});
}
public void performHapticFeedback(int duration) {

View file

@ -25,6 +25,7 @@ import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
@ -33,12 +34,14 @@ import io.highfidelity.hifiinterface.fragment.FriendsFragment;
import io.highfidelity.hifiinterface.fragment.HomeFragment;
import io.highfidelity.hifiinterface.fragment.LoginFragment;
import io.highfidelity.hifiinterface.fragment.PolicyFragment;
import io.highfidelity.hifiinterface.fragment.SignupFragment;
import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
LoginFragment.OnLoginInteractionListener,
HomeFragment.OnHomeInteractionListener,
FriendsFragment.OnHomeInteractionListener {
FriendsFragment.OnHomeInteractionListener,
SignupFragment.OnSignupInteractionListener {
private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
public static final String DEFAULT_FRAGMENT = "Home";
@ -139,6 +142,12 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
loadFragment(fragment, getString(R.string.login), getString(R.string.tagFragmentLogin), true);
}
private void loadSignupFragment() {
Fragment fragment = SignupFragment.newInstance();
loadFragment(fragment, getString(R.string.signup), getString(R.string.tagFragmentSignup), true);
}
private void loadPrivacyPolicyFragment() {
Fragment fragment = PolicyFragment.newInstance();
@ -307,6 +316,22 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
}
}
@Override
public void onLoginRequested() {
loadLoginFragment();
}
@Override
public void onSignupRequested() {
loadSignupFragment();
}
@Override
public void onSignupCompleted() {
Toast.makeText(this, "Sign up succeeded", Toast.LENGTH_SHORT).show();
loadLoginFragment();
}
public void handleUsernameChanged(String username) {
runOnUiThread(() -> updateProfileHeader(username));
}

View file

@ -30,6 +30,7 @@ public class LoginFragment extends Fragment {
private EditText mPassword;
private TextView mError;
private TextView mForgotPassword;
private TextView mSignup;
private Button mLoginButton;
private ProgressDialog mDialog;
@ -58,10 +59,12 @@ public class LoginFragment extends Fragment {
mError = rootView.findViewById(R.id.error);
mLoginButton = rootView.findViewById(R.id.loginButton);
mForgotPassword = rootView.findViewById(R.id.forgotPassword);
mSignup = rootView.findViewById(R.id.signup);
mLoginButton.setOnClickListener(view -> login());
mForgotPassword.setOnClickListener(view -> forgotPassword());
mSignup.setOnClickListener(view -> signup());
mPassword.setOnEditorActionListener(
(textView, actionId, keyEvent) -> {
@ -121,6 +124,12 @@ public class LoginFragment extends Fragment {
}
}
public void signup() {
if (mListener != null) {
mListener.onSignupRequested();
}
}
private void hideKeyboard() {
View view = getActivity().getCurrentFocus();
if (view != null) {
@ -182,6 +191,7 @@ public class LoginFragment extends Fragment {
public interface OnLoginInteractionListener {
void onLoginCompleted();
void onSignupRequested();
}
}

View file

@ -0,0 +1,190 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Fragment;
import android.app.ProgressDialog;
import android.content.Context;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import org.qtproject.qt5.android.QtNative;
import io.highfidelity.hifiinterface.R;
import static org.qtproject.qt5.android.QtActivityDelegate.ApplicationActive;
import static org.qtproject.qt5.android.QtActivityDelegate.ApplicationInactive;
public class SignupFragment extends Fragment {
private EditText mEmail;
private EditText mUsername;
private EditText mPassword;
private TextView mError;
private TextView mLogin;
private Button mSignupButton;
private ProgressDialog mDialog;
public native void nativeSignup(String email, String username, String password); // move to SignupFragment
public native void nativeCancelSignup();
private SignupFragment.OnSignupInteractionListener mListener;
public SignupFragment() {
// Required empty public constructor
}
public static SignupFragment newInstance() {
SignupFragment fragment = new SignupFragment();
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_signup, container, false);
mEmail = rootView.findViewById(R.id.email);
mUsername = rootView.findViewById(R.id.username);
mPassword = rootView.findViewById(R.id.password);
mError = rootView.findViewById(R.id.error);
mSignupButton = rootView.findViewById(R.id.signupButton);
mLogin = rootView.findViewById(R.id.login);
mSignupButton.setOnClickListener(view -> signup());
mLogin.setOnClickListener(view -> login());
mPassword.setOnEditorActionListener(
(textView, actionId, keyEvent) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) {
mSignupButton.performClick();
return true;
}
return false;
});
return rootView;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnSignupInteractionListener) {
mListener = (OnSignupInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnSignupInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
@Override
public void onResume() {
super.onResume();
// This hack intends to keep Qt threads running even after the app comes from background
QtNative.setApplicationState(ApplicationActive);
}
@Override
public void onStop() {
super.onStop();
cancelActivityIndicator();
// Leave the Qt app paused
QtNative.setApplicationState(ApplicationInactive);
hideKeyboard();
}
private void login() {
if (mListener != null) {
mListener.onLoginRequested();
}
}
public void signup() {
String email = mEmail.getText().toString().trim();
String username = mUsername.getText().toString().trim();
String password = mPassword.getText().toString();
hideKeyboard();
if (email.isEmpty() || username.isEmpty() || password.isEmpty()) {
showError(getString(R.string.signup_email_username_or_password_incorrect));
} else {
mSignupButton.setEnabled(false);
hideError();
showActivityIndicator();
nativeSignup(email, username, password);
}
}
private void hideKeyboard() {
View view = getActivity().getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
private void showActivityIndicator() {
if (mDialog == null) {
mDialog = new ProgressDialog(getContext());
}
mDialog.setMessage(getString(R.string.creating_account));
mDialog.setCancelable(true);
mDialog.setOnCancelListener(dialogInterface -> {
nativeCancelSignup();
cancelActivityIndicator();
mSignupButton.setEnabled(true);
});
mDialog.show();
}
private void cancelActivityIndicator() {
if (mDialog != null) {
mDialog.cancel();
}
}
private void showError(String error) {
mError.setText(error);
mError.setVisibility(View.VISIBLE);
}
private void hideError() {
mError.setText("");
mError.setVisibility(View.INVISIBLE);
}
public interface OnSignupInteractionListener {
void onSignupCompleted();
void onLoginRequested();
}
public void handleSignupCompleted() {
getActivity().runOnUiThread(() -> {
mSignupButton.setEnabled(true);
cancelActivityIndicator();
if (mListener != null) {
mListener.onSignupCompleted();
}
mError.setText("handleSignupCompleted");
});
}
public void handleSignupFailed(String error) {
getActivity().runOnUiThread(() -> {
mSignupButton.setEnabled(true);
cancelActivityIndicator();
mError.setText(error);
mError.setVisibility(View.VISIBLE);
});
}
}

View file

@ -120,6 +120,20 @@
app:layout_constraintRight_toLeftOf="@id/loginButton"
android:textColor="@color/colorButton1"/>
<TextView
android:id="@+id/signup"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway_semibold"
android:textSize="14dp"
android:text="@string/signup"
android:textStyle="italic"
android:paddingRight="10dp"
android:layout_marginTop="20dp"
app:layout_constraintLeft_toLeftOf="@id/passwordLayout"
app:layout_constraintTop_toBottomOf="@id/forgotPassword"
app:layout_constraintRight_toLeftOf="@id/loginButton"
android:textColor="@color/colorButton1"/>

View file

@ -0,0 +1,146 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLight">
<ImageView
android:id="@+id/header"
android:layout_width="@dimen/header_hifi_width"
android:layout_height="@dimen/header_hifi_height"
android:layout_marginTop="@dimen/header_hifi_margin_top"
android:contentDescription="HighFidelity"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/hifi_header" />
<TextView
android:id="@+id/error"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:fontFamily="@font/raleway"
android:textColor="@color/colorLoginError"
android:textSize="12sp"
app:layout_constraintBottom_toTopOf="@id/email"
app:layout_constraintLeft_toLeftOf="@id/email"
app:layout_constraintRight_toRightOf="@id/email"
android:visibility="invisible"/>
<EditText
android:id="@+id/email"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginLeft="46dp"
android:layout_marginRight="46dp"
android:background="@drawable/rounded_edit"
android:padding="7dp"
android:paddingRight="12dp"
android:paddingTop="14dp"
android:ems="10"
android:fontFamily="@font/raleway"
android:textSize="17sp"
android:inputType="textEmailAddress"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="left|center_vertical"
app:layout_constraintTop_toBottomOf="@id/header"
android:layout_marginTop="70dp"
android:hint="@string/email" />
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginLeft="46dp"
android:layout_marginRight="46dp"
android:background="@drawable/rounded_edit"
android:padding="7dp"
android:paddingRight="12dp"
android:paddingTop="14dp"
android:ems="10"
android:fontFamily="@font/raleway"
android:textSize="17sp"
android:inputType="text"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="left|center_vertical"
app:layout_constraintTop_toBottomOf="@id/email"
android:layout_marginTop="13dp"
android:hint="@string/username" />
<android.support.design.widget.TextInputLayout
android:id="@+id/passwordLayout"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="46dp"
android:layout_marginRight="46dp"
app:passwordToggleTint="@color/showPasswordColor"
app:passwordToggleEnabled="true"
app:hintAnimationEnabled="false"
app:passwordToggleDrawable="@drawable/selector_show_password"
app:hintEnabled="false"
app:layout_constraintTop_toBottomOf="@id/username"
android:layout_marginTop="13dp"
>
<android.support.design.widget.TextInputEditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="35dp"
android:background="@drawable/rounded_edit"
android:padding="7dp"
android:drawablePadding="55dp"
android:paddingTop="14dp"
android:drawableEnd="@drawable/ic_eye_noshow"
android:ems="10"
android:fontFamily="@font/raleway"
android:textSize="17sp"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="left|center_vertical"
android:imeOptions="actionDone"
android:hint="@string/password"
android:inputType="textPassword" />
</android.support.design.widget.TextInputLayout>
<Button
android:id="@+id/signupButton"
android:layout_width="154dp"
android:layout_height="38dp"
android:layout_marginTop="16dp"
android:background="@drawable/rounded_button"
android:fontFamily="@font/raleway_semibold"
android:paddingBottom="0dp"
android:paddingLeft="20dp"
android:paddingRight="20dp"
android:paddingTop="0dp"
android:text="@string/signup"
android:textColor="@color/white_opaque"
android:textAllCaps="false"
android:textSize="15sp"
app:layout_constraintRight_toRightOf="@id/username"
app:layout_constraintTop_toBottomOf="@id/passwordLayout"
app:layout_goneMarginTop="4dp"/>
<TextView
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway_semibold"
android:textSize="14dp"
android:text="@string/login"
android:textStyle="italic"
android:paddingRight="10dp"
app:layout_constraintLeft_toLeftOf="@id/passwordLayout"
app:layout_constraintTop_toTopOf="@id/signupButton"
app:layout_constraintRight_toLeftOf="@id/loginButton"
android:textColor="@color/colorButton1"/>
</android.support.constraint.ConstraintLayout>

View file

@ -10,6 +10,8 @@
<string name="popular">POPULAR</string>
<string name="bookmarks">BOOKMARKS</string>
<string name="goto_url_hint">Type a domain url</string>
<string name="email">Email</string>
<string name="username">Username</string>
<string name="username_or_email">Username or email</string>
<string name="password">Password</string>
<string name="login">Login</string>
@ -23,10 +25,14 @@
<string name="privacyPolicy">Privacy Policy</string>
<string name="your_last_location">Your Last Location</string>
<string name="online">Online</string>
<string name="signup">Sign up</string>
<string name="creating_account">Creating your High Fidelity account</string>
<string name="signup_email_username_or_password_incorrect">Email, username or password incorrect.</string>
<!-- tags -->
<string name="tagFragmentHome">tagFragmentHome</string>
<string name="tagFragmentLogin">tagFragmentLogin</string>
<string name="tagFragmentSignup">tagFragmentSignup</string>
<string name="tagFragmentPolicy">tagFragmentPolicy</string>
<string name="tagFragmentPeople">tagFragmentPeople</string>
</resources>

View file

@ -10,6 +10,7 @@
//
#include "AndroidHelper.h"
#include <QDebug>
#include <AccountManager.h>
#include "Application.h"
#if defined(qApp)
@ -56,3 +57,78 @@ void AndroidHelper::processURL(const QString &url) {
qApp->acceptURL(url);
}
}
void AndroidHelper::signup(QString email, QString username, QString password) {
JSONCallbackParameters callbackParams;
callbackParams.callbackReceiver = this;
callbackParams.jsonCallbackMethod = "signupCompleted";
callbackParams.errorCallbackMethod = "signupFailed";
QJsonObject payload;
QJsonObject userObject;
userObject.insert("email", email);
userObject.insert("username", username);
userObject.insert("password", password);
payload.insert("user", userObject);
static const QString API_SIGNUP_PATH = "api/v1/users";
auto accountManager = DependencyManager::get<AccountManager>();
accountManager->sendRequest(API_SIGNUP_PATH, AccountManagerAuth::None,
QNetworkAccessManager::PostOperation, callbackParams,
QJsonDocument(payload).toJson());
}
void AndroidHelper::signupCompleted(QNetworkReply* reply) {
emit handleSignupCompleted();
}
QString AndroidHelper::errorStringFromAPIObject(const QJsonValue& apiObject) {
if (apiObject.isArray()) {
return apiObject.toArray()[0].toString();
} else if (apiObject.isString()) {
return apiObject.toString();
} else {
return "is invalid";
}
}
void AndroidHelper::signupFailed(QNetworkReply* reply) {
// parse the returned JSON to see what the problem was
auto jsonResponse = QJsonDocument::fromJson(reply->readAll());
static const QString RESPONSE_DATA_KEY = "data";
auto dataJsonValue = jsonResponse.object()[RESPONSE_DATA_KEY];
if (dataJsonValue.isObject()) {
auto dataObject = dataJsonValue.toObject();
static const QString EMAIL_DATA_KEY = "email";
static const QString USERNAME_DATA_KEY = "username";
static const QString PASSWORD_DATA_KEY = "password";
QStringList errorStringList;
if (dataObject.contains(EMAIL_DATA_KEY)) {
errorStringList.append(QString("Email %1.").arg(errorStringFromAPIObject(dataObject[EMAIL_DATA_KEY])));
}
if (dataObject.contains(USERNAME_DATA_KEY)) {
errorStringList.append(QString("Username %1.").arg(errorStringFromAPIObject(dataObject[USERNAME_DATA_KEY])));
}
if (dataObject.contains(PASSWORD_DATA_KEY)) {
errorStringList.append(QString("Password %1.").arg(errorStringFromAPIObject(dataObject[PASSWORD_DATA_KEY])));
}
emit handleSignupFailed(errorStringList.join('\n'));
} else {
static const QString DEFAULT_SIGN_UP_FAILURE_MESSAGE = "There was an unknown error while creating your account. Please try again later.";
emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE);
}
}

View file

@ -13,6 +13,8 @@
#define hifi_Android_Helper_h
#include <QObject>
#include <QNetworkReply>
#include <QtCore/QEventLoop>
class AndroidHelper : public QObject {
Q_OBJECT
@ -33,8 +35,12 @@ public:
AndroidHelper(AndroidHelper const&) = delete;
void operator=(AndroidHelper const&) = delete;
void signup(QString email, QString username, QString password);
public slots:
void showLoginDialog();
void signupCompleted(QNetworkReply* reply);
void signupFailed(QNetworkReply* reply);
signals:
void androidActivityRequested(const QString &activityName, const bool backToScene, QList<QString> args = QList<QString>());
@ -45,9 +51,14 @@ signals:
void hapticFeedbackRequested(int duration);
void handleSignupCompleted();
void handleSignupFailed(QString errorString);
private:
AndroidHelper();
~AndroidHelper();
QString errorStringFromAPIObject(const QJsonValue& apiObject);
};
#endif