From ff232f2e4bfb2749c5dcabf1a0ce72f18bf60e4e Mon Sep 17 00:00:00 2001 From: Michael Zanetti Date: Fri, 22 Nov 2019 17:31:46 +0100 Subject: [PATCH] Update password entry fields --- nymea-app/ui/appsettings/CloudLoginPage.qml | 33 +++---- nymea-app/ui/components/PasswordTextField.qml | 97 ++++++++++--------- nymea-app/ui/connection/LoginPage.qml | 6 +- .../ui/thingconfiguration/SetupWizard.qml | 4 +- 4 files changed, 74 insertions(+), 66 deletions(-) diff --git a/nymea-app/ui/appsettings/CloudLoginPage.qml b/nymea-app/ui/appsettings/CloudLoginPage.qml index 57bd7c7f..2ee11a4c 100644 --- a/nymea-app/ui/appsettings/CloudLoginPage.qml +++ b/nymea-app/ui/appsettings/CloudLoginPage.qml @@ -180,23 +180,10 @@ Page { } RowLayout { Layout.leftMargin: app.margins; Layout.rightMargin: app.margins - TextField { + PasswordTextField { id: passwordTextField Layout.fillWidth: true - echoMode: hiddenPassword ? TextInput.Password : TextInput.Normal - property bool hiddenPassword: true - } - ColorIcon { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: app.iconSize - name: "../images/eye.svg" - color: passwordTextField.hiddenPassword ? keyColor : app.accentColor - MouseArea { - anchors.fill: parent - onClicked: { - passwordTextField.hiddenPassword = !passwordTextField.hiddenPassword - } - } + signup: false } } @@ -208,7 +195,7 @@ Page { enabled: usernameTextField.acceptableInput onClicked: { busyOverlay.shown = true - AWSClient.login(usernameTextField.text, passwordTextField.text); + AWSClient.login(usernameTextField.text, passwordTextField.password); } } @@ -338,13 +325,18 @@ Page { id: passwordTextField Layout.leftMargin: app.margins; Layout.rightMargin: app.margins Layout.fillWidth: true + minPasswordLength: 8 + requireLowerCaseLetter: true + requireUpperCaseLetter: true + requireNumber: true + requireSpecialChar: false } Button { Layout.fillWidth: true Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.topMargin: app.margins text: qsTr("Sign up") - enabled: usernameTextField.acceptableInput && passwordTextField.isValidPassword + enabled: usernameTextField.acceptableInput && passwordTextField.isValid onClicked: { busyOverlay.shown = true; AWSClient.signup(usernameTextField.text, passwordTextField.password) @@ -576,13 +568,18 @@ Page { PasswordTextField { id: passwordTextField + minPasswordLength: 8 + requireLowerCaseLetter: true + requireUpperCaseLetter: true + requireNumber: true + requireSpecialChar: false Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins } Button { Layout.fillWidth: true; Layout.leftMargin: app.margins; Layout.rightMargin: app.margins text: qsTr("Reset password") - enabled: passwordTextField.isValidPassword && codeTextField.text.length > 0 + enabled: passwordTextField.isValid && codeTextField.text.length > 0 onClicked: { busyOverlay.shown = true AWSClient.confirmForgotPassword(confirmResetPasswordPage.email, codeTextField.text, passwordTextField.password) diff --git a/nymea-app/ui/components/PasswordTextField.qml b/nymea-app/ui/components/PasswordTextField.qml index 9822a037..4ee3d03e 100644 --- a/nymea-app/ui/components/PasswordTextField.qml +++ b/nymea-app/ui/components/PasswordTextField.qml @@ -5,18 +5,31 @@ import QtQuick.Layouts 1.2 ColumnLayout { id: root - property int minPasswordLength: 12 property bool signup: true + // Only used when signup is true + property int minPasswordLength: 12 + property bool requireSpecialChar: true + property bool requireNumber: true + property bool requireUpperCaseLetter: true + property bool requireLowerCaseLetter: true + readonly property alias password: passwordTextField.text - readonly property bool isValidPassword: isLongEnough && hasLower && hasUpper && hasNumbers && hasSpecialChar && (confirmationMatches || !signup) + readonly property bool isValidPassword: + isLongEnough && + (hasLower || !requireLowerCaseLetter) && + (hasUpper || !requireUpperCaseLetter) && + (hasNumbers || !requireNumber) && + (hasSpecialChar || !requireSpecialChar) + + readonly property bool isValid: !signup || (isValidPassword && confirmationMatches) readonly property bool isLongEnough: passwordTextField.text.length >= minPasswordLength readonly property bool hasLower: passwordTextField.text.search(/[a-z]/) >= 0 readonly property bool hasUpper: passwordTextField.text.search(/[A-Z/]/) >= 0 - readonly property bool hasNumbers: passwordTextField.text.search(/[0-9]/) >= 0 - readonly property bool hasSpecialChar: passwordTextField.text.search(/[\.,\*!"$%&/()=?`'+#'¡^°²³¼\[\]|{}\\@]/) >= 0 + readonly property bool hasNumbers: passwordTextField.text.search(/[0-9]/) >= 0 + readonly property bool hasSpecialChar: passwordTextField.text.search(/(?=.*?[$*.\[\]{}()?\-'"!@#%&/\\,><':;|_~`^])/) >= 0 readonly property bool confirmationMatches: passwordTextField.text === confirmationPasswordTextField.text property bool hiddenPassword: true @@ -29,6 +42,41 @@ ColumnLayout { Layout.fillWidth: true echoMode: root.hiddenPassword ? TextInput.Password : TextInput.Normal placeholderText: root.signup ? qsTr("Pick a password") : "" + + ToolTip.visible: root.signup && focus && !root.isValidPassword + ToolTip.delay: 1000 + ToolTip.onVisibleChanged: print("Tooltip visible changed:", ToolTip.visible, focus, root.isValidPassword) + ToolTip.text: { + var texts = [] + var checks = [] + texts.push(qsTr("Minimum %1 characters").arg(root.minPasswordLength)) + checks.push(root.isLongEnough) + if (root.requireLowerCaseLetter) { + texts.push(qsTr("Lowercase letters")) + checks.push(root.hasLower) + } + if (root.requireUpperCaseLetter) { + texts.push(qsTr("Uppercase letters")) + checks.push(root.hasUpper) + } + if (root.requireNumber) { + texts.push(qsTr("Numbers")) + checks.push(root.hasNumbers) + } + if (root.requireSpecialChar) { + texts.push(qsTr("Special characters")) + checks.push(root.hasSpecialChar) + } + var ret = [] + for (var i = 0; i < texts.length; i++) { + var entry = "• ".arg(checks[i] ? app.foregroundColor : app.accentColor) + entry += texts[i] + entry += "" + ret.push(entry) + } + return ret.join("
") + } + } ColorIcon { Layout.preferredHeight: app.iconSize @@ -44,24 +92,6 @@ ColumnLayout { } } - - Label { - Layout.fillWidth: true - wrapMode: Text.WordWrap - visible: root.signup - - // TRANSLATORS: %1 will be replaced with the normal text color, %2 the color for the length check - text: qsTr("The password needs to be at least %3 characters long, contain lowercase, uppercase letters as well as numbers and special characters.") - .arg(app.accentColor) - .arg(!root.isLongEnough ? "red" : app.accentColor) - .arg(root.minPasswordLength) - .arg(!root.hasLower ? "red" : app.accentColor) - .arg(!root.hasUpper ? "red" : app.accentColor) - .arg(!root.hasNumbers ? "red" : app.accentColor) - .arg(!root.hasSpecialChar ? "red" : app.accentColor) - font.pixelSize: app.smallFont - } - RowLayout { visible: root.signup @@ -71,28 +101,5 @@ ColumnLayout { echoMode: root.hiddenPassword ? TextInput.Password : TextInput.Normal placeholderText: qsTr("Confirm password") } - - ColorIcon { - Layout.preferredHeight: app.iconSize - Layout.preferredWidth: app.iconSize - name: "../images/eye.svg" - color: root.hiddenPassword ? keyColor : app.accentColor - MouseArea { - anchors.fill: parent - onClicked: { - root.hiddenPassword = !root.hiddenPassword - } - } - } - } - - - Label { - Layout.fillWidth: true - wrapMode: Text.WordWrap - visible: root.signup - - text: root.confirmationMatches ? qsTr("The passwords match.").arg(app.accentColor) : qsTr("The passwords do not match.").arg(app.accentColor).arg("red") - font.pixelSize: app.smallFont } } diff --git a/nymea-app/ui/connection/LoginPage.qml b/nymea-app/ui/connection/LoginPage.qml index b5baef09..b9d49e92 100644 --- a/nymea-app/ui/connection/LoginPage.qml +++ b/nymea-app/ui/connection/LoginPage.qml @@ -104,6 +104,10 @@ Page { id: passwordTextField Layout.fillWidth: true minPasswordLength: 8 + requireLowerCaseLetter: true + requireUpperCaseLetter: true + requireNumber: true + requireSpecialChar: false signup: engine.jsonRpcClient.initialSetupRequired } } @@ -112,7 +116,7 @@ Page { Layout.fillWidth: true Layout.leftMargin: app.margins; Layout.rightMargin: app.margins; Layout.bottomMargin: app.margins text: qsTr("OK") - enabled: passwordTextField.isValidPassword + enabled: passwordTextField.isValid onClicked: { if (engine.jsonRpcClient.initialSetupRequired) { print("create user") diff --git a/nymea-app/ui/thingconfiguration/SetupWizard.qml b/nymea-app/ui/thingconfiguration/SetupWizard.qml index b3560e43..55afc4a6 100644 --- a/nymea-app/ui/thingconfiguration/SetupWizard.qml +++ b/nymea-app/ui/thingconfiguration/SetupWizard.qml @@ -440,11 +440,11 @@ Page { visible: pairingPage.setupMethod === "SetupMethodUserAndPassword" } - TextField { + PasswordTextField { id: pinTextField Layout.fillWidth: true visible: pairingPage.setupMethod === "SetupMethodDisplayPin" || pairingPage.setupMethod === "SetupMethodUserAndPassword" - echoMode: TextField.Password + signup: false }