diff --git a/interface/resources/images/eyeClosed.svg b/interface/resources/images/eyeClosed.svg
new file mode 100644
index 0000000000..6719471b3d
--- /dev/null
+++ b/interface/resources/images/eyeClosed.svg
@@ -0,0 +1,5 @@
+
diff --git a/interface/resources/images/eyeOpen.svg b/interface/resources/images/eyeOpen.svg
new file mode 100644
index 0000000000..ec5ceb5238
--- /dev/null
+++ b/interface/resources/images/eyeOpen.svg
@@ -0,0 +1,4 @@
+
diff --git a/interface/resources/qml/LoginDialog.qml b/interface/resources/qml/LoginDialog.qml
index e21e8b7354..336858502d 100644
--- a/interface/resources/qml/LoginDialog.qml
+++ b/interface/resources/qml/LoginDialog.qml
@@ -23,6 +23,7 @@ ModalWindow {
objectName: "LoginDialog"
implicitWidth: 520
implicitHeight: 320
+ closeButtonVisible: true
destroyOnCloseButton: true
destroyOnHidden: true
visible: true
diff --git a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml
index bf7807c85d..96b638c911 100644
--- a/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml
+++ b/interface/resources/qml/LoginDialog/+android/LinkAccountBody.qml
@@ -117,27 +117,27 @@ Item {
}
spacing: hifi.dimensions.contentSpacing.y / 2
- TextField {
- id: usernameField
- anchors {
- horizontalCenter: parent.horizontalCenter
- }
- width: 1080
- placeholderText: qsTr("Username or Email")
+ TextField {
+ id: usernameField
+ anchors {
+ horizontalCenter: parent.horizontalCenter
}
+ width: 1080
+ placeholderText: qsTr("Username or Email")
+ }
- TextField {
- id: passwordField
- anchors {
- horizontalCenter: parent.horizontalCenter
- }
- width: 1080
-
- placeholderText: qsTr("Password")
- echoMode: TextInput.Password
-
- Keys.onReturnPressed: linkAccountBody.login()
+ TextField {
+ id: passwordField
+ anchors {
+ horizontalCenter: parent.horizontalCenter
}
+ width: 1080
+
+ placeholderText: qsTr("Password")
+ echoMode: TextInput.Password
+
+ Keys.onReturnPressed: linkAccountBody.login()
+ }
}
InfoItem {
@@ -176,7 +176,7 @@ Item {
anchors {
left: parent.left
top: form.bottom
- topMargin: hifi.dimensions.contentSpacing.y / 2
+ topMargin: hifi.dimensions.contentSpacing.y / 2
}
spacing: hifi.dimensions.contentSpacing.x
@@ -201,7 +201,7 @@ Item {
anchors {
right: parent.right
top: form.bottom
- topMargin: hifi.dimensions.contentSpacing.y / 2
+ topMargin: hifi.dimensions.contentSpacing.y / 2
}
spacing: hifi.dimensions.contentSpacing.x
onHeightChanged: d.resize(); onWidthChanged: d.resize();
diff --git a/interface/resources/qml/LoginDialog/LinkAccountBody.qml b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
index 4c6e5f6fce..4196b7f168 100644
--- a/interface/resources/qml/LoginDialog/LinkAccountBody.qml
+++ b/interface/resources/qml/LoginDialog/LinkAccountBody.qml
@@ -15,7 +15,6 @@ import QtQuick.Controls.Styles 1.4 as OriginalStyles
import "../controls-uit"
import "../styles-uit"
-
Item {
id: linkAccountBody
clip: true
@@ -87,6 +86,23 @@ Item {
height: 48
}
+ ShortcutText {
+ id: flavorText
+ anchors {
+ top: parent.top
+ left: parent.left
+ margins: 0
+ topMargin: hifi.dimensions.contentSpacing.y
+ }
+
+ text: qsTr("Sign in to High Fidelity to make friends, get HFC, and buy interesting things on the Marketplace!")
+ width: parent.width
+ wrapMode: Text.WordWrap
+ lineHeight: 1
+ lineHeightMode: Text.ProportionalHeight
+ horizontalAlignment: Text.AlignHCenter
+ }
+
ShortcutText {
id: mainTextContainer
anchors {
@@ -97,7 +113,6 @@ Item {
}
visible: false
-
text: qsTr("Username or password incorrect.")
wrapMode: Text.WordWrap
color: hifi.colors.redAccent
@@ -117,22 +132,21 @@ Item {
}
spacing: 2 * hifi.dimensions.contentSpacing.y
-
TextField {
id: usernameField
text: Settings.getValue("wallet/savedUsername", "");
width: parent.width
focus: true
- label: "Username or Email"
+ placeholderText: "Username or Email"
activeFocusOnPress: true
ShortcutText {
z: 10
+ y: usernameField.height
anchors {
- left: usernameField.left
- top: usernameField.top
- leftMargin: usernameField.textFieldLabel.contentWidth + 10
- topMargin: -19
+ right: usernameField.right
+ top: usernameField.bottom
+ topMargin: 4
}
text: "Forgot Username?"
@@ -143,26 +157,32 @@ Item {
onLinkActivated: loginDialog.openUrl(link)
}
+
onFocusChanged: {
root.text = "";
}
+ Component.onCompleted: {
+ var savedUsername = Settings.getValue("wallet/savedUsername", "");
+ usernameField.text = savedUsername === "Unknown user" ? "" : savedUsername;
+ }
}
TextField {
id: passwordField
width: parent.width
-
- label: "Password"
- echoMode: showPassword.checked ? TextInput.Normal : TextInput.Password
+ placeholderText: "Password"
activeFocusOnPress: true
+ echoMode: TextInput.Password
+ onHeightChanged: d.resize(); onWidthChanged: d.resize();
ShortcutText {
+ id: forgotPasswordShortcut
+ y: passwordField.height
z: 10
anchors {
- left: passwordField.left
- top: passwordField.top
- leftMargin: passwordField.textFieldLabel.contentWidth + 10
- topMargin: -19
+ right: passwordField.right
+ top: passwordField.bottom
+ topMargin: 4
}
text: "Forgot Password?"
@@ -179,12 +199,45 @@ Item {
root.isPassword = true;
}
- Keys.onReturnPressed: linkAccountBody.login()
- }
+ Rectangle {
+ id: showPasswordHitbox
+ z: 10
+ x: passwordField.width - ((passwordField.height) * 31 / 23)
+ width: parent.width - (parent.width - (parent.height * 31/16))
+ height: parent.height
+ anchors {
+ right: parent.right
+ }
+ color: "transparent"
- CheckBox {
- id: showPassword
- text: "Show password"
+ Image {
+ id: showPasswordImage
+ y: (passwordField.height - (passwordField.height * 16 / 23)) / 2
+ width: passwordField.width - (passwordField.width - (((passwordField.height) * 31/23)))
+ height: passwordField.height * 16 / 23
+ anchors {
+ right: parent.right
+ rightMargin: 3
+ }
+ source: "../../images/eyeOpen.svg"
+ }
+
+ MouseArea {
+ id: passwordFieldMouseArea
+ anchors.fill: parent
+ acceptedButtons: Qt.LeftButton
+ property bool showPassword: false
+ onClicked: {
+ showPassword = !showPassword;
+ passwordField.echoMode = showPassword ? TextInput.Normal : TextInput.Password;
+ showPasswordImage.source = showPassword ? "../../images/eyeClosed.svg" : "../../images/eyeOpen.svg";
+ showPasswordImage.height = showPassword ? passwordField.height : passwordField.height * 16 / 23;
+ showPasswordImage.y = showPassword ? 0 : (passwordField.height - showPasswordImage.height) / 2;
+ }
+ }
+ }
+
+ Keys.onReturnPressed: linkAccountBody.login()
}
InfoItem {
@@ -206,6 +259,26 @@ Item {
onHeightChanged: d.resize(); onWidthChanged: d.resize();
anchors.horizontalCenter: parent.horizontalCenter
+ CheckBox {
+ id: autoLogoutCheckbox
+ checked: !Settings.getValue("wallet/autoLogout", true)
+ text: "Keep me signed in"
+ boxSize: 20;
+ labelFontSize: 15
+ color: hifi.colors.black
+ onCheckedChanged: {
+ Settings.setValue("wallet/autoLogout", !checked);
+ if (checked) {
+ Settings.setValue("wallet/savedUsername", Account.username);
+ } else {
+ Settings.setValue("wallet/savedUsername", "");
+ }
+ }
+ Component.onDestruction: {
+ Settings.setValue("wallet/autoLogout", !checked);
+ }
+ }
+
Button {
id: linkAccountButton
anchors.verticalCenter: parent.verticalCenter
@@ -216,12 +289,6 @@ Item {
onClicked: linkAccountBody.login()
}
-
- Button {
- anchors.verticalCenter: parent.verticalCenter
- text: qsTr("Cancel")
- onClicked: root.tryDestroy()
- }
}
Row {
@@ -234,7 +301,7 @@ Item {
RalewaySemiBold {
size: hifi.fontSizes.inputLabel
anchors.verticalCenter: parent.verticalCenter
- text: qsTr("Don't have an account?")
+ text: qsTr("New to High Fidelity?")
}
Button {
@@ -287,11 +354,23 @@ Item {
bodyLoader.item.width = root.pane.width
bodyLoader.item.height = root.pane.height
}
+ if (Settings.getValue("loginDialogPoppedUp", false)) {
+ var data = {
+ "action": "user logged in"
+ };
+ UserActivityLogger.logAction("encourageLoginDialog", data);
+ }
}
onHandleLoginFailed: {
console.log("Login Failed")
mainTextContainer.visible = true
toggleLoading(false)
+ if (Settings.getValue("loginDialogPoppedUp", false)) {
+ var data = {
+ "action": "user failed logging in"
+ };
+ UserActivityLogger.logAction("encourageLoginDialog", data);
+ }
}
onHandleLinkCompleted: {
console.log("Link Succeeded")
diff --git a/interface/resources/qml/windows/ModalFrame.qml b/interface/resources/qml/windows/ModalFrame.qml
index 211353b5f3..cb23ccd5ad 100644
--- a/interface/resources/qml/windows/ModalFrame.qml
+++ b/interface/resources/qml/windows/ModalFrame.qml
@@ -94,5 +94,25 @@ Frame {
color: hifi.colors.lightGray
}
}
+
+
+ GlyphButton {
+ id: closeButton
+ visible: window.closeButtonVisible
+ width: 30
+ y: -hifi.dimensions.modalDialogTitleHeight
+ anchors {
+ top: parent.top
+ right: parent.right
+ topMargin: 10
+ rightMargin: 10
+ }
+ glyph: hifi.glyphs.close
+ size: 23
+ onClicked: {
+ window.clickedCloseButton = true;
+ window.destroy();
+ }
+ }
}
}
diff --git a/interface/resources/qml/windows/ModalWindow.qml b/interface/resources/qml/windows/ModalWindow.qml
index 2d56099051..ebd5293b0a 100644
--- a/interface/resources/qml/windows/ModalWindow.qml
+++ b/interface/resources/qml/windows/ModalWindow.qml
@@ -19,6 +19,9 @@ ScrollingWindow {
destroyOnHidden: true
frame: ModalFrame { }
+ property bool closeButtonVisible: false
+ // only applicable for if close button is visible.
+ property bool clickedCloseButton: false
property int colorScheme: hifi.colorSchemes.light
property bool draggable: false
diff --git a/interface/src/Application.cpp b/interface/src/Application.cpp
index ffbd481bcb..468bb31ff3 100644
--- a/interface/src/Application.cpp
+++ b/interface/src/Application.cpp
@@ -2299,6 +2299,24 @@ Application::Application(int& argc, char** argv, QElapsedTimer& startupTimer, bo
connect(&AndroidHelper::instance(), &AndroidHelper::enterForeground, this, &Application::enterForeground);
AndroidHelper::instance().notifyLoadComplete();
#endif
+
+ static int CHECK_LOGIN_TIMER = 3000;
+ QTimer* checkLoginTimer = new QTimer(this);
+ checkLoginTimer->setInterval(CHECK_LOGIN_TIMER);
+ checkLoginTimer->setSingleShot(true);
+ connect(checkLoginTimer, &QTimer::timeout, this, [this]() {
+ auto accountManager = DependencyManager::get();
+ auto dialogsManager = DependencyManager::get();
+ if (!accountManager->isLoggedIn()) {
+ Setting::Handle{"loginDialogPoppedUp", false}.set(true);
+ dialogsManager->showLoginDialog();
+ QJsonObject loginData = {};
+ loginData["action"] = "login dialog shown";
+ UserActivityLogger::getInstance().logAction("encourageLoginDialog", loginData);
+ }
+ });
+ Setting::Handle{"loginDialogPoppedUp", false}.set(false);
+ checkLoginTimer->start();
}
void Application::updateVerboseLogging() {
@@ -2432,6 +2450,8 @@ void Application::onAboutToQuit() {
// so its persisted explicitly here
Setting::Handle{ ACTIVE_DISPLAY_PLUGIN_SETTING_NAME }.set(getActiveDisplayPlugin()->getName());
+ Setting::Handle{"loginDialogPoppedUp", false}.set(false);
+
getActiveDisplayPlugin()->deactivate();
if (_autoSwitchDisplayModeSupportedHMDPlugin
&& _autoSwitchDisplayModeSupportedHMDPlugin->isSessionActive()) {
diff --git a/interface/src/Application.h b/interface/src/Application.h
index f988795bc6..2bc679e9d6 100644
--- a/interface/src/Application.h
+++ b/interface/src/Application.h
@@ -331,6 +331,8 @@ signals:
void uploadRequest(QString path);
+ void loginDialogPoppedUp();
+
public slots:
QVector pasteEntities(float x, float y, float z);
bool exportEntities(const QString& filename, const QVector& entityIDs, const glm::vec3* givenOffset = nullptr);
diff --git a/interface/src/ui/LoginDialog.cpp b/interface/src/ui/LoginDialog.cpp
index 8a40ee2f83..43da6ea863 100644
--- a/interface/src/ui/LoginDialog.cpp
+++ b/interface/src/ui/LoginDialog.cpp
@@ -19,6 +19,7 @@
#include
#include
#include
+#include
#include "AccountManager.h"
#include "DependencyManager.h"
@@ -37,11 +38,19 @@ LoginDialog::LoginDialog(QQuickItem *parent) : OffscreenQmlDialog(parent) {
connect(accountManager.data(), &AccountManager::loginFailed,
this, &LoginDialog::handleLoginFailed);
#endif
-
}
-void LoginDialog::showWithSelection()
-{
+LoginDialog::~LoginDialog() {
+ Setting::Handle loginDialogPoppedUp{ "loginDialogPoppedUp", false };
+ if (loginDialogPoppedUp.get()) {
+ QJsonObject data;
+ data["action"] = "user opted out";
+ UserActivityLogger::getInstance().logAction("encourageLoginDialog", data);
+ }
+ loginDialogPoppedUp.set(false);
+}
+
+void LoginDialog::showWithSelection() {
auto tabletScriptingInterface = DependencyManager::get();
auto tablet = dynamic_cast(tabletScriptingInterface->getTablet("com.highfidelity.interface.tablet.system"));
auto hmd = DependencyManager::get();
@@ -73,9 +82,7 @@ void LoginDialog::toggleAction() {
} else {
// change the menu item to login
loginAction->setText("Login / Sign Up");
- connection = connect(loginAction, &QAction::triggered, [] {
- LoginDialog::showWithSelection();
- });
+ connection = connect(loginAction, &QAction::triggered, [] { LoginDialog::showWithSelection(); });
}
}
@@ -158,7 +165,6 @@ void LoginDialog::createAccountFromStream(QString username) {
QJsonDocument(payload).toJson());
});
}
-
}
void LoginDialog::openUrl(const QString& url) const {
@@ -200,25 +206,24 @@ void LoginDialog::createFailed(QNetworkReply* reply) {
}
void LoginDialog::signup(const QString& email, const QString& username, const QString& password) {
-
JSONCallbackParameters callbackParams;
callbackParams.callbackReceiver = this;
callbackParams.jsonCallbackMethod = "signupCompleted";
callbackParams.errorCallbackMethod = "signupFailed";
-
+
QJsonObject payload;
-
+
QJsonObject userObject;
userObject.insert("email", email);
userObject.insert("username", username);
userObject.insert("password", password);
-
+
payload.insert("user", userObject);
-
+
static const QString API_SIGNUP_PATH = "api/v1/users";
-
+
qDebug() << "Sending a request to create an account for" << username;
-
+
auto accountManager = DependencyManager::get();
accountManager->sendRequest(API_SIGNUP_PATH, AccountManagerAuth::None,
QNetworkAccessManager::PostOperation, callbackParams,
@@ -240,41 +245,37 @@ QString errorStringFromAPIObject(const QJsonValue& apiObject) {
}
void LoginDialog::signupFailed(QNetworkReply* reply) {
-
// parse the returned JSON to see what the problem was
auto jsonResponse = QJsonDocument::fromJson(reply->readAll());
-
+
static const QString RESPONSE_DATA_KEY = "data";
-
+
auto dataJsonValue = jsonResponse.object()[RESPONSE_DATA_KEY];
-
+
if (dataJsonValue.isObject()) {
auto dataObject = dataJsonValue.toObject();
-
+
static const QString EMAIL_DATA_KEY = "email";
static const QString USERNAME_DATA_KEY = "username";
static const QString PASSWORD_DATA_KEY = "password";
-
+
QStringList errorStringList;
-
+
if (dataObject.contains(EMAIL_DATA_KEY)) {
errorStringList.append(QString("Email %1.").arg(errorStringFromAPIObject(dataObject[EMAIL_DATA_KEY])));
}
-
+
if (dataObject.contains(USERNAME_DATA_KEY)) {
errorStringList.append(QString("Username %1.").arg(errorStringFromAPIObject(dataObject[USERNAME_DATA_KEY])));
}
-
+
if (dataObject.contains(PASSWORD_DATA_KEY)) {
errorStringList.append(QString("Password %1.").arg(errorStringFromAPIObject(dataObject[PASSWORD_DATA_KEY])));
}
-
+
emit handleSignupFailed(errorStringList.join('\n'));
} else {
static const QString DEFAULT_SIGN_UP_FAILURE_MESSAGE = "There was an unknown error while creating your account. Please try again later.";
emit handleSignupFailed(DEFAULT_SIGN_UP_FAILURE_MESSAGE);
}
-
-
}
-
diff --git a/interface/src/ui/LoginDialog.h b/interface/src/ui/LoginDialog.h
index ad8cab9699..ae069f9ab1 100644
--- a/interface/src/ui/LoginDialog.h
+++ b/interface/src/ui/LoginDialog.h
@@ -27,7 +27,10 @@ public:
LoginDialog(QQuickItem* parent = nullptr);
+ ~LoginDialog();
+
static void showWithSelection();
+
signals:
void handleLoginCompleted();
void handleLoginFailed();
@@ -62,7 +65,6 @@ protected slots:
Q_INVOKABLE void signup(const QString& email, const QString& username, const QString& password);
Q_INVOKABLE void openUrl(const QString& url) const;
-
};
#endif // hifi_LoginDialog_h
diff --git a/libraries/networking/src/UserActivityLogger.cpp b/libraries/networking/src/UserActivityLogger.cpp
index a5ee417939..c05daf0217 100644
--- a/libraries/networking/src/UserActivityLogger.cpp
+++ b/libraries/networking/src/UserActivityLogger.cpp
@@ -38,10 +38,10 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
if (_disabled.get()) {
return;
}
-
+
auto accountManager = DependencyManager::get();
QHttpMultiPart* multipart = new QHttpMultiPart(QHttpMultiPart::FormDataType);
-
+
// Adding the action name
QHttpPart actionPart;
actionPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"action_name\"");
@@ -53,7 +53,7 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
elapsedPart.setHeader(QNetworkRequest::ContentDispositionHeader, "form-data; name=\"elapsed_ms\"");
elapsedPart.setBody(QString::number(_timer.elapsed()).toLocal8Bit());
multipart->append(elapsedPart);
-
+
// If there are action details, add them to the multipart
if (!details.isEmpty()) {
QHttpPart detailsPart;
@@ -62,13 +62,13 @@ void UserActivityLogger::logAction(QString action, QJsonObject details, JSONCall
detailsPart.setBody(QJsonDocument(details).toJson(QJsonDocument::Compact));
multipart->append(detailsPart);
}
-
+
// if no callbacks specified, call our owns
if (params.isEmpty()) {
params.callbackReceiver = this;
params.errorCallbackMethod = "requestError";
}
-
+
accountManager->sendRequest(USER_ACTIVITY_URL,
AccountManagerAuth::Optional,
QNetworkAccessManager::PostOperation,
@@ -88,7 +88,7 @@ void UserActivityLogger::launch(QString applicationVersion, bool previousSession
actionDetails.insert(VERSION_KEY, applicationVersion);
actionDetails.insert(CRASH_KEY, previousSessionCrashed);
actionDetails.insert(RUNTIME_KEY, previousSessionRuntime);
-
+
logAction(ACTION_NAME, actionDetails);
}
@@ -105,9 +105,9 @@ void UserActivityLogger::changedDisplayName(QString displayName) {
const QString ACTION_NAME = "changed_display_name";
QJsonObject actionDetails;
const QString DISPLAY_NAME = "display_name";
-
+
actionDetails.insert(DISPLAY_NAME, displayName);
-
+
logAction(ACTION_NAME, actionDetails);
}
@@ -116,10 +116,10 @@ void UserActivityLogger::changedModel(QString typeOfModel, QString modelURL) {
QJsonObject actionDetails;
const QString TYPE_OF_MODEL = "type_of_model";
const QString MODEL_URL = "model_url";
-
+
actionDetails.insert(TYPE_OF_MODEL, typeOfModel);
actionDetails.insert(MODEL_URL, modelURL);
-
+
logAction(ACTION_NAME, actionDetails);
}
@@ -127,9 +127,9 @@ void UserActivityLogger::changedDomain(QString domainURL) {
const QString ACTION_NAME = "changed_domain";
QJsonObject actionDetails;
const QString DOMAIN_URL = "domain_url";
-
+
actionDetails.insert(DOMAIN_URL, domainURL);
-
+
logAction(ACTION_NAME, actionDetails);
}
@@ -151,10 +151,10 @@ void UserActivityLogger::connectedDevice(QString typeOfDevice, QString deviceNam
QJsonObject actionDetails;
const QString TYPE_OF_DEVICE = "type_of_device";
const QString DEVICE_NAME = "device_name";
-
+
actionDetails.insert(TYPE_OF_DEVICE, typeOfDevice);
actionDetails.insert(DEVICE_NAME, deviceName);
-
+
logAction(ACTION_NAME, actionDetails);
}
@@ -163,9 +163,9 @@ void UserActivityLogger::loadedScript(QString scriptName) {
const QString ACTION_NAME = "loaded_script";
QJsonObject actionDetails;
const QString SCRIPT_NAME = "script_name";
-
+
actionDetails.insert(SCRIPT_NAME, scriptName);
-
+
logAction(ACTION_NAME, actionDetails);
}
@@ -199,10 +199,10 @@ void UserActivityLogger::wentTo(AddressManager::LookupTrigger lookupTrigger, QSt
const QString TRIGGER_TYPE_KEY = "trigger";
const QString DESTINATION_TYPE_KEY = "destination_type";
const QString DESTINATION_NAME_KEY = "detination_name";
-
+
actionDetails.insert(TRIGGER_TYPE_KEY, trigger);
actionDetails.insert(DESTINATION_TYPE_KEY, destinationType);
actionDetails.insert(DESTINATION_NAME_KEY, destinationName);
-
+
logAction(ACTION_NAME, actionDetails);
}