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
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()
}
}
}
}

View File

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

View File

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

View File

@ -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()

View File

@ -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()

View File

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

View File

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

View File

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

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.fillHeight: true
placeholderText: qsTr("Quick Find")
placeholderTextColor: hovered || activeFocus ? iconButtonHovered : iconButtonNormal
placeholderTextColor: iconButtonNormal
leftPadding: 28
rightPadding: 8
width: parent.width

View File

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