Merge branch 'master' of github.com:highfidelity/hifi into black-bis

This commit is contained in:
sam gateau 2018-10-02 15:25:19 -07:00
commit 680f5f5703
20 changed files with 1525 additions and 745 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) {
@ -267,6 +268,14 @@ Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeCancelLogin(JNIE
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_nativeCancelLogin(JNIEnv *env,
jobject instance) {
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeCancelLogin(env, instance);
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *env, jobject instance,
jstring username_, jstring password_,
@ -308,6 +317,67 @@ Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *en
Q_ARG(const QString&, username), Q_ARG(const QString&, password));
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_SignupFragment_nativeLogin(JNIEnv *env,
jobject instance,
jstring username_,
jstring password_,
jobject usernameChangedListener) {
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(env, instance, username_, password_, usernameChangedListener);
}
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

@ -69,6 +69,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;
@ -351,6 +352,9 @@ public class InterfaceActivity extends QtActivity implements WebViewFragment.OnW
if (nativeEnterBackgroundCallEnqueued) {
nativeEnterBackground();
}
runOnUiThread(() -> {
nativeInitAfterAppLoaded();
});
}
public void performHapticFeedback(int duration) {

View file

@ -37,12 +37,15 @@ import io.highfidelity.hifiinterface.fragment.HomeFragment;
import io.highfidelity.hifiinterface.fragment.LoginFragment;
import io.highfidelity.hifiinterface.fragment.PolicyFragment;
import io.highfidelity.hifiinterface.fragment.SettingsFragment;
import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
import io.highfidelity.hifiinterface.fragment.SignedInFragment;
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,
SignedInFragment.OnSignedInInteractionListener {
private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
public static final String DEFAULT_FRAGMENT = "Home";
@ -147,35 +150,44 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
private void loadHomeFragment(boolean addToBackStack) {
Fragment fragment = HomeFragment.newInstance();
loadFragment(fragment, getString(R.string.home), getString(R.string.tagFragmentHome), addToBackStack);
loadFragment(fragment, getString(R.string.home), getString(R.string.tagFragmentHome), addToBackStack, true);
}
private void loadLoginFragment() {
Fragment fragment = LoginFragment.newInstance();
loadFragment(fragment, getString(R.string.login), getString(R.string.tagFragmentLogin), true, true);
}
loadFragment(fragment, getString(R.string.login), getString(R.string.tagFragmentLogin), true);
private void loadSignedInFragment() {
Fragment fragment = SignedInFragment.newInstance();
loadFragment(fragment, getString(R.string.welcome), getString(R.string.tagFragmentSignedIn), true, true);
}
private void loadSignupFragment() {
Fragment fragment = SignupFragment.newInstance();
loadFragment(fragment, getString(R.string.signup), getString(R.string.tagFragmentSignup), true, false);
}
private void loadPrivacyPolicyFragment() {
Fragment fragment = PolicyFragment.newInstance();
loadFragment(fragment, getString(R.string.privacyPolicy), getString(R.string.tagFragmentPolicy), true);
loadFragment(fragment, getString(R.string.privacyPolicy), getString(R.string.tagFragmentPolicy), true, true);
}
private void loadPeopleFragment() {
Fragment fragment = FriendsFragment.newInstance();
loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true);
loadFragment(fragment, getString(R.string.people), getString(R.string.tagFragmentPeople), true, true);
}
private void loadSettingsFragment() {
SettingsFragment fragment = SettingsFragment.newInstance();
loadFragment(fragment, getString(R.string.settings), getString(R.string.tagSettings), true);
loadFragment(fragment, getString(R.string.settings), getString(R.string.tagSettings), true, true);
}
private void loadFragment(Fragment fragment, String title, String tag, boolean addToBackStack) {
private void loadFragment(Fragment newFragment, String title, String tag, boolean addToBackStack, boolean goBackUntilHome) {
FragmentManager fragmentManager = getFragmentManager();
// check if it's the same fragment
@ -187,17 +199,19 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
return; // cancel as we are already in that fragment
}
// go back until first transaction
int backStackEntryCount = fragmentManager.getBackStackEntryCount();
for (int i = 0; i < backStackEntryCount - 1; i++) {
fragmentManager.popBackStackImmediate();
if (goBackUntilHome) {
// go back until first transaction
int backStackEntryCount = fragmentManager.getBackStackEntryCount();
for (int i = 0; i < backStackEntryCount - 1; i++) {
fragmentManager.popBackStackImmediate();
}
}
// this case is when we wanted to go home.. rollback already did that!
// But asking for a new Home fragment makes it easier to have an updated list so we let it to continue
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.replace(R.id.content_frame, fragment, tag);
ft.replace(R.id.content_frame, newFragment, tag);
if (addToBackStack) {
ft.addToBackStack(title);
@ -334,6 +348,32 @@ public class MainActivity extends AppCompatActivity implements NavigationView.On
}
}
@Override
public void onGettingStarted() {
loadHomeFragment(false);
if (backToScene) {
backToScene = false;
goToLastLocation();
}
}
@Override
public void onLoginRequested() {
// go back from signup to login
onBackPressed();
}
@Override
public void onSignupRequested() {
loadSignupFragment();
}
@Override
public void onSignupCompleted() {
loadSignedInFragment();
updateLoginMenu();
}
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.signupButton);
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,73 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.text.Html;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import io.highfidelity.hifiinterface.R;
public class SignedInFragment extends Fragment {
private Button mGetStartedButton;
private OnSignedInInteractionListener mListener;
public SignedInFragment() {
// Required empty public constructor
}
public static SignedInFragment newInstance() {
SignedInFragment fragment = new SignedInFragment();
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_signedin, container, false);
mGetStartedButton = rootView.findViewById(R.id.getStarted);
mGetStartedButton.setOnClickListener(view -> {
getStarted();
});
return rootView;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof SignedInFragment.OnSignedInInteractionListener) {
mListener = (SignedInFragment.OnSignedInInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnSignedInInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public void getStarted() {
if (mListener != null) {
mListener.onGettingStarted();
}
}
public interface OnSignedInInteractionListener {
void onGettingStarted();
}
}

View file

@ -0,0 +1,217 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Activity;
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 mCancelButton;
private Button mSignupButton;
private ProgressDialog mDialog;
public native void nativeSignup(String email, String username, String password); // move to SignupFragment
public native void nativeCancelSignup();
public native void nativeLogin(String username, String password, Activity usernameChangedListener);
public native void nativeCancelLogin();
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);
mCancelButton = rootView.findViewById(R.id.cancelButton);
mSignupButton.setOnClickListener(view -> signup());
mCancelButton.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() {
String username = mUsername.getText().toString().trim();
String password = mPassword.getText().toString();
mDialog.setMessage(getString(R.string.logging_in));
mDialog.setCancelable(true);
mDialog.setOnCancelListener(dialogInterface -> {
nativeCancelLogin();
cancelActivityIndicator();
if (mListener != null) {
mListener.onLoginRequested();
}
});
mDialog.show();
nativeLogin(username, password, getActivity());
}
public void handleSignupFailed(String error) {
getActivity().runOnUiThread(() -> {
mSignupButton.setEnabled(true);
cancelActivityIndicator();
mError.setText(error);
mError.setVisibility(View.VISIBLE);
});
}
public void handleLoginCompleted(boolean success) {
getActivity().runOnUiThread(() -> {
mSignupButton.setEnabled(true);
cancelActivityIndicator();
if (success) {
if (mListener != null) {
mListener.onSignupCompleted();
}
} else {
// Registration was successful but login failed.
// Let the user to login manually
mListener.onLoginRequested();
}
});
}
}

View file

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android" >
<item android:state_pressed="true" >
<shape android:shape="rectangle" >
<corners android:radius="4dip" />
<stroke android:width="1dip" android:color="@color/colorButton2" />
<solid android:color="@color/colorButton2"/>
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="rectangle" >
<corners android:radius="4dip" />
<stroke android:width="1dip" android:color="@color/colorButton2" />
<solid android:color="@color/colorButton2"/>
</shape>
</item>
<item>
<shape android:shape="rectangle" >
<corners android:radius="4dip" />
<stroke android:width="1dip" android:color="@color/colorButton2" />
<solid android:color="@color/colorButton2"/>
</shape>
</item>
</selector>

View file

@ -21,10 +21,12 @@
android:id="@+id/error"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="16dp"
android:layout_marginBottom="25dp"
android:layout_marginLeft="9dp"
android:layout_marginRight="9dp"
android:fontFamily="@font/raleway"
android:textColor="@color/colorLoginError"
android:textSize="12sp"
android:textSize="14sp"
app:layout_constraintBottom_toTopOf="@id/username"
app:layout_constraintLeft_toLeftOf="@id/username"
android:visibility="invisible"/>
@ -91,35 +93,50 @@
android:id="@+id/loginButton"
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="55dp"
android:paddingRight="55dp"
android:paddingTop="0dp"
android:text="@string/login"
android:textColor="@color/white_opaque"
android:textAllCaps="false"
android:textSize="15sp"
android:textSize="18sp"
app:layout_constraintRight_toRightOf="@id/username"
app:layout_constraintTop_toBottomOf="@id/passwordLayout"
app:layout_constraintTop_toBottomOf="@id/forgotPassword"
app:layout_goneMarginTop="4dp"/>
<TextView
android:id="@+id/forgotPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:paddingTop="9dp"
android:paddingBottom="16dp"
android:fontFamily="@font/raleway_semibold"
android:textSize="14dp"
android:text="@string/forgot_password"
android:textStyle="italic"
android:paddingRight="10dp"
app:layout_constraintRight_toRightOf="@id/passwordLayout"
app:layout_constraintTop_toBottomOf="@id/passwordLayout"
android:textColor="@color/colorButton1"/>
<Button
android:id="@+id/signupButton"
android:layout_width="0dp"
app:layout_constraintWidth_default="spread"
android:layout_height="38dp"
android:background="@drawable/rounded_secondary_button"
android:fontFamily="@font/raleway_semibold"
android:paddingBottom="0dp"
android:paddingTop="0dp"
android:layout_marginRight="15dp"
android:text="@string/signup"
android:textColor="@color/white_opaque"
android:textAllCaps="false"
android:textSize="18sp"
app:layout_constraintLeft_toLeftOf="@id/passwordLayout"
app:layout_constraintTop_toTopOf="@id/loginButton"
app:layout_constraintRight_toLeftOf="@id/loginButton"
android:textColor="@color/colorButton1"/>
app:layout_goneMarginTop="4dp"/>

View file

@ -0,0 +1,63 @@
<?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/welcome"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="50dp"
android:paddingLeft="86dp"
android:paddingRight="86dp"
android:fontFamily="@font/raleway"
android:textColor="@color/clearText"
android:textSize="24sp"
android:text="@string/signedin_welcome"
app:layout_constraintTop_toBottomOf="@id/header"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:gravity="center"
/>
<Button
android:id="@+id/getStarted"
android:layout_width="217dp"
android:layout_height="38dp"
android:layout_marginTop="30dp"
android:background="@drawable/rounded_button"
android:fontFamily="@font/raleway_semibold"
android:paddingBottom="0dp"
android:paddingLeft="25dp"
android:paddingRight="25dp"
android:paddingTop="0dp"
android:text="@string/get_started"
android:textColor="@color/white_opaque"
android:textAllCaps="false"
android:textSize="18sp"
app:layout_constraintTop_toBottomOf="@id/welcome"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_goneMarginTop="4dp"/>
</android.support.constraint.ConstraintLayout>

View file

@ -0,0 +1,151 @@
<?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:layout_marginLeft="9dp"
android:layout_marginRight="9dp"
android:fontFamily="@font/raleway"
android:textColor="@color/colorLoginError"
android:textSize="14sp"
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="7dp"
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="7dp"
>
<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="44dp"
android:background="@drawable/rounded_button"
android:fontFamily="@font/raleway_semibold"
android:paddingBottom="0dp"
android:paddingTop="0dp"
android:text="@string/signup"
android:textColor="@color/white_opaque"
android:textAllCaps="false"
android:textSize="18sp"
app:layout_constraintRight_toRightOf="@id/username"
app:layout_constraintTop_toBottomOf="@id/passwordLayout"
app:layout_goneMarginTop="4dp"/>
<Button
android:id="@+id/cancelButton"
android:layout_width="0dp"
app:layout_constraintWidth_default="spread"
android:layout_height="38dp"
android:background="@drawable/rounded_secondary_button"
android:fontFamily="@font/raleway_semibold"
android:paddingBottom="0dp"
android:paddingTop="0dp"
android:layout_marginRight="15dp"
android:text="@string/cancel"
android:textColor="@color/white_opaque"
android:textAllCaps="false"
android:textSize="18sp"
app:layout_constraintLeft_toLeftOf="@id/passwordLayout"
app:layout_constraintTop_toTopOf="@id/signupButton"
app:layout_constraintRight_toLeftOf="@id/signupButton"
app:layout_goneMarginTop="4dp"/>
</android.support.constraint.ConstraintLayout>

View file

@ -9,6 +9,7 @@
<color name="showPasswordColor">#3D3D3D</color>
<color name="tabs">#1EB5EC</color>
<color name="colorButton1">#00B4EF</color>
<color name="colorButton2">#828282</color>
<color name="backgroundDark">#333333</color>
<color name="backgroundLight">#4F4F4F</color>
<color name="backgroundSearch">#33999999</color>
@ -23,4 +24,6 @@
<color name="starSelectedTint">#FBD92A</color>
<color name="starUnselectedTint">#8A8A8A</color>
<color name="slidingUpPanelFadeColor">#40000000</color>
<color name="clearText">#F2F2F2</color>
</resources>

View file

@ -10,11 +10,13 @@
<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>
<string name="logout">Logout</string>
<string name="forgot_password">Forgot password?\u00A0</string>
<string name="forgot_password"><u>Forgot password?</u>\u00A0</string>
<string name="login_username_or_password_incorrect">Username or password incorrect.</string>
<string name="logging_in">Logging into High Fidelity</string>
<string name="search_hint"><i>Search for a place by name</i>\u00A0</string>
@ -23,13 +25,22 @@
<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>
<string name="signedin_welcome">You are now signed into High Fidelity</string>
<string name="welcome">Welcome</string>
<string name="cancel">Cancel</string>
<string name="get_started">Get Started</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>
<string name="tagSettings">tagSettings</string>
<string name="tagFragmentSignedIn">tagFragmentSignedIn</string>
<string name="settings">Settings</string>
<string name="AEC">AEC</string>
<string name="acoustic_echo_cancellation">Acoustic Echo Cancellation</string>

View file

@ -10,8 +10,11 @@
//
#include "AndroidHelper.h"
#include <QDebug>
#include <AccountManager.h>
#include <AudioClient.h>
#include <src/ui/LoginDialog.h>
#include "Application.h"
#include "Constants.h"
#if defined(qApp)
#undef qApp
@ -69,3 +72,75 @@ void AndroidHelper::notifyHeadsetOn(bool pluggedIn) {
}
#endif
}
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);
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

@ -16,6 +16,9 @@
#include <QMap>
#include <QUrl>
#include <QNetworkReply>
#include <QtCore/QEventLoop>
class AndroidHelper : public QObject {
Q_OBJECT
public:
@ -36,9 +39,12 @@ public:
AndroidHelper(AndroidHelper const&) = delete;
void operator=(AndroidHelper const&) = delete;
void signup(QString email, QString username, QString password);
public slots:
void showLoginDialog(QUrl url);
void signupCompleted(QNetworkReply* reply);
void signupFailed(QNetworkReply* reply);
signals:
void androidActivityRequested(const QString &activityName, const bool backToScene, QMap<QString, QString> args = QMap<QString, QString>());
void qtAppLoadComplete();
@ -48,9 +54,14 @@ signals:
void hapticFeedbackRequested(int duration);
void handleSignupCompleted();
void handleSignupFailed(QString errorString);
private:
AndroidHelper();
~AndroidHelper();
QString errorStringFromAPIObject(const QJsonValue& apiObject);
};
#endif

15
interface/src/Constants.h Normal file
View file

@ -0,0 +1,15 @@
//
// Constants.h
// interface
//
// Created by Gabriel Calero on 9/28/18.
// Copyright 2015 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
#pragma once
#include <QString>
static const QString API_SIGNUP_PATH = "api/v1/users";

View file

@ -505,14 +505,16 @@ void AvatarManager::clearOtherAvatars() {
void AvatarManager::deleteAllAvatars() {
assert(_avatarsToChangeInPhysics.empty());
QWriteLocker locker(&_hashLock);
AvatarHash::iterator avatarIterator = _avatarHash.begin();
QReadLocker locker(&_hashLock);
AvatarHash::iterator avatarIterator = _avatarHash.begin();
while (avatarIterator != _avatarHash.end()) {
auto avatar = std::static_pointer_cast<OtherAvatar>(avatarIterator.value());
auto avatar = std::static_pointer_cast<Avatar>(avatarIterator.value());
avatarIterator = _avatarHash.erase(avatarIterator);
avatar->die();
assert(!avatar->_motionState);
if (avatar != _myAvatar) {
auto otherAvatar = std::static_pointer_cast<OtherAvatar>(avatar);
assert(!otherAvatar->_motionState);
}
}
}

View file

@ -27,6 +27,7 @@
#include "Application.h"
#include "scripting/HMDScriptingInterface.h"
#include "Constants.h"
HIFI_QML_DEF(LoginDialog)
@ -220,8 +221,6 @@ void LoginDialog::signup(const QString& email, const QString& username, const QS
payload.insert("user", userObject);
static const QString API_SIGNUP_PATH = "api/v1/users";
qDebug() << "Sending a request to create an account for" << username;
auto accountManager = DependencyManager::get<AccountManager>();

View file

@ -210,7 +210,8 @@ void RenderableModelEntityItem::updateModelBounds() {
}
if (model->getScaleToFitDimensions() != getScaledDimensions() ||
model->getRegistrationPoint() != getRegistrationPoint()) {
model->getRegistrationPoint() != getRegistrationPoint() ||
!model->getIsScaledToFit()) {
// The machinery for updateModelBounds will give existing models the opportunity to fix their
// translation/rotation/scale/registration. The first two are straightforward, but the latter two
// have guards to make sure they don't happen after they've already been set. Here we reset those guards.

View file

@ -77,14 +77,10 @@ static glm::mat4 computeOffset(glm::mat4 defaultToReferenceMat, glm::mat4 defaul
return glm::inverse(poseMat) * referenceJointMat;
}
static bool sortPucksYPosition(PuckPosePair firstPuck, PuckPosePair secondPuck) {
static bool sortPucksYPosition(const PuckPosePair& firstPuck, const PuckPosePair& secondPuck) {
return (firstPuck.second.translation.y < secondPuck.second.translation.y);
}
static bool sortPucksXPosition(PuckPosePair firstPuck, PuckPosePair secondPuck) {
return (firstPuck.second.translation.x < secondPuck.second.translation.x);
}
static bool determineLimbOrdering(const controller::Pose& poseA, const controller::Pose& poseB, glm::vec3 axis, glm::vec3 axisOrigin) {
glm::vec3 poseAPosition = poseA.getTranslation();
glm::vec3 poseBPosition = poseB.getTranslation();
@ -568,6 +564,7 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr
return;
}
// Compute the defaultToRefrenceMat, this will take inputCalibration default poses into the reference frame. (sensor space)
glm::mat4 defaultToReferenceMat = glm::mat4();
if (_headConfig == HeadConfig::HMD) {
defaultToReferenceMat = calculateDefaultToReferenceForHmd(inputCalibration);
@ -591,7 +588,17 @@ void ViveControllerManager::InputDevice::calibrate(const controller::InputCalibr
}
bool ViveControllerManager::InputDevice::configureHands(const glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration) {
std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), sortPucksXPosition);
// Sort valid tracked objects in the default frame by the x dimension (left to right).
// Because the sort is in the default frame we guarentee that poses are relative to the head facing.
// i.e. -x will always be to the left of the head, and +x will be to the right.
// This allows the user to be facing in any direction in sensor space while calibrating.
glm::mat4 referenceToDefaultMat = glm::inverse(defaultToReferenceMat);
std::sort(_validTrackedObjects.begin(), _validTrackedObjects.end(), [&referenceToDefaultMat](const PuckPosePair& a, const PuckPosePair& b) {
glm::vec3 aPos = transformPoint(referenceToDefaultMat, a.second.translation);
glm::vec3 bPos = transformPoint(referenceToDefaultMat, b.second.translation);
return (aPos.x < bPos.x);
});
int puckCount = (int)_validTrackedObjects.size();
if (_handConfig == HandConfig::Pucks && puckCount >= MIN_PUCK_COUNT) {
glm::vec3 headXAxis = getReferenceHeadXAxis(defaultToReferenceMat, inputCalibration.defaultHeadMat);
@ -1025,25 +1032,23 @@ void ViveControllerManager::InputDevice::hapticsHelper(float deltaTime, bool lef
void ViveControllerManager::InputDevice::calibrateLeftHand(const glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& handPair) {
controller::Pose& handPose = handPair.second;
glm::mat4 handPoseAvatarMat = createMatFromQuatAndPos(handPose.getRotation(), handPose.getTranslation());
glm::vec3 handPoseTranslation = extractTranslation(handPoseAvatarMat);
glm::vec3 handPoseZAxis = glmExtractRotation(handPoseAvatarMat) * glm::vec3(0.0f, 0.0f, 1.0f);
glm::vec3 avatarHandYAxis = transformVectorFast(inputCalibration.defaultLeftHand, glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 handPoseZAxis = handPose.getRotation() * glm::vec3(0.0f, 0.0f, 1.0f);
glm::vec3 referenceHandYAxis = transformVectorFast(defaultToReferenceMat * inputCalibration.defaultLeftHand, glm::vec3(0.0f, 1.0f, 0.0f));
const float EPSILON = 1.0e-4f;
if (fabsf(fabsf(glm::dot(glm::normalize(avatarHandYAxis), glm::normalize(handPoseZAxis))) - 1.0f) < EPSILON) {
if (fabsf(fabsf(glm::dot(glm::normalize(referenceHandYAxis), glm::normalize(handPoseZAxis))) - 1.0f) < EPSILON) {
handPoseZAxis = glm::vec3(0.0f, 0.0f, 1.0f);
}
// This allows the user to not have to match the t-pose exactly. We assume that the y facing of the hand lies in the plane of the puck.
// Where the plane of the puck is defined by the the local z-axis of the puck, which is facing out of the vive logo/power button.
glm::vec3 zPrime = handPoseZAxis;
glm::vec3 xPrime = glm::normalize(glm::cross(avatarHandYAxis, handPoseZAxis));
glm::vec3 xPrime = glm::normalize(glm::cross(referenceHandYAxis, handPoseZAxis));
glm::vec3 yPrime = glm::normalize(glm::cross(zPrime, xPrime));
glm::mat4 newHandMat = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f),
glm::vec4(zPrime, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
glm::vec3 translationOffset = glm::vec3(0.0f, _handPuckYOffset, _handPuckZOffset);
glm::quat initialRotation = glmExtractRotation(handPoseAvatarMat);
glm::quat initialRotation = handPose.getRotation();
glm::quat finalRotation = glmExtractRotation(newHandMat);
glm::quat rotationOffset = glm::inverse(initialRotation) * finalRotation;
@ -1056,25 +1061,23 @@ void ViveControllerManager::InputDevice::calibrateLeftHand(const glm::mat4& defa
void ViveControllerManager::InputDevice::calibrateRightHand(const glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& handPair) {
controller::Pose& handPose = handPair.second;
glm::mat4 handPoseAvatarMat = createMatFromQuatAndPos(handPose.getRotation(), handPose.getTranslation());
glm::vec3 handPoseTranslation = extractTranslation(handPoseAvatarMat);
glm::vec3 handPoseZAxis = glmExtractRotation(handPoseAvatarMat) * glm::vec3(0.0f, 0.0f, 1.0f);
glm::vec3 avatarHandYAxis = transformVectorFast(inputCalibration.defaultRightHand, glm::vec3(0.0f, 1.0f, 0.0f));
glm::vec3 handPoseZAxis = handPose.getRotation() * glm::vec3(0.0f, 0.0f, 1.0f);
glm::vec3 referenceHandYAxis = transformVectorFast(defaultToReferenceMat * inputCalibration.defaultRightHand, glm::vec3(0.0f, 1.0f, 0.0f));
const float EPSILON = 1.0e-4f;
if (fabsf(fabsf(glm::dot(glm::normalize(avatarHandYAxis), glm::normalize(handPoseZAxis))) - 1.0f) < EPSILON) {
if (fabsf(fabsf(glm::dot(glm::normalize(referenceHandYAxis), glm::normalize(handPoseZAxis))) - 1.0f) < EPSILON) {
handPoseZAxis = glm::vec3(0.0f, 0.0f, 1.0f);
}
// This allows the user to not have to match the t-pose exactly. We assume that the y facing of the hand lies in the plane of the puck.
// Where the plane of the puck is defined by the the local z-axis of the puck, which is facing out of the vive logo/power button.
glm::vec3 zPrime = handPoseZAxis;
glm::vec3 xPrime = glm::normalize(glm::cross(avatarHandYAxis, handPoseZAxis));
glm::vec3 xPrime = glm::normalize(glm::cross(referenceHandYAxis, handPoseZAxis));
glm::vec3 yPrime = glm::normalize(glm::cross(zPrime, xPrime));
glm::mat4 newHandMat = glm::mat4(glm::vec4(xPrime, 0.0f), glm::vec4(yPrime, 0.0f),
glm::vec4(zPrime, 0.0f), glm::vec4(0.0f, 0.0f, 0.0f, 1.0f));
glm::vec3 translationOffset = glm::vec3(0.0f, _handPuckYOffset, _handPuckZOffset);
glm::quat initialRotation = glmExtractRotation(handPoseAvatarMat);
glm::quat initialRotation = handPose.getRotation();
glm::quat finalRotation = glmExtractRotation(newHandMat);
glm::quat rotationOffset = glm::inverse(initialRotation) * finalRotation;
@ -1105,15 +1108,18 @@ void ViveControllerManager::InputDevice::calibrateFeet(const glm::mat4& defaultT
void ViveControllerManager::InputDevice::calibrateFoot(const glm::mat4& defaultToReferenceMat, const controller::InputCalibrationData& inputCalibration, PuckPosePair& footPair, bool isLeftFoot){
controller::Pose footPose = footPair.second;
glm::mat4 puckPoseAvatarMat = createMatFromQuatAndPos(footPose.getRotation(), footPose.getTranslation());
glm::mat4 puckPoseMat = createMatFromQuatAndPos(footPose.getRotation(), footPose.getTranslation());
glm::mat4 defaultFoot = isLeftFoot ? inputCalibration.defaultLeftFoot : inputCalibration.defaultRightFoot;
glm::mat4 footOffset = computeOffset(defaultToReferenceMat, defaultFoot, footPose);
glm::quat rotationOffset = glmExtractRotation(footOffset);
glm::vec3 translationOffset = extractTranslation(footOffset);
glm::vec3 avatarXAxisInPuckFrame = glm::normalize(transformVectorFast(glm::inverse(puckPoseAvatarMat), glm::vec3(-1.0f, 0.0f, 0.0f)));
float distance = glm::dot(translationOffset, avatarXAxisInPuckFrame);
glm::vec3 finalTranslation = translationOffset - (distance * avatarXAxisInPuckFrame);
glm::vec3 localXAxisInPuckFrame = glm::normalize(transformVectorFast(glm::inverse(puckPoseMat) * defaultToReferenceMat, glm::vec3(-1.0f, 0.0f, 0.0f)));
float distance = glm::dot(translationOffset, localXAxisInPuckFrame);
// We ensure the offset vector lies in the sagittal plane of the avatar.
// This helps prevent wide or narrow stances due to the user not matching the t-pose perfectly.
glm::vec3 finalTranslation = translationOffset - (distance * localXAxisInPuckFrame);
glm::mat4 finalOffset = createMatFromQuatAndPos(rotationOffset, finalTranslation);
if (isLeftFoot) {

File diff suppressed because it is too large Load diff