Merge pull request #12939 from Cristo86/android_places_goto

Android Search & Go To
This commit is contained in:
John Conklin II 2018-05-15 16:43:38 -07:00 committed by GitHub
commit aca261af3e
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
39 changed files with 1273 additions and 757 deletions

View file

@ -104,6 +104,11 @@ dependencies {
implementation 'com.android.support:design:26.1.0'
implementation 'com.android.support:appcompat-v7:26.1.0'
compile 'com.android.support:recyclerview-v7:26.1.0'
compile 'com.android.support:cardview-v7:26.1.0'
compile 'com.squareup.retrofit2:retrofit:2.4.0'
compile 'com.squareup.retrofit2:converter-gson:2.4.0'
implementation 'com.squareup.picasso:picasso:2.71828'
implementation fileTree(include: ['*.jar'], dir: 'libs')
}

View file

@ -42,18 +42,18 @@
android:name=".HomeActivity"
android:label="@string/home"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar">
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar">
</activity>
<activity
android:name=".GotoActivity"
android:label="@string/go_to"
android:theme="@style/AppTheme">
</activity>
android:theme="@style/AppTheme" />
<activity android:name=".LoginActivity"
android:theme="@style/AppTheme"/>
<activity
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation"
android:name=".InterfaceActivity"
android:label="@string/app_name"
android:screenOrientation="landscape"
android:launchMode="singleTop"
>
@ -75,6 +75,11 @@
<meta-data
android:name="preloaded_fonts"
android:resource="@array/preloaded_fonts" />
<activity
android:name=".SplashActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar" />
</application>
<uses-feature android:name="android.software.vr.mode" android:required="true"/>

View file

@ -1,24 +0,0 @@
{
"hifi_domains" : [
{
"name": "Your last location",
"url": "",
"thumbnail": ""
},
{
"name": "dev-mobile-master",
"url": "hifi://dev-mobile-master",
"thumbnail": ""
},
{
"name": "dev-mobile",
"url": "hifi://dev-mobile",
"thumbnail": ""
},
{
"name": "dev-welcome",
"url": "hifi://dev-welcome",
"thumbnail": ""
}
]
}

View file

@ -21,8 +21,11 @@
#include <AddressManager.h>
#include "AndroidHelper.h"
#include <udt/PacketHeaders.h>
QAndroidJniObject __activity;
QAndroidJniObject __interfaceActivity;
QAndroidJniObject __loginActivity;
QAndroidJniObject __loadCompleteListener;
void tempMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (!message.isEmpty()) {
@ -142,16 +145,16 @@ void unpackAndroidAssets() {
extern "C" {
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCreate(JNIEnv* env, jobject obj, jobject instance, jobject asset_mgr) {
qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
g_assetManager = AAssetManager_fromJava(env, asset_mgr);
__activity = QAndroidJniObject(instance);
qRegisterMetaType<QAndroidJniObject>("QAndroidJniObject");
__interfaceActivity = QAndroidJniObject(instance);
auto oldMessageHandler = qInstallMessageHandler(tempMessageHandler);
unpackAndroidAssets();
qInstallMessageHandler(oldMessageHandler);
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [](const QString& a) {
QAndroidJniObject string = QAndroidJniObject::fromString(a);
__activity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;)V", string.object<jstring>());
__interfaceActivity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;)V", string.object<jstring>());
});
}
@ -167,15 +170,12 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUr
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnPause(JNIEnv* env, jobject obj) {
qDebug() << "nativeOnPause";
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnResume(JNIEnv* env, jobject obj) {
qDebug() << "nativeOnResume";
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnExitVr(JNIEnv* env, jobject obj) {
qDebug() << "nativeOnCreate On thread " << QThread::currentThreadId();
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoBackFromAndroidActivity(JNIEnv *env, jobject instance) {
@ -204,4 +204,68 @@ JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_getCurren
return env->NewStringUTF(str.toLatin1().data());
}
JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_protocolVersionSignature(JNIEnv *env, jobject instance) {
return env->NewStringUTF(protocolVersionsSignatureBase64().toLatin1().data());
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_LoginActivity_nativeLogin(JNIEnv *env, jobject instance,
jstring username_, jstring password_) {
const char *c_username = env->GetStringUTFChars(username_, 0);
const char *c_password = env->GetStringUTFChars(password_, 0);
QString username = QString(c_username);
QString password = QString(c_password);
env->ReleaseStringUTFChars(username_, c_username);
env->ReleaseStringUTFChars(password_, c_password);
auto accountManager = AndroidHelper::instance().getAccountManager();
__loginActivity = QAndroidJniObject(instance);
QObject::connect(accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
jboolean jSuccess = (jboolean) true;
__loginActivity.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
});
QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() {
jboolean jSuccess = (jboolean) false;
__loginActivity.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
});
QMetaObject::invokeMethod(accountManager.data(), "requestAccessToken", Q_ARG(const QString&, username), Q_ARG(const QString&, password));
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(JNIEnv *env,
jobject instance) {
__loadCompleteListener = QAndroidJniObject(instance);
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, []() {
__loadCompleteListener.callMethod<void>("onAppLoadedComplete", "()V");
__interfaceActivity.callMethod<void>("onAppLoadedComplete", "()V");
QObject::disconnect(&AndroidHelper::instance(), &AndroidHelper::qtAppLoadComplete, nullptr,
nullptr);
});
}
JNIEXPORT jboolean JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
return AndroidHelper::instance().getAccountManager()->isLoggedIn();
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeLogout(JNIEnv *env, jobject instance) {
AndroidHelper::instance().getAccountManager()->logout();
}
JNIEXPORT jstring JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeGetDisplayName(JNIEnv *env,
jobject instance) {
QString username = AndroidHelper::instance().getAccountManager()->getAccountInfo().getUsername();
return env->NewStringUTF(username.toLatin1().data());
}
}

View file

@ -9,12 +9,8 @@ import android.support.v7.widget.Toolbar;
import android.view.KeyEvent;
import android.view.MenuItem;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.EditText;
import java.net.URI;
import java.net.URISyntaxException;
public class GotoActivity extends AppCompatActivity {
public static final String PARAM_DOMAIN_URL = "domain_url";
@ -61,15 +57,7 @@ public class GotoActivity extends AppCompatActivity {
private void actionGo() {
String urlString = mUrlEditText.getText().toString();
if (!urlString.trim().isEmpty()) {
URI uri;
try {
uri = new URI(urlString);
} catch (URISyntaxException e) {
return;
}
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
urlString = "hifi://" + urlString;
}
urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString);
Intent intent = new Intent();
intent.putExtra(GotoActivity.PARAM_DOMAIN_URL, urlString);

View file

@ -1,11 +1,16 @@
package io.highfidelity.hifiinterface;
import java.net.URI;
import java.net.URISyntaxException;
/**
* Created by Gabriel Calero & Cristian Duarte on 4/13/18.
*/
public class HifiUtils {
public static final String BASE_URL = "https://metaverse.highfidelity.com";
private static HifiUtils instance;
private HifiUtils() {
@ -18,6 +23,40 @@ public class HifiUtils {
return instance;
}
public String sanitizeHifiUrl(String urlString) {
urlString = urlString.trim();
if (!urlString.isEmpty()) {
URI uri;
try {
uri = new URI(urlString);
} catch (URISyntaxException e) {
return urlString;
}
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
urlString = "hifi://" + urlString;
}
}
return urlString;
}
public String absoluteHifiAssetUrl(String urlString) {
urlString = urlString.trim();
if (!urlString.isEmpty()) {
URI uri;
try {
uri = new URI(urlString);
} catch (URISyntaxException e) {
return urlString;
}
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
urlString = BASE_URL + urlString;
}
}
return urlString;
}
public native String getCurrentAddress();
public native String protocolVersionSignature();
}

View file

@ -1,12 +1,11 @@
package io.highfidelity.hifiinterface;
import android.app.ProgressDialog;
import android.content.Intent;
import android.graphics.Color;
import android.os.AsyncTask;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
@ -14,30 +13,43 @@ import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.SearchView;
import android.widget.TabHost;
import android.widget.TabWidget;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import io.highfidelity.hifiinterface.QtPreloader.QtPreloader;
import org.qtproject.qt5.android.bindings.QtActivity;
import io.highfidelity.hifiinterface.view.DomainAdapter;
public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
public native boolean nativeIsLoggedIn();
public native void nativeLogout();
public native String nativeGetDisplayName();
/**
* Set this intent extra param to NOT start a new InterfaceActivity after a domain is selected"
*/
public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity";
//public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity";
public static final int ENTER_DOMAIN_URL = 1;
private DomainAdapter domainAdapter;
private DomainAdapter mDomainAdapter;
private DrawerLayout mDrawerLayout;
private ProgressDialog mDialog;
private NavigationView mNavigationView;
private RecyclerView mDomainsView;
private TextView searchNoResultsView;
private ImageView mSearchIconView;
private ImageView mClearSearch;
private EditText mSearchView;
@Override
protected void onCreate(Bundle savedInstanceState) {
@ -56,114 +68,111 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On
mNavigationView = (NavigationView) findViewById(R.id.nav_view);
mNavigationView.setNavigationItemSelectedListener(this);
TabHost tabs = (TabHost)findViewById(R.id.tabhost);
tabs.setup();
searchNoResultsView = findViewById(R.id.searchNoResultsView);
TabHost.TabSpec spec=tabs.newTabSpec("featured");
spec.setContent(R.id.featured);
spec.setIndicator(getString(R.string.featured));
tabs.addTab(spec);
spec = tabs.newTabSpec("popular");
spec.setContent(R.id.popular);
spec.setIndicator(getString(R.string.popular));
tabs.addTab(spec);
spec = tabs.newTabSpec("bookmarks");
spec.setContent(R.id.bookmarks);
spec.setIndicator(getString(R.string.bookmarks));
tabs.addTab(spec);
tabs.setCurrentTab(0);
TabWidget tabwidget=tabs.getTabWidget();
for(int i=0; i<tabwidget.getChildCount(); i++){
TextView tv=(TextView) tabwidget.getChildAt(i).findViewById(android.R.id.title);
tv.setTextAppearance(R.style.TabText);
}
RecyclerView domainsView = findViewById(R.id.rvDomains);
mDomainsView = findViewById(R.id.rvDomains);
int numberOfColumns = 1;
GridLayoutManager gridLayoutMgr = new GridLayoutManager(this, numberOfColumns);
domainsView.setLayoutManager(gridLayoutMgr);
domainAdapter = new DomainAdapter(this);
domainAdapter.setClickListener(new DomainAdapter.ItemClickListener() {
mDomainsView.setLayoutManager(gridLayoutMgr);
mDomainAdapter = new DomainAdapter(this, HifiUtils.getInstance().protocolVersionSignature());
mDomainAdapter.setClickListener(new DomainAdapter.ItemClickListener() {
@Override
public void onItemClick(View view, int position, DomainAdapter.Domain domain) {
gotoDomain(domain.url);
new Handler(getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
gotoDomain(domain.url);
}
}, 400); // a delay so the ripple effect can be seen
}
});
domainsView.setAdapter(domainAdapter);
mDomainAdapter.setListener(new DomainAdapter.AdapterListener() {
@Override
public void onEmptyAdapter() {
searchNoResultsView.setText(R.string.search_no_results);
searchNoResultsView.setVisibility(View.VISIBLE);
mDomainsView.setVisibility(View.GONE);
}
SearchView searchView = findViewById(R.id.searchView);
int searchPlateId = searchView.getContext().getResources().getIdentifier("android:id/search_plate", null, null);
View searchPlate = searchView.findViewById(searchPlateId);
if (searchPlate != null) {
searchPlate.setBackgroundColor (Color.TRANSPARENT);
int searchTextId = searchPlate.getContext ().getResources ().getIdentifier ("android:id/search_src_text", null, null);
TextView searchTextView = searchView.findViewById(searchTextId);
searchTextView.setTextAppearance(R.style.SearchText);
@Override
public void onNonEmptyAdapter() {
searchNoResultsView.setVisibility(View.GONE);
mDomainsView.setVisibility(View.VISIBLE);
}
@Override
public void onError(Exception e, String message) {
}
});
mDomainsView.setAdapter(mDomainAdapter);
mSearchView = findViewById(R.id.searchView);
mSearchIconView = findViewById(R.id.search_mag_icon);
mClearSearch = findViewById(R.id.search_clear);
mSearchView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
mDomainAdapter.loadDomains(editable.toString());
if (editable.length() > 0) {
mSearchIconView.setVisibility(View.GONE);
mClearSearch.setVisibility(View.VISIBLE);
} else {
mSearchIconView.setVisibility(View.VISIBLE);
mClearSearch.setVisibility(View.GONE);
}
}
});
mSearchView.setOnKeyListener((view, i, keyEvent) -> {
if (i == KeyEvent.KEYCODE_ENTER) {
String urlString = mSearchView.getText().toString();
if (!urlString.trim().isEmpty()) {
urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString);
}
gotoDomain(urlString);
return true;
}
return false;
});
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
updateLoginMenu();
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(ContextCompat.getColor(this, R.color.statusbar_color));
}
private void updateLoginMenu() {
TextView loginOption = findViewById(R.id.login);
TextView logoutOption = findViewById(R.id.logout);
if (nativeIsLoggedIn()) {
loginOption.setVisibility(View.GONE);
logoutOption.setVisibility(View.VISIBLE);
} else {
loginOption.setVisibility(View.VISIBLE);
logoutOption.setVisibility(View.GONE);
}
if (getIntent() == null ||
!getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) ||
!getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) {
preloadQt();
showActivityIndicator();
}
}
private void gotoDomain(String domainUrl) {
Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class);
intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl);
HomeActivity.this.finish();
if (getIntent() != null &&
getIntent().hasExtra(PARAM_NOT_START_INTERFACE_ACTIVITY) &&
getIntent().getBooleanExtra(PARAM_NOT_START_INTERFACE_ACTIVITY, false)) {
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
}
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
}
private void showActivityIndicator() {
if (mDialog == null) {
mDialog = new ProgressDialog(this);
}
mDialog.setMessage("Please wait...");
mDialog.setCancelable(false);
mDialog.show();
}
private void cancelActivityIndicator() {
if (mDialog != null) {
mDialog.cancel();
}
}
private AsyncTask preloadTask;
private void preloadQt() {
if (preloadTask == null) {
preloadTask = new AsyncTask() {
@Override
protected Object doInBackground(Object[] objects) {
new QtPreloader(HomeActivity.this).initQt();
runOnUiThread(new Runnable() {
@Override
public void run() {
cancelActivityIndicator();
}
});
return null;
}
};
preloadTask.execute();
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
@ -188,7 +197,6 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On
@Override
protected void onDestroy() {
cancelActivityIndicator();
super.onDestroy();
}
@ -199,8 +207,6 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On
Intent i = new Intent(this, GotoActivity.class);
startActivityForResult(i, ENTER_DOMAIN_URL);
return true;
case R.id.action_settings:
return true;
}
return false;
}
@ -213,8 +219,27 @@ public class HomeActivity extends AppCompatActivity implements NavigationView.On
}
}
@Override
protected void onStart() {
super.onStart();
updateLoginMenu();
}
public void onLoginClicked(View view) {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
public void onLogoutClicked(View view) {
nativeLogout();
updateLoginMenu();
}
@Override
public void onBackPressed() {
finishAffinity();
}
public void onSearchClear(View view) {
mSearchView.setText("");
}
}

View file

@ -36,8 +36,6 @@ public class InterfaceActivity extends QtActivity {
//public static native void handleHifiURL(String hifiURLString);
private native long nativeOnCreate(InterfaceActivity instance, AssetManager assetManager);
//private native void nativeOnPause();
//private native void nativeOnResume();
private native void nativeOnDestroy();
private native void nativeGotoUrl(String url);
private native void nativeGoBackFromAndroidActivity();
@ -63,6 +61,7 @@ public class InterfaceActivity extends QtActivity {
@Override
public void onCreate(Bundle savedInstanceState) {
super.isLoading = true;
Intent intent = getIntent();
if (intent.hasExtra(DOMAIN_URL) && !intent.getStringExtra(DOMAIN_URL).isEmpty()) {
intent.putExtra("applicationArguments", "--url " + intent.getStringExtra(DOMAIN_URL));
@ -112,31 +111,33 @@ public class InterfaceActivity extends QtActivity {
}
}
});
startActivity(new Intent(this, SplashActivity.class));
}
@Override
protected void onPause() {
super.onPause();
//nativeOnPause();
nativeEnterBackground();
//gvrApi.pauseTracking();
}
@Override
protected void onStart() {
super.onStart();
nativeEnterForeground();
setRequestedOrientation(ActivityInfo.SCREEN_ORIENTATION_LANDSCAPE);
}
@Override
protected void onStop() {
super.onStop();
nativeEnterBackground();
}
@Override
protected void onResume() {
super.onResume();
//nativeOnResume();
nativeEnterForeground();
//gvrApi.resumeTracking();
}
@ -201,7 +202,6 @@ public class InterfaceActivity extends QtActivity {
switch (activityName) {
case "Home": {
Intent intent = new Intent(this, HomeActivity.class);
intent.putExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, true);
startActivity(intent);
break;
}
@ -212,6 +212,10 @@ public class InterfaceActivity extends QtActivity {
}
}
public void onAppLoadedComplete() {
super.isLoading = false;
}
@Override
public void onBackPressed() {
openAndroidActivity("Home");

View file

@ -0,0 +1,106 @@
package io.highfidelity.hifiinterface;
import android.app.ProgressDialog;
import android.support.annotation.MainThread;
import android.support.v7.app.AppCompatActivity;
import android.os.Bundle;
import android.util.Log;
import android.view.KeyEvent;
import android.view.View;
import android.view.inputmethod.EditorInfo;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import android.widget.Toast;
public class LoginActivity extends AppCompatActivity {
public native void nativeLogin(String username, String password);
private EditText mUsername;
private EditText mPassword;
private TextView mError;
private Button mLoginButton;
private ProgressDialog mDialog;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);
mUsername = findViewById(R.id.username);
mPassword = findViewById(R.id.password);
mError = findViewById(R.id.error);
mLoginButton = findViewById(R.id.loginButton);
mPassword.setOnEditorActionListener(
new TextView.OnEditorActionListener() {
@Override
public boolean onEditorAction(TextView textView, int actionId, KeyEvent keyEvent) {
if (actionId == EditorInfo.IME_ACTION_DONE) {
mLoginButton.performClick();
return true;
}
return false;
}
});
}
@Override
protected void onStop() {
super.onStop();
cancelActivityIndicator();
}
public void login(View view) {
String username = mUsername.getText().toString();
String password = mPassword.getText().toString();
if (username.isEmpty() || password.isEmpty()) {
showError(getString(R.string.login_username_or_password_incorrect));
} else {
mLoginButton.setEnabled(false);
hideError();
showActivityIndicator();
nativeLogin(username, password);
}
}
private void showActivityIndicator() {
if (mDialog == null) {
mDialog = new ProgressDialog(this);
}
mDialog.setMessage(getString(R.string.logging_in));
mDialog.setCancelable(false);
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 void handleLoginCompleted(boolean success) {
runOnUiThread(() -> {
mLoginButton.setEnabled(true);
cancelActivityIndicator();
if (success) {
finish();
} else {
showError(getString(R.string.login_username_or_password_incorrect));
}
});
}
}

View file

@ -63,8 +63,7 @@ public class PermissionChecker extends Activity {
}
private void launchActivityWithPermissions(){
finish();
Intent i = new Intent(this, HomeActivity.class);
Intent i = new Intent(this, InterfaceActivity.class);
startActivity(i);
finish();
}

View file

@ -1,315 +0,0 @@
package io.highfidelity.hifiinterface.QtPreloader;
import android.content.ComponentName;
import android.content.Context;
import android.content.pm.ComponentInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.os.Bundle;
import android.util.Log;
import org.qtproject.qt5.android.bindings.QtApplication;
import java.io.DataInputStream;
import java.io.DataOutputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.lang.reflect.Method;
import java.util.ArrayList;
import dalvik.system.DexClassLoader;
/**
* Created by Gabriel Calero & Cristian Duarte on 3/22/18.
*/
public class QtPreloader {
public String ENVIRONMENT_VARIABLES = "QT_USE_ANDROID_NATIVE_DIALOGS=1";
private ComponentInfo m_contextInfo;
private String[] m_qtLibs = null; // required qt libs
private Context m_context;
private static final String DEX_PATH_KEY = "dex.path";
private static final String LIB_PATH_KEY = "lib.path";
private static final String NATIVE_LIBRARIES_KEY = "native.libraries";
private static final String ENVIRONMENT_VARIABLES_KEY = "environment.variables";
private static final String BUNDLED_LIBRARIES_KEY = "bundled.libraries";
private static final String BUNDLED_IN_LIB_RESOURCE_ID_KEY = "android.app.bundled_in_lib_resource_id";
private static final String BUNDLED_IN_ASSETS_RESOURCE_ID_KEY = "android.app.bundled_in_assets_resource_id";
private static final String MAIN_LIBRARY_KEY = "main.library";
private static final int BUFFER_SIZE = 1024;
public QtPreloader(Context context) {
m_context = context;
}
public void initQt() {
try {
m_contextInfo = m_context.getPackageManager().getActivityInfo(new ComponentName("io.highfidelity.hifiinterface", "io.highfidelity.hifiinterface.InterfaceActivity"),
PackageManager.GET_META_DATA);
if (m_contextInfo.metaData.containsKey("android.app.qt_libs_resource_id")) {
int resourceId = m_contextInfo.metaData.getInt("android.app.qt_libs_resource_id");
m_qtLibs = m_context.getResources().getStringArray(resourceId);
}
ArrayList<String> libraryList = new ArrayList<>();
String localPrefix = m_context.getApplicationInfo().dataDir + "/";
String pluginsPrefix = localPrefix + "qt-reserved-files/";
cleanOldCacheIfNecessary(localPrefix, pluginsPrefix);
extractBundledPluginsAndImports(pluginsPrefix);
for (String lib : m_qtLibs) {
libraryList.add(localPrefix + "lib/lib" + lib + ".so");
}
if (m_contextInfo.metaData.containsKey("android.app.load_local_libs")) {
String[] extraLibs = m_contextInfo.metaData.getString("android.app.load_local_libs").split(":");
for (String lib : extraLibs) {
if (lib.length() > 0) {
if (lib.startsWith("lib/")) {
libraryList.add(localPrefix + lib);
} else {
libraryList.add(pluginsPrefix + lib);
}
}
}
}
Bundle loaderParams = new Bundle();
loaderParams.putString(DEX_PATH_KEY, new String());
loaderParams.putStringArrayList(NATIVE_LIBRARIES_KEY, libraryList);
loaderParams.putString(ENVIRONMENT_VARIABLES_KEY, ENVIRONMENT_VARIABLES
+ "\tQML2_IMPORT_PATH=" + pluginsPrefix + "/qml"
+ "\tQML_IMPORT_PATH=" + pluginsPrefix + "/imports"
+ "\tQT_PLUGIN_PATH=" + pluginsPrefix + "/plugins");
// add all bundled Qt libs to loader params
ArrayList<String> libs = new ArrayList<>();
String libName = m_contextInfo.metaData.getString("android.app.lib_name");
loaderParams.putString(MAIN_LIBRARY_KEY, libName); //main library contains main() function
loaderParams.putStringArrayList(BUNDLED_LIBRARIES_KEY, libs);
// load and start QtLoader class
DexClassLoader classLoader = new DexClassLoader(loaderParams.getString(DEX_PATH_KEY), // .jar/.apk files
m_context.getDir("outdex", Context.MODE_PRIVATE).getAbsolutePath(), // directory where optimized DEX files should be written.
loaderParams.containsKey(LIB_PATH_KEY) ? loaderParams.getString(LIB_PATH_KEY) : null, // libs folder (if exists)
m_context.getClassLoader()); // parent loader
Class<?> loaderClass = classLoader.loadClass(loaderClassName()); // load QtLoader class
Object qtLoader = loaderClass.newInstance(); // create an instance
Method prepareAppMethod = qtLoader.getClass().getMethod("loadApplication",
contextClassName(),
ClassLoader.class,
Bundle.class);
prepareAppMethod.invoke(qtLoader, m_context, classLoader, loaderParams);
// now load the application library so it's accessible from this class loader
if (libName != null) {
System.loadLibrary(libName);
}
} catch (Exception e) {
Log.e(QtApplication.QtTAG, "Error pre-loading HiFi Qt app", e);
}
}
protected String loaderClassName() {
return "org.qtproject.qt5.android.QtActivityDelegate";
}
protected Class<?> contextClassName() {
return android.app.Activity.class;
}
private void deleteRecursively(File directory) {
File[] files = directory.listFiles();
if (files != null) {
for (File file : files) {
if (file.isDirectory()) {
deleteRecursively(file);
} else {
file.delete();
}
}
directory.delete();
}
}
private void cleanOldCacheIfNecessary(String oldLocalPrefix, String localPrefix) {
File newCache = new File(localPrefix);
if (!newCache.exists()) {
{
File oldPluginsCache = new File(oldLocalPrefix + "plugins/");
if (oldPluginsCache.exists() && oldPluginsCache.isDirectory()) {
deleteRecursively(oldPluginsCache);
}
}
{
File oldImportsCache = new File(oldLocalPrefix + "imports/");
if (oldImportsCache.exists() && oldImportsCache.isDirectory()) {
deleteRecursively(oldImportsCache);
}
}
{
File oldQmlCache = new File(oldLocalPrefix + "qml/");
if (oldQmlCache.exists() && oldQmlCache.isDirectory()) {
deleteRecursively(oldQmlCache);
}
}
}
}
static private void copyFile(InputStream inputStream, OutputStream outputStream)
throws IOException {
byte[] buffer = new byte[BUFFER_SIZE];
int count;
while ((count = inputStream.read(buffer)) > 0) {
outputStream.write(buffer, 0, count);
}
}
private void copyAsset(String source, String destination)
throws IOException {
// Already exists, we don't have to do anything
File destinationFile = new File(destination);
if (destinationFile.exists()) {
return;
}
File parentDirectory = destinationFile.getParentFile();
if (!parentDirectory.exists()) {
parentDirectory.mkdirs();
}
destinationFile.createNewFile();
AssetManager assetsManager = m_context.getAssets();
InputStream inputStream = assetsManager.open(source);
OutputStream outputStream = new FileOutputStream(destinationFile);
copyFile(inputStream, outputStream);
inputStream.close();
outputStream.close();
}
private static void createBundledBinary(String source, String destination)
throws IOException {
// Already exists, we don't have to do anything
File destinationFile = new File(destination);
if (destinationFile.exists()) {
return;
}
File parentDirectory = destinationFile.getParentFile();
if (!parentDirectory.exists()) {
parentDirectory.mkdirs();
}
destinationFile.createNewFile();
InputStream inputStream = new FileInputStream(source);
OutputStream outputStream = new FileOutputStream(destinationFile);
copyFile(inputStream, outputStream);
inputStream.close();
outputStream.close();
}
private boolean cleanCacheIfNecessary(String pluginsPrefix, long packageVersion) {
File versionFile = new File(pluginsPrefix + "cache.version");
long cacheVersion = 0;
if (versionFile.exists() && versionFile.canRead()) {
try {
DataInputStream inputStream = new DataInputStream(new FileInputStream(versionFile));
cacheVersion = inputStream.readLong();
inputStream.close();
} catch (Exception e) {
e.printStackTrace();
}
}
if (cacheVersion != packageVersion) {
deleteRecursively(new File(pluginsPrefix));
return true;
} else {
return false;
}
}
private void extractBundledPluginsAndImports(String pluginsPrefix) throws IOException {
String libsDir = m_context.getApplicationInfo().nativeLibraryDir + "/";
long packageVersion = -1;
try {
PackageInfo packageInfo = m_context.getPackageManager().getPackageInfo(m_context.getPackageName(), 0);
packageVersion = packageInfo.lastUpdateTime;
} catch (Exception e) {
e.printStackTrace();
}
if (!cleanCacheIfNecessary(pluginsPrefix, packageVersion)) {
return;
}
{
File versionFile = new File(pluginsPrefix + "cache.version");
File parentDirectory = versionFile.getParentFile();
if (!parentDirectory.exists()) {
parentDirectory.mkdirs();
}
versionFile.createNewFile();
DataOutputStream outputStream = new DataOutputStream(new FileOutputStream(versionFile));
outputStream.writeLong(packageVersion);
outputStream.close();
}
{
String key = BUNDLED_IN_LIB_RESOURCE_ID_KEY;
if (m_contextInfo.metaData.containsKey(key)) {
String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key));
for (String bundledImportBinary : list) {
String[] split = bundledImportBinary.split(":");
String sourceFileName = libsDir + split[0];
String destinationFileName = pluginsPrefix + split[1];
createBundledBinary(sourceFileName, destinationFileName);
}
}
}
{
String key = BUNDLED_IN_ASSETS_RESOURCE_ID_KEY;
if (m_contextInfo.metaData.containsKey(key)) {
String[] list = m_context.getResources().getStringArray(m_contextInfo.metaData.getInt(key));
for (String fileName : list) {
String[] split = fileName.split(":");
String sourceFileName = split[0];
String destinationFileName = pluginsPrefix + split[1];
copyAsset(sourceFileName, destinationFileName);
}
}
}
}
}

View file

@ -0,0 +1,33 @@
package io.highfidelity.hifiinterface;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
public class SplashActivity extends Activity {
private native void registerLoadCompleteListener();
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_splash);
registerLoadCompleteListener();
}
@Override
protected void onStart() {
super.onStart();
}
@Override
protected void onStop() {
super.onStop();
}
public void onAppLoadedComplete() {
startActivity(new Intent(this, HomeActivity.class)); // + 2 sec
SplashActivity.this.finish();
}
}

View file

@ -0,0 +1,9 @@
package io.highfidelity.hifiinterface.provider;
/**
* Created by cduarte on 4/18/18.
*/
public interface Callback<T> {
public void callback(T t);
}

View file

@ -0,0 +1,19 @@
package io.highfidelity.hifiinterface.provider;
import java.util.List;
import io.highfidelity.hifiinterface.view.DomainAdapter;
/**
* Created by cduarte on 4/17/18.
*/
public interface DomainProvider {
void retrieve(String filterText, DomainCallback domainCallback);
interface DomainCallback {
void retrieveOk(List<DomainAdapter.Domain> domain);
void retrieveError(Exception e, String message);
}
}

View file

@ -0,0 +1,235 @@
package io.highfidelity.hifiinterface.provider;
import android.util.Log;
import android.util.MutableInt;
import java.util.ArrayList;
import java.util.List;
import io.highfidelity.hifiinterface.HifiUtils;
import io.highfidelity.hifiinterface.view.DomainAdapter;
import retrofit2.Call;
import retrofit2.Response;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.http.GET;
import retrofit2.http.Query;
/**
* Created by cduarte on 4/17/18.
*/
public class UserStoryDomainProvider implements DomainProvider {
public static final String BASE_URL = "https://metaverse.highfidelity.com/";
private static final String INCLUDE_ACTIONS_FOR_PLACES = "concurrency";
private static final String INCLUDE_ACTIONS_FOR_FULL_SEARCH = "concurrency,announcements,snapshot";
private static final int MAX_PAGES_TO_GET = 10;
private String mProtocol;
private Retrofit mRetrofit;
private UserStoryDomainProviderService mUserStoryDomainProviderService;
private boolean startedToGetFromAPI = false;
private List<UserStory> allStories; // All retrieved stories from the API
private List<DomainAdapter.Domain> suggestions; // Filtered places to show
public UserStoryDomainProvider(String protocol) {
mRetrofit = new Retrofit.Builder()
.baseUrl(BASE_URL)
.addConverterFactory(GsonConverterFactory.create())
.build();
mUserStoryDomainProviderService = mRetrofit.create(UserStoryDomainProviderService.class);
mProtocol = protocol;
allStories = new ArrayList<>();
suggestions = new ArrayList<>();
}
private void fillDestinations(String filterText, DomainCallback domainCallback) {
StoriesFilter filter = new StoriesFilter(filterText);
final MutableInt counter = new MutableInt(0);
allStories.clear();
getUserStoryPage(1,
e -> {
allStories.subList(counter.value, allStories.size()).forEach(userStory -> {
filter.filterOrAdd(userStory);
});
if (domainCallback != null) {
domainCallback.retrieveOk(suggestions); //ended
}
},
a -> {
allStories.forEach(userStory -> {
counter.value++;
filter.filterOrAdd(userStory);
});
}
);
}
private void handleError(String url, Throwable t, Callback<Exception> restOfPagesCallback) {
restOfPagesCallback.callback(new Exception("Error accessing url [" + url + "]", t));
}
private void getUserStoryPage(int pageNumber, Callback<Exception> restOfPagesCallback, Callback<Void> firstPageCallback) {
Call<UserStories> userStories = mUserStoryDomainProviderService.getUserStories(
INCLUDE_ACTIONS_FOR_PLACES,
"open",
true,
mProtocol,
pageNumber);
Log.d("API-USER-STORY-DOMAINS", "Protocol [" + mProtocol + "] include_actions [" + INCLUDE_ACTIONS_FOR_PLACES + "]");
userStories.enqueue(new retrofit2.Callback<UserStories>() {
@Override
public void onResponse(Call<UserStories> call, Response<UserStories> response) {
UserStories data = response.body();
allStories.addAll(data.user_stories);
if (data.current_page < data.total_pages && data.current_page <= MAX_PAGES_TO_GET) {
if (pageNumber == 1 && firstPageCallback != null) {
firstPageCallback.callback(null);
}
getUserStoryPage(pageNumber + 1, restOfPagesCallback, null);
return;
}
restOfPagesCallback.callback(null);
}
@Override
public void onFailure(Call<UserStories> call, Throwable t) {
handleError(call.request().url().toString(), t, restOfPagesCallback);
}
});
}
private class StoriesFilter {
String[] mWords = new String[]{};
public StoriesFilter(String filterText) {
mWords = filterText.toUpperCase().split("\\s+");
}
private boolean matches(UserStory story) {
if (mWords.length <= 0) {
return true;
}
for (String word : mWords) {
if (!story.searchText().contains(word)) {
return false;
}
}
return true;
}
private void addToSuggestions(UserStory story) {
suggestions.add(story.toDomain());
}
public void filterOrAdd(UserStory story) {
if (matches(story)) {
addToSuggestions(story);
}
}
}
private void filterChoicesByText(String filterText, DomainCallback domainCallback) {
suggestions.clear();
StoriesFilter storiesFilter = new StoriesFilter(filterText);
allStories.forEach(story -> {
storiesFilter.filterOrAdd(story);
});
domainCallback.retrieveOk(suggestions);
}
@Override
public synchronized void retrieve(String filterText, DomainCallback domainCallback) {
if (!startedToGetFromAPI) {
startedToGetFromAPI = true;
fillDestinations(filterText, domainCallback);
} else {
filterChoicesByText(filterText, domainCallback);
}
}
public void retrieveNot(DomainCallback domainCallback) {
// TODO Call multiple pages
Call<UserStories> userStories = mUserStoryDomainProviderService.getUserStories(
INCLUDE_ACTIONS_FOR_PLACES,
"open",
true,
mProtocol,
1);
Log.d("API-USER-STORY-DOMAINS", "Protocol [" + mProtocol + "] include_actions [" + INCLUDE_ACTIONS_FOR_PLACES + "]");
userStories.enqueue(new retrofit2.Callback<UserStories>() {
@Override
public void onResponse(Call<UserStories> call, Response<UserStories> response) {
UserStories userStories = response.body();
if (userStories == null) {
domainCallback.retrieveOk(new ArrayList<>(0));
}
List<DomainAdapter.Domain> domains = new ArrayList<>(userStories.total_entries);
userStories.user_stories.forEach(userStory -> {
domains.add(userStory.toDomain());
});
domainCallback.retrieveOk(domains);
}
@Override
public void onFailure(Call<UserStories> call, Throwable t) {
domainCallback.retrieveError(new Exception(t), t.getMessage());
}
});
}
public interface UserStoryDomainProviderService {
@GET("api/v1/user_stories")
Call<UserStories> getUserStories(@Query("include_actions") String includeActions,
@Query("restriction") String restriction,
@Query("require_online") boolean requireOnline,
@Query("protocol") String protocol,
@Query("page") int pageNumber);
}
class UserStory {
public UserStory() {}
String place_name;
String path;
String thumbnail_url;
String place_id;
String domain_id;
private String searchText;
// New fields? tags, description
String searchText() {
if (searchText == null) {
searchText = place_name == null? "" : place_name.toUpperCase();
}
return searchText;
}
DomainAdapter.Domain toDomain() {
// TODO Proper url creation (it can or can't have hifi
// TODO Or use host value from api?
String absoluteThumbnailUrl = HifiUtils.getInstance().absoluteHifiAssetUrl(thumbnail_url);
DomainAdapter.Domain domain = new DomainAdapter.Domain(
place_name,
HifiUtils.getInstance().sanitizeHifiUrl(place_name) + "/" + path,
absoluteThumbnailUrl
);
return domain;
}
}
class UserStories {
String status;
int current_page;
int total_pages;
int total_entries;
List<UserStory> user_stories;
}
}

View file

@ -1,6 +1,7 @@
package io.highfidelity.hifiinterface.view;
import android.content.Context;
import android.net.Uri;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.LayoutInflater;
@ -9,6 +10,8 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Picasso;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
@ -19,69 +22,59 @@ import java.util.ArrayList;
import java.util.List;
import io.highfidelity.hifiinterface.R;
import io.highfidelity.hifiinterface.provider.DomainProvider;
import io.highfidelity.hifiinterface.provider.UserStoryDomainProvider;
/**
* Created by Gabriel Calero & Cristian Duarte on 3/20/18.
*/
public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder> {
private static final java.lang.String DOMAINS_FILE = "hifi_domains.json";
private static final String TAG = "HiFi Interface";
private Context mContext;
private LayoutInflater mInflater;
private ItemClickListener mClickListener;
public class Domain {
public String name;
public String url;
public String thumbnail;
Domain(String name, String url, String thumbnail) {
this.name = name;
this.thumbnail = thumbnail;
this.url = url;
}
}
private String mProtocol;
private UserStoryDomainProvider domainProvider;
private AdapterListener mAdapterListener;
// references to our domains
private Domain[] mDomains = {};
public DomainAdapter(Context c) {
public DomainAdapter(Context c, String protocol) {
mContext = c;
this.mInflater = LayoutInflater.from(mContext);
loadDomains();
mProtocol = protocol;
domainProvider = new UserStoryDomainProvider(mProtocol);
loadDomains("");
}
private void loadDomains() {
try {
JSONObject json = new JSONObject(loadJSONFromAsset());
JSONArray hifiDomains = json.getJSONArray("hifi_domains");
List<Domain> domains = new ArrayList<>();
for (int i = 0; i < hifiDomains.length(); i++) {
JSONObject jDomain = hifiDomains.getJSONObject(i);
public void setListener(AdapterListener adapterListener) {
mAdapterListener = adapterListener;
}
String domainName = jDomain.getString("name");
String domainUrl = jDomain.getString("url");
String domainThumb = jDomain.getString("thumbnail");
domains.add(new Domain(domainName, domainUrl, domainThumb));
public void loadDomains(String filterText) {
domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() {
@Override
public void retrieveOk(List<Domain> domain) {
mDomains = new Domain[domain.size()];
mDomains = domain.toArray(mDomains);
notifyDataSetChanged();
if (mAdapterListener != null) {
if (mDomains.length == 0) {
mAdapterListener.onEmptyAdapter();
} else {
mAdapterListener.onNonEmptyAdapter();
}
}
}
mDomains = domains.toArray(mDomains);
} catch (IOException e) {
Log.e(TAG, "Error loading domains from local file", e);
} catch (JSONException e) {
Log.e(TAG, "Error loading domains from local file", e);
}
}
public String loadJSONFromAsset() throws IOException {
String json = null;
InputStream is = mContext.getAssets().open(DOMAINS_FILE);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
json = new String(buffer, "UTF-8");
return json;
@Override
public void retrieveError(Exception e, String message) {
Log.e("DOMAINS", message, e);
if (mAdapterListener != null) mAdapterListener.onError(e, message);
}
});
}
@Override
@ -94,7 +87,10 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
public void onBindViewHolder(ViewHolder holder, int position) {
// TODO
//holder.thumbnail.setImageResource(mDomains[position].thumbnail);
holder.mDomainName.setText(mDomains[position].name);
Domain domain = mDomains[position];
holder.mDomainName.setText(domain.name);
Uri uri = Uri.parse(domain.thumbnail);
Picasso.get().load(uri).into(holder.mThumbnail);
}
@Override
@ -128,4 +124,21 @@ public class DomainAdapter extends RecyclerView.Adapter<DomainAdapter.ViewHolder
public interface ItemClickListener {
void onItemClick(View view, int position, Domain domain);
}
public static class Domain {
public String name;
public String url;
public String thumbnail;
public Domain(String name, String url, String thumbnail) {
this.name = name;
this.thumbnail = thumbnail;
this.url = url;
}
}
public interface AdapterListener {
void onEmptyAdapter();
void onNonEmptyAdapter();
void onError(Exception e, String message);
}
}

View file

@ -68,6 +68,8 @@ public class QtActivity extends Activity {
public final String QT_ANDROID_DEFAULT_THEME = QT_ANDROID_THEMES[0]; // sets the default theme.
private QtActivityLoader m_loader = new QtActivityLoader(this);
public boolean isLoading;
public QtActivity() {
}
@ -499,7 +501,11 @@ public class QtActivity extends Activity {
@Override
protected void onPause() {
super.onPause();
QtApplication.invokeDelegate();
// GC: this trick allow us to show a splash activity until Qt app finishes
// loading
if (!isLoading) {
QtApplication.invokeDelegate();
}
}
//---------------------------------------------------------------------------
@ -640,6 +646,7 @@ public class QtActivity extends Activity {
super.onStop();
QtApplication.invokeDelegate();
}
//---------------------------------------------------------------------------
@Override

View file

@ -1,10 +0,0 @@
<?xml version="1.0" encoding="UTF-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:top="1dp" android:left="12dp" android:bottom="6dp" android:right="12dp">
<shape android:shape="rectangle">
<solid android:color="@color/backgroundDark" />
<corners android:radius="4dp"/>
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,50 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="350dp"
android:height="100dp"
android:viewportWidth="350"
android:viewportHeight="100">
<path
android:fillColor="#00B4F0"
android:pathData="M132.646,40.971 L132.646,62.955 L127.959,62.955 L127.959,53.361 L120.222,53.361 L120.222,62.955 L115.535,62.955 L115.535,40.971 L120.222,40.971 L120.222,49.453 L127.959,49.453 L127.959,40.971 L132.646,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M140.959,62.955 L136.271,62.955 L136.271,40.971 L140.959,40.971 L140.959,62.955 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M154.488,44.479 C153.383,44.479,152.454,44.568,151.658,44.79 C150.906,45.012,150.287,45.367,149.801,45.856 C149.315,46.345,149.006,47.055,148.784,47.899 C148.563,48.743,148.474,49.809,148.474,51.053 L148.474,53.051 C148.474,54.383,148.563,55.449,148.74,56.293 C148.917,57.137,149.226,57.803,149.668,58.247 C150.066,58.736,150.641,59.047,151.305,59.225 C151.968,59.403,152.808,59.492,153.781,59.492 C154.047,59.492,154.356,59.492,154.665,59.448 C154.976,59.448,155.284,59.403,155.638,59.358 L155.638,53.408 L152.898,53.408 L153.339,49.765 L160.06,49.765 L160.06,62.29 C159.22,62.601,158.16,62.823,156.876,63.001 C155.638,63.179,154.312,63.267,152.985,63.267 C151.305,63.267,149.89,63.045,148.741,62.646 C147.548,62.247,146.619,61.625,145.867,60.782 C145.116,59.937,144.585,58.916,144.231,57.628 C143.877,56.385,143.745,54.874,143.745,53.188 L143.745,50.921 C143.745,49.367,143.922,47.99,144.231,46.702 C144.585,45.414,145.16,44.348,145.956,43.46 C146.751,42.572,147.812,41.861,149.095,41.373 C150.377,40.884,152.013,40.618,153.958,40.618 C155.02,40.618,156.081,40.706,157.097,40.884 C158.115,41.062,158.954,41.284,159.618,41.506 L158.866,45.059 C158.291,44.881,157.628,44.748,156.921,44.615 C156.212,44.523,155.416,44.479,154.488,44.479 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M181.104,40.971 L181.104,62.955 L176.417,62.955 L176.417,53.361 L168.724,53.361 L168.724,62.955 L164.037,62.955 L164.037,40.971 L168.724,40.971 L168.724,49.453 L176.461,49.453 L176.461,40.971 L181.104,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M206.084,40.971 L205.555,44.79 L197.375,44.79 L197.375,49.365 L204.936,49.365 L204.405,53.183 L197.377,53.183 L197.377,62.954 L192.689,62.954 L192.689,40.971 L206.084,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M213.688,62.955 L209.001,62.955 L209.001,40.971 L213.688,40.971 L213.688,62.955 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M217.447,40.971 L224.609,40.971 C226.465,40.971,228.013,41.237,229.252,41.726 C230.491,42.215,231.463,42.925,232.215,43.769 C232.966,44.657,233.452,45.679,233.762,46.922 C234.073,48.166,234.203,49.498,234.203,50.92 L234.203,53.007 C234.203,54.473,234.071,55.805,233.762,57.049 C233.452,58.292,232.922,59.315,232.215,60.202 C231.463,61.09,230.49,61.756,229.252,62.245 C228.014,62.732,226.467,63,224.609,63 L217.491,63 L217.491,40.971 L217.447,40.971 L217.447,40.971 Z M222.09,59.137 L224.123,59.137 C225.008,59.137,225.76,59.047,226.467,58.825 C227.13,58.603,227.705,58.248,228.147,57.76 C228.589,57.272,228.899,56.606,229.121,55.805 C229.342,55.006,229.431,53.985,229.431,52.742 L229.431,51.098 C229.431,49.854,229.342,48.832,229.121,48.034 C228.899,47.235,228.591,46.569,228.147,46.08 C227.707,45.591,227.174,45.236,226.467,45.059 C225.803,44.881,225.008,44.748,224.123,44.748 L222.09,44.748 L222.09,59.137 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M250.871,40.971 L250.385,44.657 L241.942,44.657 L241.942,49.453 L249.899,49.453 L249.459,53.141 L241.985,53.141 L241.985,59.315 L250.918,59.315 L250.43,63 L237.254,63 L237.254,40.971 L250.871,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M258.609,40.971 L258.609,59.137 L267.496,59.137 L266.965,62.955 L253.922,62.955 L253.922,40.971 L258.609,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M275.232,62.955 L270.544,62.955 L270.544,40.971 L275.232,40.971 L275.232,62.955 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M295.482,40.971 L294.952,44.79 L289.027,44.79 L289.027,62.955 L284.339,62.955 L284.339,44.79 L277.884,44.79 L278.415,40.971 L295.482,40.971 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M306.58,49.543 L311.134,40.971 L315.953,40.971 L308.791,53.363 L308.791,62.956 L304.103,62.956 L304.103,53.363 L297.03,40.971 L302.159,40.971 L306.58,49.543 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M68.848,83.209 C64.427,83.209,60.094,82.32,56.07,80.633 C52.135,78.989,48.642,76.591,45.636,73.571 C42.63,70.551,40.242,66.999,38.606,63.091 C36.837,58.96,35.953,54.653,35.953,50.166 C35.953,45.725,36.837,41.372,38.517,37.33 C40.153,33.377,42.54,29.869,45.547,26.848 C48.553,23.828,52.09,21.43,55.981,19.786 C60.049,18.054,64.337,17.21,68.758,17.21 C73.179,17.21,77.513,18.098,81.536,19.786 C85.471,21.429,88.964,23.828,91.97,26.848 C94.977,29.869,97.365,33.422,98.999,37.33 C100.724,41.416,101.563,45.725,101.563,50.166 C101.563,54.606,100.681,58.959,99,63.001 C97.365,66.954,94.978,70.462,91.971,73.483 C88.965,76.503,85.428,78.901,81.537,80.544 C77.602,82.32,73.269,83.209,68.848,83.209 Z M68.848,20.584 C52.621,20.584,39.402,33.864,39.402,50.164 C39.402,66.465,52.621,79.744,68.848,79.744 S98.293,66.465,98.293,50.164 C98.293,33.864,85.074,20.584,68.848,20.584 Z" />
<path
android:fillColor="#00B4F0"
android:pathData="M78.884,64.199 L78.884,41.014 C80.343,40.525,81.36,39.149,81.36,37.55 C81.36,35.551,79.725,33.908,77.735,33.908 C75.745,33.908,74.11,35.552,74.11,37.55 C74.11,39.105,75.038,40.393,76.409,40.97 L76.409,51.762 L61.376,44.745 L61.376,36.217 C62.835,35.729,63.852,34.352,63.852,32.753 C63.852,30.754,62.216,29.111,60.227,29.111 C58.238,29.111,56.602,30.755,56.602,32.753 C56.602,34.308,57.531,35.596,58.901,36.173 L58.901,59.491 C57.574,60.023,56.602,61.355,56.602,62.911 C56.602,64.91,58.238,66.553,60.227,66.553 C62.216,66.553,63.852,64.91,63.852,62.911 C63.852,61.312,62.835,59.935,61.376,59.447 L61.376,47.676 L76.409,54.694 L76.409,64.244 C75.083,64.775,74.11,66.109,74.11,67.663 C74.11,69.662,75.745,71.305,77.735,71.305 C79.725,71.305,81.36,69.662,81.36,67.663 C81.359,66.02,80.343,64.688,78.884,64.199 Z" />
</vector>

View file

@ -0,0 +1,12 @@
<vector android:height="24dp" android:viewportHeight="15.0"
android:viewportWidth="15.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#404040" android:pathData="M7.5,7.5m-7.5,0a7.5,7.5 0,1 1,15 0a7.5,7.5 0,1 1,-15 0"/>
<path android:fillColor="#00000000"
android:pathData="M4.683,4.583L10,10.27"
android:strokeColor="#E3E3E3" android:strokeLineCap="round"
android:strokeLineJoin="bevel" android:strokeWidth="1"/>
<path android:fillColor="#00000000"
android:pathData="M10,4.73L4.683,10.417"
android:strokeColor="#E3E3E3" android:strokeLineCap="round"
android:strokeLineJoin="bevel" android:strokeWidth="1"/>
</vector>

View file

@ -0,0 +1,4 @@
<vector android:height="24dp" android:viewportHeight="18.0"
android:viewportWidth="18.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="#F2F2F2" android:fillType="evenOdd" android:pathData="M12.5,11L11.7,11L11.4,10.7C12.4,9.6 13,8.1 13,6.5C13,2.9 10.1,0 6.5,0C2.9,0 0,2.9 0,6.5C0,10.1 2.9,13 6.5,13C8.1,13 9.6,12.4 10.7,11.4L11,11.7L11,12.5L16,17.5L17.5,16L12.5,11ZM6.5,11C4,11 2,9 2,6.5C2,4 4,2 6.5,2C9,2 11,4 11,6.5C11,9 9,11 6.5,11Z"/>
</vector>

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/colorButton1" />
<solid android:color="@color/colorButton1"/>
</shape>
</item>
<item android:state_focused="true">
<shape android:shape="rectangle" >
<corners android:radius="4dip" />
<stroke android:width="1dip" android:color="@color/colorButton1" />
<solid android:color="@color/colorButton1"/>
</shape>
</item>
<item>
<shape android:shape="rectangle" >
<corners android:radius="4dip" />
<stroke android:width="1dip" android:color="@color/colorButton1" />
<solid android:color="@color/colorButton1"/>
</shape>
</item>
</selector>

View file

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle" android:padding="9dp">
<corners android:radius="4dip" />
<stroke android:width="1dip" android:color="@android:color/black" />
<solid android:color="@color/backgroundEditText"/>
</shape>

View file

@ -18,8 +18,9 @@
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
android:background="@color/colorPrimaryDark"
android:elevation="4dp"
/>
<include layout="@layout/content_home" />
@ -32,7 +33,31 @@
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
android:background="@color/colorPrimaryDark"
app:menu="@menu/menu_home"
/>
>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
android:clickable="true"
android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin">
<TextView
android:id="@+id/login"
android:text="@string/login"
android:onClick="onLoginClicked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/menuOption" />
<TextView
android:id="@+id/logout"
android:text="@string/logout"
android:onClick="onLogoutClicked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/menuOption" />
</LinearLayout>
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>

View file

@ -0,0 +1,106 @@
<?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"
tools:context="io.highfidelity.hifiinterface.LoginActivity">
<ImageView
android:id="@+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/hifi_header"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintBottom_toTopOf="@id/username"
android:layout_marginBottom="75dp"
/>
<TextView
android:id="@+id/error"
android:layout_width="wrap_content"
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/username"
app:layout_constraintLeft_toLeftOf="@id/username"
android:visibility="invisible"/>
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:background="@drawable/rounded_edit"
android:padding="9dp"
android:paddingRight="12dp"
android:ems="10"
android:fontFamily="@font/raleway"
android:inputType="textEmailAddress"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="right"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
android:hint="@string/username_or_email" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:background="@drawable/rounded_edit"
android:padding="9dp"
android:paddingRight="12dp"
android:ems="10"
android:fontFamily="@font/raleway"
android:inputType="textPassword"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="right"
android:layout_marginTop="14dp"
app:layout_constraintTop_toBottomOf="@id/username"
android:hint="@string/password"
android:imeOptions="actionDone"/>
<Button
android:id="@+id/loginButton"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/rounded_button"
android:textColor="@color/white_opaque"
android:text="@string/login"
app:layout_constraintRight_toRightOf="@id/username"
app:layout_constraintTop_toBottomOf="@id/password"
android:paddingRight="30dp"
android:paddingLeft="30dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_marginTop="16dp"
android:onClick="login"/>
<TextView
android:id="@+id/textView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:fontFamily="@font/raleway_semibold"
android:text="@string/forgot_password"
android:textStyle="italic"
android:padding="10dp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="@id/loginButton"
android:textColor="@color/colorButton1"/>
</android.support.constraint.ConstraintLayout>

View file

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout 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:id="@+id/root_activity_splash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLight">
<!-- TODO -->
</RelativeLayout>

View file

@ -2,75 +2,75 @@
<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:id="@+id/contentHomeRoot"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:background="@color/colorPrimary"
tools:context="io.highfidelity.hifiinterface.HomeActivity"
tools:showIn="@layout/activity_home">
<TabHost
android:id="@+id/tabhost"
<EditText
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
android:layout_height="@dimen/searchEditHeight"
app:layout_constraintTop_toTopOf="@id/contentHomeRoot"
android:background="@drawable/search_bg"
android:layout_marginTop="@dimen/searchEditAdditionalMarginTop"
android:gravity="center_vertical|end"
android:paddingStart="@dimen/searchEditPaddingStart"
android:paddingEnd="@dimen/searchEditPaddingEnd"
android:fontFamily="@font/raleway"
android:hint="@string/search_hint"
android:textSize="@dimen/searchEditTextSize"
android:inputType="textUri"
android:imeOptions="actionGo"
/>
<ImageView
android:id="@+id/search_mag_icon"
android:layout_width="@dimen/searchEditMagIconWidth"
android:layout_height="@dimen/searchEditMagIconHeight"
app:layout_constraintEnd_toEndOf="@id/searchView"
app:layout_constraintTop_toTopOf="@id/searchView"
app:layout_constraintBottom_toBottomOf="@id/searchView"
android:layout_marginEnd="@dimen/searchEditMagMarginEnd"
android:src="@drawable/ic_search"
/>
<ImageView
android:id="@+id/search_clear"
android:layout_width="@dimen/searchEditClearIconWidth"
android:layout_height="@dimen/searchEditClearIconHeight"
app:layout_constraintEnd_toEndOf="@id/searchView"
app:layout_constraintTop_toTopOf="@id/searchView"
app:layout_constraintBottom_toBottomOf="@id/searchView"
android:layout_marginEnd="@dimen/searchEditClearMarginEnd"
android:visibility="gone"
android:src="@drawable/ic_clear"
android:onClick="onSearchClear"
/>
<TextView
android:id="@+id/searchNoResultsView"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/SearchText"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/searchView"
android:layout_marginTop="32dp"
android:text="@string/search_no_results"
android:visibility="gone"
/>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<TabWidget
android:id="@android:id/tabs"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:background="@color/backgroundDark"/>
<SearchView
android:id="@+id/searchView"
android:layout_width="match_parent"
android:layout_height="42dp"
android:background="@drawable/search_bg"
android:gravity="right"
android:layout_gravity="right"
/>
<FrameLayout
android:id="@android:id/tabcontent"
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout
android:id="@+id/featured"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
<android.support.v7.widget.RecyclerView
android:id="@+id/rvDomains"
android:layout_width="match_parent"
android:layout_height="match_parent"/>
</LinearLayout>
<LinearLayout
android:id="@+id/popular"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
<LinearLayout
android:id="@+id/bookmarks"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical">
</LinearLayout>
</FrameLayout>
</LinearLayout>
</TabHost>
<android.support.v7.widget.RecyclerView
android:id="@+id/rvDomains"
app:layout_constraintTop_toBottomOf="@id/searchView"
app:layout_constraintBottom_toBottomOf="@id/contentHomeRoot"
app:layout_constraintStart_toStartOf="@id/contentHomeRoot"
app:layout_constraintEnd_toEndOf="@id/contentHomeRoot"
android:layout_width="0dp"
android:layout_height="0dp" />
</android.support.constraint.ConstraintLayout>

View file

@ -1,74 +1,44 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
<android.support.v7.widget.CardView 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="113dp"
android:paddingLeft="14dp"
android:paddingRight="14dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:background="@drawable/domain_bg">
<ImageView
android:id="@+id/domainThumbnail"
android:layout_height="@dimen/domainItemHeight"
android:layout_marginStart="@dimen/domainMarginStart"
android:layout_marginEnd="@dimen/domainMarginEnd"
android:layout_marginTop="@dimen/domainMarginTop"
android:layout_marginBottom="@dimen/domainMarginBottom"
android:foreground="@drawable/rippleable"
android:clickable="true"
android:focusable="true"
android:elevation="0dp"
app:cardElevation="0dp"
app:cardCornerRadius="@dimen/item_corner_radius">
<android.support.constraint.ConstraintLayout
android:layout_width="match_parent"
android:layout_height="match_parent"
app:srcCompat="@android:drawable/ic_menu_gallery" />
android:layout_height="match_parent">
<TextView
android:id="@+id/domainName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginBottom="14dp"
android:layout_marginLeft="10dp"
android:fontFamily="@font/raleway_semibold"
android:text=""
android:textSize="21sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
<ImageView
android:id="@+id/domainThumbnail"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scaleType="centerCrop"
app:srcCompat="@android:drawable/ic_menu_gallery" />
<TextView
android:id="@+id/amountOfPeople"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="7dp"
android:layout_marginTop="8dp"
android:fontFamily="@font/raleway"
android:text="10"
android:textSize="16sp"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent" />
<TextView
android:id="@+id/domainName"
android:layout_width="match_parent"
android:layout_height="@dimen/domainNameHeight"
android:fontFamily="@font/raleway_semibold"
android:paddingStart="10dp"
android:paddingEnd="10dp"
android:gravity="center_vertical"
android:text=""
android:textSize="21sp"
android:textColor="@color/white_opaque"
android:background="@color/black_060"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintLeft_toLeftOf="parent" />
<ImageView
android:id="@+id/imageView2"
android:layout_width="12dp"
android:layout_height="13dp"
android:layout_marginLeft="4dp"
android:layout_marginBottom="3dp"
app:srcCompat="@drawable/ic_person"
app:layout_constraintBottom_toBottomOf="@id/amountOfPeople"
app:layout_constraintLeft_toRightOf="@id/amountOfPeople"/>
<ImageButton
android:id="@+id/imageButtonBookmark"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:background="@drawable/ic_bookmark"
android:tint="@color/colorPrimary"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintRight_toRightOf="parent"
android:padding="1dp"
android:layout_marginRight="10dp"
android:layout_marginTop="12dp"/>
<ImageButton
android:id="@+id/imageViewShare"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginTop="9dp"
app:layout_constraintTop_toBottomOf="@id/imageButtonBookmark"
app:layout_constraintLeft_toLeftOf="@id/imageButtonBookmark"
app:layout_constraintRight_toRightOf="@id/imageButtonBookmark"
android:background="@drawable/ic_share"
/>
</android.support.constraint.ConstraintLayout>
</android.support.constraint.ConstraintLayout>
</android.support.v7.widget.CardView>

View file

@ -1,13 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="white_opaque">#ffffff</color>
<color name="colorPrimary">#272727</color>
<color name="colorPrimaryDark">#000000</color>
<color name="colorPrimary">@color/backgroundLight</color>
<color name="colorPrimaryDark">@color/backgroundDark</color>
<color name="colorAccent">#54D7FD</color>
<color name="backgroundEditText">#E3E3E3</color>
<color name="editTextColor">#575757</color>
<color name="tabs">#1EB5EC</color>
<color name="colorButton1">#00B4EF</color>
<color name="backgroundDark">#333333</color>
<color name="backgroundLight">#4F4F4F</color>
<color name="backgroundSearch">#33999999</color>
<color name="editText">#212121</color>
<color name="editTextHint">#9e9e9e</color>
<color name="menuOption">#F2F2F2</color>
<color name="colorLoginError">#FF7171</color>
<color name="black_060">#99000000</color>
<color name="statusbar_color">#292929</color>
</resources>

View file

@ -11,4 +11,26 @@
<dimen name="text_size_subtitle_material_toolbar">12dp</dimen>
<dimen name="button_horizontal_margin">12dp</dimen>
<dimen name="edit_text_padding">8dp</dimen>
<dimen name="item_corner_radius">4dp</dimen>
<!-- Search (domains) screen dimensions -->
<dimen name="searchEditHeight">47.5dp</dimen>
<dimen name="searchEditAdditionalMarginTop">11dp</dimen>
<dimen name="searchEditPaddingStart">24dp</dimen>
<dimen name="searchEditPaddingEnd">51dp</dimen>
<dimen name="searchEditTextSize">19sp</dimen>
<dimen name="searchEditMagIconWidth">16dp</dimen>
<dimen name="searchEditMagIconHeight">16dp</dimen>
<dimen name="searchEditMagMarginEnd">22dp</dimen>
<dimen name="searchEditClearIconWidth">16dp</dimen>
<dimen name="searchEditClearIconHeight">16dp</dimen>
<dimen name="searchEditClearMarginEnd">22dp</dimen>
<dimen name="domainItemHeight">163dp</dimen>
<dimen name="domainMarginStart">14dp</dimen>
<dimen name="domainMarginEnd">14dp</dimen>
<dimen name="domainMarginTop">2dp</dimen>
<dimen name="domainMarginBottom">6dp</dimen>
<dimen name="domainNameHeight">64dp</dimen>
</resources>

View file

@ -13,5 +13,14 @@
<string name="action_goto">Go To</string>
<string name="goto_url_hint">Type a domain url</string>
<string name="go">Go</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?</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>
<string name="search_loading">Loading places…</string>
<string name="search_no_results">No places exist with that name</string>
</resources>

View file

@ -7,7 +7,14 @@
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
<item name="colorAccent">@color/colorAccent</item>
</style>
<style name="Theme.AppCompat.Translucent.NoActionBar" parent="Theme.AppCompat.NoActionBar">
<item name="android:windowNoTitle">true</item>
<item name="android:windowBackground">@android:color/transparent</item>
<item name="android:colorBackgroundCacheHint">@null</item>
<item name="android:windowIsTranslucent">true</item>
<item name="android:windowAnimationStyle">@android:style/Animation</item>
<item name="android:windowActionBar">false</item>
</style>
<style name="AppTheme.NoActionBar">
<item name="windowActionBar">false</item>
<item name="windowNoTitle">true</item>
@ -35,7 +42,7 @@
<style name="SearchText">
<item name="android:fontFamily">@font/raleway_light_italic</item>
<item name="android:textSize">15sp</item>
<item name="android:textSize">19sp</item>
</style>
<!-- Overriding text size so it's not so big in portrait -->

View file

@ -10,11 +10,42 @@
//
#include "AndroidHelper.h"
#include <QDebug>
#include <AccountManager.h>
AndroidHelper::AndroidHelper() {
}
AndroidHelper::~AndroidHelper() {
workerThread.quit();
workerThread.wait();
}
void AndroidHelper::init() {
workerThread.start();
_accountManager = QSharedPointer<AccountManager>(new AccountManager, &QObject::deleteLater);
_accountManager->setIsAgent(true);
_accountManager->setAuthURL(NetworkingConstants::METAVERSE_SERVER_URL());
_accountManager->setSessionID(DependencyManager::get<AccountManager>()->getSessionID());
connect(_accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
DependencyManager::get<AccountManager>()->setAccountInfo(AndroidHelper::instance().getAccountManager()->getAccountInfo());
DependencyManager::get<AccountManager>()->setAuthURL(authURL);
});
connect(_accountManager.data(), &AccountManager::logoutComplete, [] () {
DependencyManager::get<AccountManager>()->logout();
});
_accountManager->moveToThread(&workerThread);
}
void AndroidHelper::requestActivity(const QString &activityName) {
emit androidActivityRequested(activityName);
}
void AndroidHelper::notifyLoadComplete() {
emit qtAppLoadComplete();
}
void AndroidHelper::goBackFromAndroidActivity() {
emit backFromAndroidActivity();
}
}

View file

@ -13,6 +13,8 @@
#define hifi_Android_Helper_h
#include <QObject>
#include <QThread>
#include <AccountManager.h>
class AndroidHelper : public QObject {
Q_OBJECT
@ -21,17 +23,24 @@ public:
static AndroidHelper instance;
return instance;
}
void init();
void requestActivity(const QString &activityName);
void notifyLoadComplete();
void goBackFromAndroidActivity();
QSharedPointer<AccountManager> getAccountManager() { return _accountManager; }
AndroidHelper(AndroidHelper const&) = delete;
void operator=(AndroidHelper const&) = delete;
signals:
void androidActivityRequested(const QString &activityName);
void backFromAndroidActivity();
void qtAppLoadComplete();
private:
AndroidHelper() {}
AndroidHelper();
~AndroidHelper();
QSharedPointer<AccountManager> _accountManager;
QThread workerThread;
};
#endif

View file

@ -851,7 +851,11 @@ bool setupEssentials(int& argc, char** argv, bool runningMarkerExisted) {
DependencyManager::set<Cursor::Manager>();
DependencyManager::set<VirtualPad::Manager>();
DependencyManager::set<DesktopPreviewProvider>();
#if defined(Q_OS_ANDROID)
DependencyManager::set<AccountManager>(); // use the default user agent getter
#else
DependencyManager::set<AccountManager>(std::bind(&Application::getUserAgent, qApp));
#endif
DependencyManager::set<StatTracker>();
DependencyManager::set<ScriptEngines>(ScriptEngine::CLIENT_SCRIPT);
DependencyManager::set<ScriptInitializerMixin, NativeScriptInitializers>();
@ -2252,6 +2256,11 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
_pendingRenderEvent = false;
qCDebug(interfaceapp) << "Metaverse session ID is" << uuidStringWithoutCurlyBraces(accountManager->getSessionID());
#if defined(Q_OS_ANDROID)
AndroidHelper::instance().init();
AndroidHelper::instance().notifyLoadComplete();
#endif
}
void Application::updateVerboseLogging() {
@ -8262,17 +8271,17 @@ void Application::openAndroidActivity(const QString& activityName) {
void Application::enterBackground() {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
"stop", Qt::BlockingQueuedConnection);
//GC: commenting it out until we fix it
//getActiveDisplayPlugin()->deactivate();
if (getActiveDisplayPlugin()->isActive()) {
getActiveDisplayPlugin()->deactivate();
}
}
void Application::enterForeground() {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
"start", Qt::BlockingQueuedConnection);
//GC: commenting it out until we fix it
/*if (!getActiveDisplayPlugin() || !getActiveDisplayPlugin()->activate()) {
if (!getActiveDisplayPlugin() || getActiveDisplayPlugin()->isActive() || !getActiveDisplayPlugin()->activate()) {
qWarning() << "Could not re-activate display plugin";
}*/
}
}
#endif

View file

@ -28,69 +28,76 @@ void Basic2DWindowOpenGLDisplayPlugin::customizeContext() {
qreal dpi = getFullscreenTarget()->physicalDotsPerInch();
_virtualPadPixelSize = dpi * VirtualPad::Manager::BASE_DIAMETER_PIXELS / VirtualPad::Manager::DPI;
auto iconPath = PathUtils::resourcesPath() + "images/analog_stick.png";
auto image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
if (!_virtualPadStickTexture) {
auto iconPath = PathUtils::resourcesPath() + "images/analog_stick.png";
auto image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
_virtualPadStickTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadStickTexture->setSource("virtualPad stick");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadStickTexture->setUsage(usage.build());
_virtualPadStickTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadStickTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadStickTexture->setAutoGenerateMips(true);
_virtualPadStickTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadStickTexture->setSource("virtualPad stick");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadStickTexture->setUsage(usage.build());
_virtualPadStickTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadStickTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadStickTexture->setAutoGenerateMips(true);
}
}
iconPath = PathUtils::resourcesPath() + "images/analog_stick_base.png";
image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
if (!_virtualPadStickBaseTexture) {
auto iconPath = PathUtils::resourcesPath() + "images/analog_stick_base.png";
auto image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadPixelSize, _virtualPadPixelSize, Qt::KeepAspectRatio);
_virtualPadStickBaseTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadStickBaseTexture->setSource("virtualPad base");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadStickBaseTexture->setUsage(usage.build());
_virtualPadStickBaseTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadStickBaseTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadStickBaseTexture->setAutoGenerateMips(true);
_virtualPadStickBaseTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadStickBaseTexture->setSource("virtualPad base");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadStickBaseTexture->setUsage(usage.build());
_virtualPadStickBaseTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadStickBaseTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadStickBaseTexture->setAutoGenerateMips(true);
}
}
_virtualPadJumpBtnPixelSize = dpi * VirtualPad::Manager::JUMP_BTN_FULL_PIXELS / VirtualPad::Manager::DPI;
iconPath = PathUtils::resourcesPath() + "images/fly.png";
image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize, Qt::KeepAspectRatio);
image = image.mirrored();
if (!_virtualPadJumpBtnTexture) {
auto iconPath = PathUtils::resourcesPath() + "images/fly.png";
auto image = QImage(iconPath);
if (image.format() != QImage::Format_ARGB32) {
image = image.convertToFormat(QImage::Format_ARGB32);
}
if ((image.width() > 0) && (image.height() > 0)) {
image = image.scaled(_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize, Qt::KeepAspectRatio);
image = image.mirrored();
_virtualPadJumpBtnTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadJumpBtnTexture->setSource("virtualPad jump");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadJumpBtnTexture->setUsage(usage.build());
_virtualPadJumpBtnTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadJumpBtnTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadJumpBtnTexture->setAutoGenerateMips(true);
_virtualPadJumpBtnTexture = gpu::Texture::createStrict(
gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA),
image.width(), image.height(),
gpu::Texture::MAX_NUM_MIPS,
gpu::Sampler(gpu::Sampler::FILTER_MIN_MAG_MIP_LINEAR));
_virtualPadJumpBtnTexture->setSource("virtualPad jump");
auto usage = gpu::Texture::Usage::Builder().withColor().withAlpha();
_virtualPadJumpBtnTexture->setUsage(usage.build());
_virtualPadJumpBtnTexture->setStoredMipFormat(gpu::Element(gpu::VEC4, gpu::NUINT8, gpu::RGBA));
_virtualPadJumpBtnTexture->assignStoredMip(0, image.byteCount(), image.constBits());
_virtualPadJumpBtnTexture->setAutoGenerateMips(true);
}
}
#endif
Parent::customizeContext();
@ -124,44 +131,32 @@ void Basic2DWindowOpenGLDisplayPlugin::compositeExtra() {
// render stick base
auto stickBaseTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getLeftVirtualPad()->getFirstTouch(),
_virtualPadPixelSize, _virtualPadPixelSize);
render([&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.setProjectionTransform(mat4());
batch.setPipeline(_cursorPipeline);
batch.setResourceTexture(0, _virtualPadStickBaseTexture);
batch.resetViewTransform();
batch.setModelTransform(stickBaseTransform);
batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize()));
batch.draw(gpu::TRIANGLE_STRIP, 4);
});
// render stick head
auto stickTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getLeftVirtualPad()->getCurrentTouch(),
_virtualPadPixelSize, _virtualPadPixelSize);
_virtualPadPixelSize, _virtualPadPixelSize);
auto jumpTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(),
_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize);
render([&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.setFramebuffer(_compositeFramebuffer);
batch.resetViewTransform();
batch.setProjectionTransform(mat4());
batch.setPipeline(_cursorPipeline);
batch.setResourceTexture(0, _virtualPadStickTexture);
batch.resetViewTransform();
batch.setModelTransform(stickTransform);
batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize()));
batch.setResourceTexture(0, _virtualPadStickBaseTexture);
batch.setModelTransform(stickBaseTransform);
batch.draw(gpu::TRIANGLE_STRIP, 4);
});
if (!virtualPadManager.getLeftVirtualPad()->isBeingTouched()) {
// render stick head
auto jumpTransform = DependencyManager::get<CompositorHelper>()->getPoint2DTransform(virtualPadManager.getJumpButtonPosition(),
_virtualPadJumpBtnPixelSize, _virtualPadJumpBtnPixelSize);
render([&](gpu::Batch& batch) {
batch.enableStereo(false);
batch.setProjectionTransform(mat4());
batch.setPipeline(_cursorPipeline);
batch.setResourceTexture(0, _virtualPadStickTexture);
batch.setModelTransform(stickTransform);
batch.draw(gpu::TRIANGLE_STRIP, 4);
if (!virtualPadManager.getLeftVirtualPad()->isBeingTouched()) {
batch.setResourceTexture(0, _virtualPadJumpBtnTexture);
batch.resetViewTransform();
batch.setModelTransform(jumpTransform);
batch.setViewportTransform(ivec4(uvec2(0), getRecommendedRenderSize()));
batch.draw(gpu::TRIANGLE_STRIP, 4);
});
}
}
});
}
#endif
Parent::compositeExtra();

View file

@ -453,6 +453,20 @@ void AccountManager::removeAccountFromFile() {
<< "from settings file.";
}
void AccountManager::setAccountInfo(const DataServerAccountInfo &newAccountInfo) {
_accountInfo = newAccountInfo;
_pendingPrivateKey.clear();
if (_isAgent && !_accountInfo.getAccessToken().token.isEmpty() && !_accountInfo.hasProfile()) {
// we are missing profile information, request it now
requestProfile();
}
// prepare to refresh our token if it is about to expire
if (needsToRefreshToken()) {
refreshAccessToken();
}
}
bool AccountManager::hasValidAccessToken() {
if (_accountInfo.getAccessToken().token.isEmpty() || _accountInfo.getAccessToken().isExpired()) {

View file

@ -88,6 +88,7 @@ public:
void requestProfile();
DataServerAccountInfo& getAccountInfo() { return _accountInfo; }
void setAccountInfo(const DataServerAccountInfo &newAccountInfo);
static QJsonObject dataObjectFromResponse(QNetworkReply& requestReply);

View file

@ -58,7 +58,7 @@ namespace VirtualPad {
private:
Instance _leftVPadInstance;
bool _enabled;
bool _enabled {true};
bool _hidden;
glm::vec2 _jumpButtonPosition;
int _extraBottomMargin {0};