Merge pull request #12935 from gcalero/android_new_login

Android new login
This commit is contained in:
Sam Gondelman 2018-05-17 15:53:25 -07:00 committed by GitHub
commit 74840d2130
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
53 changed files with 1428 additions and 667 deletions

View file

@ -110,6 +110,10 @@ dependencies {
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,17 +39,10 @@
</activity>
-->
<activity
android:name=".HomeActivity"
android:label="@string/home"
android:name=".MainActivity"
android:screenOrientation="portrait"
android:theme="@style/Theme.AppCompat.Translucent.NoActionBar">
</activity>
<activity
android:name=".GotoActivity"
android:label="@string/go_to"
android:theme="@style/AppTheme" />
<activity android:name=".LoginActivity"
android:theme="@style/AppTheme"/>
<activity
android:configChanges="orientation|uiMode|screenLayout|screenSize|smallestScreenSize|locale|fontScale|keyboard|keyboardHidden|navigation"
android:name=".InterfaceActivity"

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

@ -24,9 +24,9 @@
#include <udt/PacketHeaders.h>
QAndroidJniObject __interfaceActivity;
QAndroidJniObject __loginActivity;
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();
@ -152,9 +152,15 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnCrea
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);
__interfaceActivity.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);
});
}
@ -178,10 +184,6 @@ JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnResu
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeOnExitVr(JNIEnv* env, jobject obj) {
}
JNIEXPORT void Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeGoBackFromAndroidActivity(JNIEnv *env, jobject instance) {
AndroidHelper::instance().goBackFromAndroidActivity();
}
// HifiUtils
JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_getCurrentAddress(JNIEnv *env, jobject instance) {
QSharedPointer<AddressManager> addressManager = DependencyManager::get<AddressManager>();
@ -209,8 +211,9 @@ JNIEXPORT jstring JNICALL Java_io_highfidelity_hifiinterface_HifiUtils_protocolV
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_LoginActivity_nativeLogin(JNIEnv *env, jobject instance,
jstring username_, jstring password_) {
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);
@ -220,19 +223,26 @@ Java_io_highfidelity_hifiinterface_LoginActivity_nativeLogin(JNIEnv *env, jobjec
auto accountManager = AndroidHelper::instance().getAccountManager();
__loginActivity = QAndroidJniObject(instance);
__loginCompletedListener = QAndroidJniObject(instance);
__usernameChangedListener = QAndroidJniObject(usernameChangedListener);
QObject::connect(accountManager.data(), &AccountManager::loginComplete, [](const QUrl& authURL) {
jboolean jSuccess = (jboolean) true;
__loginActivity.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
});
QObject::connect(accountManager.data(), &AccountManager::loginFailed, []() {
jboolean jSuccess = (jboolean) false;
__loginActivity.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
__loginCompletedListener.callMethod<void>("handleLoginCompleted", "(Z)V", jSuccess);
});
QMetaObject::invokeMethod(accountManager.data(), "requestAccessToken", Q_ARG(const QString&, username), Q_ARG(const QString&, password));
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
@ -252,20 +262,30 @@ Java_io_highfidelity_hifiinterface_SplashActivity_registerLoadCompleteListener(J
}
JNIEXPORT jboolean JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
Java_io_highfidelity_hifiinterface_MainActivity_nativeIsLoggedIn(JNIEnv *env, jobject instance) {
return AndroidHelper::instance().getAccountManager()->isLoggedIn();
}
JNIEXPORT void JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeLogout(JNIEnv *env, jobject instance) {
Java_io_highfidelity_hifiinterface_MainActivity_nativeLogout(JNIEnv *env, jobject instance) {
AndroidHelper::instance().getAccountManager()->logout();
}
JNIEXPORT jstring JNICALL
Java_io_highfidelity_hifiinterface_HomeActivity_nativeGetDisplayName(JNIEnv *env,
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,82 +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.widget.EditText;
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()) {
urlString = HifiUtils.getInstance().sanitizeHifiUrl(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

@ -9,7 +9,7 @@ import java.net.URISyntaxException;
public class HifiUtils {
public static final String BASE_URL = "https://metaverse.highfidelity.com";
public static final String METAVERSE_BASE_URL = "https://metaverse.highfidelity.com";
private static HifiUtils instance;
@ -39,7 +39,12 @@ public class HifiUtils {
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;
@ -49,7 +54,7 @@ public class HifiUtils {
return urlString;
}
if (uri.getScheme() == null || uri.getScheme().isEmpty()) {
urlString = BASE_URL + urlString;
urlString = baseUrl + urlString;
}
}
return urlString;

View file

@ -1,245 +0,0 @@
package io.highfidelity.hifiinterface;
import android.content.Intent;
import android.os.Bundle;
import android.os.Handler;
import android.support.annotation.NonNull;
import android.support.design.widget.NavigationView;
import android.support.v4.content.ContextCompat;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBar;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.GridLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.support.v7.widget.Toolbar;
import android.text.Editable;
import android.text.TextWatcher;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.Window;
import android.view.WindowManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.TextView;
import org.qtproject.qt5.android.bindings.QtActivity;
import io.highfidelity.hifiinterface.view.DomainAdapter;
public class HomeActivity extends AppCompatActivity implements NavigationView.OnNavigationItemSelectedListener {
public native boolean nativeIsLoggedIn();
public native void nativeLogout();
public native String nativeGetDisplayName();
/**
* Set this intent extra param to NOT start a new InterfaceActivity after a domain is selected"
*/
//public static final String PARAM_NOT_START_INTERFACE_ACTIVITY = "not_start_interface_activity";
public static final int ENTER_DOMAIN_URL = 1;
private DomainAdapter mDomainAdapter;
private DrawerLayout mDrawerLayout;
private NavigationView mNavigationView;
private RecyclerView mDomainsView;
private TextView searchNoResultsView;
private ImageView mSearchIconView;
private ImageView mClearSearch;
private EditText mSearchView;
@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);
searchNoResultsView = findViewById(R.id.searchNoResultsView);
mDomainsView = findViewById(R.id.rvDomains);
int numberOfColumns = 1;
GridLayoutManager gridLayoutMgr = new GridLayoutManager(this, numberOfColumns);
mDomainsView.setLayoutManager(gridLayoutMgr);
mDomainAdapter = new DomainAdapter(this, HifiUtils.getInstance().protocolVersionSignature());
mDomainAdapter.setClickListener(new DomainAdapter.ItemClickListener() {
@Override
public void onItemClick(View view, int position, DomainAdapter.Domain domain) {
new Handler(getMainLooper()).postDelayed(new Runnable() {
@Override
public void run() {
gotoDomain(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 = findViewById(R.id.searchView);
mSearchIconView = findViewById(R.id.search_mag_icon);
mClearSearch = findViewById(R.id.search_clear);
mSearchView.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void onTextChanged(CharSequence charSequence, int i, int i1, int i2) {}
@Override
public void afterTextChanged(Editable editable) {
mDomainAdapter.loadDomains(editable.toString());
if (editable.length() > 0) {
mSearchIconView.setVisibility(View.GONE);
mClearSearch.setVisibility(View.VISIBLE);
} else {
mSearchIconView.setVisibility(View.VISIBLE);
mClearSearch.setVisibility(View.GONE);
}
}
});
mSearchView.setOnKeyListener((view, i, keyEvent) -> {
if (i == KeyEvent.KEYCODE_ENTER) {
String urlString = mSearchView.getText().toString();
if (!urlString.trim().isEmpty()) {
urlString = HifiUtils.getInstance().sanitizeHifiUrl(urlString);
}
gotoDomain(urlString);
return true;
}
return false;
});
getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_ALWAYS_HIDDEN);
updateLoginMenu();
Window window = getWindow();
window.clearFlags(WindowManager.LayoutParams.FLAG_TRANSLUCENT_STATUS);
window.addFlags(WindowManager.LayoutParams.FLAG_DRAWS_SYSTEM_BAR_BACKGROUNDS);
window.setStatusBarColor(ContextCompat.getColor(this, R.color.statusbar_color));
}
private void updateLoginMenu() {
TextView loginOption = findViewById(R.id.login);
TextView logoutOption = findViewById(R.id.logout);
if (nativeIsLoggedIn()) {
loginOption.setVisibility(View.GONE);
logoutOption.setVisibility(View.VISIBLE);
} else {
loginOption.setVisibility(View.VISIBLE);
logoutOption.setVisibility(View.GONE);
}
}
private void gotoDomain(String domainUrl) {
Intent intent = new Intent(HomeActivity.this, InterfaceActivity.class);
intent.putExtra(InterfaceActivity.DOMAIN_URL, domainUrl);
HomeActivity.this.finish();
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP);
startActivity(intent);
}
@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() {
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;
}
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
protected void onStart() {
super.onStart();
updateLoginMenu();
}
public void onLoginClicked(View view) {
Intent intent = new Intent(this, LoginActivity.class);
startActivity(intent);
}
public void onLogoutClicked(View view) {
nativeLogout();
updateLoginMenu();
}
@Override
public void onBackPressed() {
finishAffinity();
}
public void onSearchClear(View view) {
mSearchView.setText("");
}
}

View file

@ -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,16 +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 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;
@ -89,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);
@ -102,17 +102,13 @@ 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
@ -195,13 +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);
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;
}
@ -216,8 +215,14 @@ public class InterfaceActivity extends QtActivity {
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

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

View file

@ -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

@ -4,6 +4,7 @@ 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 {
@ -21,13 +22,22 @@ public class SplashActivity extends Activity {
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, HomeActivity.class)); // + 2 sec
startActivity(new Intent(this, MainActivity.class));
SplashActivity.this.finish();
}
}

View file

@ -0,0 +1,156 @@
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 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());
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,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

@ -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

@ -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,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

@ -22,8 +22,10 @@
android:elevation="4dp"
/>
<include layout="@layout/content_home" />
<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 -->
@ -32,9 +34,10 @@
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_home"
app:menu="@menu/menu_navigation"
>
<LinearLayout
android:layout_width="match_parent"
@ -43,20 +46,24 @@
android:clickable="true"
android:orientation="vertical"
android:padding="@dimen/activity_horizontal_margin">
<TextView
android:id="@+id/login"
android:text="@string/login"
android:onClick="onLoginClicked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/menuOption" />
<TextView
android:id="@+id/logout"
android:text="@string/logout"
android:onClick="onLogoutClicked"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/menuOption" />
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>

View file

@ -5,8 +5,11 @@
android:id="@+id/root_activity_splash"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLight">
<!-- TODO -->
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

@ -7,7 +7,7 @@
android:layout_height="match_parent"
app:layout_behavior="@string/appbar_scrolling_view_behavior"
android:background="@color/colorPrimary"
tools:context="io.highfidelity.hifiinterface.HomeActivity"
tools:context="io.highfidelity.hifiinterface.MainActivity"
tools:showIn="@layout/activity_home">
<EditText
@ -48,7 +48,6 @@
android:layout_marginEnd="@dimen/searchEditClearMarginEnd"
android:visibility="gone"
android:src="@drawable/ic_clear"
android:onClick="onSearchClear"
/>
<TextView

View file

@ -4,19 +4,18 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/backgroundLight"
tools:context="io.highfidelity.hifiinterface.LoginActivity">
android:background="@color/backgroundLight">
<ImageView
android:id="@+id/imageView3"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:srcCompat="@drawable/hifi_header"
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_constraintBottom_toTopOf="@id/username"
android:layout_marginBottom="75dp"
/>
app:layout_constraintTop_toTopOf="parent"
app:srcCompat="@drawable/hifi_header" />
<TextView
android:id="@+id/error"
@ -33,71 +32,79 @@
<EditText
android:id="@+id/username"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_height="35dp"
android:layout_marginLeft="46dp"
android:layout_marginRight="46dp"
android:background="@drawable/rounded_edit"
android:padding="9dp"
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"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toBottomOf="parent"
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="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:layout_marginRight="@dimen/activity_horizontal_margin"
android:layout_height="35dp"
android:layout_marginLeft="46dp"
android:layout_marginRight="46dp"
android:background="@drawable/rounded_edit"
android:padding="9dp"
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"
android:layout_marginTop="14dp"
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="wrap_content"
android:layout_height="wrap_content"
android:layout_width="154dp"
android:layout_height="38dp"
android:layout_marginTop="16dp"
android:background="@drawable/rounded_button"
android:textColor="@color/white_opaque"
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"
android:paddingRight="30dp"
android:paddingLeft="30dp"
android:paddingTop="4dp"
android:paddingBottom="4dp"
android:layout_marginTop="16dp"
android:onClick="login"/>
app:layout_goneMarginTop="4dp"/>
<TextView
android:id="@+id/textView"
android:id="@+id/forgotPassword"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginLeft="@dimen/activity_horizontal_margin"
android:fontFamily="@font/raleway_semibold"
android:textSize="14dp"
android:text="@string/forgot_password"
android:textStyle="italic"
android:padding="10dp"
app:layout_constraintLeft_toLeftOf="parent"
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"/>

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

@ -33,4 +33,8 @@
<dimen name="domainMarginBottom">6dp</dimen>
<dimen name="domainNameHeight">64dp</dimen>
</resources>
<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,18 +8,17 @@
<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</string>
<string name="password">Password</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?</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>
</resources>

View file

@ -37,15 +37,26 @@ void AndroidHelper::init() {
_accountManager->moveToThread(&workerThread);
}
void AndroidHelper::requestActivity(const QString &activityName) {
emit androidActivityRequested(activityName);
void AndroidHelper::requestActivity(const QString &activityName, const bool backToScene) {
emit androidActivityRequested(activityName, backToScene);
}
void AndroidHelper::notifyLoadComplete() {
emit qtAppLoadComplete();
}
void AndroidHelper::goBackFromAndroidActivity() {
emit backFromAndroidActivity();
void AndroidHelper::notifyEnterForeground() {
emit enterForeground();
}
void AndroidHelper::notifyEnterBackground() {
emit enterBackground();
}
void AndroidHelper::performHapticFeedback(int duration) {
emit hapticFeedbackRequested(duration);
}
void AndroidHelper::showLoginDialog() {
emit androidActivityRequested("Login", true);
}

View file

@ -24,17 +24,27 @@ public:
return instance;
}
void init();
void requestActivity(const QString &activityName);
void requestActivity(const QString &activityName, const bool backToScene);
void notifyLoadComplete();
void goBackFromAndroidActivity();
QSharedPointer<AccountManager> getAccountManager() { return _accountManager; }
void notifyEnterForeground();
void notifyEnterBackground();
void performHapticFeedback(int duration);
QSharedPointer<AccountManager> getAccountManager() { return _accountManager; }
AndroidHelper(AndroidHelper const&) = delete;
void operator=(AndroidHelper const&) = delete;
public slots:
void showLoginDialog();
signals:
void androidActivityRequested(const QString &activityName);
void backFromAndroidActivity();
void androidActivityRequested(const QString &activityName, const bool backToScene);
void qtAppLoadComplete();
void enterForeground();
void enterBackground();
void hapticFeedbackRequested(int duration);
private:
AndroidHelper();
@ -43,4 +53,4 @@ private:
QThread workerThread;
};
#endif
#endif

View file

@ -1238,7 +1238,13 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
nodeList.data(), SLOT(reset()));
auto dialogsManager = DependencyManager::get<DialogsManager>();
#if defined(Q_OS_ANDROID)
connect(accountManager.data(), &AccountManager::authRequired, this, []() {
AndroidHelper::instance().showLoginDialog();
});
#else
connect(accountManager.data(), &AccountManager::authRequired, dialogsManager.data(), &DialogsManager::showLoginDialog);
#endif
connect(accountManager.data(), &AccountManager::usernameChanged, this, &Application::updateWindowTitle);
connect(accountManager.data(), &AccountManager::usernameChanged, [](QString username){
setCrashAnnotation("username", username.toStdString());
@ -2259,6 +2265,8 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
#if defined(Q_OS_ANDROID)
AndroidHelper::instance().init();
connect(&AndroidHelper::instance(), &AndroidHelper::enterBackground, this, &Application::enterBackground);
connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
AndroidHelper::instance().notifyLoadComplete();
#endif
}
@ -2858,6 +2866,13 @@ void Application::initializeUi() {
}
if (TouchscreenVirtualPadDevice::NAME == inputPlugin->getName()) {
_touchscreenVirtualPadDevice = std::dynamic_pointer_cast<TouchscreenVirtualPadDevice>(inputPlugin);
#if defined(Q_OS_ANDROID)
auto& virtualPadManager = VirtualPad::Manager::instance();
connect(&virtualPadManager, &VirtualPad::Manager::hapticFeedbackRequested,
this, [](int duration) {
AndroidHelper::instance().performHapticFeedback(duration);
});
#endif
}
}
@ -3874,7 +3889,7 @@ void Application::keyReleaseEvent(QKeyEvent* event) {
#if defined(Q_OS_ANDROID)
if (event->key() == Qt::Key_Back) {
event->accept();
openAndroidActivity("Home");
AndroidHelper::instance().requestActivity("Home", false);
}
#endif
_controllerScriptingInterface->emitKeyReleaseEvent(event); // send events to any registered scripts
@ -6591,8 +6606,6 @@ void Application::registerScriptEngineWithApplicationServices(ScriptEnginePointe
scriptEngine->registerGlobalObject("Wallet", DependencyManager::get<WalletScriptingInterface>().data());
scriptEngine->registerGlobalObject("AddressManager", DependencyManager::get<AddressManager>().data());
scriptEngine->registerGlobalObject("App", this);
qScriptRegisterMetaType(scriptEngine.data(), OverlayIDtoScriptValue, OverlayIDfromScriptValue);
DependencyManager::get<PickScriptingInterface>()->registerMetaTypes(scriptEngine.data());
@ -8269,12 +8282,6 @@ void Application::saveNextPhysicsStats(QString filename) {
_physicsEngine->saveNextPhysicsStats(filename);
}
void Application::openAndroidActivity(const QString& activityName) {
#if defined(Q_OS_ANDROID)
AndroidHelper::instance().requestActivity(activityName);
#endif
}
#if defined(Q_OS_ANDROID)
void Application::enterBackground() {
QMetaObject::invokeMethod(DependencyManager::get<AudioClient>().data(),
@ -8293,5 +8300,4 @@ void Application::enterForeground() {
}
#endif
#include "Application_jni.cpp"
#include "Application.moc"

View file

@ -415,10 +415,7 @@ public slots:
void setIsServerlessMode(bool serverlessDomain);
void loadServerlessDomain(QUrl domainURL);
Q_INVOKABLE bool askBeforeSetAvatarUrl(const QString& avatarUrl) { return askToSetAvatarUrl(avatarUrl); }
void updateVerboseLogging();
Q_INVOKABLE void openAndroidActivity(const QString& activityName);
private slots:
void onDesktopRootItemCreated(QQuickItem* qmlContext);

View file

@ -1,24 +0,0 @@
#if defined(Q_OS_ANDROID)
#include <QtAndroidExtras/QAndroidJniObject>
#include "AndroidHelper.h"
extern "C" {
JNIEXPORT void
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterBackground(JNIEnv *env, jobject obj) {
if (qApp) {
qApp->enterBackground();
}
}
JNIEXPORT void
Java_io_highfidelity_hifiinterface_InterfaceActivity_nativeEnterForeground(JNIEnv *env, jobject obj) {
if (qApp) {
qApp->enterForeground();
}
}
}
#endif

View file

@ -15,12 +15,13 @@
#include <QtCore/QDir>
#include <QMessageBox>
#include <QScriptValue>
#include <QtGui/QDesktopServices>
#include <shared/QtHelpers.h>
#include <SettingHandle.h>
#include <display-plugins/CompositorHelper.h>
#include <AddressManager.h>
#include "AndroidHelper.h"
#include "Application.h"
#include "DomainHandler.h"
#include "MainWindow.h"
@ -131,6 +132,24 @@ void WindowScriptingInterface::disconnectedFromDomain() {
emit domainChanged(QUrl());
}
void WindowScriptingInterface::openUrl(const QUrl& url) {
if (!url.isEmpty()) {
if (url.scheme() == URL_SCHEME_HIFI) {
DependencyManager::get<AddressManager>()->handleLookupString(url.toString());
} else {
// address manager did not handle - ask QDesktopServices to handle
QDesktopServices::openUrl(url);
}
}
}
void WindowScriptingInterface::openAndroidActivity(const QString& activityName, const bool backToScene) {
#if defined(Q_OS_ANDROID)
AndroidHelper::instance().requestActivity(activityName, backToScene);
#endif
}
QString fixupPathForMac(const QString& directory) {
// On OS X `directory` does not work as expected unless a file is included in the path, so we append a bogus
// filename if the directory is valid.

View file

@ -520,6 +520,20 @@ public slots:
*/
int openMessageBox(QString title, QString text, int buttons, int defaultButton);
/**jsdoc
* Open the given resource in the Interface window or in a web browser depending on the url scheme
* @function Window.openUrl
* @param {string} url - The resource to open
*/
void openUrl(const QUrl& url);
/**jsdoc
* (Android only) Open the requested Activity and optionally back to the scene when the activity is done
* @function Window.openAndroidActivity
* @param {string} activityName - The name of the activity to open. One of "Home", "Login" or "Privacy Policy"
*/
void openAndroidActivity(const QString& activityName, const bool backToScene);
/**jsdoc
* Update the content of a message box that was opened with {@link Window.openMessageBox|openMessageBox}.
* @function Window.updateMessageBox

View file

@ -31,10 +31,13 @@ HIFI_QML_DEF(LoginDialog)
LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) {
auto accountManager = DependencyManager::get<AccountManager>();
#if !defined(Q_OS_ANDROID)
connect(accountManager.data(), &AccountManager::loginComplete,
this, &LoginDialog::handleLoginCompleted);
connect(accountManager.data(), &AccountManager::loginFailed,
this, &LoginDialog::handleLoginFailed);
#endif
}
void LoginDialog::showWithSelection()

View file

@ -187,6 +187,13 @@ void TouchscreenVirtualPadDevice::InputDevice::update(float deltaTime, const con
_axisStateMap.clear();
}
bool TouchscreenVirtualPadDevice::InputDevice::triggerHapticPulse(float strength, float duration, controller::Hand hand) {
auto& virtualPadManager = VirtualPad::Manager::instance();
virtualPadManager.requestHapticFeedback((int) duration);
return true;
}
void TouchscreenVirtualPadDevice::InputDevice::focusOutEvent() {
}

View file

@ -63,6 +63,8 @@ protected:
// Device functions
virtual controller::Input::NamedVector getAvailableInputs() const override;
virtual QString getDefaultMappingConfig() const override;
virtual bool triggerHapticPulse(float strength, float duration, controller::Hand hand) override;
virtual void update(float deltaTime, const controller::InputCalibrationData& inputCalibrationData) override;
virtual void focusOutEvent() override;

View file

@ -84,6 +84,10 @@ namespace VirtualPad {
_jumpButtonPosition = point;
}
void Manager::requestHapticFeedback(int duration) {
emit hapticFeedbackRequested(duration);
}
Instance* Manager::getLeftVirtualPad() {
return &_leftVPadInstance;
}

View file

@ -31,8 +31,8 @@ namespace VirtualPad {
};
class Manager : public QObject, public Dependency {
Q_OBJECT
SINGLETON_DEPENDENCY
Manager();
Manager(const Manager& other) = delete;
public:
@ -46,6 +46,7 @@ namespace VirtualPad {
void setExtraBottomMargin(int margin);
glm::vec2 getJumpButtonPosition();
void setJumpButtonPosition(glm::vec2 point);
void requestHapticFeedback(int duration);
static const float DPI;
static const float BASE_DIAMETER_PIXELS;
@ -56,6 +57,9 @@ namespace VirtualPad {
static const float JUMP_BTN_BOTTOM_MARGIN_PIXELS;
static const float JUMP_BTN_RIGHT_MARGIN_PIXELS;
signals:
void hapticFeedbackRequested(int duration);
private:
Instance _leftVPadInstance;
bool _enabled {true};

View file

@ -33,18 +33,24 @@ function init() {
hoverBgOpacity: 0.0,
activeBgOpacity: 0.0
});
backButton.clicked.connect(onBackPressed);
backButton.entered.connect(onBackPressed);
backButton.clicked.connect(onBackClicked);
}
function onBackPressed() {
App.openAndroidActivity("Home");
Controller.triggerHapticPulseOnDevice(Controller.findDevice("TouchscreenVirtualPad"), 0.1, 40.0, 0);
}
function onBackClicked() {
Window.openAndroidActivity("Home", false);
}
Script.scriptEnding.connect(function() {
if(backButton) {
backButton.clicked.disconnect(onBackPressed);
backButton.entered.disconnect(onBackPressed);
backButton.clicked.disconnect(onBackClicked);
}
});

View file

@ -15,7 +15,7 @@
var audiobar;
var audioButton;
var logEnabled = true;
var logEnabled = false;
function printd(str) {
if (logEnabled)
@ -40,15 +40,18 @@ function init() {
onMuteToggled();
audioButton.clicked.connect(onMuteClicked);
audioButton.entered.connect(onMutePressed);
Audio.mutedChanged.connect(onMuteToggled);
}
function onMuteClicked() {
printd("On Mute Clicked");
//Menu.setIsOptionChecked("Mute Microphone", !Menu.isOptionChecked("Mute Microphone"));
Audio.muted = !Audio.muted;
}
function onMutePressed() {
Controller.triggerHapticPulseOnDevice(Controller.findDevice("TouchscreenVirtualPad"), 0.1, 40.0, 0);
}
function onMuteToggled() {
printd("On Mute Toggled");
audioButton.isActive = Audio.muted; // Menu.isOptionChecked("Mute Microphone")
@ -58,6 +61,7 @@ function onMuteToggled() {
Script.scriptEnding.connect(function () {
if(audioButton) {
audioButton.clicked.disconnect(onMuteClicked);
audioButton.entered.disconnect(onMutePressed);
Audio.mutedChanged.connect(onMuteToggled);
}
});

View file

@ -58,14 +58,22 @@ function init() {
});
switchToMode(getCurrentModeSetting());
modeButton.clicked.connect(function() {
switchToMode(nextMode[currentMode]);
});
modeButton.entered.connect(modeButtonPressed);
modeButton.clicked.connect(modeButtonClicked);
}
function shutdown() {
modeButton.entered.disconnect(modeButtonPressed);
modeButton.clicked.disconnect(modeButtonClicked);
}
function modeButtonPressed() {
Controller.triggerHapticPulseOnDevice(Controller.findDevice("TouchscreenVirtualPad"), 0.1, 40.0, 0);
}
function modeButtonClicked() {
switchToMode(nextMode[currentMode]);
}
function saveCurrentModeSetting(mode) {

View file

@ -466,7 +466,7 @@ CreatePalette = function (side, leftInputs, rightInputs, uiCommandCallback) {
if (handJointIndex === NONE) {
// Don't display if joint isn't available (yet) to attach to.
// User can clear this condition by toggling the app off and back on once avatar finishes loading.
App.log(side, "ERROR: CreatePalette: Hand joint index isn't available!");
console.log(side, "ERROR: CreatePalette: Hand joint index isn't available!");
return;
}

View file

@ -180,11 +180,11 @@ Groups = function () {
}
if (entitiesSelectedCount === 0) {
App.log("ERROR: Groups: Nothing to ungroup!");
console.log("ERROR: Groups: Nothing to ungroup!");
return;
}
if (entitiesSelectedCount === 1) {
App.log("ERROR: Groups: Cannot ungroup sole entity!");
console.log("ERROR: Groups: Cannot ungroup sole entity!");
return;
}

View file

@ -54,7 +54,7 @@ Preload = (function () {
findURLsInObject(items[i]);
break;
default:
App.log("ERROR: Cannot find URL in item type " + (typeof items[i]));
console.log("ERROR: Cannot find URL in item type " + (typeof items[i]));
}
}
@ -120,7 +120,7 @@ Preload = (function () {
deleteTimer = Script.setTimeout(deleteOverlay, DELETE_INTERVAL);
}
} else {
App.log("ERROR: Cannot preload asset " + url);
console.log("ERROR: Cannot preload asset " + url);
}
}

View file

@ -654,7 +654,7 @@ SelectionManager = function (side) {
try {
userData = JSON.parse(userDataString);
} catch (e) {
App.log(side, "ERROR: Invalid userData in entity being updated! " + userDataString);
console.log(side, "ERROR: Invalid userData in entity being updated! " + userDataString);
}
}

View file

@ -108,7 +108,7 @@ ToolIcon = function (side) {
if (handJointIndex === -1) {
// Don't display if joint isn't available (yet) to attach to.
// User can clear this condition by toggling the app off and back on once avatar finishes loading.
App.log(side, "ERROR: ToolIcon: Hand joint index isn't available!");
console.log(side, "ERROR: ToolIcon: Hand joint index isn't available!");
return;
}

View file

@ -2929,7 +2929,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
break;
default:
App.log(side, "ERROR: ToolsMenu: Unexpected command! " + command);
console.log(side, "ERROR: ToolsMenu: Unexpected command! " + command);
}
};
@ -2946,7 +2946,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
Settings.setValue(optionsSettings[parameter].key, null); // Delete settings value.
break;
default:
App.log(side, "ERROR: ToolsMenu: Unexpected command! " + command);
console.log(side, "ERROR: ToolsMenu: Unexpected command! " + command);
}
}
@ -3199,7 +3199,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
// Nothing to do.
break;
default:
App.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + hoveredElementType);
console.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + hoveredElementType);
}
// Update status variables.
@ -3317,7 +3317,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
// Nothing to do.
break;
default:
App.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + hoveredElementType);
console.log(side, "ERROR: ToolsMenu: Unexpected hover item! " + hoveredElementType);
}
}
@ -3566,7 +3566,7 @@ ToolsMenu = function (side, leftInputs, rightInputs, uiCommandCallback) {
if (handJointIndex === NONE) {
// Don't display if joint isn't available (yet) to attach to.
// User can clear this condition by toggling the app off and back on once avatar finishes loading.
App.log(side, "ERROR: ToolsMenu: Hand joint index isn't available!");
console.log(side, "ERROR: ToolsMenu: Hand joint index isn't available!");
return;
}

View file

@ -1932,7 +1932,7 @@
tablet = Tablet.getTablet("com.highfidelity.interface.tablet.system");
if (!tablet) {
App.log("ERROR: Tablet not found! App not started.");
console.log("ERROR: Tablet not found! App not started.");
return;
}