From b5b477a3ce9326e90946c23b763e36df900daed4 Mon Sep 17 00:00:00 2001 From: Alexander Bilyak Date: Wed, 3 Nov 2021 13:47:44 +0000 Subject: [PATCH] GODT-1316: Set default TextArea and TextField behavior --- internal/frontend/qml/BugReportView.qml | 92 +++-- internal/frontend/qml/PortSettings.qml | 69 ++-- internal/frontend/qml/Proton/TextArea.qml | 377 +++++++++++++-------- internal/frontend/qml/Proton/TextField.qml | 69 +++- internal/frontend/qml/SignIn.qml | 107 +++--- internal/frontend/qml/tests/TextAreas.qml | 112 +++--- internal/frontend/qml/tests/TextFields.qml | 20 +- 7 files changed, 490 insertions(+), 356 deletions(-) diff --git a/internal/frontend/qml/BugReportView.qml b/internal/frontend/qml/BugReportView.qml index e1f4a9c2..08739ba1 100644 --- a/internal/frontend/qml/BugReportView.qml +++ b/internal/frontend/qml/BugReportView.qml @@ -37,7 +37,6 @@ SettingsView { id: description property int _minLength: 150 property int _maxLength: 800 - property bool _inputOK: description.text.length>=description._minLength && description.text.length<=description._maxLength label: qsTr("Description") colorScheme: root.colorScheme @@ -46,71 +45,59 @@ SettingsView { hint: description.text.length + "/" + _maxLength placeholderText: qsTr("Tell us what went wrong or isn't working (min. %1 characters).").arg(_minLength) - onEditingFinished: { - if (!description._inputOK) { - description.error = true - if (description.text.length <= description._minLength) { - description.assistiveText = qsTr("Enter a problem description (min. %1 characters).").arg(_minLength) - } else { - description.assistiveText = qsTr("Enter a problem description (max. %1 characters).").arg(_maxLength) - } - } else { - description.error = false - description.assistiveText = "" + validator: function(text) { + if (description.text.length < description._minLength) { + return qsTr("Enter a problem description (min. %1 characters).").arg(_minLength) + } + + if (description.text.length > description._maxLength) { + return qsTr("Enter a problem description (max. %1 characters).").arg(_maxLength) + } + + return + } + + onTextChanged: { + // Rise max length error imidiatly while typing + if (description.text.length > description._maxLength) { + validate() } } - onTextChanged: { - description.error = false - description.assistiveText = "" - } + + KeyNavigation.priority: KeyNavigation.BeforeItem + KeyNavigation.tab: address } TextField { id: address - property bool _inputOK: root.isValidEmail(address.text) label: qsTr("Your contact email") colorScheme: root.colorScheme Layout.fillWidth: true placeholderText: qsTr("e.g. jane.doe@protonmail.com") - onEditingFinished: { - if (!address._inputOK) { - address.error = true - address.assistiveText = qsTr("Enter valid email address") - } else { - address.assistiveText = "" - address.error = false + validator: function(str) { + if (!isValidEmail(str)) { + return qsTr("Enter valid email address") } - } - onTextChanged: { - address.error = false - address.assistiveText = "" + return } } TextField { id: emailClient - property bool _inputOK: emailClient.text.length > 0 label: qsTr("Your email client (including version)") colorScheme: root.colorScheme Layout.fillWidth: true placeholderText: qsTr("e.g. Apple Mail 14.0") - onEditingFinished: { - if (!emailClient._inputOK) { - emailClient.assistiveText = qsTr("Enter an email client name and version") - emailClient.error = true - } else { - emailClient.assistiveText = "" - emailClient.error = false + validator: function(str) { + if (str.length === 0) { + return qsTr("Enter an email client name and version") } - } - onTextChanged: { - emailClient.error = false - emailClient.assistiveText = "" + return } } @@ -148,25 +135,26 @@ SettingsView { id: sendButton text: qsTr("Send") colorScheme: root.colorScheme - onClicked: root.submit() - enabled: description._inputOK && address._inputOK && emailClient._inputOK + + onClicked: { + description.validate() + address.validate() + emailClient.validate() + + if (description.error || address.error || emailClient.error) { + return + } + + submit() + } Connections {target: root.backend; onReportBugFinished: sendButton.loading = false } } function setDefaultValue() { description.text = "" - description.error = false - description.assistiveText = "" - address.text = root.selectedAddress - address.error = false - address.assistiveText = "" - emailClient.text = root.backend.currentEmailClient - emailClient.error = false - emailClient.assistiveText = "" - includeLogs.checked = true } @@ -185,8 +173,6 @@ SettingsView { ) } - Component.onCompleted: root.setDefaultValue() - onVisibleChanged: { root.setDefaultValue() } diff --git a/internal/frontend/qml/PortSettings.qml b/internal/frontend/qml/PortSettings.qml index 7d5ce1e6..cf25bff9 100644 --- a/internal/frontend/qml/PortSettings.qml +++ b/internal/frontend/qml/PortSettings.qml @@ -25,10 +25,9 @@ import Proton 4.0 SettingsView { id: root - property bool _valuesOK: !imapField.error && !smtpField.error property bool _valuesChanged: ( - imapField.text*1 != root.backend.portIMAP || - smtpField.text*1 != root.backend.portSMTP + imapField.text*1 !== root.backend.portIMAP || + smtpField.text*1 !== root.backend.portSMTP ) Label { @@ -55,14 +54,14 @@ SettingsView { colorScheme: root.colorScheme label: qsTr("IMAP port") Layout.preferredWidth: 160 - onEditingFinished: root.validate(imapField) + validator: root.validate } TextField { id: smtpField colorScheme: root.colorScheme label: qsTr("SMTP port") Layout.preferredWidth: 160 - onEditingFinished: root.validate(smtpField) + validator: root.validate } } @@ -79,10 +78,34 @@ SettingsView { id: submitButton colorScheme: root.colorScheme text: qsTr("Save and restart") - enabled: root._valuesOK && root._valuesChanged + enabled: root._valuesChanged onClicked: { + // removing error here because we may have set it manually (port occupied) + imapField.error = false + smtpField.error = false + + // checking errors seperatly because we want to display "same port" error only once + imapField.validate() + if (imapField.error) { + return + } + smtpField.validate() + if (smtpField.error) { + return + } + submitButton.loading = true - root.submit() + + // check both ports before returning an error + var err = false + err |= !isPortFree(imapField) + err |= !isPortFree(smtpField) + if (err) { + submitButton.loading = false + return + } + + root.backend.changePorts(imapField.text, smtpField.text) } } @@ -104,48 +127,32 @@ SettingsView { root.setDefaultValues() } - function validate(field) { - var num = field.text*1 + function validate(port) { + var num = port*1 if (! (num > 1 && num < 65536) ) { - field.error = true - field.assistiveText = qsTr("Invalid port number") - return + return qsTr("Invalid port number") } if (imapField.text == smtpField.text) { - field.error = true - field.assistiveText = qsTr("Port numbers must be different") - return + return qsTr("Port numbers must be different") } - field.error = false - field.assistiveText = "" + return } function isPortFree(field) { - field.error = false - field.assistiveText = "" - var num = field.text*1 - if (num == root.backend.portIMAP) return true - if (num == root.backend.portSMTP) return true + if (num === root.backend.portIMAP) return true + if (num === root.backend.portSMTP) return true if (!root.backend.isPortFree(num)) { field.error = true - field.assistiveText = qsTr("Port occupied") - submitButton.loading = false + field.errorString = qsTr("Port occupied") return false } return true } - function submit(){ - submitButton.loading = true - if (!isPortFree(imapField)) return - if (!isPortFree(smtpField)) return - root.backend.changePorts(imapField.text, smtpField.text) - } - function setDefaultValues(){ imapField.text = backend.portIMAP smtpField.text = backend.portSMTP diff --git a/internal/frontend/qml/Proton/TextArea.qml b/internal/frontend/qml/Proton/TextArea.qml index 7a3d0d19..d834c562 100644 --- a/internal/frontend/qml/Proton/TextArea.qml +++ b/internal/frontend/qml/Proton/TextArea.qml @@ -20,10 +20,11 @@ import QtQuick 2.12 import QtQuick.Controls 2.12 import QtQuick.Controls.impl 2.12 import QtQuick.Templates 2.12 as T +import QtQuick.Layouts 1.12 import "." as Proton -Item { +FocusScope { id: root property ColorScheme colorScheme @@ -84,191 +85,269 @@ Item { property alias textFormat: control.textFormat property alias textMargin: control.textMargin property alias topPadding: control.topPadding + // We are using our own type of validators. It should be a function + // returning an error string in case of error and undefined if no error + property var validator property alias verticalAlignment: control.verticalAlignment property alias wrapMode: control.wrapMode - implicitWidth: background.width - implicitHeight: control.implicitHeight + Math.max( - label.implicitHeight + label.anchors.topMargin + label.anchors.bottomMargin, - hint.implicitHeight + hint.anchors.topMargin + hint.anchors.bottomMargin - ) + assistiveText.implicitHeight + implicitWidth: children[0].implicitWidth + implicitHeight: children[0].implicitHeight property alias label: label.text property alias hint: hint.text - property alias assistiveText: assistiveText.text + property string assistiveText + property string errorString property bool error: false signal editingFinished() - // Backgroud is moved away from within control as it will be clipped with scrollview - Rectangle { - id: background + function append(text) { return control.append(text) } + function clear() { return control.clear() } + function copy() { return control.copy() } + function cut() { return control.cut() } + function deselect() { return control.deselect() } + function getFormattedText(start, end) { return control.getFormattedText(start, end) } + function getText(start, end) { return control.getText(start, end) } + function insert(position, text) { return control.insert(position, text) } + function isRightToLeft(start, end) { return control.isRightToLeft(start, end) } + function linkAt(x, y) { return control.linkAt(x, y) } + function moveCursorSelection(position, mode) { return control.moveCursorSelection(position, mode) } + function paste() { return control.paste() } + function positionAt(x, y) { return control.positionAt(x, y) } + function positionToRectangle(position) { return control.positionToRectangle(position) } + function redo() { return control.redo() } + function remove(start, end) { return control.remove(start, end) } + function select(start, end) { return control.select(start, end) } + function selectAll() { return control.selectAll() } + function selectWord() { return control.selectWord() } + function undo() { return control.undo() } - anchors.fill: controlView + ColumnLayout { + anchors.fill: parent + spacing: 0 - radius: 4 - visible: true - color: root.colorScheme.background_norm - border.color: { - if (!control.enabled) { - return root.colorScheme.field_disabled + RowLayout { + Layout.fillWidth: true + spacing: 0 + + Proton.Label { + colorScheme: root.colorScheme + id: label + + Layout.fillWidth: true + + color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled + + type: Proton.Label.LabelType.Body_semibold } - if (control.activeFocus) { - return root.colorScheme.interaction_norm + Proton.Label { + colorScheme: root.colorScheme + id: hint + + Layout.fillWidth: true + + color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled + horizontalAlignment: Text.AlignRight + type: Proton.Label.LabelType.Caption } - - if (root.error) { - return root.colorScheme.signal_danger - } - - if (control.hovered) { - return root.colorScheme.field_hover - } - - return root.colorScheme.field_norm - } - border.width: 1 - } - - Proton.Label { - colorScheme: root.colorScheme - id: label - - anchors.top: root.top - anchors.left: root.left - anchors.bottomMargin: 4 - - color: root.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled - - type: Proton.Label.LabelType.Body_semibold - } - - Proton.Label { - colorScheme: root.colorScheme - id: hint - - anchors.right: root.right - anchors.bottom: controlView.top - anchors.bottomMargin: 5 - - color: root.enabled ? root.colorScheme.text_weak : root.colorScheme.text_disabled - - type: Proton.Label.LabelType.Caption - } - - ColorImage { - id: errorIcon - visible: root.error - anchors.left: parent.left - anchors.top: assistiveText.top - anchors.bottom: assistiveText.bottom - source: "../icons/ic-exclamation-circle-filled.svg" - sourceSize.height: height - color: root.colorScheme.signal_danger - } - - Proton.Label { - colorScheme: root.colorScheme - id: assistiveText - - anchors.left: root.error ? errorIcon.right : parent.left - anchors.leftMargin: root.error ? 5 : 0 - anchors.bottom: root.bottom - anchors.topMargin: 4 - - color: { - if (!root.enabled) { - return root.colorScheme.text_disabled - } - - if (root.error) { - return root.colorScheme.signal_danger - } - - return root.colorScheme.text_weak } - type: root.error ? Proton.Label.LabelType.Caption_semibold : Proton.Label.LabelType.Caption - } + ScrollView { + id: controlView - ScrollView { - id: controlView + Layout.fillHeight: true + Layout.fillWidth: true - anchors.top: label.bottom - anchors.left: root.left - anchors.right: root.right - anchors.bottom: assistiveText.top + clip: true - clip: true + T.TextArea { + id: control - T.TextArea { - id: control + implicitWidth: Math.max( + contentWidth + leftPadding + rightPadding, + implicitBackgroundWidth + leftInset + rightInset, + placeholder.implicitWidth + leftPadding + rightPadding + ) + implicitHeight: Math.max( + contentHeight + topPadding + bottomPadding, + implicitBackgroundHeight + topInset + bottomInset, + placeholder.implicitHeight + topPadding + bottomPadding + ) - implicitWidth: Math.max( - contentWidth + leftPadding + rightPadding, - implicitBackgroundWidth + leftInset + rightInset, - placeholder.implicitWidth + leftPadding + rightPadding - ) - implicitHeight: Math.max( - contentHeight + topPadding + bottomPadding, - implicitBackgroundHeight + topInset + bottomInset, - placeholder.implicitHeight + topPadding + bottomPadding - ) + topPadding: 8 + bottomPadding: 8 + leftPadding: 12 + rightPadding: 12 - padding: 8 - leftPadding: 12 + font.family: Style.font_family + font.weight: Style.fontWeight_400 + font.pixelSize: Style.body_font_size + font.letterSpacing: Style.body_letter_spacing - color: control.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled - placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled + color: control.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled + placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled + selectionColor: control.palette.highlight + selectedTextColor: control.palette.highlightedText - selectionColor: control.palette.highlight - selectedTextColor: control.palette.highlightedText + onEditingFinished: root.editingFinished() - onEditingFinished: root.editingFinished() + wrapMode: TextInput.Wrap - cursorDelegate: Rectangle { - id: cursor - width: 1 - color: root.colorScheme.interaction_norm - visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd + // enforcing default focus here within component + focus: root.focus - Connections { - target: control - onCursorPositionChanged: { - // keep a moving cursor visible - cursor.opacity = 1 - timer.restart() + KeyNavigation.priority: root.KeyNavigation.priority + KeyNavigation.backtab: root.KeyNavigation.backtab + KeyNavigation.tab: root.KeyNavigation.tab + KeyNavigation.up: root.KeyNavigation.up + KeyNavigation.down: root.KeyNavigation.down + KeyNavigation.left: root.KeyNavigation.left + KeyNavigation.right: root.KeyNavigation.right + + selectByMouse: true + + cursorDelegate: Rectangle { + id: cursor + width: 1 + color: root.colorScheme.interaction_norm + visible: control.activeFocus && !control.readOnly && control.selectionStart === control.selectionEnd + + Connections { + target: control + onCursorPositionChanged: { + // keep a moving cursor visible + cursor.opacity = 1 + timer.restart() + } + } + + Timer { + id: timer + running: control.activeFocus && !control.readOnly + repeat: true + interval: Qt.styleHints.cursorFlashTime / 2 + onTriggered: cursor.opacity = !cursor.opacity ? 1 : 0 + // force the cursor visible when gaining focus + onRunningChanged: cursor.opacity = 1 } } - Timer { - id: timer - running: control.activeFocus && !control.readOnly - repeat: true - interval: Qt.styleHints.cursorFlashTime / 2 - onTriggered: cursor.opacity = !cursor.opacity ? 1 : 0 - // force the cursor visible when gaining focus - onRunningChanged: cursor.opacity = 1 + PlaceholderText { + id: placeholder + x: control.leftPadding + y: control.topPadding + width: control.width - (control.leftPadding + control.rightPadding) + height: control.height - (control.topPadding + control.bottomPadding) + + text: control.placeholderText + font: control.font + color: control.placeholderTextColor + verticalAlignment: control.verticalAlignment + visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter) + elide: Text.ElideRight + renderType: control.renderType + } + + background: Rectangle { + anchors.fill: parent + + radius: 4 + visible: true + color: root.colorScheme.background_norm + border.color: { + if (!control.enabled) { + return root.colorScheme.field_disabled + } + + if (control.activeFocus) { + return root.colorScheme.interaction_norm + } + + if (root.error) { + return root.colorScheme.signal_danger + } + + if (control.hovered) { + return root.colorScheme.field_hover + } + + return root.colorScheme.field_norm + } + border.width: 1 } } + } - PlaceholderText { - id: placeholder - x: control.leftPadding - y: control.topPadding - width: control.width - (control.leftPadding + control.rightPadding) - height: control.height - (control.topPadding + control.bottomPadding) + RowLayout { + Layout.fillWidth: true + spacing: 0 - text: control.placeholderText - font: control.font - color: control.placeholderTextColor - verticalAlignment: control.verticalAlignment - visible: !control.length && !control.preeditText && (!control.activeFocus || control.horizontalAlignment !== Qt.AlignHCenter) - elide: Text.ElideRight - renderType: control.renderType + ColorImage { + id: errorIcon + + Layout.rightMargin: 4 + + visible: root.error && (assistiveText.text.length > 0) + source: "../icons/ic-exclamation-circle-filled.svg" + color: root.colorScheme.signal_danger + height: assistiveText.height + sourceSize.height: assistiveText.height + } + + Proton.Label { + colorScheme: root.colorScheme + id: assistiveText + + Layout.fillWidth: true + + text: root.error ? root.errorString : root.assistiveText + + color: { + if (!root.enabled) { + return root.colorScheme.text_disabled + } + + if (root.error) { + return root.colorScheme.signal_danger + } + + return root.colorScheme.text_weak + } + + type: root.error ? Proton.Label.LabelType.Caption_semibold : Proton.Label.LabelType.Caption } } } + + property bool validateOnEditingFinished: true + onEditingFinished: { + if (!validateOnEditingFinished) { + return + } + validate() + } + + function validate() { + if (validator === undefined) { + return + } + + var error = validator(text) + + if (error) { + root.error = true + root.errorString = error + } else { + root.error = false + root.errorString = "" + } + } + + onTextChanged: { + root.error = false + root.errorString = "" + } } diff --git a/internal/frontend/qml/Proton/TextField.qml b/internal/frontend/qml/Proton/TextField.qml index d3bfbd8a..5a6ec6d7 100644 --- a/internal/frontend/qml/Proton/TextField.qml +++ b/internal/frontend/qml/Proton/TextField.qml @@ -24,7 +24,7 @@ import QtQuick.Layouts 1.12 import "." as Proton -Item { +FocusScope { id: root property ColorScheme colorScheme @@ -82,7 +82,9 @@ Item { property alias selectionEnd: control.selectionEnd property alias selectionStart: control.selectionStart property alias text: control.text - property alias validator: control.validator + // We are using our own type of validators. It should be a function + // returning an error string in case of error and undefined if no error + property var validator property alias verticalAlignment: control.verticalAlignment property alias wrapMode: control.wrapMode @@ -91,7 +93,8 @@ Item { property alias label: label.text property alias hint: hint.text - property alias assistiveText: assistiveText.text + property string assistiveText + property string errorString property int echoMode: TextInput.Normal @@ -200,18 +203,38 @@ Item { contentHeight + topPadding + bottomPadding, placeholder.implicitHeight + topPadding + bottomPadding) - padding: 8 + topPadding: 8 + bottomPadding: 8 leftPadding: 12 + rightPadding: 12 + + font.family: Style.font_family + font.weight: Style.fontWeight_400 + font.pixelSize: Style.body_font_size + font.letterSpacing: Style.body_letter_spacing color: control.enabled ? root.colorScheme.text_norm : root.colorScheme.text_disabled - + placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled selectionColor: control.palette.highlight selectedTextColor: control.palette.highlightedText - placeholderTextColor: control.enabled ? root.colorScheme.text_hint : root.colorScheme.text_disabled + verticalAlignment: TextInput.AlignVCenter echoMode: eyeButton.checked ? TextInput.Normal : root.echoMode + // enforcing default focus here within component + focus: true + + KeyNavigation.priority: root.KeyNavigation.priority + KeyNavigation.backtab: root.KeyNavigation.backtab + KeyNavigation.tab: root.KeyNavigation.tab + KeyNavigation.up: root.KeyNavigation.up + KeyNavigation.down: root.KeyNavigation.down + KeyNavigation.left: root.KeyNavigation.left + KeyNavigation.right: root.KeyNavigation.right + + selectByMouse: true + cursorDelegate: Rectangle { id: cursor width: 1 @@ -292,6 +315,8 @@ Item { ColorImage { id: errorIcon + Layout.rightMargin: 4 + visible: root.error && (assistiveText.text.length > 0) source: "../icons/ic-exclamation-circle-filled.svg" color: root.colorScheme.signal_danger @@ -305,7 +330,8 @@ Item { Layout.fillHeight: true Layout.fillWidth: true - Layout.leftMargin: 4 + + text: root.error ? root.errorString : root.assistiveText color: { if (!root.enabled) { @@ -323,4 +349,33 @@ Item { } } } + + property bool validateOnEditingFinished: true + onEditingFinished: { + if (!validateOnEditingFinished) { + return + } + validate() + } + + function validate() { + if (validator === undefined) { + return + } + + var error = validator(text) + + if (error) { + root.error = true + root.errorString = error + } else { + root.error = false + root.errorString = "" + } + } + + onTextChanged: { + root.error = false + root.errorString = "" + } } diff --git a/internal/frontend/qml/SignIn.qml b/internal/frontend/qml/SignIn.qml index 98ec7469..09725265 100644 --- a/internal/frontend/qml/SignIn.qml +++ b/internal/frontend/qml/SignIn.qml @@ -99,7 +99,7 @@ Item { twoFactorPasswordTextField.enabled = true twoFactorPasswordTextField.error = true - twoFactorPasswordTextField.assistiveText = qsTr("Your code is incorrect") + twoFactorPasswordTextField.errorString = qsTr("Your code is incorrect") } onLogin2FAErrorAbort: { console.assert(stackLayout.currentIndex == 1, "Unexpected login2FAErrorAbort") @@ -118,7 +118,7 @@ Item { secondPasswordTextField.enabled = true secondPasswordTextField.error = true - secondPasswordTextField.assistiveText = qsTr("Your mailbox password is incorrect") + secondPasswordTextField.errorString = qsTr("Your mailbox password is incorrect") } onLogin2PasswordErrorAbort: { console.assert(stackLayout.currentIndex == 2, "Unexpected login2PasswordErrorAbort") @@ -142,11 +142,11 @@ Item { usernameTextField.enabled = true usernameTextField.error = false - usernameTextField.assistiveText = "" + usernameTextField.errorString = "" passwordTextField.enabled = true passwordTextField.error = false - passwordTextField.assistiveText = "" + passwordTextField.errorString = "" passwordTextField.text = "" } @@ -202,18 +202,22 @@ Item { Layout.fillWidth: true Layout.topMargin: 24 - onTextEdited: { // TODO: repeating? + onTextChanged: { + // remove "invalid username / password error" if (error || errorLabel.text.length > 0) { errorLabel.text = "" - usernameTextField.error = false - usernameTextField.assistiveText = "" - passwordTextField.error = false - passwordTextField.assistiveText = "" } } + validator: function(str) { + if (str.length === 0) { + return qsTr("Enter username or email") + } + return + } + onAccepted: passwordTextField.forceActiveFocus() } @@ -226,18 +230,22 @@ Item { Layout.topMargin: 8 echoMode: TextInput.Password - onTextEdited: { + onTextChanged: { + // remove "invalid username / password error" if (error || errorLabel.text.length > 0) { errorLabel.text = "" - usernameTextField.error = false - usernameTextField.assistiveText = "" - passwordTextField.error = false - passwordTextField.assistiveText = "" } } + validator: function(str) { + if (str.length === 0) { + return qsTr("Enter password") + } + return + } + onAccepted: signInButton.checkAndSignIn() } @@ -253,27 +261,10 @@ Item { onClicked: checkAndSignIn() function checkAndSignIn() { - var err = false + usernameTextField.validate() + passwordTextField.validate() - if (usernameTextField.text.length == 0) { - usernameTextField.error = true - usernameTextField.assistiveText = qsTr("Enter username or email") - err = true - } else { - usernameTextField.error = false - usernameTextField.assistiveText = qsTr("") - } - - if (passwordTextField.text.length == 0) { - passwordTextField.error = true - passwordTextField.assistiveText = qsTr("Enter password") - err = true - } else { - passwordTextField.error = false - passwordTextField.assistiveText = qsTr("") - } - - if (err) { + if (usernameTextField.error || passwordTextField.error) { return } @@ -310,8 +301,8 @@ Item { twoFactorPasswordTextField.enabled = true twoFactorPasswordTextField.error = false - twoFactorPasswordTextField.assistiveText = "" - twoFactorPasswordTextField.text="" + twoFactorPasswordTextField.errorString = "" + twoFactorPasswordTextField.text = "" } spacing: 0 @@ -343,11 +334,11 @@ Item { Layout.fillWidth: true Layout.topMargin: 32 - onTextEdited: { - if (error) { - twoFactorPasswordTextField.error = false - twoFactorPasswordTextField.assistiveText = "" + validator: function(str) { + if (str.length === 0) { + return qsTr("Enter username or email") } + return } } @@ -360,18 +351,9 @@ Item { Layout.topMargin: 24 onClicked: { - var err = false + twoFactorPasswordTextField.validate() - if (twoFactorPasswordTextField.text.length == 0) { - twoFactorPasswordTextField.error = true - twoFactorPasswordTextField.assistiveText = qsTr("Enter username or email") - err = true - } else { - twoFactorPasswordTextField.error = false - twoFactorPasswordTextField.assistiveText = qsTr("") - } - - if (err) { + if (twoFactorPasswordTextField.error) { return } @@ -393,7 +375,7 @@ Item { secondPasswordTextField.enabled = true secondPasswordTextField.error = false - secondPasswordTextField.assistiveText = "" + secondPasswordTextField.errorString = "" secondPasswordTextField.text = "" } @@ -416,11 +398,11 @@ Item { Layout.topMargin: 8 + implicitHeight + 24 + subTitle.implicitHeight echoMode: TextInput.Password - onTextEdited: { - if (error) { - secondPasswordTextField.error = false - secondPasswordTextField.assistiveText = "" + validator: function(str) { + if (str.length === 0) { + return qsTr("Enter username or email") } + return } } @@ -433,18 +415,9 @@ Item { Layout.topMargin: 24 onClicked: { - var err = false + secondPasswordTextField.validate() - if (secondPasswordTextField.text.length == 0) { - secondPasswordTextField.error = true - secondPasswordTextField.assistiveText = qsTr("Enter username or email") - err = true - } else { - secondPasswordTextField.error = false - secondPasswordTextField.assistiveText = qsTr("") - } - - if (err) { + if (secondPasswordTextField.error) { return } diff --git a/internal/frontend/qml/tests/TextAreas.qml b/internal/frontend/qml/tests/TextAreas.qml index 9f2531ee..94fb5173 100644 --- a/internal/frontend/qml/tests/TextAreas.qml +++ b/internal/frontend/qml/tests/TextAreas.qml @@ -22,66 +22,92 @@ import QtQuick.Controls 2.12 import "../Proton" -RowLayout { +ColumnLayout { id: root property ColorScheme colorScheme - ColumnLayout { + spacing: 10 + + TextArea { + colorScheme: root.colorScheme Layout.fillWidth: true + Layout.preferredHeight: 100 - spacing: parent.spacing + placeholderText: "Placeholder" + label: "Label" + hint: "Hint" + assistiveText: "Assistive text" - TextArea { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.preferredHeight: 100 + wrapMode: TextInput.Wrap + } - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" - } + TextArea { + colorScheme: root.colorScheme + Layout.fillWidth: true + Layout.preferredHeight: 100 - TextArea { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.preferredHeight: 100 + text: "Value" + placeholderText: "Placeholder" + label: "Label" + hint: "Hint" + assistiveText: "Assistive text" - text: "Value" - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" - } + wrapMode: TextInput.Wrap + } - TextArea { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.preferredHeight: 100 + TextArea { + colorScheme: root.colorScheme + Layout.fillWidth: true + Layout.preferredHeight: 100 - error: true + error: true - text: "Value" - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - assistiveText: "Error message" - } + text: "Value" + placeholderText: "Placeholder" + label: "Label" + hint: "Hint" + errorString: "Error message" + + wrapMode: TextInput.Wrap + } - TextArea { - colorScheme: root.colorScheme - Layout.fillWidth: true - Layout.preferredHeight: 100 + TextArea { + colorScheme: root.colorScheme + Layout.fillWidth: true + Layout.preferredHeight: 100 - enabled: false + enabled: false - text: "Value" - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" + text: "Value" + placeholderText: "Placeholder" + label: "Label" + hint: "Hint" + assistiveText: "Assistive text" + + wrapMode: TextInput.Wrap + } + + TextArea { + colorScheme: root.colorScheme + Layout.fillWidth: true + Layout.preferredHeight: 100 + + placeholderText: "Type 42 here" + label: "42 Validator" + hint: "Accepts only \"42\"" + assistiveText: "Type sometihng here, preferably 42" + + wrapMode: TextInput.Wrap + + validator: function(str) { + if (str === "42") { + return + } + + return "Not 42" } } } + diff --git a/internal/frontend/qml/tests/TextFields.qml b/internal/frontend/qml/tests/TextFields.qml index 9c6e29ec..2a3e7547 100644 --- a/internal/frontend/qml/tests/TextFields.qml +++ b/internal/frontend/qml/tests/TextFields.qml @@ -63,7 +63,7 @@ RowLayout { placeholderText: "Placeholder" label: "Label" hint: "Hint" - assistiveText: "Error message" + errorString: "Error message" } @@ -119,7 +119,7 @@ RowLayout { placeholderText: "Password" label: "Label" hint: "Hint" - assistiveText: "Error message" + errorString: "Error message" } TextField { @@ -146,10 +146,18 @@ RowLayout { colorScheme: root.colorScheme Layout.fillWidth: true - placeholderText: "Placeholder" - label: "Label" - hint: "Hint" - assistiveText: "Assistive text" + placeholderText: "Type 42 here" + label: "42 Validator" + hint: "Accepts only \"42\"" + assistiveText: "Type sometihng here, preferably 42" + + validator: function(str) { + if (str === "42") { + return + } + + return "Not 42" + } } TextField {