From ebfebbeae13d69aafc43e1464e2ab7fb9290f767 Mon Sep 17 00:00:00 2001 From: miruka Date: Tue, 27 Aug 2019 22:25:13 -0400 Subject: [PATCH] Improve import keys password popup --- TODO.md | 7 +- src/icons/light-thin/ok.svg | 51 +++++++++++++ src/python/backend.py | 10 ++- src/qml/Base/HInterfaceBox.qml | 8 +- src/qml/Base/HPasswordPopup.qml | 97 +++++++++++++++++++----- src/qml/Base/HTextField.qml | 6 +- src/qml/Pages/EditAccount/Encryption.qml | 14 ++-- src/themes/Default.qpl | 1 + 8 files changed, 160 insertions(+), 34 deletions(-) create mode 100644 src/icons/light-thin/ok.svg diff --git a/TODO.md b/TODO.md index c709e96e..4a8f56be 100644 --- a/TODO.md +++ b/TODO.md @@ -8,8 +8,6 @@ - Remove the filled theme - Have a default background - - Use [Animators](https://doc.qt.io/qt-5/qml-qtquick-animator.html) - - Choose a better default easing type for animations - Sendbox - Room Sidepane - Hide when window too small @@ -34,6 +32,7 @@ - Terrible performance using `QT_QPA_PLATFORM=wayland-egl`, must use `xcb` - UI + - Choose a better default easing type for animations - Make invite icon blink if there's no one but ourself in the room, but never do it again once the user hovered it long enough to show tooltip or clicked on it once @@ -42,9 +41,7 @@ - Adapt UI for small heights - Popup: - - label size - - Accept/cancel buttons - - Transitions + - Transitions - Restoring UI state: - Sendbox content diff --git a/src/icons/light-thin/ok.svg b/src/icons/light-thin/ok.svg new file mode 100644 index 00000000..c2bef64e --- /dev/null +++ b/src/icons/light-thin/ok.svg @@ -0,0 +1,51 @@ + + + + + + image/svg+xml + + + + + + + + diff --git a/src/python/backend.py b/src/python/backend.py index 1cbefb43..436df170 100644 --- a/src/python/backend.py +++ b/src/python/backend.py @@ -121,13 +121,21 @@ class Backend: # General functions - @staticmethod def hsluv(hue: int, saturation: int, lightness: int) -> List[float]: # (0-360, 0-100, 0-100) -> [0-1, 0-1, 0-1] return hsluv.hsluv_to_rgb([hue, saturation, lightness]) + @staticmethod + def check_exported_keys_password(file_path: str, password: str) -> bool: + try: + nio.crypto.key_export.decrypt_and_read(file_path, password) + return True + except (FileNotFoundError, ValueError): + return False + + async def load_settings(self) -> tuple: from .config_files import Theme settings = await self.ui_settings.read() diff --git a/src/qml/Base/HInterfaceBox.qml b/src/qml/Base/HInterfaceBox.qml index d6d5c8b2..9e71a8e1 100644 --- a/src/qml/Base/HInterfaceBox.qml +++ b/src/qml/Base/HInterfaceBox.qml @@ -59,11 +59,15 @@ HRectangle { spacing: interfaceBox.verticalSpacing Layout.fillWidth: true + Layout.topMargin: + interfaceTitle.visible ? 0 : interfaceBox.verticalSpacing Layout.leftMargin: interfaceBox.horizontalSpacing Layout.rightMargin: interfaceBox.horizontalSpacing } HRowLayout { + visible: buttonModel.length > 0 + Repeater { id: interfaceButtonsRepeater model: [] @@ -74,7 +78,9 @@ HRectangle { id: button text: modelData.text icon.name: modelData.iconName || "" - enabled: modelData.enabled && ! button.loading + enabled: (modelData.enabled == undefined ? + true : modelData.enabled) && + ! button.loading onClicked: buttonCallbacks[modelData.name](button) Layout.fillWidth: true diff --git a/src/qml/Base/HPasswordPopup.qml b/src/qml/Base/HPasswordPopup.qml index 0da954ba..7b1793b9 100644 --- a/src/qml/Base/HPasswordPopup.qml +++ b/src/qml/Base/HPasswordPopup.qml @@ -5,48 +5,105 @@ import "../SidePane" Popup { id: popup - width: window.width anchors.centerIn: Overlay.overlay modal: true + padding: 0 onOpened: passwordField.forceActiveFocus() + + property bool validateWhileTyping: false + + property string acceptedPassword: "" + property var passwordValid: null + property bool okClicked: false + property alias label: popupLabel property alias field: passwordField - property string password: "" + + + function verifyPassword(pass) { + // Implement me when using this component + return false + } + + + enter: Transition { + HNumberAnimation { property: "scale"; from: 0; to: 1; overshoot: 4 } + } + + exit: Transition { + HNumberAnimation { property: "scale"; to: 0 } + } background: HRectangle { color: theme.controls.popup.background } - HColumnLayout { - width: parent.width - spacing: theme.spacing + contentItem: HInterfaceBox { + id: box + implicitWidth: theme.minimumSupportedWidthPlusSpacing + enterButtonTarget: "ok" + buttonModel: [ + { name: "ok", text: qsTr("OK"), iconName: "ok", + enabled: passwordField.text && + (validateWhileTyping ? passwordValid : true) }, + { name: "cancel", text: qsTr("Cancel"), iconName: "cancel" }, + ] + buttonCallbacks: ({ + ok: button => { + let password = passwordField.text + okClicked = true + button.loading = true + + if (verifyPassword(password)) { + passwordValid = true + popup.acceptedPassword = password + popup.close() + } else { + passwordValid = false + } + + button.loading = false + }, + cancel: button => { popup.close() }, + }) + HLabel { id: popupLabel wrapMode: Text.Wrap - Layout.alignment: Qt.AlignCenter - Layout.minimumWidth: theme.minimumSupportedWidth - Layout.maximumWidth: - Math.min(480, window.width - theme.spacing * 2) + Layout.fillWidth: true } - HTextField { - id: passwordField - echoMode: TextInput.Password - focus: true - onAccepted: { - popup.password = text - popup.close() + HRowLayout { + spacing: box.horizontalSpacing + + HTextField { + id: passwordField + placeholderText: qsTr("Passphrase") + echoMode: TextInput.Password + focus: true + error: passwordValid === false + + onTextChanged: passwordValid = + validateWhileTyping ? verifyPassword(text) : null + + Layout.fillWidth: true } - Layout.alignment: Qt.AlignCenter - Layout.fillWidth: true + HIcon { + svgName: passwordValid ? "ok" : "cancel" + visible: Layout.preferredWidth > 0 - Layout.preferredWidth: popupLabel.width - Layout.maximumWidth: popupLabel.width + Layout.preferredWidth: + passwordValid == null || + (validateWhileTyping && ! okClicked && ! passwordValid) ? + 0 :implicitWidth + + Behavior on Layout.preferredWidth { HNumberAnimation {} } + } } } } diff --git a/src/qml/Base/HTextField.qml b/src/qml/Base/HTextField.qml index 7c4dfed9..f052c3ff 100644 --- a/src/qml/Base/HTextField.qml +++ b/src/qml/Base/HTextField.qml @@ -8,9 +8,12 @@ TextField { readonly property QtObject _tf: theme.controls.textField + property bool error: false + property bool bordered: true property color backgroundColor: _tf.background property color borderColor: _tf.border + property color errorBorder: _tf.errorBorder property color focusedBackgroundColor: _tf.focusedBackground property color focusedBorderColor: _tf.focusedBorder property alias radius: textFieldBackground.radius @@ -20,7 +23,8 @@ TextField { background: Rectangle { id: textFieldBackground color: field.activeFocus ? focusedBackgroundColor : backgroundColor - border.color: field.activeFocus ? focusedBorderColor : borderColor + border.color: error ? errorBorder : + field.activeFocus ? focusedBorderColor : borderColor border.width: bordered ? theme.controls.textField.borderWidth : 0 Behavior on color { HColorAnimation { factor: 0.25 } } diff --git a/src/qml/Pages/EditAccount/Encryption.qml b/src/qml/Pages/EditAccount/Encryption.qml index 6e4da196..bcbb097e 100644 --- a/src/qml/Pages/EditAccount/Encryption.qml +++ b/src/qml/Pages/EditAccount/Encryption.qml @@ -63,14 +63,16 @@ HColumnLayout { HPasswordPopup { property url file: "" + function verifyPassword(pass) { + return py.callSync( + "check_exported_keys_password", [file.toString(), pass] + ) + } + id: importPasswordPopup label.text: qsTr( - "Please enter the passphrase that was used to protect this " + - "file.\n\n" + - "The import can take a few minutes. " + - "You can leave the account settings page while it is running. " + - "Messages may not be sent or received until the operation is done." + "Please enter the passphrase that was used to protect this file:" ) - onPasswordChanged: importKeys(file, password) + onAcceptedPasswordChanged: importKeys(file, password) } } diff --git a/src/themes/Default.qpl b/src/themes/Default.qpl index d93d8995..97aa74a4 100644 --- a/src/themes/Default.qpl +++ b/src/themes/Default.qpl @@ -133,6 +133,7 @@ controls: int borderWidth: 1 color border: "transparent" color focusedBorder: colors.strongAccentBackground + color errorBorder: colors.errorText color text: colors.text color focusedText: colors.text