feat(Desktop/Password): New password view design

Created new PasswordView.qml component.

Added logic in new component:
- Type of characters introduced.
- Length of strength indicator bar depending on characters length.
- Show/hide icons.
- Error messages.
- Added algorithm for password categorisation.

Added Onboarding screens and modified flow.

Replaced ChangePasswordModal content.

Closes #4785
This commit is contained in:
Noelia 2022-03-01 16:59:38 +01:00 committed by Noelia
parent 4c677d55e8
commit 6a023443ac
8 changed files with 685 additions and 165 deletions

View File

@ -64,6 +64,28 @@ QtObject {
}
}
DSM.State {
id: createPasswordState
onEntered: loader.sourceComponent = createPassword
DSM.SignalTransition {
targetState: appState
signal: startupModule.appStateChanged
guard: state === Constants.appState.main
}
}
DSM.State {
id: confirmPasswordState
onEntered: loader.sourceComponent = confirmPassword
DSM.SignalTransition {
targetState: appState
signal: startupModule.appStateChanged
guard: state === Constants.appState.main
}
}
DSM.State {
id: stateLogin
onEntered: { onBoardingStepChanged(login, ""); }
@ -105,6 +127,18 @@ QtObject {
guard: path === "KeycardFlowSelection"
}
DSM.SignalTransition {
targetState: createPasswordState
signal: applicationWindow.navigateTo
guard: path === "CreatePassword"
}
DSM.SignalTransition {
targetState: confirmPasswordState
signal: applicationWindow.navigateTo
guard: path === "ConfirmPassword"
}
DSM.FinalState {
id: onboardingDoneState
}
@ -154,6 +188,7 @@ QtObject {
property var existingKeyComponent: Component {
id: existingKey
ExistingKeyView {
onShowCreatePasswordView: { Global.applicationWindow.navigateTo("CreatePassword") }
onClosed: function () {
if (root.hasAccounts) {
Global.applicationWindow.navigateTo("InitialState")
@ -167,6 +202,7 @@ QtObject {
property var genKeyComponent: Component {
id: genKey
GenKeyView {
onShowCreatePasswordView: { Global.applicationWindow.navigateTo("CreatePassword") }
onClosed: function () {
if (root.hasAccounts) {
Global.applicationWindow.navigateTo("InitialState")
@ -201,4 +237,38 @@ QtObject {
}
}
}
property var d: QtObject {
property string newPassword
property string confirmationPassword
}
property var createPasswordComponent: Component {
id: createPassword
CreatePasswordView {
newPassword: d.newPassword
confirmationPassword: d.confirmationPassword
onPasswordCreated: {
d.newPassword = newPassword
d.confirmationPassword = confirmationPassword
applicationWindow.navigateTo("ConfirmPassword")
}
onBackClicked: {
d.newPassword = ""
d.confirmationPassword = ""
applicationWindow.navigateTo("InitialState");
console.warn("TODO: Integration with onboarding flow!")
}
}
}
property var confirmPasswordComponent: Component {
id: confirmPassword
ConfirmPasswordView {
password: d.newPassword
onBackClicked: { applicationWindow.navigateTo("CreatePassword") }
}
}
}

View File

@ -0,0 +1,140 @@
import QtQuick 2.0
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.12
import shared.controls 1.0
import shared 1.0
import shared.panels 1.0
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import "../stores"
Page {
id: root
property string password
signal backClicked()
anchors.fill: parent
Component.onCompleted: confPswInput.forceActiveFocus(Qt.MouseFocusReason)
Column {
id: view
spacing: 4 * Style.current.padding
width: 416
anchors.centerIn: parent
StatusBaseText {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Have you written down your password?")
font.pixelSize: 22
font.bold: true
color: Theme.palette.directColor1
}
Column {
anchors.horizontalCenter: parent.horizontalCenter
spacing: Style.current.padding
StatusBaseText {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("You will never be able to recover your password if you lose it.")
font.pixelSize: 12
color: Theme.palette.dangerColor1
}
StatusBaseText {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("If you need to, write it using pen and paper and keep in a safe place.")
font.pixelSize: 12
color: Theme.palette.baseColor1
}
StatusBaseText {
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("If you lose your password you will lose access to your Status profile.")
font.pixelSize: 12
color: Theme.palette.baseColor1
}
}
// TODO replace with StatusInput as soon as it supports password
Input {
id: confPswInput
property bool showPassword: false
width: parent.width
enabled: !submitBtn.loading
placeholderText: submitBtn.loading ?
qsTr("Connecting...") :
qsTr("Confirm you password (again)")
textField.echoMode: showPassword ? TextInput.Normal : TextInput.Password
textField.validator: RegExpValidator { regExp: /^[!-~]+$/ } // That incudes NOT extended ASCII printable characters less space
keepHeight: true
textField.rightPadding: showHideCurrentIcon.width + showHideCurrentIcon.anchors.rightMargin + Style.current.padding / 2
StatusFlatRoundButton {
id: showHideCurrentIcon
visible: confPswInput.text !== ""
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 16
width: 24
height: 24
icon.name: confPswInput.showPassword ? "hide" : "show"
icon.color: Theme.palette.baseColor1
onClicked: confPswInput.showPassword = !confPswInput.showPassword
}
}
// Just a column filler to fit the design
Item {
height: Style.current.padding
width: parent.width
}
StatusButton {
id: submitBtn
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Finalize Status Password Creation")
enabled: !submitBtn.loading && confPswInput.text === root.password
property Timer sim: Timer {
id: pause
interval: 20
onTriggered: {
// Create new password call action to the backend
OnboardingStore.onBoardingModul.storeSelectedAccountAndLogin(root.password)
Global.applicationWindow.prepareForStoring(root.password, false)
}
}
onClicked: {
confPswInput.text = ""
submitBtn.loading = true
// Create password operation blocks the UI so loading = true; will never have any affect until changePassword/createPassword is done.
// Getting around it with a small pause (timer) in order to get the desired behavior
pause.start()
}
}
}
// Back button:
StatusRoundButton {
enabled: !submitBtn.loading
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.current.padding
icon.name: "arrow-left"
onClicked: { root.backClicked() }
}
}

View File

@ -0,0 +1,69 @@
import QtQuick 2.0
import QtQuick.Controls 2.13
import QtQuick.Layouts 1.12
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import utils 1.0
import "../../Profile/views"
Page {
id: root
property string newPassword
property string confirmationPassword
signal passwordCreated(string newPassword, string confirmationPassword)
signal backClicked()
Component.onCompleted: { view.forceNewPswInputFocus() }
QtObject {
id: d
readonly property int zBehind: 1
readonly property int zFront: 100
}
anchors.fill: parent
Column {
spacing: 4 * Style.current.padding
anchors.centerIn: parent
z: view.zFront
PasswordView {
id: view
newPswText: root.newPassword
confirmationPswText: root.confirmationPassword
}
StatusButton {
id: submitBtn
z: d.zFront
anchors.horizontalCenter: parent.horizontalCenter
text: qsTr("Create password")
enabled: view.ready
onClicked: { passwordCreated(view.newPswText, view.confirmationPswText) }
}
}
// Back button:
StatusRoundButton {
z: d.zFront // Focusable / clickable component
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
anchors.bottom: parent.bottom
anchors.bottomMargin: Style.current.padding
icon.name: "arrow-left"
onClicked: { root.backClicked() }
}
// By clicking anywhere outside password entries fields or focusable element in the view, it is needed to check if passwords entered matches
MouseArea {
anchors.fill: parent
z: d.zBehind // Behind focusable components
onClicked: { view.checkPasswordMatches() }
}
}

View File

@ -9,6 +9,9 @@ import "../shared"
Item {
property var onClosed: function () {}
signal showCreatePasswordView()
id: existingKeyView
anchors.fill: parent
@ -38,7 +41,7 @@ Item {
onButtonClicked: {
recoverySuccessModal.wentNext = true
recoverySuccessModal.close()
createPasswordModal.open()
showCreatePasswordView()
}
onClosed: function () {
if (!recoverySuccessModal.wentNext) {
@ -46,11 +49,4 @@ Item {
}
}
}
CreatePasswordModal {
id: createPasswordModal
onClosed: function () {
existingKeyView.onClosed()
}
}
}

View File

@ -6,6 +6,9 @@ import "../shared"
Item {
property var onClosed: function () {}
signal showCreatePasswordView()
id: genKeyView
anchors.fill: parent
@ -19,7 +22,7 @@ Item {
onNextClick: function (selectedIndex, displayName) {
wentNext = true
OnboardingStore.setCurrentAccountAndDisplayName(selectedIndex, displayName)
createPasswordModal.open()
showCreatePasswordView()
}
onClosed: function () {
if (!wentNext) {
@ -27,11 +30,4 @@ Item {
}
}
}
CreatePasswordModal {
id: createPasswordModal
onClosed: function () {
genKeyView.onClosed()
}
}
}

View File

@ -10,6 +10,9 @@ import shared.controls 1.0
import StatusQ.Popups 0.1
import StatusQ.Controls 0.1
import StatusQ.Core.Theme 0.1
import "../views"
StatusModal {
id: root
@ -17,33 +20,15 @@ StatusModal {
property var privacyStore
signal passwordChanged()
width: 480
height: 510
closePolicy: Popup.NoAutoClose
header.title: qsTr("Change password")
onOpened: root.reset()
function lengthValidator(text) {
return text.length >= 6 ? "" : qsTr("At least 6 characters")
}
function reset() {
currentPasswordInput.state = "init"
passwordInput.state = "init"
confirmPasswordInput.state = "init"
currentPasswordInput.forceActiveFocus(Qt.MouseFocusReason)
}
function onChangePasswordResponse(success) {
if (success) {
passwordChanged()
submitBtn.enabled = false;
submitBtn.enabled = false
} else {
currentPasswordInput.state = "incorrect"
currentPasswordInput.forceActiveFocus(Qt.MouseFocusReason)
view.reset()
console.warn("TODO: Display error message when change password action failure! ")
}
submitBtn.loading = false;
submitBtn.loading = false
}
Connections {
@ -51,152 +36,50 @@ StatusModal {
onPasswordChanged: onChangePasswordResponse(success)
}
contentItem: ColumnLayout {
id: contentItem
anchors.fill: parent
anchors {
topMargin: Style.current.xlPadding + root.topPadding
leftMargin: Style.current.xlPadding
rightMargin: Style.current.xlPadding
bottomMargin: Style.current.xlPadding + root.bottomPadding
}
spacing: Style.current.padding
width: 480
height: 546
closePolicy: Popup.NoAutoClose
header.title: qsTr("Change password")
// TODO replace with StatusInput as soon as it supports password
Input {
id: currentPasswordInput
onOpened: view.reset()
readonly property bool ready: state == "typing" && validationError == ""
anchors.left: undefined
anchors.right: undefined
Layout.fillWidth: true
label: qsTr("Current password")
textField.echoMode: TextInput.Password
keepHeight: true
placeholderText: ""
state: "init"
onTextChanged: if (text != "" && state != "typing") state = "typing"
onStateChanged: if (state == "init") resetInternal()
states: [
State {
name: "init"
},
State {
name: "typing"
PropertyChanges { target: currentPasswordInput; validationError: root.lengthValidator(text) }
},
State {
name: "incorrect"
PropertyChanges { target: currentPasswordInput; validationError: qsTr("Incorrect password") }
}
]
}
// TODO replace with StatusInput as soon as it supports password
Input {
id: passwordInput
readonly property bool ready: state == "typing" && validationError == ""
anchors.left: undefined
anchors.right: undefined
Layout.fillWidth: true
label: qsTr("New password")
textField.echoMode: TextInput.Password
keepHeight: true
placeholderText: ""
state: "init"
onTextChanged: if (text != "" && state != "typing") state = "typing"
onStateChanged: if (state == "init") resetInternal()
states: [
State {
name: "init"
},
State {
name: "typing"
PropertyChanges { target: passwordInput; validationError: root.lengthValidator(text) }
}
]
}
// TODO replace with StatusInput as soon as it supports password
Input {
id: confirmPasswordInput
readonly property bool ready: state == "typing" && validationError == ""
anchors.left: undefined
anchors.right: undefined
Layout.fillWidth: true
label: qsTr("Confirm new password")
textField.echoMode: TextInput.Password
keepHeight: true
placeholderText: ""
state: "init"
onTextChanged: if (text != "" && state != "typing") state = "typing"
onStateChanged: if (state == "init") resetInternal()
states: [
State {
name: "init"
},
State {
name: "typing"
PropertyChanges {
target: confirmPasswordInput;
validationError: confirmPasswordInput.text != passwordInput.text ? qsTr("Password does not match") : ""
}
}
]
}
Item {
Layout.fillHeight: true
}
StyledText {
text: qsTr("Your password protects your keys. You need it to unlock Status and transact.")
wrapMode: Text.WordWrap
Layout.preferredWidth: 340
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
color: Style.current.secondaryText
font.pixelSize: Style.current.tertiaryTextFontSize
}
PasswordView {
id: view
anchors.topMargin: Style.current.padding
anchors.centerIn: parent
titleVisible: false
introText: qsTr("Change password used to unlock Status on this device & sign transactions.")
createNewPsw: false
}
rightButtons: [
StatusButton {
id: submitBtn
text: qsTr("Change password")
enabled: !submitBtn.loading && currentPasswordInput.ready &&
passwordInput.ready && confirmPasswordInput.ready
text: qsTr("Change Password")
enabled: !submitBtn.loading && view.ready
property Timer sim: Timer {
id: pause
interval: 20
onTriggered: {
root.privacyStore.changePassword(currentPasswordInput.text, passwordInput.text)
// Change current password call action to the backend
root.privacyStore.changePassword(view.currentPswText, view.newPswText)
}
}
onClicked: {
submitBtn.loading = true;
//changePassword operation blocks the UI so loading = true; will never
//have any affect until changePassword is done. Getting around it with a
//small pause (timer) in order to get the desired behavior
// ChangePassword operation blocks the UI so loading = true; will never have any affect until changePassword/createPassword is done.
// Getting around it with a small pause (timer) in order to get the desired behavior
pause.start();
}
}
]
// By clicking anywhere outside password entries fields or focusable element in the view, it is needed to check if passwords entered matches
MouseArea {
anchors.fill: parent
z: view.zBehind // Behind focusable components in the view
onClicked: { view.checkPasswordMatches() }
}
}

View File

@ -58,6 +58,7 @@ ModalPopup {
checked: localAccountSettings.storeToKeychainValue === Constants.storeToKeychainValueStore
onCheckedChanged: {
if (checked && localAccountSettings.storeToKeychainValue !== Constants.storeToKeychainValueStore) {
// TODO: REFACTOR TO NEW PASWORD VIEW
var storePassPopup = Global.openPopup(storePasswordModal)
if(storePassPopup)
{

View File

@ -0,0 +1,365 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.12
import shared.panels 1.0
import shared.controls 1.0
import utils 1.0
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
Column {
id: root
property bool ready: newPswInput.text.length >= root.minPswLen && newPswInput.text === confirmPswInput.text && errorTxt.text === ""
property int minPswLen: 6
property bool createNewPsw: true
property string title: qsTr("Create a password")
property bool titleVisible: true
property string introText: qsTr("Create a password to unlock Status on this device & sign transactions.")
property string recoverText: qsTr("You will not be able to recover this password if it is lost.")
property string strengthenText: qsTr("Minimum 6 characers. To strengthen your password consider including:")
readonly property int zBehind: 1
readonly property int zFront: 100
property alias currentPswText: currentPswInput.text
property alias newPswText: newPswInput.text
property alias confirmationPswText: confirmPswInput.text
property alias errorMsgText: errorTxt.text
function forceNewPswInputFocus() { newPswInput.forceActiveFocus(Qt.MouseFocusReason) }
function reset() {
newPswInput.text = ""
currentPswInput.text = ""
confirmPswInput.text = ""
errorTxt.text = ""
strengthInditactor.strength = StatusPasswordStrengthIndicator.Strength.None
// Update focus:
if(root.createNewPsw)
newPswInput.forceActiveFocus(Qt.MouseFocusReason)
else
currentPswInput.forceActiveFocus(Qt.MouseFocusReason)
}
function checkPasswordMatches() {
if(newPswInput.text.length >= root.minPswLen) {
errorTxt.text = ""
if(confirmPswInput.text !== newPswInput.text) {
errorTxt.text = qsTr("Passwords don't match")
}
}
}
QtObject {
id: d
property bool containsLower: false
property bool containsUpper: false
property bool containsNumbers: false
property bool containsSymbols: false
readonly property var validator: RegExpValidator { regExp: /^[!-~]+$/ } // That incudes NOT extended ASCII printable characters less space
// Password strength categorization / validation
function lowerCaseValidator(text) { return (/[a-z]/.test(text)) }
function upperCaseValidator(text) { return (/[A-Z]/.test(text)) }
function numbersValidator(text) { return (/\d/.test(text)) }
// That incudes NOT extended ASCII printable symbols less space:
function symbolsValidator(text) { return (/[!-\/:-@[-`{-~]/.test(text)) }
function findUniqueChars(text) {
// The variable that contains the unique values
let uniq = "";
for(let i = 0; i < text.length; i++) {
// Checking if the uniq contains the character
if(uniq.includes(text[i]) === false) {
// If the character not present in uniq
// Concatenate the character with uniq
uniq += text[i]
}
}
return uniq
}
// Algorithm defined in functional requirements / Password categorization
function getPswStrength() {
let rules = 0
let points = 0
let strengthType = StatusPasswordStrengthIndicator.Strength.None
if(newPswInput.text.length >= root.minPswLen) { points += 10; rules++ }
if(d.containsLower) { points += 5; rules++ }
if(d.containsUpper) { points += 5; rules++ }
if(d.containsNumbers) { points += 5; rules++ }
if(d.containsSymbols) { points += 10; rules++ }
let uniq = d.findUniqueChars(newPswInput.text)
if(uniq.length >= 5) { points += 5; rules++ }
// Update points according to rules used:
points += rules * 10/*factor*/
// Strength decision taken:
if(points > 0 && points < 40) strengthType = StatusPasswordStrengthIndicator.Strength.VeryWeak
else if(points >= 40 && points < 60) strengthType = StatusPasswordStrengthIndicator.Strength.Weak
else if(points >= 60 && points < 80) strengthType = StatusPasswordStrengthIndicator.Strength.SoSo
else if(points >= 80 && points < 100) strengthType = StatusPasswordStrengthIndicator.Strength.Good
else if(points >= 100) strengthType = StatusPasswordStrengthIndicator.Strength.Great
return strengthType
}
// Password validation / error message selection:
function passwordValidation() {
errorTxt.text = ""
// 3 rules to validate:
// * Password is in pwnd passwords database
if(isInPwndDatabase())
errorTxt.text = qsTr("This password has been pwned and shouldn't be used")
// * Common password
else if(isCommonPassword())
errorTxt.text = qsTr("This password is a common word and shouldn't be used")
// * Password too short
else if(isTooShort())
errorTxt.text = qsTr("Password must be at least 6 characters long")
}
function isInPwndDatabase() {
// "TODO - Nice To Have: Pwnd password validation NOT implemented yet! "
return false
}
function isCommonPassword() {
// "TODO - Nice To Have: Common password validation NOT implemented yet! "
return false
}
function isTooShort() { return newPswInput.text.length < root.minPswLen }
}
spacing: 3 * Style.current.padding / 2
z: root.zFront
width: 416
// View visual content:
StatusBaseText {
id: title
anchors.horizontalCenter: parent.horizontalCenter
visible: root.titleVisible
text: root.title
font.pixelSize: 22
font.bold: true
color: Theme.palette.directColor1
}
Column {
StatusBaseText {
id: introTxt
anchors.horizontalCenter: parent.horizontalCenter
text: root.introText
font.pixelSize: 12
color: Theme.palette.baseColor1
}
StatusBaseText {
id: recoverTxt
anchors.horizontalCenter: parent.horizontalCenter
text: root.recoverText
font.pixelSize: 12
color: Theme.palette.dangerColor1
}
}
// TODO replace with StatusInput as soon as it supports password
Input {
id: currentPswInput
property bool showPassword
z: root.zFront
visible: !root.createNewPsw
width: parent.width
placeholderText: qsTr("Current password")
textField.echoMode: showPassword ? TextInput.Normal : TextInput.Password
textField.validator: d.validator
keepHeight: true
textField.rightPadding: showHideCurrentIcon.width + showHideCurrentIcon.anchors.rightMargin + Style.current.padding / 2
StatusFlatRoundButton {
id: showHideCurrentIcon
visible: currentPswInput.text !== ""
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 16
width: 24
height: 24
icon.name: currentPswInput.showPassword ? "hide" : "show"
icon.color: Theme.palette.baseColor1
onClicked: currentPswInput.showPassword = !currentPswInput.showPassword
}
}
Column {
spacing: Style.current.padding / 2
z: root.zFront
width: parent.width
// TODO replace with StatusInput as soon as it supports password
Input {
id: newPswInput
property bool showPassword
width: parent.width
placeholderText: qsTr("New password")
textField.echoMode: showPassword ? TextInput.Normal : TextInput.Password
textField.validator: d.validator
keepHeight: true
textField.rightPadding: showHideNewIcon.width + showHideNewIcon.anchors.rightMargin + Style.current.padding / 2
onTextChanged: {
// Update password checkers
d.containsLower = d.lowerCaseValidator(text)
d.containsUpper = d.upperCaseValidator(text)
d.containsNumbers = d.numbersValidator(text)
d.containsSymbols = d.symbolsValidator(text)
// Update strength indicator:
strengthInditactor.strength = d.getPswStrength()
}
StatusFlatRoundButton {
id: showHideNewIcon
visible: newPswInput.text !== ""
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 16
width: 24
height: 24
icon.name: newPswInput.showPassword ? "hide" : "show"
icon.color: Theme.palette.baseColor1
onClicked: newPswInput.showPassword = !newPswInput.showPassword
}
}
StatusPasswordStrengthIndicator {
id: strengthInditactor
width: parent.width
anchors.horizontalCenter: parent.horizontalCenter
value: newPswInput.text.length > root.minPswLen ? root.minPswLen : newPswInput.text.length
from: 0
to: root.minPswLen
labelVeryWeak: qsTr("Very weak")
labelWeak: qsTr("Weak")
labelSoso: qsTr("So-so")
labelGood: qsTr("Good")
labelGreat: qsTr("Great")
}
}
StatusBaseText {
id: strengthenTxt
anchors.horizontalCenter: parent.horizontalCenter
text: root.strengthenText
font.pixelSize: 12
color: Theme.palette.baseColor1
}
Row {
spacing: Style.current.padding
anchors.horizontalCenter: parent.horizontalCenter
StatusBaseText {
id: lowerCaseTxt
text: "• " + qsTr("Lower case")
font.pixelSize: 12
color: d.containsLower ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
id: upperCaseTxt
text: "• " + qsTr("Upper case")
font.pixelSize: 12
color: d.containsUpper ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
id: numbersTxt
text: "• " + qsTr("Numbers")
font.pixelSize: 12
color: d.containsNumbers ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
StatusBaseText {
id: symbolsTxt
text: "• " + qsTr("Symbols")
font.pixelSize: 12
color: d.containsSymbols ? Theme.palette.successColor1 : Theme.palette.baseColor1
}
}
// TODO replace with StatusInput as soon as it supports password
Input {
id: confirmPswInput
property bool showPassword
z: root.zFront
width: parent.width
placeholderText: qsTr("Confirm password")
textField.echoMode: showPassword ? TextInput.Normal : TextInput.Password
textField.validator: d.validator
keepHeight: true
textField.rightPadding: showHideConfirmIcon.width + showHideConfirmIcon.anchors.rightMargin + Style.current.padding / 2
onTextChanged: { if(textField.text.length === newPswInput.text.length) root.checkPasswordMatches() }
textField.onFocusChanged: {
// When clicking into the confirmation input, validate if new password:
if(textField.focus) d.passwordValidation()
// When leaving the confirmation input because of the button or other input component is focused, check if password matches
else root.checkPasswordMatches(true)
}
StatusFlatRoundButton {
id: showHideConfirmIcon
visible: confirmPswInput.text !== ""
anchors.verticalCenter: parent.verticalCenter
anchors.right: parent.right
anchors.rightMargin: 16
width: 24
height: 24
icon.name: confirmPswInput.showPassword ? "hide" : "show"
icon.color: Theme.palette.baseColor1
onClicked: confirmPswInput.showPassword = !confirmPswInput.showPassword
}
}
StatusBaseText {
id: errorTxt
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 12
color: Theme.palette.dangerColor1
onTextChanged: {
if(text === "") filler.visible = true
else filler.visible = false
}
}
// Just a column filler to keep the component height althought errorTxt.text is ""
Item {
id: filler
width: root.width
visible: true
height: errorTxt.height
}
}