From f4be767d228b54abd0a74df7ff5623bfffdc1ab7 Mon Sep 17 00:00:00 2001 From: Gabriel Calero Date: Fri, 21 Sep 2018 14:58:11 -0300 Subject: [PATCH 01/14] Android sign up --- android/app/src/main/cpp/native.cpp | 53 +++++ .../hifiinterface/InterfaceActivity.java | 4 + .../hifiinterface/MainActivity.java | 27 ++- .../hifiinterface/fragment/LoginFragment.java | 10 + .../fragment/SignupFragment.java | 190 ++++++++++++++++++ .../src/main/res/layout/fragment_login.xml | 14 ++ .../src/main/res/layout/fragment_signup.xml | 146 ++++++++++++++ android/app/src/main/res/values/strings.xml | 6 + interface/src/AndroidHelper.cpp | 76 +++++++ interface/src/AndroidHelper.h | 11 + 10 files changed, 536 insertions(+), 1 deletion(-) create mode 100644 android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java create mode 100644 android/app/src/main/res/layout/fragment_signup.xml diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index ba956ba322..518c958f26 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -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("handleSignupCompleted", "()V", jSuccess); + } + }); + + QObject::connect(&AndroidHelper::instance(), &AndroidHelper::handleSignupFailed, [](QString errorString) { + jboolean jSuccess = (jboolean) false; + jstring jError = QAndroidJniObject::fromString(errorString).object(); + if (__signupCompletedListener.isValid()) { + QAndroidJniObject string = QAndroidJniObject::fromString(errorString); + __signupCompletedListener.callMethod("handleSignupFailed", "(Ljava/lang/String;)V", string.object()); + } + }); + + 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(); diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index f161783d6a..94dd958600 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -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) { diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java index db6f0fca24..ab85852ed0 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/MainActivity.java @@ -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)); } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java index 92cdec19a1..764e0c8ed5 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/LoginFragment.java @@ -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(); } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java new file mode 100644 index 0000000000..eb3a20b449 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/fragment/SignupFragment.java @@ -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); + }); + } + +} diff --git a/android/app/src/main/res/layout/fragment_login.xml b/android/app/src/main/res/layout/fragment_login.xml index 46ed783898..6066c46a10 100644 --- a/android/app/src/main/res/layout/fragment_login.xml +++ b/android/app/src/main/res/layout/fragment_login.xml @@ -120,6 +120,20 @@ app:layout_constraintRight_toLeftOf="@id/loginButton" android:textColor="@color/colorButton1"/> + diff --git a/android/app/src/main/res/layout/fragment_signup.xml b/android/app/src/main/res/layout/fragment_signup.xml new file mode 100644 index 0000000000..09e9ec352f --- /dev/null +++ b/android/app/src/main/res/layout/fragment_signup.xml @@ -0,0 +1,146 @@ + + + + + + + + + + + + + + + + +