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
}