Merge pull request #487 from Yubico/improved-confirmation-prompts

made critical prompts more obvious and some minor cleanup
This commit is contained in:
Rikard Braathen 2019-10-31 14:12:54 +01:00 committed by GitHub
commit 1a8fbc52be
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 141 additions and 95 deletions

View File

@ -6,11 +6,11 @@ Dialog {
margins: 0 margins: 0
spacing: 0 spacing: 0
modal: true modal: true
focus: true
x: (parent.width - width) / 2 x: (parent.width - width) / 2
y: (parent.height - height) / 2 y: (parent.height - height) / 2
width: app.width * 0.9 > 600 ? 600 : app.width * 0.9
width: app.width > 600 ? 600 : app.width * 0.90
focus: true
background: Rectangle { background: Rectangle {
color: defaultBackground color: defaultBackground
@ -29,7 +29,6 @@ Dialog {
Component.onCompleted: { Component.onCompleted: {
navigator.isShowingAbout = true navigator.isShowingAbout = true
btnCancel.forceActiveFocus()
} }
function getDeviceDescription() { function getDeviceDescription() {
@ -171,23 +170,5 @@ Dialog {
Layout.maximumWidth: parent.width Layout.maximumWidth: parent.width
width: 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()
}
}
} }
} }

View File

@ -3,14 +3,15 @@ import QtQuick.Controls 2.2
import QtQuick.Layouts 1.2 import QtQuick.Layouts 1.2
Dialog { Dialog {
padding: 16
margins: 0 margins: 0
spacing: 0 spacing: 0
modal: true modal: true
focus: true
x: (parent.width - width) / 2 x: (parent.width - width) / 2
y: (parent.height - height) / 2 y: (parent.height - height) / 2
width: app.width * 0.9 > 600 ? 600 : app.width * 0.9
width: app.width > 600 ? 600 : app.width * 0.8
focus: true
background: Rectangle { background: Rectangle {
color: defaultBackground color: defaultBackground
@ -35,50 +36,87 @@ Dialog {
Component.onCompleted: btnCancel.forceActiveFocus() Component.onCompleted: btnCancel.forceActiveFocus()
property var acceptedCb property var acceptedCb
property string heading property string heading
property string message property string primaryMessage
property string secondaryMessage
property string buttonCancel: qsTr("Cancel") property string buttonCancel: qsTr("Cancel")
property string buttonAccept: qsTr("Accept") property string buttonAccept: qsTr("Accept")
ColumnLayout { ColumnLayout {
width: parent.width width: parent.width
Layout.fillWidth: true
spacing: 0
Label { Label {
id: confirmationHeading
text: heading text: heading
font.pixelSize: 14 font.pixelSize: 14
font.weight: Font.Medium font.weight: Font.Medium
width: parent.width
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
Layout.maximumWidth: parent.width 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 { Label {
id: confirmationLbl Layout.topMargin: 16
text: message text: secondaryMessage
color: formText color: formText
font.pixelSize: 13 font.pixelSize: 13
lineHeight: 1.2 lineHeight: 1.2
visible: secondaryMessage
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
Layout.topMargin: 16
Layout.maximumWidth: parent.width Layout.maximumWidth: parent.width
width: parent.width
} }
DialogButtonBox { DialogButtonBox {
Layout.alignment: Qt.AlignRight | Qt.AlignVCenter Layout.alignment: Qt.AlignRight | Qt.AlignVCenter
Layout.topMargin: 14 Layout.topMargin: 8
Layout.rightMargin: -22 Layout.rightMargin: -8
Layout.bottomMargin: -22 Layout.bottomMargin: -8
StyledButton { StyledButton {
id: btnAccept id: btnAccept
text: qsTr(buttonAccept) text: qsTr(buttonAccept)
flat: true flat: true
enabled: true enabled: true
critical: primaryMessage
font.capitalization: Font.capitalization font.capitalization: Font.capitalization
DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole DialogButtonBox.buttonRole: DialogButtonBox.AcceptRole
KeyNavigation.tab: btnCancel KeyNavigation.tab: btnCancel
@ -89,6 +127,7 @@ Dialog {
id: btnCancel id: btnCancel
text: qsTr(buttonCancel) text: qsTr(buttonCancel)
flat: true flat: true
critical: primaryMessage
enabled: true enabled: true
font.capitalization: Font.capitalization font.capitalization: Font.capitalization
DialogButtonBox.buttonRole: DialogButtonBox.RejectRole DialogButtonBox.buttonRole: DialogButtonBox.RejectRole

View File

@ -75,14 +75,6 @@ Pane {
text: qsTr("Copy to clipboard") text: qsTr("Copy to clipboard")
onTriggered: calculateCard(true) onTriggered: calculateCard(true)
} }
MenuItem {
icon.source: "../images/delete.svg"
icon.color: iconButtonNormal
icon.width: 20
icon.height: 20
text: "Delete account"
onTriggered: deleteCard()
}
MenuItem { MenuItem {
icon.source: favorite ? "../images/star.svg" : "../images/star_border.svg" icon.source: favorite ? "../images/star.svg" : "../images/star_border.svg"
icon.color: iconButtonNormal icon.color: iconButtonNormal
@ -91,32 +83,13 @@ Pane {
text: favorite ? qsTr("Remove as favorite") : qsTr("Set as favorite") text: favorite ? qsTr("Remove as favorite") : qsTr("Set as favorite")
onTriggered: toggleFavorite() onTriggered: toggleFavorite()
} }
MenuSeparator {
padding: 0
topPadding: 4
bottomPadding: 4
contentItem: Rectangle {
implicitWidth: 200
implicitHeight: 1
color: formUnderline
}
}
MenuItem { MenuItem {
icon.source: "../images/add.svg" icon.source: "../images/delete.svg"
icon.color: iconButtonNormal icon.color: iconButtonNormal
icon.width: 20 icon.width: 20
icon.height: 20 icon.height: 20
enabled: !!yubiKey.currentDevice && yubiKey.currentDeviceValidated text: "Delete account"
text: qsTr("Add account") onTriggered: deleteCard()
onTriggered: yubiKey.scanQr()
}
MenuItem {
icon.source: "../images/cogwheel.svg"
icon.color: iconButtonNormal
icon.width: 20
icon.height: 20
text: qsTr("Settings")
onTriggered: navigator.goToSettings()
} }
} }
} }
@ -244,8 +217,9 @@ Pane {
function deleteCard() { function deleteCard() {
navigator.confirm( navigator.confirm(
"Delete " + formattedName() + " ?", qsTr("Delete %1 ?").arg(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("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 () { function () {
if (settings.otpMode) { if (settings.otpMode) {
yubiKey.otpDeleteCredential(credential, yubiKey.otpDeleteCredential(credential,

View File

@ -80,10 +80,11 @@ StackView {
}), StackView.Immediate) }), StackView.Immediate)
} }
function confirm(heading, message, cb) { function confirm(heading, primaryMessage, secondaryMessage, cb) {
var popup = confirmationPopup.createObject(app, { var popup = confirmationPopup.createObject(app, {
"heading": heading, "heading": heading,
"message": message, "primaryMessage": primaryMessage,
"secondaryMessage": secondaryMessage,
"acceptedCb": cb "acceptedCb": cb
}) })
popup.open() popup.open()

View File

@ -51,7 +51,8 @@ Flickable {
navigator.confirm( navigator.confirm(
qsTr("Overwrite?"), qsTr("Overwrite?"),
qsTr("An account with this name already exists, do you want to overwrite it?"), qsTr("An account with this name already exists, do you want to overwrite it?"),
_ccidAddCredentialOverwrite) "",
_ccidAddCredentialOverwrite)
} else { } else {
navigator.snackBarError(navigator.getErrorMessage(resp.error_id)) navigator.snackBarError(navigator.getErrorMessage(resp.error_id))
console.log("addCredential failed:", resp.error_id) console.log("addCredential failed:", resp.error_id)
@ -93,7 +94,8 @@ Flickable {
otpSlotComboBox.currentText) - 1]) { otpSlotComboBox.currentText) - 1]) {
navigator.confirm( navigator.confirm(
qsTr("Overwrite?"), 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) _otpAddCredential)
} else { } else {
_otpAddCredential() _otpAddCredential()

View File

@ -27,16 +27,16 @@ Flickable {
Keys.onEscapePressed: navigator.home() Keys.onEscapePressed: navigator.home()
function getDeviceLabel(device) { function getDeviceLabel(device) {
if (!!device.serial) { if (!!device) {
return ("%1 [#%2]").arg(device.name).arg(device.serial)
} else {
return ("%1").arg(device.name) return ("%1").arg(device.name)
} else {
return qsTr("Insert your YubiKey")
} }
} }
function getDeviceDescription() { function getDeviceDescription(device) {
if (!!yubiKey.currentDevice) { if (!!device) {
return yubiKey.currentDevice.usbInterfacesEnabled.join('+') return qsTr("Serial number: %1").arg(!!device.serial ? device.serial : "Not Available")
} else if (yubiKey.availableDevices.length > 0 } else if (yubiKey.availableDevices.length > 0
&& !yubiKey.availableDevices.some(dev => dev.selectable)) { && !yubiKey.availableDevices.some(dev => dev.selectable)) {
return qsTr("No compatible device found") return qsTr("No compatible device found")
@ -45,7 +45,6 @@ Flickable {
} }
} }
function clearPasswordFields() { function clearPasswordFields() {
currentPasswordField.text = "" currentPasswordField.text = ""
newPasswordField.text = "" newPasswordField.text = ""
@ -184,8 +183,8 @@ Flickable {
StyledExpansionPanel { StyledExpansionPanel {
id: currentDevicePanel id: currentDevicePanel
label: !!yubiKey.currentDevice ? getDeviceLabel(yubiKey.currentDevice) : qsTr("Insert your YubiKey") label: getDeviceLabel(yubiKey.currentDevice)
description: getDeviceDescription() description: getDeviceDescription(yubiKey.currentDevice)
keyImage: !!yubiKey.currentDevice ? yubiKey.getCurrentDeviceImage() : "../images/yubikeys-large-transparent" keyImage: !!yubiKey.currentDevice ? yubiKey.getCurrentDeviceImage() : "../images/yubikeys-large-transparent"
isTopPanel: true isTopPanel: true
Layout.fillWidth: true Layout.fillWidth: true
@ -200,14 +199,15 @@ Flickable {
Repeater { Repeater {
model: yubiKey.availableDevices model: yubiKey.availableDevices
RadioButton { StyledRadioButton {
Layout.fillWidth: true Layout.fillWidth: true
objectName: index objectName: index
checked: !!yubiKey.currentDevice checked: !!yubiKey.currentDevice
&& modelData.serial === yubiKey.currentDevice.serial && modelData.serial === yubiKey.currentDevice.serial
text: getDeviceLabel(modelData) text: getDeviceLabel(modelData)
description: getDeviceDescription(modelData)
enabled: modelData.selectable enabled: modelData.selectable
ButtonGroup.group: deviceButtonGroup buttonGroup: deviceButtonGroup
} }
} }
@ -285,6 +285,7 @@ Flickable {
flat: true flat: true
onClicked: navigator.confirm( onClicked: navigator.confirm(
qsTr("Remove password?"), qsTr("Remove password?"),
"",
qsTr("A password will not be required to access the accounts anymore."), qsTr("A password will not be required to access the accounts anymore."),
function () { function () {
removePassword() removePassword()
@ -302,14 +303,15 @@ Flickable {
StyledExpansionPanel { StyledExpansionPanel {
label: qsTr("Reset") 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 isEnabled: false
visible: !!yubiKey.currentDevice && !settings.otpMode visible: !!yubiKey.currentDevice && !settings.otpMode
toolButtonIcon: "../images/reset.svg" toolButtonIcon: "../images/reset.svg"
toolButtonToolTip: qsTr("Reset OATH Application") toolButtonToolTip: qsTr("Reset device")
toolButton.onClicked: navigator.confirm( toolButton.onClicked: navigator.confirm(
qsTr("Reset OATH application?"), qsTr("Reset device?"),
qsTr("This will delete all accounts and restore factory defaults."), 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 () { function () {
navigator.goToLoading() navigator.goToLoading()
yubiKey.reset(function (resp) { yubiKey.reset(function (resp) {
@ -339,7 +341,7 @@ Flickable {
StyledExpansionPanel { StyledExpansionPanel {
label: qsTr("Appearance") label: qsTr("Appearance")
description: qsTr("Change the appearance of the application.") description: qsTr("Change the visual appearance of the application.")
isTopPanel: true isTopPanel: true
ColumnLayout { ColumnLayout {

View File

@ -7,12 +7,13 @@ import QtGraphicalEffects 1.0
Button { Button {
property alias toolTipText: buttonToolTip.text property alias toolTipText: buttonToolTip.text
property bool critical: false
id: button id: button
font.capitalization: Font.capitalization font.capitalization: Font.capitalization
font.weight: Font.Medium font.weight: Font.Medium
Material.foreground: button.flat ? Material.primary : yubicoWhite Material.foreground: button.flat ? (critical ? yubicoRed : Material.primary) : yubicoWhite
Material.background: button.flat ? "transparent" : Material.primary Material.background: button.flat ? "transparent" : (critical ? yubicoRed : Material.primary)
Material.elevation: button.flat ? 0 : 1 Material.elevation: button.flat ? 0 : 1
ToolTip { ToolTip {

View File

@ -150,7 +150,8 @@ Pane {
color: formLabel color: formLabel
text: description text: description
wrapMode: Text.WordWrap wrapMode: Text.WordWrap
Layout.rowSpan: 1 maximumLineCount: isExpanded ? 4 : 2
elide: Text.ElideRight
lineHeight: 1.1 lineHeight: 1.1
visible: description visible: description
} }

45
qml/StyledRadioButton.qml Normal file
View File

@ -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
}
}
}
}
}

View File

@ -148,7 +148,7 @@ ToolBar {
Layout.fillWidth: true Layout.fillWidth: true
Layout.fillHeight: true Layout.fillHeight: true
placeholderText: qsTr("Quick Find") placeholderText: qsTr("Quick Find")
placeholderTextColor: hovered || activeFocus ? iconButtonHovered : iconButtonNormal placeholderTextColor: iconButtonNormal
leftPadding: 28 leftPadding: 28
rightPadding: 8 rightPadding: 8
width: parent.width width: parent.width

View File

@ -27,7 +27,7 @@ ApplicationWindow {
readonly property string yubicoBlue: "#284c61" readonly property string yubicoBlue: "#284c61"
readonly property string yubicoWhite: "#ffffff" readonly property string yubicoWhite: "#ffffff"
readonly property string yubicoGrey: "#939598" readonly property string yubicoGrey: "#939598"
readonly property string yubicoRed: "#fd5552" readonly property string yubicoRed: "#dc4b4c"
readonly property string defaultDark: "#303030" readonly property string defaultDark: "#303030"
readonly property string defaultDarkLighter: "#383838" readonly property string defaultDarkLighter: "#383838"