feat(desktop/wallet2) Add account with seed modal

Added adding account with seed phrase feature

Closes #3311
This commit is contained in:
Alexandra Betouni 2021-09-02 20:49:15 +03:00 committed by Iuri Matias
parent 470144db6a
commit 24b704f398
5 changed files with 415 additions and 64 deletions

View File

@ -0,0 +1,47 @@
import QtQuick 2.13
import QtQuick.Layouts 1.12
import StatusQ.Controls 0.1
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
ColumnLayout {
id: root
spacing: 8
signal generateAccountClicked()
signal proceedWithSeedClicked()
StatusBaseText {
Layout.alignment: Qt.AlignHCenter
font.pixelSize: 15
text: qsTr("Is your seed phrase secure?")
color: Theme.palette.dangerColor1
}
StatusBaseText {
Layout.preferredWidth: 345
Layout.alignment: Qt.AlignHCenter
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
font.pixelSize: 15
text: qsTr("We found no active accounts with that seed phrase. If it is a new account please ensure that it is secure. Scammers often provide you with a phrase and siphon funds later.\n")
color: Theme.palette.baseColor1
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
text: qsTr("Generate an account using Status")
onClicked: {
root.generateAccountClicked();
}
}
StatusButton {
Layout.alignment: Qt.AlignHCenter
type: StatusBaseButton.Type.Danger
text: qsTr("Proceed with seed phrase")
onClicked: {
root.proceedWithSeedClicked();
}
}
}

View File

@ -14,8 +14,7 @@ import StatusQ.Popups 0.1
StatusModal { StatusModal {
id: popup id: popup
width: 574 height: (keyOrSeedPhraseInput.input.edit.contentHeight > 56 || seedPhraseInserted) ? 517 : 498
height: (keyOrSeedPhraseInput.height > 100) ? 517 : 498
header.title: qsTr("Add account") header.title: qsTr("Add account")
onOpened: { onOpened: {
keyOrSeedPhraseInput.input.edit.forceActiveFocus(Qt.MouseFocusReason); keyOrSeedPhraseInput.input.edit.forceActiveFocus(Qt.MouseFocusReason);
@ -34,7 +33,17 @@ StatusModal {
} }
function validate() { function validate() {
return (keyOrSeedPhraseInput.valid && accountNameInput.valid); if (popup.isSeedCountValid && !popup.seedPhraseNotFound()) {
var validCount = 0;
var accountsList = seedAccountDetails.activeAccountsList;
for (var i = 0; i < accountsList.count; i++) {
if (accountsList.itemAtIndex(i).nameInputValid) {
validCount++;
}
}
}
return (popup.isSeedCountValid && !popup.seedPhraseNotFound()) ? (validCount === accountsList.count) :
(keyOrSeedPhraseInput.valid && pkeyAccountDetails.nameInputValid);
} }
contentItem: Item { contentItem: Item {
@ -46,10 +55,7 @@ StatusModal {
height: parent.height height: parent.height
Item { Item {
id: seedOrPKInputContainer id: leftContent
width: parent.width
height: 120 + ((keyOrSeedPhraseInput.height > 100) ? 30 : 0)
StatusInput { StatusInput {
id: keyOrSeedPhraseInput id: keyOrSeedPhraseInput
width: parent.width width: parent.width
@ -74,6 +80,12 @@ StatusModal {
] ]
onTextChanged: { onTextChanged: {
popup.seedPhraseInserted = keyOrSeedPhraseInput.text.includes(" "); popup.seedPhraseInserted = keyOrSeedPhraseInput.text.includes(" ");
if (popup.seedPhraseInserted) {
popup.seedPhraseInserted = true;
seedAccountDetails.searching = true;
seedAccountDetails.timer.start();
}
popup.isSeedCountValid = (!!keyOrSeedPhraseInput.text && (keyOrSeedPhraseInput.text.match(/(\w+)/g).length === 12)); popup.isSeedCountValid = (!!keyOrSeedPhraseInput.text && (keyOrSeedPhraseInput.text.match(/(\w+)/g).length === 12));
if (text === "") { if (text === "") {
errorMessage = qsTr("You need to enter a valid private key or seed phrase"); errorMessage = qsTr("You need to enter a valid private key or seed phrase");
@ -95,68 +107,93 @@ StatusModal {
} }
} }
Separator { Rectangle {
id: separator id: separator
anchors.left: parent.left color: Theme.palette.statusPopupMenu.separatorColor
anchors.leftMargin: 16
anchors.right: parent.right
anchors.rightMargin: 16
anchors.top: seedOrPKInputContainer.bottom
anchors.topMargin: (2*popup.marginBetweenInputs)
} }
Row { PKeyAccountDetails {
id: accountNameInputRow id: pkeyAccountDetails
anchors.left: parent.left width: parent.width
anchors.right: parent.right height: parent.height/2
anchors.rightMargin: 10
anchors.top: separator.bottom anchors.top: separator.bottom
anchors.topMargin: popup.marginBetweenInputs
height: (parent.height/2)
StatusInput {
id: accountNameInput
implicitWidth: (parent.width - emojiDropDown.width)
input.implicitHeight: 56
input.placeholderText: qsTrId("enter-an-account-name...")
label: qsTrId("account-name")
validators: [StatusMinLengthValidator { minLength: 1 }]
onTextChanged: {
errorMessage = (accountNameInput.text === "") ?
qsTrId("you-need-to-enter-an-account-name") : ""
}
}
Item {
id: emojiDropDown
//emoji placeholder
width: 80
height: parent.height
anchors.top: parent.top
anchors.topMargin: 11
StyledText {
id: inputLabel
text: "Emoji"
font.weight: Font.Medium
font.pixelSize: 13
color: Style.current.textColor
}
Rectangle {
width: parent.width
height: 56
anchors.top: inputLabel.bottom
anchors.topMargin: 7
radius: 10
color: "pink"
opacity: 0.6
}
}
} }
SeedAddAccountView {
id: seedAccountDetails
width: (parent.width/2)
height: parent.height
anchors.right: parent.right
}
states: [
State {
when: (popup.isSeedCountValid && !popup.seedPhraseNotFound())
PropertyChanges {
target: popup
width: 907
}
PropertyChanges {
target: pkeyAccountDetails
opacity: 0.0
}
PropertyChanges {
target: leftContent
width: contentItem.width/2
height: contentItem.height
}
PropertyChanges {
target: separator
width: 1
height: contentItem.height
}
AnchorChanges {
target: separator
anchors.left: leftContent.right
}
PropertyChanges {
target: seedAccountDetails
opacity: 1.0
}
},
State {
when: !(popup.isSeedCountValid && !popup.seedPhraseNotFound())
PropertyChanges {
target: popup
width: 574
}
PropertyChanges {
target: seedAccountDetails
opacity: 0.0
}
PropertyChanges {
target: leftContent
width: contentItem.width
height: 120
}
PropertyChanges {
target: pkeyAccountDetails
opacity: 1.0
}
PropertyChanges {
target: separator
width: contentItem.width
height: 1
anchors.topMargin: (2*popup.marginBetweenInputs)
}
AnchorChanges {
target: separator
anchors.left: contentItem.left
anchors.top: leftContent.bottom
}
}
]
} }
rightButtons: [ rightButtons: [
StatusButton { StatusButton {
text: popup.loading ? qsTrId("loading") : qsTrId("add-account") text: popup.loading ? qsTrId("loading") : qsTrId("add-account")
enabled: !popup.loading && (accountNameInput.text !== "") enabled: (!popup.loading && popup.validate())
&& (keyOrSeedPhraseInput.correctWordCount || (keyOrSeedPhraseInput.text !== ""))
MessageDialog { MessageDialog {
id: accountError id: accountError
@ -172,14 +209,25 @@ StatusModal {
popup.loading = false; popup.loading = false;
} else { } else {
//TODO account color to be verified with design //TODO account color to be verified with design
const result = popup.seedPhraseInserted ? var result;
walletModel.accountsView.addAccountsFromSeed(keyOrSeedPhraseInput.text, "", accountNameInput.text, "") : if (popup.isSeedCountValid && !popup.seedPhraseNotFound()) {
walletModel.accountsView.addAccountsFromPrivateKey(keyOrSeedPhraseInput.text, "", accountNameInput.text, ""); var accountsList = seedAccountDetails.activeAccountsList;
for (var i = 0; i < accountsList.count; i++) {
//TODO remove password requirement
if (!!accountsList.itemAtIndex(i)) {
result = walletModel.accountsView.addAccountsFromSeed(accountsList.itemAtIndex(i).accountAddress, "", accountsList.itemAtIndex(i).accountName, "")
}
}
} else {
result = walletModel.accountsView.addAccountsFromPrivateKey(keyOrSeedPhraseInput.text, "", pkeyAccountDetails.accountName, "");
}
popup.loading = false; popup.loading = false;
if (result) { if (result) {
let resultJson = JSON.parse(result); let resultJson = JSON.parse(result);
accountError.text = resultJson.error; if (!Utils.isInvalidPasswordMessage(resultJson.error)) {
accountError.open(); accountError.text = resultJson.error;
accountError.open();
}
errorSound.play(); errorSound.play();
return; return;
} }

View File

@ -0,0 +1,58 @@
import QtQuick 2.13
import "../../../../imports"
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
Item {
id: root
visible: (opacity > 0.1)
property string emoji: "" //TBD
property string accountName: accountNameInput.text
property bool nameInputValid: accountNameInput.valid
Row {
width: parent.width
anchors.verticalCenter: parent.verticalCenter
spacing: 10
StatusInput {
id: accountNameInput
width: (parent.width - 100)
input.implicitHeight: 56
input.placeholderText: qsTrId("enter-an-account-name...")
label: qsTrId("account-name")
validators: [StatusMinLengthValidator { minLength: 1 }]
onTextChanged: {
errorMessage = (accountNameInput.text === "") ?
qsTrId("you-need-to-enter-an-account-name") : ""
}
}
Item {
//emoji placeholder
width: 80
height: parent.height
anchors.top: parent.top
anchors.topMargin: 11
StatusBaseText {
id: inputLabel
text: "Emoji"
font.weight: Font.Medium
font.pixelSize: 13
color: Style.current.textColor
}
Rectangle {
width: parent.width
height: 56
anchors.top: inputLabel.bottom
anchors.topMargin: 7
radius: 10
color: "pink"
opacity: 0.6
}
}
}
}

View File

@ -0,0 +1,91 @@
import QtQuick 2.13
import QtQuick.Controls 2.12
import QtQuick.Layouts 1.12
import "../../../../imports"
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
Item {
id: root
width: parent.width
height: 120
property bool deleteButtonVisible
property string accountName: accountNameInput.text
property bool nameInputValid: accountNameInput.valid
property string accountAddress: model.address
property string emoji: "" //TODO implement emoji selection
RowLayout {
anchors.fill: parent
spacing: 0
Item {
Layout.fillWidth: true
Layout.fillHeight: true
StatusInput {
id: accountNameInput
anchors.fill: parent
input.implicitHeight: 56
input.placeholderText: qsTrId("enter-an-account-name...")
label: "Ledger" //TODO replace with derivation path, for now use Ledger
secondaryLabel: address.replace(address.substring(6, (address.length-4)), "...")
validators: [StatusMinLengthValidator { minLength: 1 }]
onTextChanged: {
errorMessage = (accountNameInput.text === "") ?
qsTrId("you-need-to-enter-an-account-name") : ""
}
}
}
Item {
//emoji placeholder
Layout.preferredWidth: 80
Layout.fillHeight: true
Layout.alignment: Qt.AlignTop
Layout.topMargin: 8
StatusBaseText {
id: inputLabel
text: "Emoji"
font.weight: Font.Medium
font.pixelSize: 13
color: Style.current.textColor
}
Rectangle {
width: parent.width
height: 56
anchors.top: inputLabel.bottom
anchors.topMargin: 7
radius: 10
color: "pink"
opacity: 0.6
}
}
Control {
id: deleteButton
Layout.preferredWidth: 50
Layout.fillHeight: true
Layout.alignment: Qt.AlignBottom
visible: root.deleteButtonVisible
background: Item {
anchors.fill: deleteButton
StatusIcon {
anchors.centerIn: parent
color: Theme.palette.baseColor1
icon: "delete"
}
MouseArea {
anchors.fill: parent
cursorShape: Qt.PointingHandCursor
onClicked: {
walletModel.accountsView.deleteAccount(address);
}
}
}
}
}
}

View File

@ -0,0 +1,107 @@
import QtQuick 2.13
import "../../../../imports"
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Components 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
Item {
id: root
visible: (opacity > 0.1)
property bool searching: false
property alias activeAccountsList: activeAccountsView
property Timer timer: Timer {
interval: 800
onTriggered: {
searching = false;
}
}
property var dummyModel: []
Column {
id: searchingColumn
width: parent.width
height: 80
anchors.verticalCenter: parent.verticalCenter
spacing: 15
StatusLoadingIndicator {
anchors.horizontalCenter: parent.horizontalCenter
color: Theme.palette.primaryColor1
}
StatusBaseText {
anchors.horizontalCenter: parent.horizontalCenter
font.pixelSize: 15
text: qsTr("Searching for active accounts")
color: Theme.palette.baseColor1
}
}
ListView {
id: activeAccountsView
anchors.fill: parent
anchors.bottomMargin: 10
clip: true
//TODO replace with active accounts model
model: walletModel.accountsView.accounts
delegate: SeedAccountDetailsDelegate {
deleteButtonVisible: (activeAccountsView.count > 1)
}
}
AccountNotFound {
id: accountNotFound
width: parent.width
anchors.verticalCenter: parent.verticalCenter
}
states: [
State {
when: searching
PropertyChanges {
target: searchingColumn
opacity: 1.0
}
PropertyChanges {
target: activeAccountsView
opacity: 0.0
}
PropertyChanges {
target: accountNotFound
opacity: 0.0
}
},
State {
when: !searching
PropertyChanges {
target: searchingColumn
opacity: 0.0
}
PropertyChanges {
target: activeAccountsView
opacity: 1.0
}
PropertyChanges {
target: accountNotFound
opacity: 0.0
}
},
State {
when: (activeAccountsView.count === 0 && !searching)
PropertyChanges {
target: searchingColumn
opacity: 0.0
}
PropertyChanges {
target: activeAccountsView
opacity: 0.0
}
PropertyChanges {
target: accountNotFound
opacity: 1.0
}
}
]
}