Skip to content

Commit

Permalink
wizard: redesign seed page
Browse files Browse the repository at this point in the history
- move mnemonic seed
- restore height into a separate page
- pdf template
- seed verification
- responsive UI
- accessibility
  • Loading branch information
rating89us authored and selsta committed Apr 6, 2022
1 parent 002fe8e commit a1916ca
Show file tree
Hide file tree
Showing 30 changed files with 994 additions and 327 deletions.
2 changes: 1 addition & 1 deletion components/InlineButton.qml
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ Item {
anchors.fill: parent
color: buttonArea.containsMouse ? MoneroComponents.Style.buttonInlineBackgroundColorHover : MoneroComponents.Style.buttonInlineBackgroundColor
radius: 4

border.width: parent.focus && parent.enabled ? 1 : 0

MoneroComponents.TextPlain {
id: inlineText
Expand Down
18 changes: 16 additions & 2 deletions components/LineEdit.qml
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,8 @@ ColumnLayout {
default property alias content: inlineButtons.children

property alias input: input
property bool inputHasFocus: input.activeFocus
property bool tabNavigationEnabled: true
property alias text: input.text

property int inputPaddingLeft: 10
Expand Down Expand Up @@ -109,6 +111,8 @@ ColumnLayout {
signal editingFinished();
signal accepted();
signal textUpdated();
signal backtabPressed();
signal tabPressed();

onActiveFocusChanged: activeFocus && input.forceActiveFocus()
onTextUpdated: {
Expand Down Expand Up @@ -212,8 +216,18 @@ ColumnLayout {

MoneroComponents.Input {
id: input
KeyNavigation.backtab: item.KeyNavigation.backtab
KeyNavigation.tab: item.KeyNavigation.tab
Keys.onBacktabPressed: {
item.backtabPressed();
if (item.KeyNavigation.backtab) {
item.KeyNavigation.backtab.forceActiveFocus()
}
}
Keys.onTabPressed: {
item.tabPressed();
if (item.KeyNavigation.tab) {
item.KeyNavigation.tab.forceActiveFocus()
}
}
Layout.fillWidth: true
Layout.preferredHeight: inputHeight

Expand Down
8 changes: 8 additions & 0 deletions components/TextPlain.qml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,14 @@ Text {
font.pixelSize: 14
textFormat: Text.PlainText

Rectangle {
width: root.contentWidth
height: root.height
anchors.left: parent.left
anchors.top: parent.top
color: root.focus ? MoneroComponents.Style.titleBarButtonHoverColor : "transparent"
}

MoneroEffects.ColorTransition {
enabled: root.themeTransition
themeTransition: root.themeTransition
Expand Down
Binary file added images/verify-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/verify-white@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/verify.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/verify@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/write-down-white.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/write-down-white@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/write-down.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file added images/write-down@2x.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
12 changes: 12 additions & 0 deletions qml.qrc
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,7 @@
<file>wizard/WizardCreateWallet2.qml</file>
<file>wizard/WizardCreateWallet3.qml</file>
<file>wizard/WizardCreateWallet4.qml</file>
<file>wizard/WizardCreateWallet5.qml</file>
<file>wizard/WizardCreateDevice1.qml</file>
<file>wizard/WizardDaemonSettings.qml</file>
<file>wizard/WizardHeader.qml</file>
Expand Down Expand Up @@ -267,5 +268,16 @@
<file>images/ledgerNanoX.png</file>
<file>images/trezor.png</file>
<file>images/trezor@2x.png</file>
<file>images/write-down.png</file>
<file>images/write-down-white.png</file>
<file>images/write-down@2x.png</file>
<file>images/write-down-white@2x.png</file>
<file>images/verify.png</file>
<file>images/verify-white.png</file>
<file>images/verify@2x.png</file>
<file>images/verify-white@2x.png</file>
<file>wizard/SeedListItem.qml</file>
<file>wizard/SeedListGrid.qml</file>
<file>wizard/template.pdf</file>
</qresource>
</RCC>
18 changes: 18 additions & 0 deletions src/main/oshelper.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -159,6 +159,18 @@ QList<QString> OSHelper::grabQrCodesFromScreen() const
return codes;
}

bool OSHelper::openFile(const QString &filePath) const
{
QString canonicalFilePath = QFileInfo(filePath).canonicalFilePath();
QUrl url = QUrl::fromLocalFile(canonicalFilePath);
if (!url.isValid())
{
qWarning() << "Malformed file path" << canonicalFilePath << url.errorString();
return false;
}
return QDesktopServices::openUrl(url);
}

bool OSHelper::openContainingFolder(const QString &filePath) const
{
QString canonicalFilePath = QFileInfo(filePath).canonicalFilePath();
Expand Down Expand Up @@ -280,3 +292,9 @@ bool OSHelper::installed() const
return false;
#endif
}

void OSHelper::openSeedTemplate() const
{
QFile::copy(":/wizard/template.pdf", QDir::tempPath() + "/seed_template.pdf");
openFile(QDir::tempPath() + "/seed_template.pdf");
}
2 changes: 2 additions & 0 deletions src/main/oshelper.h
Original file line number Diff line number Diff line change
Expand Up @@ -46,12 +46,14 @@ class OSHelper : public QObject
Q_INVOKABLE void createDesktopEntry() const;
Q_INVOKABLE QString downloadLocation() const;
Q_INVOKABLE QList<QString> grabQrCodesFromScreen() const;
Q_INVOKABLE bool openFile(const QString &filePath) const;
Q_INVOKABLE bool openContainingFolder(const QString &filePath) const;
Q_INVOKABLE QString openSaveFileDialog(const QString &title, const QString &folder, const QString &filename) const;
Q_INVOKABLE QString temporaryFilename() const;
Q_INVOKABLE QString temporaryPath() const;
Q_INVOKABLE bool removeTemporaryWallet(const QString &walletName) const;
Q_INVOKABLE bool isCapsLock() const;
Q_INVOKABLE void openSeedTemplate() const;

private:
bool installed() const;
Expand Down
26 changes: 26 additions & 0 deletions wizard/SeedListGrid.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import QtQuick 2.9
import QtQuick.Dialogs 1.2
import QtQuick.Layouts 1.2
import QtQuick.Controls 2.0

import "../js/Wizard.js" as Wizard
import "../js/Utils.js" as Utils
import "../components" as MoneroComponents

GridLayout {
id: seedGrid
Layout.alignment: Qt.AlignHCenter
flow: GridLayout.TopToBottom
columns: wizardController.layoutScale == 1 ? 5 : wizardController.layoutScale == 2 ? 4 : wizardController.layoutScale == 3 ? 3 : 2
rows: wizardController.layoutScale == 1 ? 5 :wizardController.layoutScale == 2 ? 7 : wizardController.layoutScale == 3 ? 9 : 13
columnSpacing: wizardController.layoutScale == 1 ? 25 : 18
rowSpacing: 0

Component.onCompleted: {
var seed = wizardController.walletOptionsSeed.split(" ");
var component = Qt.createComponent("SeedListItem.qml");
for(var i = 0; i < seed.length; i++) {
component.createObject(seedGrid, {wordNumber: i, word: seed[i]});
}
}
}
153 changes: 153 additions & 0 deletions wizard/SeedListItem.qml
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
import "../components" as MoneroComponents;
import QtQuick 2.9
import QtQuick.Layouts 1.2
import FontAwesome 1.0

ColumnLayout {
id: seedListItem
property var wordNumber;
property var word;
property var wordSpelled: (word.split("")).join(". ")
property var acessibleText: (wordNumber + 1) + word
property alias wordText: wordText
property alias lineEdit: lineEdit
property alias icon: icon
spacing: 0

Layout.preferredWidth: 136
Layout.maximumWidth: 136
Layout.minimumWidth: 136

Accessible.role: Accessible.StaticText
Accessible.name: lineEdit.inputHasFocus && !lineEdit.readOnly ? qsTr("Please enter the word number") + " " + (wordNumber + 1) + "." +
(icon.visible ? (icon.wordsMatch ? qsTr("Green check mark") + "."
: qsTr("Red exclamation mark") + ".")
: "")
: (wordNumber + 1) + word + ". " +
(lineEdit.inputHasFocus && lineEdit.readOnly ? qsTr("Green check mark")
: qsTr("This word is spelled ") + " " + wordSpelled + ".") +
translationManager.emptyString
KeyNavigation.up: wordNumber == 0 ? (recoveryPhraseLabel.visible ? recoveryPhraseLabel : header) : parent.children[wordNumber - 1]
KeyNavigation.backtab: wordNumber == 0 ? (recoveryPhraseLabel.visible ? recoveryPhraseLabel : header) : parent.children[wordNumber - 1]
Keys.onUpPressed: focusOnPreviousField()
Keys.onBacktabPressed: focusOnPreviousField()
Keys.onDownPressed: focusOnNextField()
Keys.onTabPressed: focusOnNextField()

function focusOnPreviousField() {
if (wizardCreateWallet2.state == "verify") {
if (wordNumber < 5) {
if (recoveryPhraseLabel.visible) {
return recoveryPhraseLabel.forceActiveFocus();
} else {
return header.forceActiveFocus();
}
} else if (wordNumber >= 5 && wordNumber < 25) {
return parent.children[wizardCreateWallet2.hiddenWords[parseInt(wordNumber / 5) - 1]].lineEdit.forceActiveFocus()
}
} else {
if (wordNumber == 0) {
if (recoveryPhraseLabel.visible) {
return recoveryPhraseLabel.forceActiveFocus();
} else {
return header.forceActiveFocus();
}
} else {
return parent.children[wordNumber - 1].forceActiveFocus()
}
}
}

function focusOnNextField() {
if (wizardCreateWallet2.state == "verify") {
if (wordNumber < 20) {
return parent.children[wizardCreateWallet2.hiddenWords[parseInt(wordNumber / 5) + 1]].lineEdit.forceActiveFocus()
} else {
return navigation.btnPrev.forceActiveFocus()
}
} else {
if (wordNumber == 24) {
if (createNewSeedButton.visible) {
return createNewSeedButton.forceActiveFocus()
} else {
return printPDFTemplate.forceActiveFocus()
}
} else {
return parent.children[wordNumber + 1].forceActiveFocus()
}
}
}

RowLayout {
id: wordRow
spacing: 0

MoneroComponents.Label {
color: lineEdit.inputHasFocus ? MoneroComponents.Style.defaultFontColor : MoneroComponents.Style.dimmedFontColor
fontSize: 13
text: (wordNumber + 1)
themeTransition: false
}

MoneroComponents.LineEdit {
id: lineEdit
property bool firstUserInput: true
inputHeight: 29
inputPaddingLeft: 10
inputPaddingBottom: 2
inputPaddingRight: 0
borderDisabled: true
visible: !wordText.visible
fontSize: 16
fontBold: true
text: ""
tabNavigationEnabled: false
onTextChanged: {
if (lineEdit.text.length == wordText.text.length) {
firstUserInput = false;
}
}
onBacktabPressed: focusOnPreviousField()
onTabPressed: focusOnNextField()
}

MoneroComponents.Label {
id: wordText
Layout.leftMargin: 10
color: MoneroComponents.Style.defaultFontColor
fontSize: seedListItem.focus ? 19 : 16
fontBold: true
text: word
themeTransition: false
}

MoneroComponents.TextPlain {
id: icon
Layout.leftMargin: wordsMatch ? 10 : 0
property bool wordsMatch: lineEdit.text === wordText.text
property bool partialWordMatches: lineEdit.text === wordText.text.substring(0, lineEdit.text.length)
visible: lineEdit.text.length > 0 && !lineEdit.firstUserInput || lineEdit.firstUserInput && !partialWordMatches
font.family: FontAwesome.fontFamilySolid
font.styleName: "Solid"
font.pixelSize: 15
text: wordsMatch ? FontAwesome.checkCircle : FontAwesome.exclamationCircle
color: wordsMatch ? (MoneroComponents.Style.blackTheme ? "#00FF00" : "#008000") : "#FF0000"
themeTransition: false
onTextChanged: {
if (wizardCreateWallet2.seedListGrid && wordsMatch) {
if (wordNumber < 20) {
focusOnNextField();
}
lineEdit.readOnly = true;
}
}
}
}

Rectangle {
id: underLine
color: lineEdit.inputHasFocus ? MoneroComponents.Style.defaultFontColor : MoneroComponents.Style.appWindowBorderColor
Layout.fillWidth: true
height: 1
}
}
32 changes: 25 additions & 7 deletions wizard/WizardController.qml
Original file line number Diff line number Diff line change
Expand Up @@ -50,13 +50,14 @@ Rectangle {
signal useMoneroClicked()
signal walletCreatedFromDevice(bool success)

function restart() {
function restart(generatingNewSeed) {
// Clear up any state, including `m_wallet`, which
// is the temp. wallet object whilst creating new wallets.
// This function is called automatically by navigating to `wizardHome`.
wizardStateView.state = "wizardHome"
wizardController.walletOptionsName = defaultAccountName;
wizardController.walletOptionsLocation = '';
if(!generatingNewSeed) {
wizardController.walletOptionsName = defaultAccountName;
wizardController.walletOptionsLocation = '';
}
wizardController.walletOptionsPassword = '';
wizardController.walletOptionsSeed = '';
wizardController.walletOptionsSeedOffset = '';
Expand Down Expand Up @@ -113,10 +114,18 @@ Rectangle {


property int layoutScale: {
if(appWindow.width < 800){
return 1;
} else {
if (appWindow.width < 506) {
//mobile (25 word mnemonic seed displayed in 2 columns)
return 4;
} else if (appWindow.width < 660) {
//tablet (25 word mnemonic seed displayed in 3 columns)
return 3;
} else if (appWindow.width < 842) {
//tablet (25 word mnemonic seed displayed in 4 columns)
return 2;
} else if (appWindow.width >= 842) {
//desktop (25 word mnemonic seed displayed in 5 columns)
return 1;
}
}

Expand All @@ -131,6 +140,7 @@ Rectangle {
property WizardCreateWallet2 wizardCreateWallet2View: WizardCreateWallet2 { }
property WizardCreateWallet3 wizardCreateWallet3View: WizardCreateWallet3 { }
property WizardCreateWallet4 wizardCreateWallet4View: WizardCreateWallet4 { }
property WizardCreateWallet5 wizardCreateWallet5View: WizardCreateWallet5 { }
property WizardRestoreWallet1 wizardRestoreWallet1View: WizardRestoreWallet1 { }
property WizardRestoreWallet2 wizardRestoreWallet2View: WizardRestoreWallet2 { }
property WizardRestoreWallet3 wizardRestoreWallet3View: WizardRestoreWallet3 { }
Expand Down Expand Up @@ -195,6 +205,10 @@ Rectangle {
name: "wizardCreateWallet4"
PropertyChanges { target: wizardStateView; currentView: wizardStateView.wizardCreateWallet4View }
PropertyChanges { target: wizardFlickable; contentHeight: wizardStateView.wizardCreateWallet4View.pageHeight + 80 }
}, State {
name: "wizardCreateWallet5"
PropertyChanges { target: wizardStateView; currentView: wizardStateView.wizardCreateWallet5View }
PropertyChanges { target: wizardFlickable; contentHeight: wizardStateView.wizardCreateWallet5View.pageHeight + 80 }
}, State {
name: "wizardRestoreWallet1"
PropertyChanges { target: wizardStateView; currentView: wizardStateView.wizardRestoreWallet1View }
Expand Down Expand Up @@ -354,6 +368,10 @@ Rectangle {
return;
}

if (wizardStateView.wizardCreateWallet2View.seedListGrid) {
wizardStateView.wizardCreateWallet2View.seedListGrid.destroy();
}

// make sure temporary wallet files are deleted
console.log("Removing temporary wallet: " + wizardController.tmpWalletFilename)
oshelper.removeTemporaryWallet(wizardController.tmpWalletFilename)
Expand Down
2 changes: 1 addition & 1 deletion wizard/WizardCreateDevice1.qml
Original file line number Diff line number Diff line change
Expand Up @@ -268,7 +268,7 @@ Rectangle {
function onCreateWalletFromDeviceCompleted(written){
hideProcessingSplash();
if(written){
wizardStateView.state = "wizardCreateWallet2";
wizardStateView.state = "wizardCreateWallet3";
} else {
errorMsg.text = qsTr("Error writing wallet from hardware device. Check application logs.") + translationManager.emptyString;
}
Expand Down
Loading

0 comments on commit a1916ca

Please sign in to comment.