Merge branch 'master' into etc2comp

This commit is contained in:
Sam Gondelman 2018-05-30 20:10:46 -04:00 committed by SamGondelman
commit ae3b933a80
548 changed files with 13557 additions and 15475 deletions

View file

@ -6,79 +6,110 @@ module.exports = {
},
"globals": {
"Account": false,
"Agent": false,
"AnimationCache": false,
"Assets": false,
"Audio": false,
"AudioDevice": false,
"AudioEffectOptions": false,
"AudioScope": false,
"AudioStats": false,
"Avatar": false,
"AvatarBookmarks": false,
"AvatarInputs": false,
"AvatarList": false,
"AvatarManager": false,
"Camera": false,
"Clipboard": false,
"console": false,
"ContextOverlay": false,
"Controller": false,
"DialogsManager": false,
"DebugDraw": false,
"Desktop": false,
"DesktopPreviewProvider": false,
"DialogsManager": false,
"document": false,
"Entities": false,
"EntityViewer": false,
"EventBridge": false,
"FaceTracker": false,
"GlobalServices": false,
"GooglePoly": false,
"Graphics": false,
"HMD": false,
"LaserPointers": false,
"location": true,
"LocationBookmarks": false,
"LODManager": false,
"Mat4": false,
"Menu": false,
"Messages": false,
"Midi": false,
"ModelCache": false,
"module": false,
"MyAvatar": false,
"OffscreenFlags": false,
"Overlays": false,
"OverlayWebWindow": false,
"OverlayWindow": false,
"Paths": false,
"Picks": false,
"PickType": false,
"PointerEvent": false,
"Pointers": false,
"print": false,
"QmlFragment": false,
"Quat": false,
"Rates": false,
"RayPick": false,
"Recording": false,
"Render": false,
"Resource": false,
"Reticle": false,
"Scene": false,
"Script": false,
"ScriptDiscoveryService": false,
"Selection": false,
"Settings": false,
"Snapshot": false,
"SoundCache": false,
"SpeechRecognizer": false,
"Stats": false,
"Steam": false,
"Tablet": false,
"TextureCache": false,
"Toolbars": false,
"Uuid": false,
"UndoStack": false,
"UserActivityLogger": false,
"Users": false,
"Uuid": false,
"Vec3": false,
"Wallet": false,
"WebSocket": false,
"WebWindow": false,
"Window": false,
"XMLHttpRequest": false,
"location": false,
"print": false,
"RayPick": false,
"LaserPointers": false,
"ContextOverlay": false,
"module": false
"XMLHttpRequest": false
},
"rules": {
"brace-style": ["error", "1tbs", { "allowSingleLine": false }],
"comma-dangle": ["error", "never"],
"brace-style": ["error", "1tbs", {"allowSingleLine": false}],
"camelcase": ["error"],
"comma-dangle": ["error", "never"],
"curly": ["error", "all"],
"eqeqeq": ["error", "always"],
"indent": ["error", 4, { "SwitchCase": 1 }],
"keyword-spacing": ["error", { "before": true, "after": true }],
"indent": ["error", 4, {"SwitchCase": 1}],
"key-spacing": ["error", {"beforeColon": false, "afterColon": true, "mode": "strict"}],
"keyword-spacing": ["error", {"before": true, "after": true}],
"max-len": ["error", 128, 4],
"new-cap": ["error"],
"no-console": ["off"],
"no-floating-decimal": ["error"],
//"no-magic-numbers": ["error", { "ignore": [0, 1], "ignoreArrayIndexes": true }],
"no-multiple-empty-lines": ["error"],
"no-magic-numbers": ["error", {"ignore": [0.5, -1, 0, 1, 2], "ignoreArrayIndexes": true}],
"no-multi-spaces": ["error"],
"no-unused-vars": ["error", { "args": "none", "vars": "local" }],
"no-multiple-empty-lines": ["error"],
"no-unused-vars": ["error", {"args": "none", "vars": "local"}],
"semi": ["error", "always"],
"spaced-comment": ["error", "always", {
"line": { "markers": ["/"] }
}],
"space-before-function-paren": ["error", {"anonymous": "ignore", "named": "never"}]
"space-before-blocks": ["error"],
"space-before-function-paren": ["error", {"anonymous": "ignore", "named": "never"}],
"spaced-comment": ["error", "always", {"line": {"markers": ["/"]}}]
}
};

View file

@ -9,15 +9,17 @@ Note: The prerequisites will require about 10 GB of space on your drive. You wil
If you dont have Community or Professional edition of Visual Studio 2017, download [Visual Studio Community 2017](https://www.visualstudio.com/downloads/).
When selecting components, check "Desktop development with C++." Also check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)" on the Summary toolbar on the right.
When selecting components, check "Desktop development with C++." Also on the right on the Summary toolbar, check "Windows 8.1 SDK and UCRT SDK" and "VC++ 2015.3 v140 toolset (x86,x64)".
### Step 2. Installing CMake
Download and install the latest version of CMake 3.9. Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). Make sure to check "Add CMake to system PATH for all users" when prompted during installation.
Download and install the latest version of CMake 3.9.
Download the file named win64-x64 Installer from the [CMake Website](https://cmake.org/download/). You can access the installer on this [3.9 Version page](https://cmake.org/files/v3.9/). During installation, make sure to check "Add CMake to system PATH for all users" when prompted.
### Step 3. Installing Qt
Download and install the [Qt Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.10.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)".
Download and install the [Qt Open Source Online Installer](https://www.qt.io/download-open-source/?hsCtaTracking=f977210e-de67-475f-a32b-65cec207fd03%7Cd62710cd-e1db-46aa-8d4d-2f1c1ffdacea). While installing, you only need to have the following components checked under Qt 5.10.1: "msvc2017 64-bit", "Qt WebEngine", and "Qt Script (Deprecated)".
Note: Installing the Sources is optional but recommended if you have room for them (~2GB).
@ -56,9 +58,9 @@ Where `%HIFI_DIR%` is the directory for the highfidelity repository.
Open `%HIFI_DIR%\build\hifi.sln` using Visual Studio.
Change the Solution Configuration (next to the green play button) from "Debug" to "Release" for best performance.
Change the Solution Configuration (menu ribbon under the menu bar, next to the green play button) from "Debug" to "Release" for best performance.
Run `Build > Build Solution`.
Run from the menu bar `Build > Build Solution`.
### Step 9. Testing Interface
@ -66,7 +68,7 @@ Create another environment variable (see Step #4)
* Set "Variable name": `_NO_DEBUG_HEAP`
* Set "Variable value": `1`
In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run `Debug > Start Debugging`.
In Visual Studio, right+click "interface" under the Apps folder in Solution Explorer and select "Set as Startup Project". Run from the menu bar `Debug > Start Debugging`.
Now, you should have a full build of High Fidelity and be able to run the Interface using Visual Studio. Please check our [Docs](https://wiki.highfidelity.com/wiki/Main_Page) for more information regarding the programming workflow.

View file

@ -179,6 +179,10 @@ endif()
add_subdirectory(tools)
if (BUILD_TESTS)
# Turn on testing so that add_test works
# MUST be in the root cmake file for ctest to work
include(CTest)
enable_testing()
add_subdirectory(tests)
add_subdirectory(tests-manual)
endif()

View file

@ -27,6 +27,14 @@ android {
'-DDISABLE_KTX_CACHE=OFF'
}
}
signingConfigs {
release {
storeFile project.hasProperty("HIFI_ANDROID_KEYSTORE") ? file(HIFI_ANDROID_KEYSTORE) : null
storePassword project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") ? HIFI_ANDROID_KEYSTORE_PASSWORD : ''
keyAlias project.hasProperty("HIFI_ANDROID_KEY_ALIAS") ? HIFI_ANDROID_KEY_ALIAS : ''
keyPassword project.hasProperty("HIFI_ANDROID_KEY_PASSWORD") ? HIFI_ANDROID_KEY_PASSWORD : ''
}
}
}
compileOptions {
@ -38,6 +46,10 @@ android {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
signingConfig project.hasProperty("HIFI_ANDROID_KEYSTORE") &&
project.hasProperty("HIFI_ANDROID_KEYSTORE_PASSWORD") &&
project.hasProperty("HIFI_ANDROID_KEY_ALIAS") &&
project.hasProperty("HIFI_ANDROID_KEY_PASSWORD")? signingConfigs.release : null
}
}
@ -104,6 +116,15 @@ 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'
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

@ -39,29 +39,16 @@
</activity>
-->
<activity
android:name=".HomeActivity"
android:label="@string/home"
android:name=".MainActivity"
android:screenOrientation="portrait"
android:theme="@style/AppTheme.NoActionBar">
</activity>
<activity
android:name=".GotoActivity"
android:label="@string/go_to"
android:theme="@style/AppTheme">
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar">
</activity>
<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"
>
<intent-filter>
<category android:name="com.google.intent.category.DAYDREAM"/>
<action android:name="android.intent.action.MAIN" />
</intent-filter>
<meta-data android:name="android.app.lib_name" android:value="native-lib"/>
<meta-data android:name="android.app.qt_libs_resource_id" android:resource="@array/qt_libs"/>
<meta-data android:name="android.app.bundled_in_lib_resource_id" android:resource="@array/bundled_in_lib"/>
@ -75,6 +62,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

@ -0,0 +1,121 @@
<html>
<body>
<b>High Fidelity Privacy Policy</b>
<br/>
<p>High Fidelity, Inc. ("High Fidelity") respects the privacy of its online visitors and users of its products and services. We recognize the importance of protecting information collected from our users and have adopted this Privacy Policy to inform you about how we gather, store and use information derived from your use of our products, services and online sites in accordance with local law in the places where we operate.
By using our online sites, products, and services (collectively, the "Service"), you agree that we may collect personally identifiable information (as defined below). We will not share your personally identifiable information except as described herein.
</p>
<b><font size="2">1. Types of Information We Collect</font></b>
<p>
We collect two basic types of information - personal information and anonymous information - and we may use personal and anonymous information to create a third type of information, aggregate information. Personal Information means information that identifies (whether directly or indirectly) a particular individual, such as the individual's first and last name, postal address, e-mail address and/or telephone number. Anonymous Information means information that does not directly or indirectly identify, and cannot reasonably be used to identify, an individual (including an individual's computing device). Aggregate Information means information about groups or categories of individuals which does not identify and cannot reasonably be used to identify an individual. We may share Aggregate and Anonymous Information with other parties without restriction.
We collect the following categories of information:
Registration information you provide when you create an account, which may include your first name and surname, country of residence, gender, date of birth, e-mail address, username and password.
Transaction information you provide when you request information or purchase a product or service from us, whether on our sites or through our applications, including your postal address, telephone number and payment information. If you conduct transactions, we may collect and retain some or all of the information related to these transactions, including transaction amount(s), parties involved, time and manner of exchange, and other transaction circumstances.
Information you provide in public forums on our Service. Please note that our sites and applications may offer chat, forums, community environments (including multiplayer gameplay) or other tools that do not have a restricted audience. If you provide Personal Information when you use any of these features, that Personal Information may be publicly posted and otherwise disclosed without limitation as to its use by us or by a third party. We have no obligation to keep private personally identifiable information that you have made available to other users or the public using these functions. To request removal of your Personal Information from a public forum on one of our sites or applications, please contact Customer Support.
Information sent either one-to-one or within a limited group using our message, chat, post or similar functionality, where we are permitted by law to collect this information.
Information you provide to us when you use our sites and applications, our applications on third-party sites or platforms (such as social networking sites), or link your profile from a third-party site or platform to your registered account.
Location information when you visit our sites or use our applications, including location information either provided by a mobile device interacting with one of our sites or applications, or associated with your IP address, where we are permitted by law to process this information.
Usage, viewing and technical data, including your device identifier or IP address, when you visit our sites, use our applications on third-party sites or platforms, or open e-mails that we send.
Additionally there are a few special circumstances to note:
Intellectual Property Claim Notices: If you notify us of an intellectual property claim, the information in your claim notice may be shared with other parties to the disagreement or third parties in our discretion and as required by law.
Beta Service User: If you volunteer to serve as a beta participant for our pre-commercial content, we may track bug reports and individual system performance in an effort to test our technology rigorously before it is deployed.
Former Customer: If you discontinue your use of our Service, we may keep your registration file in our database for use in the event that you elect to renew your use of our Service, as well as for anti-fraud and other such protective measures.
Job Postings or Unsolicited Communications: Please note that information we receive in reference to a job posting or by unsolicited communications does not fall within the terms outlined in this Privacy Policy, however information from your resume will be used solely for the purpose of evaluating your candidacy for employment.
</p>
<b>2. How We Collect Your Information</b>
<p>
We collect information you provide to us when you request products, services or information from us, register with us, participate in public forums or other activities on our sites and applications, respond to customer inquiries or surveys, or otherwise interact with us.
We collect information through technology, such as cookies and other technologies (such as web beacons and pixel tags), including when you visit our sites and applications or use our applications on third-party sites or platforms. A cookie is a small string of data which often includes an anonymous unique identifier sent to your Internet browser from a website's computers, which is stored on your computer's hard drive and is used to customize your use of a product or online site, keep records of your access to an online site or product, or store information needed by you on a regular basis (e.g. password retention functionality). High Fidelity (itself or through third parties acting on our behalf) use cookies for a number of purposes relating to our websites, applications and services, including to access your account information where you "login" to our websites, forums or other areas and to keep track of your website session data. You can configure your browser to accept all cookies, reject all cookies, or notify you when a cookie is set. Each browser is different, so consult the "Help" menu of your browser to learn how you change your cookie preferences. Please note that if you reject all cookies, you may not be able to use certain of our (or other companies') web pages.
We may participate in ad and/or affiliate networks operated by various third party companies. These companies collect and may use certain anonymous information about your visits to our Service as a function of referring Internet traffic to our Service. We do not permit these companies to collect any Personal Information about you; however these companies may collect your IP address. These companies may set and use cookies, web beacons, pixels and other technologies to collect anonymous information about your visits to our Service, and may otherwise aggregate, analyze and anonymize that data. If you would like to learn more about these specialized advertising technologies, the Network Advertising Initiative offers useful information about Internet advertising companies, including information about how to opt-out of certain information collection.
We acquire information from other trusted sources to update or supplement the information you provided or we collected automatically. Local law may require that you authorize the third party to share your information with us before we can acquire it.
</p>
<b>3. Use of Your Information by High Fidelity</b>
<p>
High Fidelity will be the data controller for your information, and will have access to your information for use for the following purposes (unless prohibited by law):
Provide you with the products and services you request
Communicate with you about your account or transactions with us and send you information about features on our sites and applications or changes to our policies
Consistent with local law and choices and controls that may be available to you:
Send you offers and promotions for our products and services or, as permitted, third-party products and services
Personalize content and experiences on our sites and applications
Provide you with advertising based on your activity on our sites and applications and on third-party sites and applications.
Optimize or improve our products, services and operations
Detect, investigate and prevent activities that may violate our policies or be illegal
Except under certain limited circumstances as set forth here and in our Terms of Service, High Fidelity does not disclose to third parties the Personal Information or other account-related information that you provide to us without your permission. You understand, however, that High Fidelity may disclose your Personal Information or other account-related information under the following circumstances:
If we believe in good faith that such disclosure is necessary under applicable law, or to comply with legal process served on High Fidelity;
In order to protect and defend the rights or interests of High Fidelity, its products and services, and/or the other users of such products and services;
In order to report to law enforcement authorities, or assist in their investigation of suspected illegal or wrongful activity, or to report any instance in which we believe a person may be in danger;
To service providers with whom we have contracted to assist us with the features or operations (such as anti-fraud functions, billing, collections, registration, customer support, e-mail delivery, age verification), to fulfill your service requests, offer new content or help us improve our products and/or services.
Our contracts with third parties prohibit them from using any of your Personal Information for purposes unrelated to the product or services they are providing;
To other third parties (a) to provide you with services you have requested, (b) to offer you information about our products or services (e.g. events or features), or (c) to whom you explicitly ask us to send your information (or about whom you are otherwise explicitly notified and consent to when using a specific service). For instance, we may provide certain information to our payment processor, to credit card associations, banks or issuers (if you are using a credit card), to PayPal (if you are using a PayPal account), or to providers of other services you request. If you choose to use these third parties' products or services, then their use of your information is governed by their privacy policies. You should evaluate the practices of third party providers before deciding to use their services; and
To other business entities, should we plan to merge with or be acquired by that business entity.
</p>
<b>4. Sharing Your Information with Other Companies</b>
<p>
We will not share your Personal Information outside of High Fidelity except in limited circumstances, including:
When you allow us to share your Personal Information with another company, such as:
Directing us to share your Personal Information with third-party sites or platforms, such as social networking sites
Please note that once we share your Personal Information with another company, the information received by the other company becomes subject to the other company's privacy practices.
When companies perform services on our behalf; however, these companies are prohibited from using your Personal Information for purposes other than those requested by us or required by law.
When we share Personal Information with third parties in connection with the sale of a business, to enforce our Terms of Service or rules, to ensure the safety and security of our users and third parties, to comply with legal process or in other cases if we believe in good faith that disclosure is required by law.
</p>
<b>5. Data Transfers, Storage and Processing Globally</b>
<p>
We operate globally and may transfer your Personal Information to locations around the world for the purposes described in this Privacy Policy. Whenever your Personal Information is transferred, stored or processed by us, we will take reasonable steps to safeguard the privacy of your Personal Information in accordance with applicable law.
</p>
<b>6. Changes to this Privacy Policy</b>
<p>
From time to time, we may change this Privacy Policy to accommodate new technologies, industry practices, regulatory requirements or for other purposes. If we decide to change our privacy policy, we will post those changes to this privacy statement, and other places we deem appropriate so that you are aware of what information we collect, how we use it, and under what circumstances, if any, we disclose it. We reserve the right to modify this privacy statement at any time, so please review it frequently. If we make material changes to this policy, we will notify you here, by email, or by means of a notice on our home page.
</p>
<b>7. Comments and Questions</b>
<p>
If you have a comment or question about this Privacy Policy or our privacy practices, please send an e-mail to privacy@highfidelity.com.
</p>
<br/>
<b>Notice to California Residents:</b>
<p>
If you are a California resident, California Civil Code Section 1798.83 permits you to request information regarding the disclosure of your Personal Information by High Fidelity to third parties for the third parties' direct marketing purposes. With respect to these entities, this Privacy Policy applies only to their activities within the State of California. To make such a request, please send an e-mail to privacy@highfidelity.com or write us at the address listed immediately above.
</p>
</body>
</html>

View file

@ -21,9 +21,13 @@
#include <AddressManager.h>
#include "AndroidHelper.h"
#include <udt/PacketHeaders.h>
#include <SettingHandle.h>
QAndroidJniObject __activity;
QAndroidJniObject __interfaceActivity;
QAndroidJniObject __loginCompletedListener;
QAndroidJniObject __loadCompleteListener;
QAndroidJniObject __usernameChangedListener;
void tempMessageHandler(QtMsgType type, const QMessageLogContext& context, const QString& message) {
if (!message.isEmpty()) {
const char * local=message.toStdString().c_str();
@ -142,16 +146,22 @@ 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) {
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::androidActivityRequested, [](const QString& a, const bool backToScene) {
QAndroidJniObject string = QAndroidJniObject::fromString(a);
__activity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;)V", string.object<jstring>());
jboolean jBackToScene = (jboolean) backToScene;
__interfaceActivity.callMethod<void>("openAndroidActivity", "(Ljava/lang/String;Z)V", string.object<jstring>(), jBackToScene);
});
QObject::connect(&AndroidHelper::instance(), &AndroidHelper::hapticFeedbackRequested, [](int duration) {
jint iDuration = (jint) duration;
__interfaceActivity.callMethod<void>("performHapticFeedback", "(I)V", iDuration);
});
}
@ -163,23 +173,16 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnDest
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGotoUrl(JNIEnv* env, jobject obj, jstring url) {
QAndroidJniObject jniUrl("java/lang/String", "(Ljava/lang/String;)V", url);
DependencyManager::get<AddressManager>()->handleLookupString(jniUrl.toString());
DependencyManager::get<AddressManager>()->loadSettings(jniUrl.toString());
}
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) {
AndroidHelper::instance().goBackFromAndroidActivity();
}
// HifiUtils
@ -204,4 +207,92 @@ 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 jstring JNICALL Java_io_highfidelity_hifiinterface_fragment_HomeFragment_nativeGetLastLocation(JNIEnv *env, jobject instance) {
Setting::Handle<QUrl> currentAddressHandle(QStringList() << "AddressManager" << "address", QString());
QUrl lastLocation = currentAddressHandle.get();
return env->NewStringUTF(lastLocation.toString().toLatin1().data());
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_fragment_LoginFragment_nativeLogin(JNIEnv *env, jobject instance,
jstring username_, jstring password_,
jobject usernameChangedListener) {
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();
__loginCompletedListener = QAndroidJniObject(instance);
__usernameChangedListener = QAndroidJniObject(usernameChangedListener);
QObject::connect(accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
jboolean jSuccess = (jboolean) true;
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
});
QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() {
jboolean jSuccess = (jboolean) false;
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
});
QObject::connect(accountManager.data(), &AccountManager::usernameChanged, [](const QString& username) {
QAndroidJniObject string = QAndroidJniObject::fromString(username);
__usernameChangedListener.callMethod<void>("handleUsernameChanged", "(Ljava/lang/String;)V", string.object<jstring>());
});
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_MainActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
return AndroidHelper::instance().getAccountManager()->isLoggedIn();
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_MainActivity_nativeLogout(JNIEnv *env, jobject instance) {
AndroidHelper::instance().getAccountManager()->logout();
}
JNIEXPORT jstring JNICALL
Java_io_highfidelity_hifiinterface_MainActivity_nativeGetDisplayName(JNIEnv *env,
jobject instance) {
QString username = AndroidHelper::instance().getAccountManager()->getAccountInfo().getUsername();
return env->NewStringUTF(username.toLatin1().data());
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) {
AndroidHelper::instance().notifyEnterBackground();
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEnv *env, jobject obj) {
AndroidHelper::instance().notifyEnterForeground();
}
}

View file

@ -1,94 +0,0 @@
package io.highfidelity.hifiinterface;
import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.AppCompatButton;
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";
private EditText mUrlEditText;
private AppCompatButton mGoBtn;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_goto);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle);
setSupportActionBar(toolbar);
ActionBar actionbar = getSupportActionBar();
actionbar.setDisplayHomeAsUpEnabled(true);
mUrlEditText = (EditText) findViewById(R.id.url_text);
mUrlEditText.setOnKeyListener(new View.OnKeyListener() {
@Override
public boolean onKey(View view, int i, KeyEvent keyEvent) {
if (i == KeyEvent.KEYCODE_ENTER) {
actionGo();
return true;
}
return false;
}
});
mUrlEditText.setText(HifiUtils.getInstance().getCurrentAddress());
mGoBtn = (AppCompatButton) findViewById(R.id.go_btn);
mGoBtn.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
actionGo();
}
});
}
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;
}
Intent intent = new Intent();
intent.putExtra(GotoActivity.PARAM_DOMAIN_URL, urlString);
setResult(RESULT_OK, intent);
finish();
}
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
switch (id) {
case android.R.id.home:
finish();
return true;
}
return super.onOptionsItemSelected(item);
}
}

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 METAVERSE_BASE_URL = "https://metaverse.highfidelity.com";
private static HifiUtils instance;
private HifiUtils() {
@ -18,6 +23,45 @@ 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) {
return absoluteHifiAssetUrl(urlString, METAVERSE_BASE_URL);
}
public String absoluteHifiAssetUrl(String urlString, String baseUrl) {
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 = baseUrl + urlString;
}
}
return urlString;
}
public native String getCurrentAddress();
public native String protocolVersionSignature();
}

View file

@ -1,220 +0,0 @@
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.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
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.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.widget.SearchView;
import android.widget.TabHost;
import android.widget.TabWidget;
import android.widget.TextView;
import io.highfidelity.hifiinterface.QtPreloader.QtPreloader;
import io.highfidelity.hifiinterface.view.DomainAdapter;
public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
/**
* 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 int ENTER_DOMAIN_URL = 1;
private DomainAdapter domainAdapter;
private DrawerLayout mDrawerLayout;
private ProgressDialog mDialog;
private NavigationView mNavigationView;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_home);
Toolbar toolbar = (Toolbar) findViewById(R.id.toolbar);
toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle);
setSupportActionBar(toolbar);
ActionBar actionbar = getSupportActionBar();
actionbar.setDisplayHomeAsUpEnabled(true);
actionbar.setHomeAsUpIndicator(R.drawable.ic_menu);
mDrawerLayout = findViewById(R.id.drawer_layout);
mNavigationView = (NavigationView) findViewById(R.id.nav_view);
mNavigationView.setNavigationItemSelectedListener(this);
TabHost tabs = (TabHost)findViewById(R.id.tabhost);
tabs.setup();
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);
int numberOfColumns = 1;
GridLayoutManager gridLayoutMgr = new GridLayoutManager(this, numberOfColumns);
domainsView.setLayoutManager(gridLayoutMgr);
domainAdapter = new DomainAdapter(this);
domainAdapter.setClickListener(new DomainAdapter.ItemClickListener() {
@Override
public void onItemClick(View view, int position, DomainAdapter.Domain domain) {
gotoDomain(domain.url);
}
});
domainsView.setAdapter(domainAdapter);
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);
}
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);
}
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.
//getMenuInflater().inflate(R.menu.menu_home, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
switch (id) {
case android.R.id.home:
mDrawerLayout.openDrawer(GravityCompat.START);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
protected void onDestroy() {
cancelActivityIndicator();
super.onDestroy();
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch(item.getItemId()) {
case R.id.action_goto:
Intent i = new Intent(this, GotoActivity.class);
startActivityForResult(i, ENTER_DOMAIN_URL);
return true;
case R.id.action_settings:
return true;
}
return false;
}
@Override
protected void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == ENTER_DOMAIN_URL && resultCode == RESULT_OK) {
gotoDomain(data.getStringExtra(GotoActivity.PARAM_DOMAIN_URL));
}
}
@Override
public void onBackPressed() {
finishAffinity();
}
}

View file

@ -15,6 +15,9 @@ import android.content.Intent;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.Vibrator;
import android.view.HapticFeedbackConstants;
import android.view.WindowManager;
import android.util.Log;
import org.qtproject.qt5.android.bindings.QtActivity;
@ -33,18 +36,14 @@ public class InterfaceActivity extends QtActivity {
public static final String DOMAIN_URL = "url";
private static final String TAG = "Interface";
private Vibrator mVibrator;
//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();
private native void nativeEnterBackground();
private native void nativeEnterForeground();
//private native void saveRealScreenSize(int width, int height);
//private native void setAppVersion(String version);
private native long nativeOnExitVr();
private AssetManager assetManager;
@ -63,6 +62,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));
@ -90,7 +90,6 @@ public class InterfaceActivity extends QtActivity {
Point size = new Point();
getWindowManager().getDefaultDisplay().getRealSize(size);
// saveRealScreenSize(size.x, size.y);
try {
PackageInfo pInfo = this.getPackageManager().getPackageInfo(getPackageName(), 0);
@ -103,40 +102,38 @@ public class InterfaceActivity extends QtActivity {
final View rootView = getWindow().getDecorView().findViewById(android.R.id.content);
// This is a workaround to hide the menu bar when the virtual keyboard is shown from Qt
rootView.getViewTreeObserver().addOnGlobalLayoutListener(new android.view.ViewTreeObserver.OnGlobalLayoutListener() {
@Override
public void onGlobalLayout() {
int heightDiff = rootView.getRootView().getHeight() - rootView.getHeight();
if (getActionBar().isShowing()) {
getActionBar().hide();
}
rootView.getViewTreeObserver().addOnGlobalLayoutListener(() -> {
if (getActionBar() != null && getActionBar().isShowing()) {
getActionBar().hide();
}
});
startActivity(new Intent(this, SplashActivity.class));
mVibrator = (Vibrator) this.getSystemService(VIBRATOR_SERVICE);
}
@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();
}
@ -194,14 +191,16 @@ public class InterfaceActivity extends QtActivity {
if (intent.hasExtra(DOMAIN_URL)) {
nativeGotoUrl(intent.getStringExtra(DOMAIN_URL));
}
nativeGoBackFromAndroidActivity();
}
public void openAndroidActivity(String activityName) {
public void openAndroidActivity(String activityName, boolean backToScene) {
switch (activityName) {
case "Home": {
Intent intent = new Intent(this, HomeActivity.class);
intent.putExtra(HomeActivity.PARAM_NOT_START_INTERFACE_ACTIVITY, true);
case "Home":
case "Privacy Policy":
case "Login": {
Intent intent = new Intent(this, MainActivity.class);
intent.putExtra(MainActivity.EXTRA_FRAGMENT, activityName);
intent.putExtra(MainActivity.EXTRA_BACK_TO_SCENE, backToScene);
startActivity(intent);
break;
}
@ -212,8 +211,18 @@ public class InterfaceActivity extends QtActivity {
}
}
public void onAppLoadedComplete() {
super.isLoading = false;
}
public void performHapticFeedback(int duration) {
if (duration > 0) {
mVibrator.vibrate(duration);
}
}
@Override
public void onBackPressed() {
openAndroidActivity("Home");
openAndroidActivity("Home", false);
}
}
}

View file

@ -0,0 +1,310 @@
package io.highfidelity.hifiinterface;
import android.app.Fragment;
import android.app.FragmentManager;
import android.app.FragmentTransaction;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.BitmapDrawable;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.content.ContextCompat;
import android.support.v4.graphics.drawable.RoundedBitmapDrawable;
import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.util.Log;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.ImageView;
import android.widget.TextView;
import com.squareup.picasso.Callback;
import com.squareup.picasso.Picasso;
import io.highfidelity.hifiinterface.fragment.HomeFragment;
import io.highfidelity.hifiinterface.fragment.LoginFragment;
import io.highfidelity.hifiinterface.fragment.PolicyFragment;
import io.highfidelity.hifiinterface.task.DownloadProfileImageTask;
public class MainActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener,
LoginFragment.OnLoginInteractionListener,
HomeFragment.OnHomeInteractionListener {
private static final int PROFILE_PICTURE_PLACEHOLDER = R.drawable.default_profile_avatar;
public static final String DEFAULT_FRAGMENT = "Home";
public static final String EXTRA_FRAGMENT = "fragment";
public static final String EXTRA_BACK_TO_SCENE = "backToScene";
private String TAG = "HighFidelity";
public native boolean nativeIsLoggedIn();
public native void nativeLogout();
public native String nativeGetDisplayName();
private DrawerLayout mDrawerLayout;
private NavigationView mNavigationView;
private ImageView mProfilePicture;
private TextView mDisplayName;
private View mLoginPanel;
private View mProfilePanel;
private TextView mLogoutOption;
private boolean backToScene;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
mNavigationView = findViewById(R.id.nav_view);
mNavigationView.setNavigationItemSelectedListener(this);
mLoginPanel = mNavigationView.getHeaderView(0).findViewById(R.id.loginPanel);
mProfilePanel = mNavigationView.getHeaderView(0).findViewById(R.id.profilePanel);
mLogoutOption = mNavigationView.findViewById(R.id.logout);
mDisplayName = mNavigationView.getHeaderView(0).findViewById(R.id.displayName);
mProfilePicture = mNavigationView.getHeaderView(0).findViewById(R.id.profilePicture);
Toolbar toolbar = findViewById(R.id.toolbar);
toolbar.setTitleTextAppearance(this, R.style.HomeActionBarTitleStyle);
setSupportActionBar(toolbar);
ActionBar actionbar = getSupportActionBar();
actionbar.setDisplayHomeAsUpEnabled(true);
actionbar.setHomeAsUpIndicator(R.drawable.ic_menu);
mDrawerLayout = findViewById(R.id.drawer_layout);
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));
if (getIntent() != null) {
if (getIntent().hasExtra(EXTRA_FRAGMENT)) {
loadFragment(getIntent().getStringExtra(EXTRA_FRAGMENT));
} else {
loadFragment(DEFAULT_FRAGMENT);
}
if (getIntent().hasExtra(EXTRA_BACK_TO_SCENE)) {
backToScene = getIntent().getBooleanExtra(EXTRA_BACK_TO_SCENE, false);
}
}
}
private void loadFragment(String fragment) {
switch (fragment) {
case "Login":
loadLoginFragment();
break;
case "Home":
loadHomeFragment();
break;
case "Privacy Policy":
loadPrivacyPolicyFragment();
break;
default:
Log.e(TAG, "Unknown fragment " + fragment);
}
}
private void loadHomeFragment() {
Fragment fragment = HomeFragment.newInstance();
loadFragment(fragment, getString(R.string.home), false);
}
private void loadLoginFragment() {
Fragment fragment = LoginFragment.newInstance();
loadFragment(fragment, getString(R.string.login), true);
}
private void loadPrivacyPolicyFragment() {
Fragment fragment = PolicyFragment.newInstance();
loadFragment(fragment, getString(R.string.privacyPolicy), true);
}
private void loadFragment(Fragment fragment, String title, boolean addToBackStack) {
FragmentManager fragmentManager = getFragmentManager();
FragmentTransaction ft = fragmentManager.beginTransaction();
ft.replace(R.id.content_frame, fragment);
if (addToBackStack) {
ft.addToBackStack(null);
}
ft.commit();
setTitle(title);
mDrawerLayout.closeDrawer(mNavigationView);
}
private void updateLoginMenu() {
if (nativeIsLoggedIn()) {
mLoginPanel.setVisibility(View.GONE);
mProfilePanel.setVisibility(View.VISIBLE);
mLogoutOption.setVisibility(View.VISIBLE);
updateProfileHeader();
} else {
mLoginPanel.setVisibility(View.VISIBLE);
mProfilePanel.setVisibility(View.GONE);
mLogoutOption.setVisibility(View.GONE);
mDisplayName.setText("");
}
}
private void updateProfileHeader() {
updateProfileHeader(nativeGetDisplayName());
}
private void updateProfileHeader(String username) {
if (!username.isEmpty()) {
mDisplayName.setText(username);
updateProfilePicture(username);
}
}
@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
//getMenuInflater().inflate(R.menu.menu_navigation, menu);
return true;
}
@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();
switch (id) {
case android.R.id.home:
mDrawerLayout.openDrawer(GravityCompat.START);
return true;
}
return super.onOptionsItemSelected(item);
}
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
switch(item.getItemId()) {
case R.id.action_home:
loadHomeFragment();
return true;
}
return false;
}
@Override
protected void onStart() {
super.onStart();
updateLoginMenu();
}
public void onLoginClicked(View view) {
loadLoginFragment();
}
public void onLogoutClicked(View view) {
nativeLogout();
updateLoginMenu();
}
public void onSelectedDomain(String domainUrl) {
goToDomain(domainUrl);
}
private void goToLastLocation() {
goToDomain("");
}
private void goToDomain(String domainUrl) {
Intent intent = new Intent(this, InterfaceActivity.class);
intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl);
finish();
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
}
@Override
public void onLoginCompleted() {
loadHomeFragment();
updateLoginMenu();
if (backToScene) {
backToScene = false;
goToLastLocation();
}
}
public void handleUsernameChanged(String username) {
runOnUiThread(() -> updateProfileHeader(username));
}
/**
* This is a temporary way to get the profile picture URL
* TODO: this should be get from an API (at the moment there is no one for this)
*/
private void updateProfilePicture(String username) {
mProfilePicture.setImageResource(PROFILE_PICTURE_PLACEHOLDER);
new DownloadProfileImageTask(url -> { if (url != null && !url.isEmpty()) {
Picasso.get().load(url).into(mProfilePicture, new RoundProfilePictureCallback());
} } ).execute(username);
}
public void onPrivacyPolicyClicked(View view) {
loadPrivacyPolicyFragment();
}
private class RoundProfilePictureCallback implements Callback {
@Override
public void onSuccess() {
Bitmap imageBitmap = ((BitmapDrawable) mProfilePicture.getDrawable()).getBitmap();
RoundedBitmapDrawable imageDrawable = RoundedBitmapDrawableFactory.create(getResources(), imageBitmap);
imageDrawable.setCircular(true);
imageDrawable.setCornerRadius(Math.max(imageBitmap.getWidth(), imageBitmap.getHeight()) / 2.0f);
mProfilePicture.setImageDrawable(imageDrawable);
}
@Override
public void onError(Exception e) {
mProfilePicture.setImageResource(PROFILE_PICTURE_PLACEHOLDER);
}
}
@Override
public void onBackPressed() {
int index = getFragmentManager().getBackStackEntryCount() - 1;
if (index > -1) {
super.onBackPressed();
if (backToScene) {
backToScene = false;
goToLastLocation();
}
} else {
finishAffinity();
}
}
@Override
public void onSaveInstanceState(Bundle savedInstanceState) {
super.onSaveInstanceState(savedInstanceState);
savedInstanceState.putBoolean(EXTRA_BACK_TO_SCENE, backToScene);
}
@Override
protected void onRestoreInstanceState(Bundle savedInstanceState) {
super.onRestoreInstanceState(savedInstanceState);
backToScene = savedInstanceState.getBoolean(EXTRA_BACK_TO_SCENE, false);
}
}

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,43 @@
package io.highfidelity.hifiinterface;
import android.app.Activity;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.view.View;
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 onResume() {
super.onResume();
View decorView = getWindow().getDecorView();
// Hide the status bar.
int uiOptions = View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN;
decorView.setSystemUiVisibility(uiOptions);
}
@Override
protected void onStop() {
super.onStop();
}
public void onAppLoadedComplete() {
startActivity(new Intent(this, MainActivity.class));
SplashActivity.this.finish();
}
}

View file

@ -0,0 +1,158 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Fragment;
import android.content.Context;
import android.os.Bundle;
import android.os.Handler;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import io.highfidelity.hifiinterface.HifiUtils;
import io.highfidelity.hifiinterface.R;
import io.highfidelity.hifiinterface.view.DomainAdapter;
public class HomeFragment extends Fragment {
private DomainAdapter mDomainAdapter;
private RecyclerView mDomainsView;
private TextView searchNoResultsView;
private ImageView mSearchIconView;
private ImageView mClearSearch;
private EditText mSearchView;
private OnHomeInteractionListener mListener;
public native String nativeGetLastLocation();
public HomeFragment() {
// Required empty public constructor
}
public static HomeFragment newInstance() {
HomeFragment fragment = new HomeFragment();
return fragment;
}
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (getArguments() != null) {
}
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_home, container, false);
searchNoResultsView = rootView.findViewById(R.id.searchNoResultsView);
mDomainsView = rootView.findViewById(R.id.rvDomains);
int numberOfColumns = 1;
GridLayoutManager gridLayoutMgr = new GridLayoutManager(getContext(), numberOfColumns);
mDomainsView.setLayoutManager(gridLayoutMgr);
mDomainAdapter = new DomainAdapter(getContext(), HifiUtils.getInstance().protocolVersionSignature(), nativeGetLastLocation());
mDomainAdapter.setClickListener((view, position, domain) -> {
new Handler(getActivity().getMainLooper()).postDelayed(() -> mListener.onSelectedDomain(domain.url), 400); // a delay so the ripple effect can be seen
});
mDomainAdapter.setListener(new DomainAdapter.AdapterListener() {
@Override
public void onEmptyAdapter() {
searchNoResultsView.setText(R.string.search_no_results);
searchNoResultsView.setVisibility(View.VISIBLE);
mDomainsView.setVisibility(View.GONE);
}
@Override
public void onNonEmptyAdapter() {
searchNoResultsView.setVisibility(View.GONE);
mDomainsView.setVisibility(View.VISIBLE);
}
@Override
public void onError(Exception e, String message) {
}
});
mDomainsView.setAdapter(mDomainAdapter);
mSearchView = rootView.findViewById(R.id.searchView);
mSearchIconView = rootView.findViewById(R.id.search_mag_icon);
mClearSearch = rootView.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);
}
mListener.onSelectedDomain(urlString);
return true;
}
return false;
});
mClearSearch.setOnClickListener(view -> onSearchClear(view));
getActivity().getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
return rootView;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnHomeInteractionListener) {
mListener = (OnHomeInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnHomeInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
public interface OnHomeInteractionListener {
void onSelectedDomain(String domainUrl);
}
public void onSearchClear(View view) {
mSearchView.setText("");
}
}

View file

@ -0,0 +1,205 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Activity;
import android.app.Fragment;
import android.app.ProgressDialog;
import android.content.Context;
import android.content.Intent;
import android.net.Uri;
import android.os.Bundle;
import android.text.Editable;
import android.text.TextWatcher;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo;
import android.view.inputmethod.InputMethodManager;
import android.widget.Button;
import android.widget.EditText;
import android.widget.TextView;
import io.highfidelity.hifiinterface.R;
public class LoginFragment extends Fragment {
private EditText mUsername;
private EditText mPassword;
private TextView mError;
private TextView mForgotPassword;
private Button mLoginButton;
private ProgressDialog mDialog;
public native void nativeLogin(String username, String password, Activity usernameChangedListener);
private LoginFragment.OnLoginInteractionListener mListener;
public LoginFragment() {
// Required empty public constructor
}
public static LoginFragment newInstance() {
LoginFragment fragment = new LoginFragment();
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_login, container, false);
mUsername = rootView.findViewById(R.id.username);
mPassword = rootView.findViewById(R.id.password);
mError = rootView.findViewById(R.id.error);
mLoginButton = rootView.findViewById(R.id.loginButton);
mForgotPassword = rootView.findViewById(R.id.forgotPassword);
mUsername.addTextChangedListener(new TextWatcher() {
boolean ignoreNextChange = false;
boolean hadBlankSpace = false;
@Override
public void beforeTextChanged(CharSequence charSequence, int start, int count, int after) {
hadBlankSpace = charSequence.length() > 0 && charSequence.charAt(charSequence.length()-1) == ' ';
}
@Override
public void onTextChanged(CharSequence charSequence, int start, int count, int after) {
}
@Override
public void afterTextChanged(Editable editable) {
if (!ignoreNextChange) {
ignoreNextChange = true;
boolean spaceFound = false;
for (int i = 0; i < editable.length(); i++) {
if (editable.charAt(i) == ' ') {
spaceFound=true;
editable.delete(i, i + 1);
i--;
}
}
if (hadBlankSpace && !spaceFound && editable.length() > 0) {
editable.delete(editable.length()-1, editable.length());
}
editable.append(' ');
ignoreNextChange = false;
}
}
});
mLoginButton.setOnClickListener(view -> login());
mForgotPassword.setOnClickListener(view -> forgotPassword());
mPassword.setOnEditorActionListener(
(textView, actionId, keyEvent) -> {
if (actionId == EditorInfo.IME_ACTION_DONE) {
mLoginButton.performClick();
return true;
}
return false;
});
return rootView;
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
if (context instanceof OnLoginInteractionListener) {
mListener = (OnLoginInteractionListener) context;
} else {
throw new RuntimeException(context.toString()
+ " must implement OnLoginInteractionListener");
}
}
@Override
public void onDetach() {
super.onDetach();
mListener = null;
}
@Override
public void onStop() {
super.onStop();
cancelActivityIndicator();
hideKeyboard();
}
public void login() {
String username = mUsername.getText().toString().trim();
String password = mPassword.getText().toString();
hideKeyboard();
if (username.isEmpty() || password.isEmpty()) {
showError(getString(R.string.login_username_or_password_incorrect));
} else {
mLoginButton.setEnabled(false);
hideError();
showActivityIndicator();
nativeLogin(username, password, getActivity());
}
}
private void hideKeyboard() {
View view = getActivity().getCurrentFocus();
if (view != null) {
InputMethodManager imm = (InputMethodManager) getContext().getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
}
private void forgotPassword() {
Intent intent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://highfidelity.com/users/password/new"));
startActivity(intent);
}
private void showActivityIndicator() {
if (mDialog == null) {
mDialog = new ProgressDialog(getContext());
}
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) {
Log.d("[LOGIN]", "handleLoginCompleted " + success);
getActivity().runOnUiThread(() -> {
mLoginButton.setEnabled(true);
cancelActivityIndicator();
if (success) {
if (mListener != null) {
mListener.onLoginCompleted();
}
} else {
showError(getString(R.string.login_username_or_password_incorrect));
}
});
}
public interface OnLoginInteractionListener {
void onLoginCompleted();
}
}

View file

@ -0,0 +1,60 @@
package io.highfidelity.hifiinterface.fragment;
import android.app.Fragment;
import android.os.Bundle;
import android.text.Html;
import android.text.Spanned;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import java.io.IOException;
import java.io.InputStream;
import io.highfidelity.hifiinterface.R;
public class PolicyFragment extends Fragment {
private static final String POLICY_FILE = "privacy_policy.html";
public PolicyFragment() {
// Required empty public constructor
}
public static PolicyFragment newInstance() {
PolicyFragment fragment = new PolicyFragment();
return fragment;
}
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_policy, container, false);
TextView policy = rootView.findViewById(R.id.policyText);
Spanned myStringSpanned = null;
try {
myStringSpanned = Html.fromHtml(loadHTMLFromAsset(), null, null);
policy.setText(myStringSpanned, TextView.BufferType.SPANNABLE);
} catch (IOException e) {
policy.setText("N/A");
}
return rootView;
}
public String loadHTMLFromAsset() throws IOException {
String html = null;
InputStream is = getContext().getAssets().open(POLICY_FILE);
int size = is.available();
byte[] buffer = new byte[size];
is.read(buffer);
is.close();
html = new String(buffer, "UTF-8");
return html;
}
}

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,205 @@
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 String TAGS_TO_SEARCH = "mobile";
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,
TAGS_TO_SEARCH,
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 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("tags") String tagsCommaSeparated,
@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

@ -0,0 +1,71 @@
package io.highfidelity.hifiinterface.task;
import android.os.AsyncTask;
import android.util.Log;
import java.io.BufferedReader;
import java.io.IOException;
import java.io.InputStreamReader;
import java.net.URL;
import io.highfidelity.hifiinterface.HifiUtils;
/**
* This is a temporary solution until the profile picture URL is
* available in an API
*/
public class DownloadProfileImageTask extends AsyncTask<String, Void, String> {
private static final String BASE_PROFILE_URL = "https://highfidelity.com";
private static final String TAG = "Interface";
private final DownloadProfileImageResultProcessor mResultProcessor;
public interface DownloadProfileImageResultProcessor {
void onResultAvailable(String url);
}
public DownloadProfileImageTask(DownloadProfileImageResultProcessor resultProcessor) {
mResultProcessor = resultProcessor;
}
@Override
protected String doInBackground(String... usernames) {
URL userPage = null;
for (String username: usernames) {
try {
userPage = new URL(BASE_PROFILE_URL + "/users/" + username);
BufferedReader in = new BufferedReader(
new InputStreamReader(
userPage.openStream()));
StringBuffer strBuff = new StringBuffer();
String inputLine;
while ((inputLine = in.readLine()) != null) {
strBuff.append(inputLine);
}
in.close();
String substr = "img class=\"users-img\" src=\"";
int indexBegin = strBuff.indexOf(substr) + substr.length();
if (indexBegin >= substr.length()) {
int indexEnd = strBuff.indexOf("\"", indexBegin);
if (indexEnd > 0) {
String url = strBuff.substring(indexBegin, indexEnd);
return HifiUtils.getInstance().absoluteHifiAssetUrl(url, BASE_PROFILE_URL);
}
}
} catch (IOException e) {
Log.e(TAG, "Error getting profile picture for username " + username);
}
}
return null;
}
@Override
protected void onPostExecute(String url) {
super.onPostExecute(url);
if (mResultProcessor != null) {
mResultProcessor.onResultAvailable(url);
}
}
}

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,79 +10,90 @@ import android.view.ViewGroup;
import android.widget.ImageView;
import android.widget.TextView;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import com.squareup.picasso.Picasso;
import java.io.IOException;
import java.io.InputStream;
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 static final String DEFAULT_THUMBNAIL_PLACE = "android.resource://io.highfidelity.hifiinterface/" + R.drawable.domain_placeholder;
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 String mLastLocation;
private UserStoryDomainProvider domainProvider;
private AdapterListener mAdapterListener;
// references to our domains
private Domain[] mDomains = {};
public DomainAdapter(Context c) {
public DomainAdapter(Context c, String protocol, String lastLocation) {
mContext = c;
this.mInflater = LayoutInflater.from(mContext);
loadDomains();
mProtocol = protocol;
mLastLocation = lastLocation;
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");
public void loadDomains(String filterText) {
domainProvider.retrieve(filterText, new DomainProvider.DomainCallback() {
@Override
public void retrieveOk(List<Domain> domain) {
if (filterText.length() == 0) {
Domain lastVisitedDomain = new Domain(mContext.getString(R.string.your_last_location), mLastLocation, DEFAULT_THUMBNAIL_PLACE);
if (!mLastLocation.isEmpty() && mLastLocation.contains("://")) {
int startIndex = mLastLocation.indexOf("://");
int endIndex = mLastLocation.indexOf("/", startIndex + 3);
String toSearch = mLastLocation.substring(0, endIndex + 1).toLowerCase();
for (Domain d : domain) {
if (d.url.toLowerCase().startsWith(toSearch)) {
lastVisitedDomain.thumbnail = d.thumbnail;
}
}
}
domain.add(0, lastVisitedDomain);
}
domains.add(new Domain(domainName, domainUrl, domainThumb));
for (Domain d : domain) {
// we override the default picture added in the server by an android specific version
if (d.thumbnail != null &&
d.thumbnail.endsWith("assets/places/thumbnail-default-place-e5a3f33e773ab699495774990a562f9f7693dc48ef90d8be6985c645a0280f75.png")) {
d.thumbnail = DEFAULT_THUMBNAIL_PLACE;
}
}
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 +106,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 +143,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

@ -0,0 +1,17 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="100dp"
android:height="100dp"
android:viewportWidth="100"
android:viewportHeight="100">
<path
android:fillColor="#8E8E8E"
android:pathData="M50.5,3.42969 C76.4962,3.42969,97.5703,24.5038,97.5703,50.5 C97.5703,76.4962,76.4962,97.5703,50.5,97.5703 C24.5038,97.5703,3.42969,76.4962,3.42969,50.5 C3.42969,24.5038,24.5038,3.42969,50.5,3.42969 Z" />
<path
android:fillColor="#333333"
android:pathData="M53.7813,80.0898 C51.5938,80.0898,49.4063,80.0898,47.1992,80.0898 C47.0625,80.0703,46.9258,80.0507,46.789,80.0312 C44.621,79.914,42.4726,79.8164,40.3046,79.6601 C37.2968,79.4257,34.3085,79.0351,31.3788,78.3515 C29.8945,78,28.4297,77.6094,27.1406,76.7695 C26.6328,76.457,26.3008,76.0664,26.3203,75.4023 C26.3789,72.8632,26.5547,70.3632,27.2383,67.9023 C27.707,66.2226,28.0586,64.5039,28.8594,62.9414 C29.4258,61.8281,29.9336,60.6758,30.5391,59.582 C31.0079,58.7422,31.5547,57.9414,32.1016,57.1406 C33.0196,55.7539,34.1328,54.5234,35.3438,53.3711 C36.3399,52.4141,37.4141,51.5547,38.5469,50.7734 C40.0703,49.7382,41.6914,48.9179,43.4297,48.2734 C45.3242,47.5703,47.2969,47.1601,49.3086,47.0625 C54.1523,46.7891,58.5664,48.1172,62.5508,50.832 C63.6836,51.5937,64.7578,52.4531,65.7149,53.4101 C66.8672,54.5429,67.9024,55.8124,68.918,57.0624 C69.5039,57.8046,70.0118,58.6249,70.4805,59.4647 C71.0664,60.5194,71.6524,61.5741,72.0821,62.6678 C72.668,64.1131,73.2149,65.5975,73.6055,67.1209 C73.9766,68.5662,74.1719,70.0506,74.4258,71.5154 C74.543,72.2771,74.6016,73.0584,74.6797,73.8396 C74.6797,74.5232,74.6797,75.2068,74.6797,75.8904 C74.3672,76.2224,74.0938,76.6326,73.7227,76.867 C73.2149,77.199,72.668,77.4529,72.1016,77.6287 C70.6172,78.0584,69.1329,78.4685,67.6094,78.7615 C64.1719,79.4256,60.6758,79.7185,57.1602,79.9138 C56.0273,79.9727,54.9141,80.0313,53.7813,80.0898 Z" />
<path
android:fillColor="#333333"
android:pathData="M51.6523,20.9102 C52.4922,21.0664,53.3516,21.2031,54.1719,21.418 C54.836,21.5938,55.5,21.7891,56.1055,22.0821 C56.9258,22.4923,57.7266,22.9415,58.4688,23.4688 C60.0508,24.5626,61.2813,25.9883,62.1993,27.6876 C64.1329,31.3009,64.1134,34.9728,62.4727,38.6642 C61.7696,40.2462,60.6758,41.6134,59.3282,42.7072 C56.047,45.383,52.297,46.3791,48.1173,45.715 C46.379,45.4416,44.7775,44.8166,43.2736,43.8791 C42.0627,43.1174,41.008,42.1799,40.09,41.0861 C39.4064,40.2658,38.8986,39.3674,38.4494,38.4103 C36.9455,35.2853,37.0041,32.0822,38.1955,28.8986 C38.5471,27.9416,39.0939,27.0236,39.7189,26.2033 C41.9259,23.3322,44.8361,21.6135,48.4103,21.0666 C48.7228,21.0275,49.0158,20.9689,49.3283,20.9104 C50.1094,20.9102,50.8906,20.9102,51.6523,20.9102 Z" />
</vector>

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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 5.6 KiB

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,14 @@
<?xml version="1.0" encoding="utf-8"?>
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="680.5dp"
android:height="680.5dp"
android:viewportWidth="680.5"
android:viewportHeight="680.5">
<path
android:fillColor="#E8E6E8"
android:pathData="M340.3,642.6 C299.5,642.6,259.9,634.6,222.6,618.8 C186.6,603.6,154.3,581.8,126.5,554 S77,493.9,61.7,457.9 C45.9,420.6,37.9,381,37.9,340.3 S45.9,259.9,61.7,222.6 C76.9,186.6,98.7,154.3,126.5,126.5 S186.6,77,222.6,61.7 C259.9,45.9,299.5,37.9,340.3,37.9 S420.7,45.9,458,61.7 C494,76.9,526.3,98.7,554.1,126.5 C581.9,154.3,603.6,186.6,618.9,222.6 C634.7,259.9,642.7,299.5,642.7,340.3 S634.7,420.7,618.9,458 C603.7,494,581.9,526.3,554.1,554.1 C526.3,581.9,494,603.6,458,618.9 C420.6,634.6,381.1,642.6,340.3,642.6 Z M340.3,69.4 C191,69.4,69.5,190.9,69.5,340.2 S191,611,340.3,611 S611,489.5,611,340.3 C611.2,190.9,489.6,69.4,340.3,69.4 Z" />
<path
android:fillColor="#E8E6E8"
android:pathData="M431.7,468.3 L431.7,256.1 C444.9,251.6,454.4,239.1,454.4,224.4 C454.4,205.9,439.4,190.9,420.9,190.9 S387.4,205.9,387.4,224.4 C387.4,238.5,396.1,250.6,408.5,255.5 L408.5,354.1 L270.4,290 L270.4,212.2 C283.6,207.7,293.1,195.2,293.1,180.5 C293.1,162,278.1,147,259.6,147 S226.1,162,226.1,180.5 C226.1,194.6,234.8,206.7,247.2,211.6 L247.2,425 C234.8,429.9,226.1,442,226.1,456.1 C226.1,474.6,241.1,489.6,259.6,489.6 S293.1,474.6,293.1,456.1 C293.1,441.4,283.6,428.9,270.4,424.4 L270.4,317.4 L408.6,381.5 L408.6,468.9 C396.2,473.8,387.5,485.9,387.5,500 C387.5,518.5,402.5,533.5,421,533.5 S454.5,518.5,454.5,500 C454.4,485.3,444.9,472.8,431.7,468.3 Z" />
</vector>

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="798.5dp"
android:height="798.1dp"
android:viewportWidth="798.5"
android:viewportHeight="798.1">
<path
android:fillColor="#23B2E7"
android:pathData="M402.9,630.1 C362.1,630.1,322.5,622.1,285.2,606.3 C249.2,591.1,216.9,569.3,189.1,541.5 C161.3,513.7,139.6,481.4,124.3,445.4 C108.5,408.1,100.5,368.5,100.5,327.7 C100.5,286.9,108.5,247.3,124.3,210 C139.5,174,161.3,141.7,189.1,113.9 C216.9,86.1,249.2,64.4,285.2,49.1 C322.5,33.3,362.1,25.3,402.9,25.3 C443.7,25.3,483.3,33.3,520.6,49.1 C556.6,64.3,588.9,86.1,616.7,113.9 C644.5,141.7,666.2,174,681.5,210 C697.3,247.3,705.3,286.9,705.3,327.7 C705.3,368.5,697.3,408.1,681.5,445.4 C666.3,481.4,644.5,513.7,616.7,541.5 C588.9,569.3,556.6,591,520.6,606.3 C483.3,622.1,443.7,630.1,402.9,630.1 Z M402.9,56.9 C253.6,56.9,132.1,178.4,132.1,327.7 C132.1,477,253.6,598.5,402.9,598.5 C552.2,598.5,673.7,477,673.7,327.7 C673.8,178.4,552.3,56.9,402.9,56.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M495.3,455.9 L495.3,243.7 C508.5,239.2,518,226.7,518,212 C518,193.5,503,178.5,484.5,178.5 S451,193.5,451,212 C451,226.1,459.7,238.2,472.1,243.1 L472.1,341.7 L333.9,277.6 L333.9,199.8 C347.1,195.3,356.6,182.8,356.6,168.1 C356.6,149.6,341.6,134.6,323.1,134.6 S289.6,149.6,289.6,168.1 C289.6,182.2,298.3,194.3,310.7,199.2 L310.7,412.6 C298.3,417.5,289.6,429.6,289.6,443.7 C289.6,462.2,304.6,477.2,323.1,477.2 S356.6,462.2,356.6,443.7 C356.6,429,347.1,416.5,333.9,412 L333.9,305 L472.1,369.1 L472.1,456.5 C459.7,461.4,451,473.5,451,487.6 C451,506.1,466,521.1,484.5,521.1 S518,506.1,518,487.6 C518,472.9,508.5,460.4,495.3,455.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M113.3,695.9 L113.3,773.1 L96.8,773.1 L96.8,739.4 L69.6,739.4 L69.6,773.1 L53.1,773.1 L53.1,695.9 L69.6,695.9 L69.6,725.6 L96.8,725.6 L96.8,695.9 L113.3,695.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M142.6,773.1 L126.1,773.1 L126.1,695.9 L142.6,695.9 L142.6,773.1 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M190.3,708.3 C186.4,708.3,183.1,708.7,180.4,709.4 C177.7,710.1,175.5,711.4,173.8,713.1 C172.1,714.9,170.9,717.2,170.2,720.2 C169.5,723.2,169.1,726.8,169.1,731.3 L169.1,738.2 C169.1,742.8,169.4,746.6,170.1,749.5 C170.8,752.4,171.8,754.7,173.3,756.4 C174.8,758.1,176.7,759.2,179.1,759.8 C181.5,760.4,184.4,760.7,187.9,760.7 C188.9,760.7,189.9,760.7,191,760.6 C192,760.5,193.2,760.4,194.4,760.3 L194.4,739.5 L184.8,739.5 L186.3,726.7 L209.9,726.7 L209.9,770.7 C206.9,771.7,203.1,772.5,198.7,773.2 C194.3,773.9,189.7,774.2,184.9,774.2 C179,774.2,174,773.5,169.9,772.1 C165.7,770.7,162.4,768.5,159.8,765.6 C157.2,762.7,155.3,759,154.1,754.6 C152.9,750.2,152.3,745,152.3,739 L152.3,731 C152.3,725.6,152.9,720.6,154.1,716.2 C155.3,711.8,157.3,707.9,160.1,704.8 C162.9,701.6,166.6,699.2,171.2,697.4 C175.8,695.7,181.5,694.8,188.3,694.8 C192.1,694.8,195.7,695.1,199.4,695.8 C203,696.5,206,697.2,208.3,698 L205.6,710.5 C203.5,709.9,201.2,709.4,198.8,709 C196.3,708.5,193.5,708.3,190.3,708.3 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M284,695.9 L284,773.1 L267.5,773.1 L267.5,739.4 L240.3,739.4 L240.3,773.1 L223.8,773.1 L223.8,695.9 L240.3,695.9 L240.3,725.6 L267.5,725.6 L267.5,695.9 L284,695.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M372,695.9 L370.2,709.3 L341.4,709.3 L341.4,725.4 L368,725.4 L366.2,738.8 L341.4,738.8 L341.4,773 L324.9,773 L324.9,695.8 L372,695.8 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M398.9,773.1 L382.4,773.1 L382.4,695.9 L398.9,695.9 L398.9,773.1 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M412.1,695.9 L437.3,695.9 C443.9,695.9,449.3,696.8,453.7,698.5 C458.1,700.2,461.6,702.6,464.2,705.7 C466.8,708.8,468.6,712.5,469.7,716.8 C470.7,721.1,471.3,725.8,471.3,730.8 L471.3,738.1 C471.3,743.2,470.8,747.9,469.7,752.2 C468.6,756.5,466.8,760.2,464.2,763.3 C461.6,766.4,458.1,768.8,453.7,770.5 C449.3,772.2,443.9,773.1,437.3,773.1 L412.2,773.1 L412.2,695.9 Z M428.5,759.6 L435.7,759.6 C438.8,759.6,441.5,759.2,443.9,758.5 C446.2,757.8,448.2,756.5,449.8,754.8 C451.4,753.1,452.5,750.8,453.3,747.9 C454.1,745,454.4,741.5,454.4,737.2 L454.4,731.5 C454.4,727.2,454,723.6,453.3,720.7 C452.5,717.8,451.4,715.6,449.8,713.9 C448.2,712.2,446.3,711,443.9,710.3 C441.6,709.6,438.8,709.3,435.7,709.3 L428.5,709.3 L428.5,759.6 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M529.9,695.9 L528.2,708.8 L498.5,708.8 L498.5,725.7 L526.5,725.7 L524.9,738.6 L498.5,738.6 L498.5,760.2 L530,760.2 L528.3,773.1 L482,773.1 L482,695.9 L529.9,695.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M557.2,695.9 L557.2,759.6 L588.5,759.6 L586.7,773 L540.8,773 L540.8,695.8 L557.2,695.8 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M615.7,773.1 L599.2,773.1 L599.2,695.9 L615.7,695.9 L615.7,773.1 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M687.1,695.9 L685.3,709.3 L664.4,709.3 L664.4,773 L647.9,773 L647.9,709.3 L625.1,709.3 L626.9,695.9 L687.1,695.9 Z" />
<path
android:fillColor="#23B2E7"
android:pathData="M726.2,726 L742.3,695.9 L759.3,695.9 L734,739.4 L734,773.1 L717.5,773.1 L717.5,739.4 L692.6,695.9 L710.7,695.9 L726.2,726 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

@ -0,0 +1,7 @@
<?xml version="1.0" encoding="utf-8"?>
<font-family xmlns:app="http://schemas.android.com/apk/res-auto"
app:fontProviderAuthority="com.google.android.gms.fonts"
app:fontProviderPackage="com.google.android.gms"
app:fontProviderQuery="name=Raleway&amp;italic=1"
app:fontProviderCerts="@array/com_google_android_gms_fonts_certs">
</font-family>

View file

@ -1,39 +0,0 @@
<?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:id="@+id/root_activity_goto"
android:layout_width="match_parent"
android:layout_height="match_parent"
tools:context="io.highfidelity.hifiinterface.GotoActivity">
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="?attr/colorPrimary"
app:layout_constraintTop_toTopOf="@id/root_activity_goto"
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar" />
<EditText
android:id="@+id/url_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/HifiEditText"
android:layout_marginStart="@dimen/activity_horizontal_margin"
android:layout_marginEnd="@dimen/activity_horizontal_margin"
android:hint="@string/goto_url_hint"
android:inputType="textUri"
android:imeOptions="actionGo"
app:layout_constraintTop_toBottomOf="@id/toolbar"
/>
<android.support.v7.widget.AppCompatButton
android:id="@+id/go_btn"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginEnd="@dimen/button_horizontal_margin"
android:text="@string/go"
app:layout_constraintTop_toBottomOf="@id/url_text"
app:layout_constraintEnd_toEndOf="@id/root_activity_goto"/>
</android.support.constraint.ConstraintLayout>

View file

@ -1,38 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay"
>
<android.support.v7.widget.Toolbar
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" />
<include layout="@layout/content_home" />
</android.support.design.widget.AppBarLayout>
<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
android:fitsSystemWindows="true"
app:menu="@menu/menu_home"
/>
</android.support.v4.widget.DrawerLayout>

View file

@ -0,0 +1,70 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.v4.widget.DrawerLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/drawer_layout"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:fitsSystemWindows="true">
<android.support.design.widget.AppBarLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/AppTheme.AppBarOverlay"
>
<android.support.v7.widget.Toolbar
android:id="@+id/toolbar"
android:layout_width="match_parent"
android:layout_height="?attr/actionBarSize"
android:background="@color/colorPrimaryDark"
android:elevation="4dp"
/>
<FrameLayout
android:id="@+id/content_frame"
android:layout_width="match_parent"
android:layout_height="match_parent" />
</android.support.design.widget.AppBarLayout>
<!-- Container for contents of drawer - use NavigationView to make configuration easier -->
<android.support.design.widget.NavigationView
android:id="@+id/nav_view"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_gravity="start"
app:headerLayout="@layout/navigation_header"
android:fitsSystemWindows="true"
android:background="@color/colorPrimaryDark"
app:menu="@menu/menu_navigation"
>
<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/logout"
android:text="@string/logout"
android:onClick="onLogoutClicked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/menuOption"
android:paddingTop="15dp"
android:paddingBottom="15dp"/>
<TextView
android:id="@+id/policy"
android:text="@string/privacyPolicy"
android:onClick="onPrivacyPolicyClicked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/menuOption"
android:paddingTop="15dp"
android:paddingBottom="30dp"/>
</LinearLayout>
</android.support.design.widget.NavigationView>
</android.support.v4.widget.DrawerLayout>

View file

@ -0,0 +1,15 @@
<?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="@android:color/black">
<ImageView
android:layout_width="242dp"
android:layout_height="242dp"
android:src="@drawable/hifi_logo_splash"
android:layout_centerHorizontal="true"
android:layout_marginTop="225dp"/>
</RelativeLayout>

View file

@ -1,76 +0,0 @@
<?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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="io.highfidelity.hifiinterface.HomeActivity"
tools:showIn="@layout/activity_home">
<TabHost
android:id="@+id/tabhost"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent">
<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.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

@ -0,0 +1,75 @@
<?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: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.MainActivity"
tools:showIn="@layout/activity_home">
<EditText
android:id="@+id/searchView"
android:layout_width="match_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"
/>
<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_constraintTop_toBottomOf="@id/searchView"
android:layout_marginTop="32dp"
android:text="@string/search_no_results"
android:visibility="gone"
/>
<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

@ -0,0 +1,113 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLight">
<ImageView
android:id="@+id/header"
android:layout_width="@dimen/header_hifi_width"
android:layout_height="@dimen/header_hifi_height"
android:layout_marginTop="@dimen/header_hifi_margin_top"
android:contentDescription="HighFidelity"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/hifi_header" />
<TextView
android:id="@+id/error"
android:layout_width="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="35dp"
android:layout_marginLeft="46dp"
android:layout_marginRight="46dp"
android:background="@drawable/rounded_edit"
android:padding="7dp"
android:paddingRight="12dp"
android:paddingTop="14dp"
android:ems="10"
android:fontFamily="@font/raleway"
android:textSize="14sp"
android:inputType="textEmailAddress"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="right|center_vertical"
app:layout_constraintTop_toBottomOf="@id/header"
android:layout_marginTop="70dp"
android:hint="@string/username_or_email" />
<EditText
android:id="@+id/password"
android:layout_width="match_parent"
android:layout_height="35dp"
android:layout_marginLeft="46dp"
android:layout_marginRight="46dp"
android:background="@drawable/rounded_edit"
android:padding="7dp"
android:paddingRight="12dp"
android:paddingTop="14dp"
android:ems="10"
android:fontFamily="@font/raleway"
android:textSize="14sp"
android:inputType="textPassword"
android:textStyle="italic"
android:textColor="@color/editTextColor"
android:textColorHint="@color/editTextColor"
android:gravity="right|center_vertical"
app:layout_constraintTop_toBottomOf="@id/username"
android:hint="@string/password"
android:layout_marginTop="13dp"
android:imeOptions="actionDone"/>
<Button
android:id="@+id/loginButton"
android:layout_width="154dp"
android:layout_height="38dp"
android:layout_marginTop="16dp"
android:background="@drawable/rounded_button"
android:fontFamily="@font/raleway_semibold"
android:paddingBottom="0dp"
android:paddingLeft="55dp"
android:paddingRight="55dp"
android:paddingTop="0dp"
android:text="@string/login"
android:textColor="@color/white_opaque"
android:textAllCaps="false"
android:textSize="15sp"
app:layout_constraintRight_toRightOf="@id/username"
app:layout_constraintTop_toBottomOf="@id/password"
app:layout_goneMarginTop="4dp"/>
<TextView
android:id="@+id/forgotPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway_semibold"
android:textSize="14dp"
android:text="@string/forgot_password"
android:textStyle="italic"
android:paddingRight="10dp"
app:layout_constraintLeft_toLeftOf="@id/password"
app:layout_constraintTop_toTopOf="@id/loginButton"
app:layout_constraintRight_toLeftOf="@id/loginButton"
android:textColor="@color/colorButton1"/>
</android.support.constraint.ConstraintLayout>

View file

@ -0,0 +1,39 @@
<?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"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
tools:context="io.highfidelity.hifiinterface.MainActivity"
android:background="@color/backgroundLight">
<ImageView
android:id="@+id/header"
android:layout_width="@dimen/header_hifi_width"
android:layout_height="@dimen/header_hifi_height"
android:layout_marginTop="@dimen/header_hifi_margin_top"
android:contentDescription="HighFidelity"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintRight_toRightOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/hifi_header" />
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent"
android:scrollbars="vertical"
android:layout_marginTop="195dp"
app:layout_constraintTop_toBottomOf="@id/header"
app:layout_constraintBottom_toBottomOf="parent"
android:paddingLeft="@dimen/activity_horizontal_margin"
android:paddingRight="@dimen/activity_horizontal_margin"
>
<TextView
android:id="@+id/policyText"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway"
android:textSize="12sp" />
</ScrollView>
</android.support.constraint.ConstraintLayout>

View file

@ -0,0 +1,63 @@
<?xml version="1.0" encoding="utf-8"?>
<android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="176dp"
android:minHeight="176dp">
<LinearLayout
android:id="@+id/loginPanel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:visibility="visible">
<ImageView
android:id="@+id/hifiLogo"
android:layout_width="74dp"
android:layout_height="74dp"
android:src="@drawable/hifi_logo_header"
android:layout_gravity="center_vertical|left" />
<TextView
android:id="@+id/login"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway_semibold"
android:text="@string/login"
android:textSize="18sp"
android:layout_marginLeft="22dp"
android:layout_gravity="center_vertical"
android:onClick="onLoginClicked"
/>
</LinearLayout>
<LinearLayout
android:id="@+id/profilePanel"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="horizontal"
app:layout_constraintLeft_toLeftOf="parent"
app:layout_constraintTop_toTopOf="parent"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:visibility="gone">
<ImageView
android:id="@+id/profilePicture"
android:layout_width="64dp"
android:layout_height="64dp"
android:src="@drawable/default_profile_avatar"
android:layout_gravity="center_vertical|left" />
<TextView
android:id="@+id/displayName"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:fontFamily="@font/raleway_semibold"
android:text=""
android:textSize="18sp"
app:layout_constraintLeft_toRightOf="@id/profilePicture"
android:layout_marginLeft="22dp"
android:layout_gravity="center_vertical"/>
</LinearLayout>
</android.support.constraint.ConstraintLayout>

View file

@ -1,15 +0,0 @@
<menu 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"
tools:context="io.highfidelity.hifiinterface.HomeActivity">
<item
android:id="@+id/action_goto"
android:orderInCategory="90"
android:title="@string/action_goto"
app:showAsAction="never" />
<item
android:id="@+id/action_settings"
android:orderInCategory="100"
android:title="@string/action_settings"
app:showAsAction="never" />
</menu>

View file

@ -0,0 +1,8 @@
<menu xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="io.highfidelity.hifiinterface.MainActivity">
<item
android:id="@+id/action_home"
android:title="@string/home"
/>
</menu>

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,30 @@
<dimen name="text_size_subtitle_material_toolbar">12dp</dimen>
<dimen name="button_horizontal_margin">12dp</dimen>
<dimen name="edit_text_padding">8dp</dimen>
</resources>
<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>
<dimen name="header_hifi_margin_top">56dp</dimen>
<dimen name="header_hifi_height">101dp</dimen>
<dimen name="header_hifi_width">425dp</dimen>
</resources>

View file

@ -2,6 +2,7 @@
<resources>
<array name="preloaded_fonts" translatable="false">
<item>@font/raleway_bold</item>
<item>@font/raleway_italic</item>
<item>@font/raleway_light_italic</item>
<item>@font/raleway_medium</item>
<item>@font/raleway_semibold</item>

View file

@ -1,7 +1,6 @@
<resources>
<string name="app_name" translatable="false">Interface</string>
<string name="home">Home</string>
<string name="go_to">Go To</string>
<string name="web_view_action_open_in_browser" translatable="false">Open in browser</string>
<string name="web_view_action_share" translatable="false">Share link</string>
<string name="web_view_action_share_subject" translatable="false">Shared a link</string>
@ -9,9 +8,18 @@
<string name="featured">FEATURED</string>
<string name="popular">POPULAR</string>
<string name="bookmarks">BOOKMARKS</string>
<string name="action_settings">Settings</string>
<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\u00A0</string>
<string name="password">Password\u00A0</string>
<string name="login">Login</string>
<string name="logout">Logout</string>
<string name="forgot_password">Forgot password?\u00A0</string>
<string name="login_username_or_password_incorrect">Username or password incorrect.</string>
<string name="logging_in">Logging into High Fidelity</string>
<string name="search_hint"><i>Search for a place by name</i>\u00A0</string>
<string name="search_loading">Loading places…</string>
<string name="search_no_results">No places exist with that name</string>
<string name="privacyPolicy">Privacy Policy</string>
<string name="your_last_location">Your Last Location</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

@ -88,9 +88,9 @@ def packages = [
checksum: qtChecksum,
],
bullet: [
file: 'bullet-2.83_armv8-libcpp.tgz',
versionId: 'L6MqTwt24yZAx0cHgLyN.GPky1kVGsq3',
checksum: '2c558d604fce337f5eba3eb7ec1252fd',
file: 'bullet-2.88_armv8-libcpp.tgz',
versionId: 'S8YaoED0Cl8sSb8fSV7Q2G1lQJSNDxqg',
checksum: '81642779ccb110f8c7338e8739ac38a0',
],
draco: [
file: 'draco_armv8-libcpp.tgz',
@ -103,9 +103,9 @@ def packages = [
checksum: 'a8ee8584cf1ccd34766c7ddd9d5e5449',
],
glm: [
file: 'glm-0.9.8.tgz',
versionId: 'CHPAk.Un_8MC4v8cIqkEJ468Y4_RQDmd',
checksum: 'd2b42cee31d2bc17bab6ce69e6b3f30a',
file: 'glm-0.9.8.5-patched.tgz',
versionId: 'cskfMoJrFlAeqI3WPxemyO_Cxt7rT9EJ',
checksum: '067b5fe16b220b5b1a1039ba51b062ae',
],
gvr: [
file: 'gvrsdk_v1.101.0.tgz',

View file

@ -33,6 +33,19 @@
#include "entities/EntityTreeHeadlessViewer.h"
#include "avatars/ScriptableAvatar.h"
/**jsdoc
* @namespace Agent
*
* @hifi-assignment-client
*
* @property {boolean} isAvatar
* @property {boolean} isPlayingAvatarSound <em>Read-only.</em>
* @property {boolean} isListeningToAudioStream
* @property {boolean} isNoiseGateEnabled
* @property {number} lastReceivedAudioLoudness <em>Read-only.</em>
* @property {Uuid} sessionUUID <em>Read-only.</em>
*/
class Agent : public ThreadedAssignment {
Q_OBJECT
@ -60,10 +73,28 @@ public:
virtual void aboutToFinish() override;
public slots:
/**jsdoc
* @function Agent.run
* @deprecated This function is being removed from the API.
*/
void run() override;
/**jsdoc
* @function Agent.playAvatarSound
* @param {object} avatarSound
*/
void playAvatarSound(SharedSoundPointer avatarSound);
/**jsdoc
* @function Agent.setIsAvatar
* @param {boolean} isAvatar
*/
void setIsAvatar(bool isAvatar);
/**jsdoc
* @function Agent.isAvatar
* @returns {boolean}
*/
bool isAvatar() const { return _isAvatar; }
private slots:

View file

@ -17,20 +17,144 @@
#include <AvatarData.h>
#include <ScriptEngine.h>
/**jsdoc
* The <code>Avatar</code> API is used to manipulate scriptable avatars on the domain. This API is a subset of the
* {@link MyAvatar} API.
*
* <p><strong>Note:</strong> In the examples, use "<code>Avatar</code>" instead of "<code>MyAvatar</code>".</p>
*
* @namespace Avatar
*
* @hifi-assignment-client
*
* @property {Vec3} position
* @property {number} scale
* @property {number} density <em>Read-only.</em>
* @property {Vec3} handPosition
* @property {number} bodyYaw - The rotation left or right about an axis running from the head to the feet of the avatar.
* Yaw is sometimes called "heading".
* @property {number} bodyPitch - The rotation about an axis running from shoulder to shoulder of the avatar. Pitch is
* sometimes called "elevation".
* @property {number} bodyRoll - The rotation about an axis running from the chest to the back of the avatar. Roll is
* sometimes called "bank".
* @property {Quat} orientation
* @property {Quat} headOrientation - The orientation of the avatar's head.
* @property {number} headPitch - The rotation about an axis running from ear to ear of the avatar's head. Pitch is
* sometimes called "elevation".
* @property {number} headYaw - The rotation left or right about an axis running from the base to the crown of the avatar's
* head. Yaw is sometimes called "heading".
* @property {number} headRoll - The rotation about an axis running from the nose to the back of the avatar's head. Roll is
* sometimes called "bank".
* @property {Vec3} velocity
* @property {Vec3} angularVelocity
* @property {number} audioLoudness
* @property {number} audioAverageLoudness
* @property {string} displayName
* @property {string} sessionDisplayName - Sanitized, defaulted version displayName that is defined by the AvatarMixer
* rather than by Interface clients. The result is unique among all avatars present at the time.
* @property {boolean} lookAtSnappingEnabled
* @property {string} skeletonModelURL
* @property {AttachmentData[]} attachmentData
* @property {string[]} jointNames - The list of joints in the current avatar model. <em>Read-only.</em>
* @property {Uuid} sessionUUID <em>Read-only.</em>
* @property {Mat4} sensorToWorldMatrix <em>Read-only.</em>
* @property {Mat4} controllerLeftHandMatrix <em>Read-only.</em>
* @property {Mat4} controllerRightHandMatrix <em>Read-only.</em>
* @property {number} sensorToWorldScale <em>Read-only.</em>
*
* @borrows MyAvatar.getDomainMinScale as getDomainMinScale
* @borrows MyAvatar.getDomainMaxScale as getDomainMaxScale
* @borrows MyAvatar.canMeasureEyeHeight as canMeasureEyeHeight
* @borrows MyAvatar.getEyeHeight as getEyeHeight
* @borrows MyAvatar.getHeight as getHeight
* @borrows MyAvatar.setHandState as setHandState
* @borrows MyAvatar.getHandState as getHandState
* @borrows MyAvatar.setRawJointData as setRawJointData
* @borrows MyAvatar.setJointData as setJointData
* @borrows MyAvatar.setJointRotation as setJointRotation
* @borrows MyAvatar.setJointTranslation as setJointTranslation
* @borrows MyAvatar.clearJointData as clearJointData
* @borrows MyAvatar.isJointDataValid as isJointDataValid
* @borrows MyAvatar.getJointRotation as getJointRotation
* @borrows MyAvatar.getJointTranslation as getJointTranslation
* @borrows MyAvatar.getJointRotations as getJointRotations
* @borrows MyAvatar.getJointTranslations as getJointTranslations
* @borrows MyAvatar.setJointRotations as setJointRotations
* @borrows MyAvatar.setJointTranslations as setJointTranslations
* @borrows MyAvatar.clearJointsData as clearJointsData
* @borrows MyAvatar.getJointIndex as getJointIndex
* @borrows MyAvatar.getJointNames as getJointNames
* @borrows MyAvatar.setBlendshape as setBlendshape
* @borrows MyAvatar.getAttachmentsVariant as getAttachmentsVariant
* @borrows MyAvatar.setAttachmentsVariant as setAttachmentsVariant
* @borrows MyAvatar.updateAvatarEntity as updateAvatarEntity
* @borrows MyAvatar.clearAvatarEntity as clearAvatarEntity
* @borrows MyAvatar.setForceFaceTrackerConnected as setForceFaceTrackerConnected
* @borrows MyAvatar.getAttachmentData as getAttachmentData
* @borrows MyAvatar.setAttachmentData as setAttachmentData
* @borrows MyAvatar.attach as attach
* @borrows MyAvatar.detachOne as detachOne
* @borrows MyAvatar.detachAll as detachAll
* @borrows MyAvatar.getAvatarEntityData as getAvatarEntityData
* @borrows MyAvatar.setAvatarEntityData as setAvatarEntityData
* @borrows MyAvatar.getSensorToWorldMatrix as getSensorToWorldMatrix
* @borrows MyAvatar.getSensorToWorldScale as getSensorToWorldScale
* @borrows MyAvatar.getControllerLeftHandMatrix as getControllerLeftHandMatrix
* @borrows MyAvatar.getControllerRightHandMatrix as getControllerRightHandMatrix
* @borrows MyAvatar.getDataRate as getDataRate
* @borrows MyAvatar.getUpdateRate as getUpdateRate
* @borrows MyAvatar.displayNameChanged as displayNameChanged
* @borrows MyAvatar.sessionDisplayNameChanged as sessionDisplayNameChanged
* @borrows MyAvatar.skeletonModelURLChanged as skeletonModelURLChanged
* @borrows MyAvatar.lookAtSnappingChanged as lookAtSnappingChanged
* @borrows MyAvatar.sessionUUIDChanged as sessionUUIDChanged
* @borrows MyAvatar.sendAvatarDataPacket as sendAvatarDataPacket
* @borrows MyAvatar.sendIdentityPacket as sendIdentityPacket
* @borrows MyAvatar.setJointMappingsFromNetworkReply as setJointMappingsFromNetworkReply
* @borrows MyAvatar.setSessionUUID as setSessionUUID
* @borrows MyAvatar.getAbsoluteJointRotationInObjectFrame as getAbsoluteJointRotationInObjectFrame
* @borrows MyAvatar.getAbsoluteJointTranslationInObjectFrame as getAbsoluteJointTranslationInObjectFrame
* @borrows MyAvatar.setAbsoluteJointRotationInObjectFrame as setAbsoluteJointRotationInObjectFrame
* @borrows MyAvatar.setAbsoluteJointTranslationInObjectFrame as setAbsoluteJointTranslationInObjectFrame
* @borrows MyAvatar.getTargetScale as getTargetScale
* @borrows MyAvatar.resetLastSent as resetLastSent
*/
class ScriptableAvatar : public AvatarData, public Dependency {
Q_OBJECT
public:
/**jsdoc
* @function Avatar.startAnimation
* @param {string} url
* @param {number} [fps=30]
* @param {number} [priority=1]
* @param {boolean} [loop=false]
* @param {boolean} [hold=false]
* @param {number} [firstFrame=0]
* @param {number} [lastFrame=3.403e+38]
* @param {string[]} [maskedJoints=[]]
*/
/// Allows scripts to run animations.
Q_INVOKABLE void startAnimation(const QString& url, float fps = 30.0f, float priority = 1.0f, bool loop = false,
bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX, const QStringList& maskedJoints = QStringList());
bool hold = false, float firstFrame = 0.0f, float lastFrame = FLT_MAX,
const QStringList& maskedJoints = QStringList());
/**jsdoc
* @function Avatar.stopAnimation
*/
Q_INVOKABLE void stopAnimation();
/**jsdoc
* @function Avatar.getAnimationDetails
* @returns {Avatar.AnimationDetails}
*/
Q_INVOKABLE AnimationDetails getAnimationDetails();
virtual void setSkeletonModelURL(const QUrl& skeletonModelURL) override;
virtual QByteArray toByteArrayStateful(AvatarDataDetail dataDetail, bool dropFaceTracking = false) override;
private slots:
void update(float deltatime);

View file

@ -44,6 +44,7 @@ EntityServer::EntityServer(ReceivedMessage& message) :
auto& packetReceiver = DependencyManager::get<NodeList>()->getPacketReceiver();
packetReceiver.registerListenerForTypes({ PacketType::EntityAdd,
PacketType::EntityClone,
PacketType::EntityEdit,
PacketType::EntityErase,
PacketType::EntityPhysics,

View file

@ -23,6 +23,13 @@
class EntitySimulation;
/**jsdoc
* @namespace EntityViewer
*
* @hifi-assignment-client
*/
// API functions are defined in OctreeHeadlessViewer.
// Generic client side Octree renderer class.
class EntityTreeHeadlessViewer : public OctreeHeadlessViewer {
Q_OBJECT

View file

@ -26,28 +26,101 @@ public:
static void trackIncomingOctreePacket(const QByteArray& packet, const SharedNodePointer& sendingNode, bool wasStatsPacket);
public slots:
/**jsdoc
* @function EntityViewer.queryOctree
*/
void queryOctree();
// setters for camera attributes
/**jsdoc
* @function EntityViewer.setPosition
* @param {Vec3} position
*/
void setPosition(const glm::vec3& position) { _hasViewFrustum = true; _viewFrustum.setPosition(position); }
/**jsdoc
* @function EntityViewer.setOrientation
* @param {Quat} orientation
*/
void setOrientation(const glm::quat& orientation) { _hasViewFrustum = true; _viewFrustum.setOrientation(orientation); }
/**jsdoc
* @function EntityViewer.setCenterRadius
* @param {number} radius
*/
void setCenterRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); }
/**jsdoc
* @function EntityViewer.setKeyholeRadius
* @param {number} radius
* @deprecated Use {@link EntityViewer.setCenterRadius|setCenterRadius} instead.
*/
void setKeyholeRadius(float radius) { _hasViewFrustum = true; _viewFrustum.setCenterRadius(radius); } // TODO: remove this legacy support
// setters for LOD and PPS
/**jsdoc
* @function EntityViewer.setVoxelSizeScale
* @param {number} sizeScale
*/
void setVoxelSizeScale(float sizeScale) { _octreeQuery.setOctreeSizeScale(sizeScale) ; }
/**jsdoc
* @function EntityViewer.setBoundaryLevelAdjust
* @param {number} boundaryLevelAdjust
*/
void setBoundaryLevelAdjust(int boundaryLevelAdjust) { _octreeQuery.setBoundaryLevelAdjust(boundaryLevelAdjust); }
/**jsdoc
* @function EntityViewer.setMaxPacketsPerSecond
* @param {number} maxPacketsPerSecond
*/
void setMaxPacketsPerSecond(int maxPacketsPerSecond) { _octreeQuery.setMaxQueryPacketsPerSecond(maxPacketsPerSecond); }
// getters for camera attributes
/**jsdoc
* @function EntityViewer.getPosition
* @returns {Vec3}
*/
const glm::vec3& getPosition() const { return _viewFrustum.getPosition(); }
/**jsdoc
* @function EntityViewer.getOrientation
* @returns {Quat}
*/
const glm::quat& getOrientation() const { return _viewFrustum.getOrientation(); }
// getters for LOD and PPS
/**jsdoc
* @function EntityViewer.getVoxelSizeScale
* @returns {number}
*/
float getVoxelSizeScale() const { return _octreeQuery.getOctreeSizeScale(); }
/**jsdoc
* @function EntityViewer.getBoundaryLevelAdjust
* @returns {number}
*/
int getBoundaryLevelAdjust() const { return _octreeQuery.getBoundaryLevelAdjust(); }
/**jsdoc
* @function EntityViewer.getMaxPacketsPerSecond
* @returns {number}
*/
int getMaxPacketsPerSecond() const { return _octreeQuery.getMaxQueryPacketsPerSecond(); }
/**jsdoc
* @function EntityViewer.getOctreeElementsCount
* @returns {number}
*/
unsigned getOctreeElementsCount() const { return _tree->getOctreeElementsCount(); }
private:

View file

@ -17,8 +17,8 @@ include(ExternalProject)
if (WIN32)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.83-ccd-and-cmake-fixes.tgz
URL_MD5 03051bf112dcc78ddd296f9cab38fd68
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.88.tgz
URL_MD5 0a6876607ebe83e227427215f15946fd
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_BULLET3=0 -DBUILD_OPENGL3_DEMOS=0 -DBUILD_BULLET2_DEMOS=0 -DBUILD_UNIT_TESTS=0 -DUSE_GLUT=0 -DUSE_DX11=0
LOG_DOWNLOAD 1
LOG_CONFIGURE 1
@ -28,8 +28,8 @@ if (WIN32)
else ()
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.83-ccd-and-cmake-fixes.tgz
URL_MD5 03051bf112dcc78ddd296f9cab38fd68
URL http://hifi-public.s3.amazonaws.com/dependencies/bullet-2.88.tgz
URL_MD5 0a6876607ebe83e227427215f15946fd
CMAKE_ARGS ${PLATFORM_CMAKE_ARGS} -DCMAKE_BUILD_TYPE=RelWithDebInfo -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> -DBUILD_EXTRAS=0 -DINSTALL_LIBS=1 -DBUILD_BULLET3=0 -DBUILD_OPENGL3_DEMOS=0 -DBUILD_BULLET2_DEMOS=0 -DBUILD_UNIT_TESTS=0 -DUSE_GLUT=0
LOG_DOWNLOAD 1
LOG_CONFIGURE 1

View file

@ -3,8 +3,8 @@ set(EXTERNAL_NAME glm)
include(ExternalProject)
ExternalProject_Add(
${EXTERNAL_NAME}
URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.zip
URL_MD5 579ac77a3110befa3244d68c0ceb7281
URL https://hifi-public.s3.amazonaws.com/dependencies/glm-0.9.8.5-patched.zip
URL_MD5 7d39ecc1cea275427534c3cfd6dd63f0
BINARY_DIR ${EXTERNAL_PROJECT_PREFIX}/build
CMAKE_ARGS -DCMAKE_INSTALL_PREFIX:PATH=<INSTALL_DIR> ${EXTERNAL_ARGS}
LOG_DOWNLOAD 1
@ -18,4 +18,4 @@ set_target_properties(${EXTERNAL_NAME} PROPERTIES FOLDER "hidden/externals")
ExternalProject_Get_Property(${EXTERNAL_NAME} INSTALL_DIR)
string(TOUPPER ${EXTERNAL_NAME} EXTERNAL_NAME_UPPER)
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glm include directories")
set(${EXTERNAL_NAME_UPPER}_INCLUDE_DIRS ${INSTALL_DIR}/include CACHE PATH "List of glm include directories")

View file

@ -4,8 +4,8 @@ set(EXTERNAL_NAME serverless-content)
ExternalProject_Add(
${EXTERNAL_NAME}
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC67-v4.zip
URL_MD5 ba32aed18bfeaac4ccaf5ebb8ea3e804
URL http://cdn.highfidelity.com/content-sets/serverless-tutorial-RC68.zip
URL_MD5 a068f74d4045e257cfa7926fe6e38ad5
CONFIGURE_COMMAND ""
BUILD_COMMAND ""
INSTALL_COMMAND ""

View file

@ -73,6 +73,8 @@ macro(SET_PACKAGING_PARAMETERS)
add_definitions(-DDEV_BUILD)
endif ()
string(TIMESTAMP BUILD_TIME "%d/%m/%Y")
if (DEPLOY_PACKAGE)
# for deployed packages always grab the serverless content
set(DOWNLOAD_SERVERLESS_CONTENT ON)

View file

@ -61,16 +61,21 @@ macro(SETUP_HIFI_TESTCASE)
endif()
endforeach()
# Find test classes to build into test executables.
# Warn about any .cpp files that are *not* test classes (*Test[s].cpp), since those files will not be used.
foreach (SRC_FILE ${TEST_PROJ_SRC_FILES})
string(REGEX MATCH ".+Tests?\\.cpp$" TEST_CPP_FILE ${SRC_FILE})
string(REGEX MATCH ".+\\.cpp$" NON_TEST_CPP_FILE ${SRC_FILE})
string(REGEX MATCH ".+\\.qrc$" QRC_FILE ${SRC_FILE})
if (TEST_CPP_FILE)
list(APPEND TEST_CASE_FILES ${TEST_CPP_FILE})
elseif (NON_TEST_CPP_FILE)
message(WARNING "ignoring .cpp file (not a test class -- this will not be linked or compiled!): " ${NON_TEST_CPP_FILE})
endif ()
if (QRC_FILE)
list(APPEND EXTRA_FILES ${QRC_FILE})
endif()
endforeach ()
if (TEST_CASE_FILES)
@ -88,7 +93,7 @@ macro(SETUP_HIFI_TESTCASE)
# grab the implemenation and header files
set(TARGET_SRCS ${TEST_FILE}) # only one source / .cpp file (the test class)
add_executable(${TARGET_NAME} ${TEST_FILE})
add_executable(${TARGET_NAME} ${TEST_FILE} ${EXTRA_FILES})
add_test(${TARGET_NAME}-test ${TARGET_NAME})
set_target_properties(${TARGET_NAME} PROPERTIES
EXCLUDE_FROM_DEFAULT_BUILD TRUE

View file

@ -26,4 +26,6 @@ namespace BuildInfo {
const QString VERSION = "@BUILD_VERSION@";
const QString BUILD_BRANCH = "@BUILD_BRANCH@";
const QString BUILD_GLOBAL_SERVICES = "@BUILD_GLOBAL_SERVICES@";
const QString BUILD_TIME = "@BUILD_TIME@";
}

View file

@ -479,7 +479,7 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
limitedNodeList->killNodeWithUUID(existingNodeID);
}
// add the connecting node (or re-use the matched one from eachNodeBreakable above)
// add the connecting node
SharedNodePointer newNode = addVerifiedNodeFromConnectRequest(nodeConnection);
// set the edit rights for this user
@ -508,26 +508,22 @@ SharedNodePointer DomainGatekeeper::processAgentConnectRequest(const NodeConnect
return newNode;
}
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
QUuid nodeID) {
SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection) {
HifiSockAddr discoveredSocket = nodeConnection.senderSockAddr;
SharedNetworkPeer connectedPeer = _icePeers.value(nodeConnection.connectUUID);
if (connectedPeer) {
// this user negotiated a connection with us via ICE, so re-use their ICE client ID
nodeID = nodeConnection.connectUUID;
if (connectedPeer->getActiveSocket()) {
// set their discovered socket to whatever the activated socket on the network peer object was
discoveredSocket = *connectedPeer->getActiveSocket();
}
} else {
// we got a connectUUID we didn't recognize, either use the hinted node ID or randomly generate a new one
if (nodeID.isNull()) {
nodeID = QUuid::createUuid();
}
if (connectedPeer && connectedPeer->getActiveSocket()) {
// set their discovered socket to whatever the activated socket on the network peer object was
discoveredSocket = *connectedPeer->getActiveSocket();
}
// create a new node ID for the verified connecting node
auto nodeID = QUuid::createUuid();
// add a mapping from connection node ID to ICE peer ID
// so that we can remove the ICE peer once we see this node connect
_nodeToICEPeerIDs.insert(nodeID, nodeConnection.connectUUID);
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
Node::LocalID newLocalID = findOrCreateLocalID(nodeID);
@ -541,6 +537,15 @@ SharedNodePointer DomainGatekeeper::addVerifiedNodeFromConnectRequest(const Node
return newNode;
}
void DomainGatekeeper::cleanupICEPeerForNode(const QUuid& nodeID) {
// remove this node ID from our node to ICE peer ID map
// and the associated ICE peer (if it still exists)
auto icePeerID = _nodeToICEPeerIDs.take(nodeID);
if (!icePeerID.isNull()) {
_icePeers.remove(icePeerID);
}
}
bool DomainGatekeeper::verifyUserSignature(const QString& username,
const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr) {

View file

@ -39,8 +39,8 @@ public:
void addPendingAssignedNode(const QUuid& nodeUUID, const QUuid& assignmentUUID,
const QUuid& walletUUID, const QString& nodeVersion);
QUuid assignmentUUIDForPendingAssignment(const QUuid& tempUUID);
void removeICEPeer(const QUuid& peerUUID) { _icePeers.remove(peerUUID); }
void cleanupICEPeerForNode(const QUuid& nodeID);
Node::LocalID findOrCreateLocalID(const QUuid& uuid);
@ -77,8 +77,7 @@ private:
SharedNodePointer processAgentConnectRequest(const NodeConnectionData& nodeConnection,
const QString& username,
const QByteArray& usernameSignature);
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection,
QUuid nodeID = QUuid());
SharedNodePointer addVerifiedNodeFromConnectRequest(const NodeConnectionData& nodeConnection);
bool verifyUserSignature(const QString& username, const QByteArray& usernameSignature,
const HifiSockAddr& senderSockAddr);
@ -101,6 +100,10 @@ private:
std::unordered_map<QUuid, PendingAssignedNodeData> _pendingAssignedNodes;
QHash<QUuid, SharedNetworkPeer> _icePeers;
using ConnectingNodeID = QUuid;
using ICEPeerID = QUuid;
QHash<ConnectingNodeID, ICEPeerID> _nodeToICEPeerIDs;
QHash<QString, QUuid> _connectionTokenHash;

View file

@ -1017,15 +1017,22 @@ void DomainServer::processListRequestPacket(QSharedPointer<ReceivedMessage> mess
sendingNode->setPublicSocket(nodeRequestData.publicSockAddr);
sendingNode->setLocalSocket(nodeRequestData.localSockAddr);
// update the NodeInterestSet in case there have been any changes
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(sendingNode->getLinkedData());
if (!nodeData->hasCheckedIn()) {
nodeData->setHasCheckedIn(true);
// on first check in, make sure we've cleaned up any ICE peer for this node
_gatekeeper.cleanupICEPeerForNode(sendingNode->getUUID());
}
// guard against patched agents asking to hear about other agents
auto safeInterestSet = nodeRequestData.interestList.toSet();
if (sendingNode->getType() == NodeType::Agent) {
safeInterestSet.remove(NodeType::Agent);
}
// update the NodeInterestSet in case there have been any changes
nodeData->setNodeInterestSet(safeInterestSet);
// update the connecting hostname in case it has changed
@ -2945,7 +2952,7 @@ void DomainServer::nodeAdded(SharedNodePointer node) {
void DomainServer::nodeKilled(SharedNodePointer node) {
// if this peer connected via ICE then remove them from our ICE peers hash
_gatekeeper.removeICEPeer(node->getUUID());
_gatekeeper.cleanupICEPeerForNode(node->getUUID());
DomainServerNodeData* nodeData = static_cast<DomainServerNodeData*>(node->getLinkedData());
@ -2978,6 +2985,8 @@ void DomainServer::nodeKilled(SharedNodePointer node) {
}
}
}
broadcastNodeDisconnect(node);
}
SharedAssignmentPointer DomainServer::dequeueMatchingAssignment(const QUuid& assignmentUUID, NodeType_t nodeType) {
@ -3163,18 +3172,23 @@ void DomainServer::handleKillNode(SharedNodePointer nodeToKill) {
const QUuid& nodeUUID = nodeToKill->getUUID();
limitedNodeList->killNodeWithUUID(nodeUUID);
}
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID);
void DomainServer::broadcastNodeDisconnect(const SharedNodePointer& disconnectedNode) {
auto limitedNodeList = DependencyManager::get<LimitedNodeList>();
static auto removedNodePacket = NLPacket::create(PacketType::DomainServerRemovedNode, NUM_BYTES_RFC4122_UUID, true);
removedNodePacket->reset();
removedNodePacket->write(nodeUUID.toRfc4122());
removedNodePacket->write(disconnectedNode->getUUID().toRfc4122());
// broadcast out the DomainServerRemovedNode message
limitedNodeList->eachMatchingNode([this, &nodeToKill](const SharedNodePointer& otherNode) -> bool {
limitedNodeList->eachMatchingNode([this, &disconnectedNode](const SharedNodePointer& otherNode) -> bool {
// only send the removed node packet to nodes that care about the type of node this was
return isInInterestSet(otherNode, nodeToKill);
return isInInterestSet(otherNode, disconnectedNode);
}, [&limitedNodeList](const SharedNodePointer& otherNode){
limitedNodeList->sendUnreliablePacket(*removedNodePacket, *otherNode);
auto removedNodePacketCopy = NLPacket::createCopy(*removedNodePacket);
limitedNodeList->sendPacket(std::move(removedNodePacketCopy), *otherNode);
});
}

View file

@ -165,6 +165,7 @@ private:
unsigned int countConnectedUsers();
void handleKillNode(SharedNodePointer nodeToKill);
void broadcastNodeDisconnect(const SharedNodePointer& disconnnectedNode);
void sendDomainListToNode(const SharedNodePointer& node, const HifiSockAddr& senderSockAddr);

View file

@ -67,8 +67,11 @@ public:
const QString& getPlaceName() { return _placeName; }
void setPlaceName(const QString& placeName) { _placeName = placeName; }
bool wasAssigned() const { return _wasAssigned; };
bool wasAssigned() const { return _wasAssigned; }
void setWasAssigned(bool wasAssigned) { _wasAssigned = wasAssigned; }
bool hasCheckedIn() const { return _hasCheckedIn; }
void setHasCheckedIn(bool hasCheckedIn) { _hasCheckedIn = hasCheckedIn; }
private:
QJsonObject overrideValuesIfNeeded(const QJsonObject& newStats);
@ -94,6 +97,8 @@ private:
QString _placeName;
bool _wasAssigned { false };
bool _hasCheckedIn { false };
};
#endif // hifi_DomainServerNodeData_h

View file

@ -7,6 +7,9 @@
//
// Sends messages over the EventBridge when text input is required.
//
/* global document, window, console, setTimeout, setInterval, EventBridge */
(function () {
var POLL_FREQUENCY = 500; // ms
var MAX_WARNINGS = 3;
@ -37,22 +40,24 @@
}
return false;
}
};
}
function shouldSetNumeric() {
return document.activeElement.type === "number";
};
}
function scheduleBringToView(timeout) {
var timer = setTimeout(function () {
clearTimeout(timer);
setTimeout(function () {
// If the element is not visible because the keyboard has been raised over the top of it, scroll it up into view.
// If the element is not visible because the keyboard raising has moved it off screen, scroll it down into view.
var elementRect = document.activeElement.getBoundingClientRect();
var absoluteElementTop = elementRect.top + window.scrollY;
var middle = absoluteElementTop - (window.innerHeight / 2);
window.scrollTo(0, middle);
var VISUAL_MARGIN = 3;
var delta = elementRect.y + elementRect.height + VISUAL_MARGIN - window.innerHeight;
if (delta > 0) {
window.scrollBy(0, delta);
} else if (elementRect.y < VISUAL_MARGIN) {
window.scrollBy(0, elementRect.y - VISUAL_MARGIN);
}
}, timeout);
}
@ -62,11 +67,13 @@
var passwordField = shouldSetPasswordField();
if (isWindowFocused &&
(keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard || passwordField !== window.isPasswordField)) {
(keyboardRaised !== window.isKeyboardRaised || numericKeyboard !== window.isNumericKeyboard
|| passwordField !== window.isPasswordField)) {
if (typeof EventBridge !== "undefined" && EventBridge !== null) {
EventBridge.emitWebEvent(
keyboardRaised ? ("_RAISE_KEYBOARD" + (numericKeyboard ? "_NUMERIC" : "") + (passwordField ? "_PASSWORD" : "")) : "_LOWER_KEYBOARD"
keyboardRaised ? ("_RAISE_KEYBOARD" + (numericKeyboard ? "_NUMERIC" : "")
+ (passwordField ? "_PASSWORD" : "")) : "_LOWER_KEYBOARD"
);
} else {
if (numWarnings < MAX_WARNINGS) {
@ -77,7 +84,7 @@
if (!window.isKeyboardRaised) {
scheduleBringToView(250); // Allow time for keyboard to be raised in QML.
// 2DO: should it be rather done from 'client area height changed' event?
// 2DO: should it be rather done from 'client area height changed' event?
}
window.isKeyboardRaised = keyboardRaised;

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6 KiB

View file

@ -281,10 +281,12 @@ Item {
text: " Pressure State: " + root.gpuTextureMemoryPressureState;
}
StatText {
text: " Resource Allocated / Populated / Pending: ";
property bool showIdeal: (root.gpuTextureResourceIdealMemory != root.gpuTextureResourceMemory);
text: " Resource Allocated " + (showIdeal ? "(Ideal)" : "") + " / Populated / Pending: ";
}
StatText {
text: " " + root.gpuTextureResourceMemory + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB";
property bool showIdeal: (root.gpuTextureResourceIdealMemory != root.gpuTextureResourceMemory);
text: " " + root.gpuTextureResourceMemory + (showIdeal ? ("(" + root.gpuTextureResourceIdealMemory + ")") : "") + " / " + root.gpuTextureResourcePopulatedMemory + " / " + root.texturePendingTransfers + " MB";
}
StatText {
text: " Resident Memory: " + root.gpuTextureResidentMemory + " MB";

View file

@ -27,6 +27,9 @@ Original.CheckBox {
property bool wrap: true;
readonly property int checkSize: Math.max(boxSize - 8, 10)
readonly property int checkRadius: 2
property string labelFontFamily: "Raleway"
property int labelFontSize: 14;
property int labelFontWeight: Font.DemiBold;
focusPolicy: Qt.ClickFocus
hoverEnabled: true
@ -105,6 +108,9 @@ Original.CheckBox {
contentItem: Label {
text: checkBox.text
color: checkBox.color
font.family: checkBox.labelFontFamily;
font.pixelSize: checkBox.labelFontSize;
font.weight: checkBox.labelFontWeight;
x: 2
verticalAlignment: Text.AlignVCenter
wrapMode: checkBox.wrap ? Text.Wrap : Text.NoWrap

View file

@ -24,6 +24,7 @@ Slider {
property alias minimumValue: slider.from
property alias maximumValue: slider.to
property alias step: slider.stepSize
property bool tickmarksEnabled: false
height: hifi.fontSizes.textFieldInput + 14 // Match height of TextField control.

View file

@ -163,10 +163,18 @@ TextField {
text: textField.label
colorScheme: textField.colorScheme
anchors.left: parent.left
anchors.right: parent.right
Binding on anchors.right {
when: parent.right
value: parent.right
}
Binding on wrapMode {
when: parent.right
value: Text.WordWrap
}
anchors.bottom: parent.top
anchors.bottomMargin: 3
wrapMode: Text.WordWrap
visible: label != ""
}
}

View file

@ -149,17 +149,17 @@ FocusScope {
}
function isModalWindow(window) {
return window.modality !== Qt.NonModal;
return window.modality !== (typeof Qt !== 'undefined' ? Qt.NonModal : 0);
}
function getTopLevelWindows(predicate) {
return findMatchingChildren(desktop, function(child) {
return (isTopLevelWindow(child) && (!predicate || predicate(child)));
return d.findMatchingChildren(desktop, function(child) {
return (d.isTopLevelWindow(child) && (!predicate || predicate(child)));
});
}
function getDesktopWindow(item) {
return findParentMatching(item, isTopLevelWindow)
return d.findParentMatching(item, d.isTopLevelWindow)
}
function fixupZOrder(windows, basis, topWindow) {
@ -205,23 +205,23 @@ FocusScope {
function raiseWindow(targetWindow) {
var predicate;
var zBasis;
if (isModalWindow(targetWindow)) {
predicate = isModalWindow;
if (d.isModalWindow(targetWindow)) {
predicate = d.isModalWindow;
zBasis = zLevels.modal
} else if (isAlwaysOnTopWindow(targetWindow)) {
} else if (d.isAlwaysOnTopWindow(targetWindow)) {
predicate = function(window) {
return (isAlwaysOnTopWindow(window) && !isModalWindow(window));
return (d.isAlwaysOnTopWindow(window) && !d.isModalWindow(window));
}
zBasis = zLevels.top
} else {
predicate = function(window) {
return (!isAlwaysOnTopWindow(window) && !isModalWindow(window));
return (!d.isAlwaysOnTopWindow(window) && !d.isModalWindow(window));
}
zBasis = zLevels.normal
}
var windows = getTopLevelWindows(predicate);
fixupZOrder(windows, zBasis, targetWindow);
var windows = d.getTopLevelWindows(predicate);
d.fixupZOrder(windows, zBasis, targetWindow);
}
Component.onCompleted: {
@ -264,7 +264,7 @@ FocusScope {
}
function getRepositionChildren(predicate) {
return findMatchingChildren(desktop, function(child) {
return d.findMatchingChildren(desktop, function(child) {
return (child.shouldReposition === true && (!predicate || predicate(child)));
});
}

View file

@ -57,7 +57,7 @@ ModalWindow {
property int iconSize: 40
property bool selectDirectory: false;
property bool showHidden: false;
property bool showHidden: true;
// FIXME implement
property bool multiSelect: false;
property bool saveDialog: false;
@ -324,8 +324,10 @@ ModalWindow {
showDirsFirst: true
showDotAndDotDot: false
showFiles: !root.selectDirectory
showHidden: root.showHidden
Component.onCompleted: {
showFiles = !root.selectDirectory
showHidden = root.showHidden
}
onFolderChanged: {

View file

@ -58,7 +58,7 @@ ModalWindow {
property int iconSize: 40
property bool selectDirectory: false;
property bool showHidden: false;
property bool showHidden: true;
// FIXME implement
property bool multiSelect: false;
property bool saveDialog: false;
@ -325,8 +325,10 @@ ModalWindow {
showDirsFirst: true
showDotAndDotDot: false
showFiles: !root.selectDirectory
showHidden: root.showHidden
Component.onCompleted: {
showFiles = !root.selectDirectory
showHidden = root.showHidden
}
onFolderChanged: {

View file

@ -26,7 +26,7 @@ ScrollingWindow {
property var showCategories: []
minSize: Qt.vector2d(400, 500)
//HifiConstants { id: hifi }
HifiConstants { id: hifi }
function saveAll() {
for (var i = 0; i < sections.length; ++i) {
@ -96,9 +96,9 @@ ScrollingWindow {
anchors {
top: parent.top
right: parent.right
rightMargin: 10//hifi.dimensions.contentMargin.x
rightMargin: hifi.dimensions.contentMargin.x
}
spacing: 1//hifi.dimensions.contentSpacing.x
spacing: hifi.dimensions.contentSpacing.x
HifiControls.Button {
text: "Save changes"

View file

@ -55,7 +55,7 @@ TabletModalWindow {
property int iconSize: 40
property bool selectDirectory: false;
property bool showHidden: false;
property bool showHidden: true;
// FIXME implement
property bool multiSelect: false;
property bool saveDialog: false;
@ -288,8 +288,10 @@ TabletModalWindow {
showDirsFirst: true
showDotAndDotDot: false
showFiles: !root.selectDirectory
showHidden: root.showHidden
Component.onCompleted: {
showFiles = !root.selectDirectory
showHidden = root.showHidden
}
onFolderChanged: {

View file

@ -0,0 +1,57 @@
//
// RadioButtonsPreference.qml
//
// Created by Cain Kilgore on 20th July 2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "../../controls-uit"
Preference {
id: root
height: control.height + hifi.dimensions.controlInterlineHeight
Component.onCompleted: {
repeater.itemAt(preference.value).checked = true
}
function save() {
var value = 0;
for (var i = 0; i < repeater.count; i++) {
if (repeater.itemAt(i).checked) {
value = i;
break;
}
}
preference.value = value;
preference.save();
}
Row {
id: control
anchors {
left: parent.left
right: parent.right
bottom: parent.bottom
}
spacing: 5
Repeater {
id: repeater
model: preference.items.length
delegate: RadioButton {
text: preference.items[index]
anchors {
verticalCenter: parent.verticalCenter
}
colorScheme: hifi.colorSchemes.dark
}
}
}
}

View file

@ -74,6 +74,7 @@ Preference {
property var comboBoxBuilder: Component { ComboBoxPreference { } }
property var spinnerSliderBuilder: Component { SpinnerSliderPreference { } }
property var primaryHandBuilder: Component { PrimaryHandPreference { } }
property var radioButtonsBuilder: Component { RadioButtonsPreference { } }
property var preferences: []
property int checkBoxCount: 0
@ -140,6 +141,10 @@ Preference {
checkBoxCount++;
builder = primaryHandBuilder;
break;
case Preference.RadioButtons:
checkBoxCount++;
builder = radioButtonsBuilder;
break;
};
if (builder) {

View file

@ -53,6 +53,9 @@ Preference {
Slider {
id: slider
value: preference.value
minimumValue: preference.min
maximumValue: preference.max
step: preference.step
width: 130
anchors {
right: parent.right

View file

@ -36,6 +36,29 @@ OriginalDesktop.Desktop {
}
}
onHeightChanged: {
if (height > 100) {
adjustToolbarPosition();
}
}
function adjustToolbarPosition() {
var toolbar = getToolbar("com.highfidelity.interface.toolbar.system")
// check if Y position was changed, if toolbar height is assigned etc
// default position is adjusted to border width
var toolbarY = toolbar.settings.y > 6 ?
toolbar.settings.y :
desktop.height - (toolbar.height > 0 ? toolbar.height : 50) - 56
if (toolbar.settings.desktopHeight > 100 && desktop.height !== toolbar.settings.desktopHeight) {
toolbarY += desktop.height - toolbar.settings.desktopHeight
}
toolbar.y = toolbarY
toolbar.settings.y = toolbarY
toolbar.settings.desktopHeight = desktop.height
}
Component { id: toolbarBuilder; Toolbar { } }
// This used to create sysToolbar dynamically with a call to getToolbar() within onCompleted.
// Beginning with QT 5.6, this stopped working, as anything added to toolbars too early got
@ -47,7 +70,6 @@ OriginalDesktop.Desktop {
anchors.horizontalCenter: settings.constrainToolbarToCenterX ? desktop.horizontalCenter : undefined;
// Literal 50 is overwritten by settings from previous session, and sysToolbar.x comes from settings when not constrained.
x: sysToolbar.x
y: 50
buttonModel: tablet.buttons;
shown: tablet.toolbarMode;
}

View file

@ -49,6 +49,7 @@ Item {
property string upgradeTitle;
property bool updateAvailable: root.upgradeUrl !== "" && !root.isShowingMyItems;
property bool isShowingMyItems;
property bool valid;
property string originalStatusText;
property string originalStatusColor;
@ -239,6 +240,7 @@ Item {
width: 62;
onLoaded: {
item.enabled = root.valid;
item.buttonGlyphText = hifi.glyphs.gift;
item.buttonText = "Gift";
item.buttonClicked = function() {
@ -463,7 +465,7 @@ Item {
Item {
id: statusContainer;
visible: root.purchaseStatus === "pending" || root.purchaseStatus === "invalidated" || root.numberSold > -1;
visible: root.purchaseStatus === "pending" || !root.valid || root.numberSold > -1;
anchors.left: itemName.left;
anchors.right: itemName.right;
anchors.top: itemName.bottom;
@ -480,7 +482,7 @@ Item {
text: {
if (root.purchaseStatus === "pending") {
"PENDING..."
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
"INVALIDATED"
} else if (root.numberSold > -1) {
("Sales: " + root.numberSold + "/" + (root.limitedRun === -1 ? "\u221e" : root.limitedRun))
@ -492,7 +494,7 @@ Item {
color: {
if (root.purchaseStatus === "pending") {
hifi.colors.blueAccent
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
hifi.colors.redAccent
} else {
hifi.colors.baseGray
@ -506,7 +508,7 @@ Item {
text: {
if (root.purchaseStatus === "pending") {
hifi.glyphs.question
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
hifi.glyphs.question
} else {
""
@ -523,7 +525,7 @@ Item {
color: {
if (root.purchaseStatus === "pending") {
hifi.colors.blueAccent
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
hifi.colors.redAccent
} else {
hifi.colors.baseGray
@ -538,7 +540,7 @@ Item {
onClicked: {
if (root.purchaseStatus === "pending") {
sendToPurchases({method: 'showPendingLightbox'});
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
sendToPurchases({method: 'showInvalidatedLightbox'});
}
}
@ -546,7 +548,7 @@ Item {
if (root.purchaseStatus === "pending") {
statusText.color = hifi.colors.blueHighlight;
statusIcon.color = hifi.colors.blueHighlight;
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
statusText.color = hifi.colors.redAccent;
statusIcon.color = hifi.colors.redAccent;
}
@ -555,7 +557,7 @@ Item {
if (root.purchaseStatus === "pending") {
statusText.color = hifi.colors.blueAccent;
statusIcon.color = hifi.colors.blueAccent;
} else if (root.purchaseStatus === "invalidated") {
} else if (!root.valid) {
statusText.color = hifi.colors.redHighlight;
statusIcon.color = hifi.colors.redHighlight;
}
@ -645,8 +647,8 @@ Item {
width: 160;
height: 40;
enabled: root.hasPermissionToRezThis &&
root.purchaseStatus !== "invalidated" &&
MyAvatar.skeletonModelURL !== root.itemHref;
MyAvatar.skeletonModelURL !== root.itemHref &&
root.valid;
onHoveredChanged: {
if (hovered) {

View file

@ -616,6 +616,7 @@ Rectangle {
upgradeTitle: model.upgrade_title;
itemType: model.itemType;
isShowingMyItems: root.isShowingMyItems;
valid: model.valid;
anchors.topMargin: 10;
anchors.bottomMargin: 10;
@ -995,10 +996,6 @@ Rectangle {
for (var i = 0; i < purchasesModel.count; i++) {
if (purchasesModel.get(i).title.toLowerCase().indexOf(filterBar.text.toLowerCase()) !== -1) {
if (!purchasesModel.get(i).valid) {
continue;
}
if (purchasesModel.get(i).status !== "confirmed" && !root.isShowingMyItems) {
tempPurchasesModel.insert(0, purchasesModel.get(i));
} else if ((root.isShowingMyItems && purchasesModel.get(i).edition_number === "0") ||
@ -1055,10 +1052,6 @@ Rectangle {
var currentId;
for (var i = 0; i < tempPurchasesModel.count; i++) {
currentId = tempPurchasesModel.get(i).id;
if (!purchasesModel.get(i).valid) {
continue;
}
filteredPurchasesModel.append(tempPurchasesModel.get(i));
filteredPurchasesModel.setProperty(i, 'cardBackVisible', false);
filteredPurchasesModel.setProperty(i, 'isInstalled', ((root.installedApps).indexOf(currentId) > -1));

View file

@ -0,0 +1,32 @@
//
// AssetDialog.qml
//
// Created by David Rowe on 18 Apr 2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.8
import "../../styles-uit"
import "../../windows"
ScrollingWindow {
id: root
resizable: false
implicitWidth: 480
implicitHeight: 706
objectName: "aboutDialog"
destroyOnCloseButton: true
destroyOnHidden: true
visible: true
minSize: Qt.vector2d(480, 706)
title: "About"
TabletAboutDialog {
width: pane.width
height: pane.height
}
}

View file

@ -7,7 +7,7 @@ PreferencesDialog {
id: root
objectName: "AvatarPreferencesDialog"
title: "Avatar Settings"
showCategories: [ "Avatar Basics", "Avatar Tuning", "Avatar Camera" ]
showCategories: [ "Avatar Basics", "Avatar Tuning" ]
property var settings: Settings {
category: root.objectName
property alias x: root.x

View file

@ -17,7 +17,7 @@ PreferencesDialog {
id: root
objectName: "GeneralPreferencesDialog"
title: "General Settings"
showCategories: ["UI", "Snapshots", "Privacy", "HMD", "Game Controller", "Sixense Controllers", "Perception Neuron", "Kinect", "Leap Motion"]
showCategories: ["User Interface", "HMD", "Snapshots", "Privacy"]
property var settings: Settings {
category: root.objectName
property alias x: root.x

View file

@ -0,0 +1,29 @@
//
// AdvancedPreferencesDialog.qml
//
// Created by Brad Hefta-Gaub on 20 Jan 2018
// Copyright 2018 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import Qt.labs.settings 1.0
import "../../dialogs"
PreferencesDialog {
id: root
objectName: "GraphicsPreferencesDialog"
title: "Graphics Settings"
showCategories: ["Graphics Quality" ]
property var settings: Settings {
category: root.objectName
property alias x: root.x
property alias y: root.y
property alias width: root.width
property alias height: root.height
}
}

View file

@ -79,7 +79,7 @@ ScrollingWindow {
interval: 1000
repeat: true
running: false
onTriggered: developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus");
onTriggered: developerMenuEnabled = MenuInterface.isOptionChecked("Developer Menu");
}
Component {
@ -98,7 +98,7 @@ ScrollingWindow {
Component.onCompleted: {
isHMD = HMD.active;
updateRunningScripts();
developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus");
developerMenuEnabled = MenuInterface.isOptionChecked("Developer Menu");
checkMenu.restart();
}

View file

@ -0,0 +1,122 @@
//
// TabletAssetDialog.qml
//
// Created by David Rowe on 18 Apr 2017
// Copyright 2017 High Fidelity, Inc.
//
// Distributed under the Apache License, Version 2.0.
// See the accompanying file LICENSE or http://www.apache.org/licenses/LICENSE-2.0.html
//
import QtQuick 2.5
import "../../styles-uit"
Rectangle {
width: 480
height: 706
color: "#404040"
Column {
x: 45
y: 30
spacing: 5
Image {
sourceSize.width: 295
sourceSize.height: 75
source: "../../../images/about-highfidelity.png"
}
Item { height: 30; width: 1 }
Column {
id: buildColumm
anchors.left: parent.left
anchors.leftMargin: 70
RalewayRegular {
text: "Build " + HiFiAbout.buildVersion
size: 16
color: "white"
}
RalewayRegular {
text: "Released " + HiFiAbout.buildDate
size: 16
color: "white"
}
}
Item { height: 10; width: 1 }
RalewayRegular {
text: "An open-source virtual reality platform."
size: 20
color: "white"
}
RalewayRegular {
textFormat: Text.StyledText
linkColor: "#00B4EF"
color: "white"
text: "<a href=\"https:/www.highfidelity.com\">www.highfidelity.com</a>."
size: 20
onLinkActivated: {
HiFiAbout.openUrl("https:/www.highfidelity.com");
}
}
Item { height: 40; width: 1 }
Row {
spacing: 5
Image {
sourceSize.width: 34
sourceSize.height: 25
source: "../../../images/about-qt.png"
MouseArea {
anchors.fill: parent
onClicked: {
HiFiAbout.openUrl("https://www.qt.io/");
}
}
}
RalewayRegular {
color: "white"
text: "Built using Qt " + HiFiAbout.qtVersion
size: 12
anchors.verticalCenter: parent.verticalCenter
}
Item { height: 1; width: 15 }
Image {
sourceSize.width: 70
sourceSize.height: 26
source: "../../../images/about-physics.png"
}
RalewayRegular {
color: "white"
text: "Physics powered by Bullet"
size: 12
anchors.verticalCenter: parent.verticalCenter
}
}
Item { height: 20; width: 1 }
RalewayRegular {
textFormat: Text.StyledText
linkColor: "#00B4EF"
color: "white"
text: "Blockchain technology from <a href=\"https://elementsproject.org/elements/\">Elements</a>."
size: 14
onLinkActivated: {
HiFiAbout.openUrl("https://elementsproject.org/elements/");
}
}
RalewayRegular {
color: "white"
text: "© 2018 High Fidelity. All rights reserved."
size: 14
}
RalewayRegular {
textFormat: Text.StyledText
color: "white"
linkColor: "#00B4EF"
text: "Distributed under the <a href=\"http://www.apache.org/licenses/LICENSE-2.0.html\">Apache License, Version 2.0.</a>."
size: 14
onLinkActivated: {
HiFiAbout.openUrl("http://www.apache.org/licenses/LICENSE-2.0.html");
}
}
}
}

View file

@ -65,7 +65,7 @@ Rectangle {
interval: 1000
repeat: true
running: false
onTriggered: developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus");
onTriggered: developerMenuEnabled = MenuInterface.isOptionChecked("Developer Menu");
}
Component {
@ -84,7 +84,7 @@ Rectangle {
Component.onCompleted: {
isHMD = HMD.active;
updateRunningScripts();
developerMenuEnabled = MenuInterface.isMenuEnabled("Developer Menus");
developerMenuEnabled = MenuInterface.isOptionChecked("Developer Menu");
checkMenu.restart();
}

View file

@ -18,7 +18,7 @@ import "../../controls-uit" as HifiControls
Rectangle {
id: info
anchors.fill: parent
signal canceled()
signal restart()

View file

@ -8,222 +8,286 @@
import QtQuick 2.7
import QtQuick.Controls 2.2
import QtQuick.Layouts 1.3
import QtGraphicalEffects 1.0
import Qt.labs.settings 1.0
import "../../styles-uit"
import "../../controls"
import "../../controls-uit" as HifiControls
import "../../dialogs"
import "../../dialogs/preferences"
import "tabletWindows"
import "../audio"
StackView {
id: stack
initialItem: inputConfiguration
property alias messageVisible: imageMessageBox.visible
property alias selectedPlugin: box.currentText
Rectangle {
id: inputConfiguration
anchors.fill: parent
Item {
id: controllerSettings
height: parent.height
width: parent.width
HifiConstants { id: hifi }
HifiConstants { id: hifi }
color: hifi.colors.baseGray
TabBar {
id: bar
spacing: 0
anchors.top: controllerSettings.top
width: parent.width
height: 40
z: 10
property var pluginSettings: null
HifiControls.ImageMessageBox {
id: imageMessageBox
anchors.fill: parent
z: 2000
imageWidth: 442
imageHeight: 670
source: "../../../images/calibration-help.png"
}
Rectangle {
width: inputConfiguration.width
height: 1
color: hifi.colors.baseGrayShadow
x: -hifi.dimensions.contentMargin.x
}
RalewayRegular {
id: header
text: "Controller Settings"
size: 22
color: "white"
anchors.top: inputConfiguration.top
anchors.left: inputConfiguration.left
anchors.leftMargin: 20
anchors.topMargin: 20
}
Separator {
id: headerSeparator
width: inputConfiguration.width
anchors.top: header.bottom
anchors.topMargin: 10
}
HiFiGlyphs {
id: sourceGlyph
text: hifi.glyphs.source
size: 36
color: hifi.colors.blueHighlight
anchors.top: headerSeparator.bottom
anchors.left: inputConfiguration.left
anchors.leftMargin: 40
anchors.topMargin: 20
}
RalewayRegular {
id: configuration
text: "SELECT DEVICE"
size: 15
color: hifi.colors.lightGrayText
anchors.top: headerSeparator.bottom
anchors.left: sourceGlyph.right
anchors.leftMargin: 10
anchors.topMargin: 30
}
Row {
id: configRow
z: 999
anchors.top: sourceGlyph.bottom
anchors.topMargin: 20
anchors.left: sourceGlyph.left
anchors.leftMargin: 40
spacing: 10
HifiControls.ComboBox {
id: box
width: 160
z: 999
editable: true
colorScheme: hifi.colorSchemes.dark
model: inputPlugins()
label: ""
onCurrentIndexChanged: {
changeSource();
}
}
HifiControls.CheckBox {
id: checkBox
colorScheme: hifi.colorSchemes.dark
text: "show all input devices"
onClicked: {
inputPlugins();
changeSource();
}
TabButton {
height: parent.height
text: qsTr("Settings")
onClicked: {
stackView.clear();
stackView.push(controllerPreferencesComponent);
}
}
Separator {
id: configurationSeparator
z: 0
width: inputConfiguration.width
anchors.top: configRow.bottom
anchors.topMargin: 10
TabButton {
height: parent.height
text: qsTr("Calibration")
onClicked: {
stackView.clear();
stackView.push(inputConfigurationComponent);
}
}
}
HiFiGlyphs {
id: sliderGlyph
text: hifi.glyphs.sliders
size: 36
color: hifi.colors.blueHighlight
StackView {
id: stackView
anchors.top: bar.bottom
anchors.bottom: controllerSettings.bottom
anchors.left: controllerSettings.left
anchors.right: controllerSettings.right
anchors.top: configurationSeparator.bottom
anchors.left: inputConfiguration.left
anchors.leftMargin: 40
anchors.topMargin: 20
}
initialItem: controllerPreferencesComponent
}
RalewayRegular {
id: configurationHeader
text: "CONFIGURATION"
size: 15
color: hifi.colors.lightGrayText
Component {
id: inputConfigurationComponent
StackView {
id: stack
initialItem: inputConfiguration
property alias messageVisible: imageMessageBox.visible
property alias selectedPlugin: box.currentText
Rectangle {
id: inputConfiguration
anchors.fill: parent
HifiConstants { id: hifi }
color: hifi.colors.baseGray
property var pluginSettings: null
HifiControls.ImageMessageBox {
id: imageMessageBox
anchors.fill: parent
z: 2000
imageWidth: 442
imageHeight: 670
source: "../../../images/calibration-help.png"
}
Rectangle {
width: inputConfiguration.width
height: 1
color: hifi.colors.baseGrayShadow
x: -hifi.dimensions.contentMargin.x
}
RalewayRegular {
id: header
text: "Control Settings"
size: 22
color: "white"
anchors.top: inputConfiguration.top
anchors.left: inputConfiguration.left
anchors.leftMargin: 20
anchors.topMargin: 20
}
Separator {
id: headerSeparator
width: inputConfiguration.width
anchors.top: header.bottom
anchors.topMargin: 10
}
HiFiGlyphs {
id: sourceGlyph
text: hifi.glyphs.source
size: 36
color: hifi.colors.blueHighlight
anchors.top: headerSeparator.bottom
anchors.left: inputConfiguration.left
anchors.leftMargin: 40
anchors.topMargin: 20
}
RalewayRegular {
id: configuration
text: "SELECT DEVICE"
size: 15
color: hifi.colors.lightGrayText
anchors.top: configurationSeparator.bottom
anchors.left: sliderGlyph.right
anchors.leftMargin: 10
anchors.topMargin: 30
}
anchors.top: headerSeparator.bottom
anchors.left: sourceGlyph.right
anchors.leftMargin: 10
anchors.topMargin: 30
}
Loader {
id: loader
asynchronous: false
Row {
id: configRow
z: 999
anchors.top: sourceGlyph.bottom
anchors.topMargin: 20
anchors.left: sourceGlyph.left
anchors.leftMargin: 40
spacing: 10
HifiControls.ComboBox {
id: box
width: 160
z: 999
editable: true
colorScheme: hifi.colorSchemes.dark
model: inputPlugins()
label: ""
width: inputConfiguration.width
anchors.left: inputConfiguration.left
anchors.right: inputConfiguration.right
anchors.top: configurationHeader.bottom
anchors.topMargin: 10
anchors.bottom: inputConfiguration.bottom
onCurrentIndexChanged: {
changeSource();
}
}
source: InputConfiguration.configurationLayout(box.currentText);
onLoaded: {
if (loader.item.hasOwnProperty("pluginName")) {
if (box.currentText === "Vive") {
loader.item.pluginName = "OpenVR";
} else {
loader.item.pluginName = box.currentText;
HifiControls.CheckBox {
id: checkBox
colorScheme: hifi.colorSchemes.dark
text: "show all input devices"
onClicked: {
inputPlugins();
changeSource();
}
}
}
if (loader.item.hasOwnProperty("displayInformation")) {
loader.item.displayConfiguration();
Separator {
id: configurationSeparator
z: 0
width: inputConfiguration.width
anchors.top: configRow.bottom
anchors.topMargin: 10
}
HiFiGlyphs {
id: sliderGlyph
text: hifi.glyphs.sliders
size: 36
color: hifi.colors.blueHighlight
anchors.top: configurationSeparator.bottom
anchors.left: inputConfiguration.left
anchors.leftMargin: 40
anchors.topMargin: 20
}
RalewayRegular {
id: configurationHeader
text: "CONFIGURATION"
size: 15
color: hifi.colors.lightGrayText
anchors.top: configurationSeparator.bottom
anchors.left: sliderGlyph.right
anchors.leftMargin: 10
anchors.topMargin: 30
}
Loader {
id: loader
asynchronous: false
width: inputConfiguration.width
anchors.left: inputConfiguration.left
anchors.right: inputConfiguration.right
anchors.top: configurationHeader.bottom
anchors.topMargin: 10
anchors.bottom: inputConfiguration.bottom
source: InputConfiguration.configurationLayout(box.currentText);
onLoaded: {
if (loader.item.hasOwnProperty("pluginName")) {
if (box.currentText === "HTC Vive") {
loader.item.pluginName = "OpenVR";
} else {
loader.item.pluginName = box.currentText;
}
}
if (loader.item.hasOwnProperty("displayInformation")) {
loader.item.displayConfiguration();
}
}
}
}
function inputPlugins() {
if (checkBox.checked) {
return InputConfiguration.inputPlugins();
} else {
return InputConfiguration.activeInputPlugins();
}
}
function initialize() {
changeSource();
}
function changeSource() {
loader.source = "";
var source = "";
if (box.currentText == "Vive") {
source = InputConfiguration.configurationLayout("OpenVR");
} else {
source = InputConfiguration.configurationLayout(box.currentText);
}
loader.source = source;
if (source === "") {
box.label = "(not configurable)";
} else {
box.label = "";
}
}
Timer {
id: timer
repeat: false
interval: 300
onTriggered: initialize()
}
Component.onCompleted: {
timer.start();
}
}
}
function inputPlugins() {
if (checkBox.checked) {
return InputConfiguration.inputPlugins();
} else {
return InputConfiguration.activeInputPlugins();
Component {
id: controllerPreferencesComponent
TabletPreferencesDialog {
anchors.fill: stackView
id: controllerPrefereneces
objectName: "TabletControllerPreferences"
showCategories: ["VR Movement", "Game Controller", "Sixense Controllers", "Perception Neuron", "Leap Motion"]
}
}
function initialize() {
changeSource();
}
function changeSource() {
loader.source = "";
var source = "";
if (box.currentText == "Vive") {
source = InputConfiguration.configurationLayout("OpenVR");
} else {
source = InputConfiguration.configurationLayout(box.currentText);
}
loader.source = source;
if (source === "") {
box.label = "(not configurable)";
} else {
box.label = "";
}
}
Timer {
id: timer
repeat: false
interval: 300
onTriggered: initialize()
}
Component.onCompleted: {
timer.start();
}
}

View file

@ -32,6 +32,6 @@ StackView {
TabletPreferencesDialog {
id: root
objectName: "TabletGeneralPreferences"
showCategories: ["UI", "Snapshots", "Scripts", "Privacy", "Octree", "HMD", "Game Controller", "Sixense Controllers", "Perception Neuron", "Kinect", "Leap Motion"]
showCategories: ["User Interface", "HMD", "Snapshots", "Privacy"]
}
}

View file

@ -32,6 +32,6 @@ StackView {
TabletPreferencesDialog {
id: root
objectName: "TabletGraphicsPreferences"
showCategories: ["Graphics"]
showCategories: ["Graphics Quality"]
}
}

Some files were not shown because too many files have changed in this diff Show more