diff --git a/android/app/build.gradle b/android/app/build.gradle index 7a6f34b58b..c63f481f8c 100644 --- a/android/app/build.gradle +++ b/android/app/build.gradle @@ -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') } diff --git a/android/app/src/main/AndroidManifest.xml b/android/app/src/main/AndroidManifest.xml index ed9caee58b..1b6f3b1304 100644 --- a/android/app/src/main/AndroidManifest.xml +++ b/android/app/src/main/AndroidManifest.xml @@ -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"> - + android:theme="@style/AppTheme" /> + @@ -75,6 +75,11 @@ + + diff --git a/android/app/src/main/assets/hifi_domains.json b/android/app/src/main/assets/hifi_domains.json deleted file mode 100644 index a63ef7b6aa..0000000000 --- a/android/app/src/main/assets/hifi_domains.json +++ /dev/null @@ -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": "" - } - ] -} \ No newline at end of file diff --git a/android/app/src/main/cpp/native.cpp b/android/app/src/main/cpp/native.cpp index facf6bd4bd..9a5d29d675 100644 --- a/android/app/src/main/cpp/native.cpp +++ b/android/app/src/main/cpp/native.cpp @@ -21,8 +21,11 @@ #include #include "AndroidHelper.h" +#include -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"); + __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("openAndroidActivity", "(Ljava/lang/String;)V", string.object()); + __interfaceActivity.callMethod("openAndroidActivity", "(Ljava/lang/String;)V", string.object()); }); } @@ -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("handleLoginCompleted", "(Z)V", jSuccess); + }); + + QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() { + jboolean jSuccess = (jboolean) false; + __loginActivity.callMethod("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("onAppLoadedComplete", "()V"); + __interfaceActivity.callMethod("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()); +} + } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java index 995e64c2a5..125483da94 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/GotoActivity.java @@ -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); diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java index 15d716548f..368862bc49 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HifiUtils.java @@ -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(); + } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java index 611c8f50cc..62e487f8b7 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/HomeActivity.java @@ -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 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(""); + } } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java index d2aff85323..419ba8c45e 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/InterfaceActivity.java @@ -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"); diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/LoginActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/LoginActivity.java new file mode 100644 index 0000000000..9fd256aa82 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/LoginActivity.java @@ -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)); + } + }); + } + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java index b1c5f570c8..45060d6d0c 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/PermissionChecker.java @@ -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(); } diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/QtPreloader/QtPreloader.java b/android/app/src/main/java/io/highfidelity/hifiinterface/QtPreloader/QtPreloader.java deleted file mode 100644 index d9ecdb9710..0000000000 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/QtPreloader/QtPreloader.java +++ /dev/null @@ -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 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 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); - } - } - - } - } -} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java b/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java new file mode 100644 index 0000000000..122884b6be --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/SplashActivity.java @@ -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(); + } +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java new file mode 100644 index 0000000000..64ca6da816 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/Callback.java @@ -0,0 +1,9 @@ +package io.highfidelity.hifiinterface.provider; + +/** + * Created by cduarte on 4/18/18. + */ + +public interface Callback { + public void callback(T t); +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java new file mode 100644 index 0000000000..7a2101a229 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/DomainProvider.java @@ -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 domain); + void retrieveError(Exception e, String message); + } +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java new file mode 100644 index 0000000000..1e29734243 --- /dev/null +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/provider/UserStoryDomainProvider.java @@ -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 allStories; // All retrieved stories from the API + private List 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 restOfPagesCallback) { + restOfPagesCallback.callback(new Exception("Error accessing url [" + url + "]", t)); + } + + private void getUserStoryPage(int pageNumber, Callback restOfPagesCallback, Callback firstPageCallback) { + Call 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() { + @Override + public void onResponse(Call call, Response 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 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 = 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() { + + @Override + public void onResponse(Call call, Response response) { + UserStories userStories = response.body(); + if (userStories == null) { + domainCallback.retrieveOk(new ArrayList<>(0)); + } + List domains = new ArrayList<>(userStories.total_entries); + userStories.user_stories.forEach(userStory -> { + domains.add(userStory.toDomain()); + }); + domainCallback.retrieveOk(domains); + } + + @Override + public void onFailure(Call call, Throwable t) { + domainCallback.retrieveError(new Exception(t), t.getMessage()); + } + + }); + } + + public interface UserStoryDomainProviderService { + @GET("api/v1/user_stories") + Call 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 user_stories; + } + +} diff --git a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java index 1129ec586f..461b71eb7c 100644 --- a/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java +++ b/android/app/src/main/java/io/highfidelity/hifiinterface/view/DomainAdapter.java @@ -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 { - 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 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) { + 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 - - - - - - - - - \ No newline at end of file diff --git a/android/app/src/main/res/drawable/hifi_header.xml b/android/app/src/main/res/drawable/hifi_header.xml new file mode 100644 index 0000000000..9f7c85297a --- /dev/null +++ b/android/app/src/main/res/drawable/hifi_header.xml @@ -0,0 +1,50 @@ + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/ic_clear.xml b/android/app/src/main/res/drawable/ic_clear.xml new file mode 100644 index 0000000000..94efe2bbdb --- /dev/null +++ b/android/app/src/main/res/drawable/ic_clear.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/android/app/src/main/res/drawable/ic_search.xml b/android/app/src/main/res/drawable/ic_search.xml new file mode 100644 index 0000000000..099c8ea953 --- /dev/null +++ b/android/app/src/main/res/drawable/ic_search.xml @@ -0,0 +1,4 @@ + + + diff --git a/android/app/src/main/res/drawable/rounded_button.xml b/android/app/src/main/res/drawable/rounded_button.xml new file mode 100644 index 0000000000..11a9f90c8b --- /dev/null +++ b/android/app/src/main/res/drawable/rounded_button.xml @@ -0,0 +1,24 @@ + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/drawable/rounded_edit.xml b/android/app/src/main/res/drawable/rounded_edit.xml new file mode 100644 index 0000000000..3c1cac4d1d --- /dev/null +++ b/android/app/src/main/res/drawable/rounded_edit.xml @@ -0,0 +1,7 @@ + + + + + + \ No newline at end of file diff --git a/android/app/src/main/res/layout/activity_home.xml b/android/app/src/main/res/layout/activity_home.xml index 144ca84a0f..91fb8603cd 100644 --- a/android/app/src/main/res/layout/activity_home.xml +++ b/android/app/src/main/res/layout/activity_home.xml @@ -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" + /> @@ -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" - /> + > + + + + + diff --git a/android/app/src/main/res/layout/activity_login.xml b/android/app/src/main/res/layout/activity_login.xml new file mode 100644 index 0000000000..ecf72b94bf --- /dev/null +++ b/android/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,106 @@ + + + + + + + + + + + +