diff --git a/qml/AboutPopup.qml b/qml/AboutPopup.qml index c9a6c619..20deadd2 100644 --- a/qml/AboutPopup.qml +++ b/qml/AboutPopup.qml @@ -6,11 +6,11 @@ Dialog { margins: 0 spacing: 0 modal: true + focus: true + x: (parent.width - width) / 2 y: (parent.height - height) / 2 - - width: app.width > 600 ? 600 : app.width * 0.90 - focus: true + width: app.width * 0.9 > 600 ? 600 : app.width * 0.9 background: Rectangle { color: defaultBackground @@ -29,7 +29,6 @@ Dialog { Component.onCompleted: { navigator.isShowingAbout = true - btnCancel.forceActiveFocus() } function getDeviceDescription() { @@ -171,23 +170,5 @@ Dialog { Layout.maximumWidth: parent.width width: parent.width } - - DialogButtonBox { - Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - Layout.topMargin: 14 - Layout.rightMargin: -22 - Layout.bottomMargin: -22 - - StyledButton { - id: btnCancel - text: qsTr("Close") - flat: true - enabled: true - font.capitalization: Font.capitalization - DialogButtonBox.buttonRole: DialogButtonBox.RejectRole - Keys.onReturnPressed: reject() - onClicked: reject() - } - } } } diff --git a/qml/ConfirmationPopup.qml b/qml/ConfirmationPopup.qml index a45ef6f8..c2b55cfc 100644 --- a/qml/ConfirmationPopup.qml +++ b/qml/ConfirmationPopup.qml @@ -3,14 +3,15 @@ import QtQuick.Controls 2.2 import QtQuick.Layouts 1.2 Dialog { + padding: 16 margins: 0 spacing: 0 modal: true + focus: true + x: (parent.width - width) / 2 y: (parent.height - height) / 2 - - width: app.width > 600 ? 600 : app.width * 0.8 - focus: true + width: app.width * 0.9 > 600 ? 600 : app.width * 0.9 background: Rectangle { color: defaultBackground @@ -35,50 +36,87 @@ Dialog { Component.onCompleted: btnCancel.forceActiveFocus() property var acceptedCb - property string heading - property string message + property string primaryMessage + property string secondaryMessage property string buttonCancel: qsTr("Cancel") property string buttonAccept: qsTr("Accept") ColumnLayout { width: parent.width - Layout.fillWidth: true - spacing: 0 Label { - id: confirmationHeading text: heading font.pixelSize: 14 font.weight: Font.Medium - width: parent.width wrapMode: Text.WordWrap Layout.maximumWidth: parent.width } + Pane { + padding: 12 + rightPadding: 16 + bottomPadding: 8 + visible: primaryMessage + width: parent.width + Layout.topMargin: 16 + Layout.maximumWidth: parent.width + Layout.fillWidth: true + background: Rectangle { + color: yubicoRed + } + + RowLayout { + spacing: 0 + width: parent.width + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + + StyledImage { + source: "../images/warning.svg" + color: yubicoWhite + iconWidth: 32 + iconHeight: 32 + Layout.alignment: Qt.AlignLeft | Qt.AlignTop + Layout.maximumWidth: 32 + } + + Label { + text: primaryMessage + color: yubicoWhite + font.pixelSize: 13 + font.weight: Font.Bold + lineHeight: 1.2 + leftPadding: 12 + wrapMode: Text.WordWrap + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + Layout.fillWidth: true + } + } + } + Label { - id: confirmationLbl - text: message + Layout.topMargin: 16 + text: secondaryMessage color: formText font.pixelSize: 13 lineHeight: 1.2 + visible: secondaryMessage wrapMode: Text.WordWrap - Layout.topMargin: 16 Layout.maximumWidth: parent.width - width: parent.width } DialogButtonBox { Layout.alignment: Qt.AlignRight | Qt.AlignVCenter - Layout.topMargin: 14 - Layout.rightMargin: -22 - Layout.bottomMargin: -22 + Layout.topMargin: 8 + Layout.rightMargin: -8 + Layout.bottomMargin: -8 StyledButton { id: btnAccept text: qsTr(buttonAccept) flat: true enabled: true + critical: primaryMessage font.capitalization: Font.capitalization DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole KeyNavigation.tab: btnCancel @@ -89,6 +127,7 @@ Dialog { id: btnCancel text: qsTr(buttonCancel) flat: true + critical: primaryMessage enabled: true font.capitalization: Font.capitalization DialogButtonBox.buttonRole: DialogButtonBox.RejectRole diff --git a/qml/CredentialCard.qml b/qml/CredentialCard.qml index 8b721cc5..51653ba1 100644 --- a/qml/CredentialCard.qml +++ b/qml/CredentialCard.qml @@ -75,14 +75,6 @@ Pane { text: qsTr("Copy to clipboard") onTriggered: calculateCard(true) } - MenuItem { - icon.source: "../images/delete.svg" - icon.color: iconButtonNormal - icon.width: 20 - icon.height: 20 - text: "Delete account" - onTriggered: deleteCard() - } MenuItem { icon.source: favorite ? "../images/star.svg" : "../images/star_border.svg" icon.color: iconButtonNormal @@ -91,32 +83,13 @@ Pane { text: favorite ? qsTr("Remove as favorite") : qsTr("Set as favorite") onTriggered: toggleFavorite() } - MenuSeparator { - padding: 0 - topPadding: 4 - bottomPadding: 4 - contentItem: Rectangle { - implicitWidth: 200 - implicitHeight: 1 - color: formUnderline - } - } MenuItem { - icon.source: "../images/add.svg" + icon.source: "../images/delete.svg" icon.color: iconButtonNormal icon.width: 20 icon.height: 20 - enabled: !!yubiKey.currentDevice && yubiKey.currentDeviceValidated - text: qsTr("Add account") - onTriggered: yubiKey.scanQr() - } - MenuItem { - icon.source: "../images/cogwheel.svg" - icon.color: iconButtonNormal - icon.width: 20 - icon.height: 20 - text: qsTr("Settings") - onTriggered: navigator.goToSettings() + text: "Delete account" + onTriggered: deleteCard() } } } @@ -244,8 +217,9 @@ Pane { function deleteCard() { navigator.confirm( - "Delete " + formattedName() + " ?", - qsTr("This will permanently delete the account from the YubiKey, as well as your ability to generate security codes for it. Make sure 2FA has been disabled BEFORE proceeding."), + qsTr("Delete %1 ?").arg(formattedName()), + qsTr("This will permanently delete the account from your YubiKey."), + qsTr("You will not be able to generate security codes for the account anymore. Make sure 2FA has been disabled before proceeding."), function () { if (settings.otpMode) { yubiKey.otpDeleteCredential(credential, diff --git a/qml/Navigator.qml b/qml/Navigator.qml index f255b765..752719cd 100644 --- a/qml/Navigator.qml +++ b/qml/Navigator.qml @@ -80,10 +80,11 @@ StackView { }), StackView.Immediate) } - function confirm(heading, message, cb) { + function confirm(heading, primaryMessage, secondaryMessage, cb) { var popup = confirmationPopup.createObject(app, { "heading": heading, - "message": message, + "primaryMessage": primaryMessage, + "secondaryMessage": secondaryMessage, "acceptedCb": cb }) popup.open() diff --git a/qml/NewCredentialView.qml b/qml/NewCredentialView.qml index c0ec6144..cf93a763 100644 --- a/qml/NewCredentialView.qml +++ b/qml/NewCredentialView.qml @@ -51,7 +51,8 @@ Flickable { navigator.confirm( qsTr("Overwrite?"), qsTr("An account with this name already exists, do you want to overwrite it?"), - _ccidAddCredentialOverwrite) + "", + _ccidAddCredentialOverwrite) } else { navigator.snackBarError(navigator.getErrorMessage(resp.error_id)) console.log("addCredential failed:", resp.error_id) @@ -93,7 +94,8 @@ Flickable { otpSlotComboBox.currentText) - 1]) { navigator.confirm( qsTr("Overwrite?"), - qsTr("The slot is already configured, do you want to overwrite it?"), + qsTr("This slot is already configured, do you want to overwrite it?"), + "", _otpAddCredential) } else { _otpAddCredential() diff --git a/qml/SettingsView.qml b/qml/SettingsView.qml index aa8570c3..a5193559 100644 --- a/qml/SettingsView.qml +++ b/qml/SettingsView.qml @@ -27,16 +27,16 @@ Flickable { Keys.onEscapePressed: navigator.home() function getDeviceLabel(device) { - if (!!device.serial) { - return ("%1 [#%2]").arg(device.name).arg(device.serial) - } else { + if (!!device) { return ("%1").arg(device.name) + } else { + return qsTr("Insert your YubiKey") } } - function getDeviceDescription() { - if (!!yubiKey.currentDevice) { - return yubiKey.currentDevice.usbInterfacesEnabled.join('+') + function getDeviceDescription(device) { + if (!!device) { + return qsTr("Serial number: %1").arg(!!device.serial ? device.serial : "Not Available") } else if (yubiKey.availableDevices.length > 0 && !yubiKey.availableDevices.some(dev => dev.selectable)) { return qsTr("No compatible device found") @@ -45,7 +45,6 @@ Flickable { } } - function clearPasswordFields() { currentPasswordField.text = "" newPasswordField.text = "" @@ -184,8 +183,8 @@ Flickable { StyledExpansionPanel { id: currentDevicePanel - label: !!yubiKey.currentDevice ? getDeviceLabel(yubiKey.currentDevice) : qsTr("Insert your YubiKey") - description: getDeviceDescription() + label: getDeviceLabel(yubiKey.currentDevice) + description: getDeviceDescription(yubiKey.currentDevice) keyImage: !!yubiKey.currentDevice ? yubiKey.getCurrentDeviceImage() : "../images/yubikeys-large-transparent" isTopPanel: true Layout.fillWidth: true @@ -200,14 +199,15 @@ Flickable { Repeater { model: yubiKey.availableDevices - RadioButton { + StyledRadioButton { Layout.fillWidth: true objectName: index checked: !!yubiKey.currentDevice && modelData.serial === yubiKey.currentDevice.serial text: getDeviceLabel(modelData) + description: getDeviceDescription(modelData) enabled: modelData.selectable - ButtonGroup.group: deviceButtonGroup + buttonGroup: deviceButtonGroup } } @@ -285,6 +285,7 @@ Flickable { flat: true onClicked: navigator.confirm( qsTr("Remove password?"), + "", qsTr("A password will not be required to access the accounts anymore."), function () { removePassword() @@ -302,14 +303,15 @@ Flickable { StyledExpansionPanel { label: qsTr("Reset") - description: qsTr("Warning: Reset will delete all accounts from the YubiKey and restore factory defaults.") + description: qsTr("Warning: Reset will delete all accounts and restore factory defaults.") isEnabled: false visible: !!yubiKey.currentDevice && !settings.otpMode toolButtonIcon: "../images/reset.svg" - toolButtonToolTip: qsTr("Reset OATH Application") + toolButtonToolTip: qsTr("Reset device") toolButton.onClicked: navigator.confirm( - qsTr("Reset OATH application?"), - qsTr("This will delete all accounts and restore factory defaults."), + qsTr("Reset device?"), + qsTr("This will delete all accounts and restore factory defaults of your YubiKey."), + qsTr("There is NO going back from here, if you do not know what you are doing, do NOT do this."), function () { navigator.goToLoading() yubiKey.reset(function (resp) { @@ -339,7 +341,7 @@ Flickable { StyledExpansionPanel { label: qsTr("Appearance") - description: qsTr("Change the appearance of the application.") + description: qsTr("Change the visual appearance of the application.") isTopPanel: true ColumnLayout { diff --git a/qml/StyledButton.qml b/qml/StyledButton.qml index c0205f2e..9a57d6b0 100644 --- a/qml/StyledButton.qml +++ b/qml/StyledButton.qml @@ -7,12 +7,13 @@ import QtGraphicalEffects 1.0 Button { property alias toolTipText: buttonToolTip.text + property bool critical: false id: button font.capitalization: Font.capitalization font.weight: Font.Medium - Material.foreground: button.flat ? Material.primary : yubicoWhite - Material.background: button.flat ? "transparent" : Material.primary + Material.foreground: button.flat ? (critical ? yubicoRed : Material.primary) : yubicoWhite + Material.background: button.flat ? "transparent" : (critical ? yubicoRed : Material.primary) Material.elevation: button.flat ? 0 : 1 ToolTip { diff --git a/qml/StyledExpansionPanel.qml b/qml/StyledExpansionPanel.qml index 801cd275..a76a665c 100644 --- a/qml/StyledExpansionPanel.qml +++ b/qml/StyledExpansionPanel.qml @@ -150,7 +150,8 @@ Pane { color: formLabel text: description wrapMode: Text.WordWrap - Layout.rowSpan: 1 + maximumLineCount: isExpanded ? 4 : 2 + elide: Text.ElideRight lineHeight: 1.1 visible: description } diff --git a/qml/StyledRadioButton.qml b/qml/StyledRadioButton.qml new file mode 100644 index 00000000..e8983007 --- /dev/null +++ b/qml/StyledRadioButton.qml @@ -0,0 +1,45 @@ +import QtQuick 2.9 +import QtQuick.Controls 2.2 +import QtQuick.Layouts 1.3 +import QtQuick.Controls.Material 2.2 +import QtGraphicalEffects 1.0 + +Item { + + property alias text: controlItem.text + property alias description: controlDescription.text + property alias enabled: control.enabled + property alias objectName: control.objectName + property alias checked: control.checked + property var buttonGroup + + height: 40 + Layout.bottomMargin: 8 + + RowLayout { + + RadioButton { + id: control + ButtonGroup.group: buttonGroup + indicator.x: 8 + + contentItem: ColumnLayout { + + Layout.alignment: Qt.AlignLeft | Qt.AlignVCenter + + Label { + id: controlItem + leftPadding: control.indicator.width + control.spacing + control.x + color: formText + } + + Label { + id: controlDescription + leftPadding: control.indicator.width + control.spacing + control.x + color: formLabel + visible: description + } + } + } + } +} diff --git a/qml/StyledToolBar.qml b/qml/StyledToolBar.qml index 9cef97f0..d79b3558 100644 --- a/qml/StyledToolBar.qml +++ b/qml/StyledToolBar.qml @@ -148,7 +148,7 @@ ToolBar { Layout.fillWidth: true Layout.fillHeight: true placeholderText: qsTr("Quick Find") - placeholderTextColor: hovered || activeFocus ? iconButtonHovered : iconButtonNormal + placeholderTextColor: iconButtonNormal leftPadding: 28 rightPadding: 8 width: parent.width diff --git a/qml/main.qml b/qml/main.qml index 8b05f622..f0b62120 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -27,7 +27,7 @@ ApplicationWindow { readonly property string yubicoBlue: "#284c61" readonly property string yubicoWhite: "#ffffff" readonly property string yubicoGrey: "#939598" - readonly property string yubicoRed: "#fd5552" + readonly property string yubicoRed: "#dc4b4c" readonly property string defaultDark: "#303030" readonly property string defaultDarkLighter: "#383838"