feat(@desktop/Wallet): Add acccounts Modal as per new design

fixes #5073
This commit is contained in:
Khushboo Mehta 2022-03-28 10:19:57 +02:00 committed by Iuri Matias
parent b732ad6e5c
commit 2852d70731
11 changed files with 598 additions and 854 deletions

@ -1 +1 @@
Subproject commit b2ac2794bbee350539e20d95f1e3adf626e8feb7
Subproject commit 46dfd594dfa889448fbeefc9a011bea38042f41e

View File

@ -25,7 +25,7 @@ Rectangle {
RowLayout {
anchors.centerIn: parent
height: sendBtn.height
height: parent.height
spacing: Style.current.padding
StatusFlatButton {

View File

@ -0,0 +1,224 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Dialogs 1.3
import QtQuick.Layouts 1.14
import utils 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import shared.controls 1.0
import "../stores"
import "../views"
StatusModal {
id: popup
property int marginBetweenInputs: 38
property string passwordValidationError: ""
property bool loading: false
property var emojiPopup: null
signal afterAddAccount()
//% "Generate an account"
header.title: qsTrId("generate-a-new-account")
function validate() {
if (passwordInput.text === "") {
//% "You need to enter a password"
passwordValidationError = qsTrId("you-need-to-enter-a-password")
} else if (passwordInput.text.length < 6) {
//% "Password needs to be 6 characters or more"
passwordValidationError = qsTrId("password-needs-to-be-6-characters-or-more")
} else {
passwordValidationError = ""
}
return passwordValidationError === "" && accountNameInput.valid
}
onOpened: {
passwordValidationError = "";
passwordInput.text = "";
accountNameInput.text = "";
accountNameInput.reset()
accountNameInput.input.icon.emoji = StatusQUtils.Emoji.getRandomEmoji()
colorSelectionGrid.selectedColorIndex = Math.floor(Math.random() * colorSelectionGrid.model.length)
advancedSelection.expanded = false
advancedSelection.reset()
passwordInput.forceActiveFocus(Qt.MouseFocusReason)
}
Connections {
enabled: popup.opened
target: emojiPopup
onEmojiSelected: function (emojiText, atCursor) {
accountNameInput.input.icon.emoji = emojiText
}
}
contentItem: ScrollView {
width: popup.width
ScrollBar.horizontal.policy: ScrollBar.AlwaysOff
topPadding: Style.current.halfPadding
bottomPadding: Style.current.halfPadding
height: 400
clip: true
Column {
property alias accountNameInput: accountNameInput
width: popup.width
spacing: Style.current.halfPadding
topPadding: 20
// To-Do Password hidden option not supported in StatusQ StatusBaseInput
Item {
width: parent.width
height: passwordInput.height
Input {
id: passwordInput
anchors.fill: parent
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
//% "Enter your password"
placeholderText: qsTrId("enter-your-password…")
//% "Password"
label: qsTrId("password")
textField.echoMode: TextInput.Password
validationError: popup.passwordValidationError
inputLabel.font.pixelSize: 15
inputLabel.font.weight: Font.Normal
}
}
StatusInput {
id: accountNameInput
//% "Enter an account name..."
input.placeholderText: qsTrId("enter-an-account-name...")
//% "Account name"
label: qsTrId("account-name")
input.isIconSelectable: true
input.icon.color: colorSelectionGrid.selectedColor ? colorSelectionGrid.selectedColor : Theme.palette.directColor1
onIconClicked: {
popup.emojiPopup.open()
popup.emojiPopup.x = popup.x + accountNameInput.x + Style.current.padding
popup.emojiPopup.y = popup.y + contentItem.y + accountNameInput.y + accountNameInput.height + Style.current.halfPadding
}
validators: [
StatusMinLengthValidator {
//% "You need to enter an account name"
errorMessage: qsTrId("you-need-to-enter-an-account-name")
minLength: 1
}
]
}
StatusColorSelectorGrid {
id: colorSelectionGrid
anchors.horizontalCenter: parent.horizontalCenter
//% "color"
titleText: qsTr("color").toUpperCase()
}
StatusExpandableItem {
id: advancedSelection
property bool isValid: true
function validate() {
if(expandableItem) {
return expandableItem.validate()
}
}
function reset() {
if(expandableItem) {
return expandableItem.reset()
}
}
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
//% "Advanced"
primaryText: qsTr("Advanced")
type: StatusExpandableItem.Type.Tertiary
expandable: true
expandableComponent: AdvancedAddAccountView {
width: parent.width
Layout.margins: Style.current.padding
Component.onCompleted: advancedSelection.isValid = Qt.binding(function(){return isValid})
}
}
}
}
rightButtons: [
StatusButton {
id: nextButton
text: loading ?
//% "Loading..."
qsTrId("loading") :
//% "Add account"
qsTrId("add-account")
enabled: !loading && passwordInput.text !== "" && accountNameInput.text !== "" && advancedSelection.isValid
MessageDialog {
id: accountError
title: "Adding the account failed"
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
onClicked : {
// TODO the loaidng doesn't work because the function freezes th eview. Might need to use threads
loading = true
if (!validate() || !advancedSelection.validate()) {
Global.playErrorSound();
return loading = false
}
var errMessage = ""
if(advancedSelection.expandableItem) {
switch(advancedSelection.expandableItem.addAccountType) {
case AdvancedAddAccountView.AddAccountType.GenerateNew:
errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji)
break
case AdvancedAddAccountView.AddAccountType.ImportSeedPhrase:
errMessage = RootStore.addAccountsFromSeed(advancedSelection.expandableItem.mnemonicText, passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji)
break
case AdvancedAddAccountView.AddAccountType.ImportPrivateKey:
errMessage = RootStore.addAccountsFromPrivateKey(advancedSelection.expandableItem.privateKey, passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji)
break
}
} else {
errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji)
}
loading = false
if (errMessage) {
Global.playErrorSound();
if (Utils.isInvalidPasswordMessage(errMessage)) {
//% "Wrong password"
popup.passwordValidationError = qsTrId("wrong-password")
} else {
accountError.text = errMessage;
accountError.open();
}
return
}
popup.afterAddAccount();
popup.close();
}
}
]
}

View File

@ -1,211 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Dialogs 1.3
import utils 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import shared.panels 1.0
import shared.controls 1.0
import "../stores"
StatusModal {
id: popup
property int marginBetweenInputs: 38
property string passwordValidationError: ""
property string privateKeyValidationError: ""
property bool loading: false
property var emojiPopup: null
signal afterAddAccount()
function validate() {
if (passwordInput.text === "") {
//% "You need to enter a password"
passwordValidationError = qsTrId("you-need-to-enter-a-password")
} else if (passwordInput.text.length < 6) {
//% "Password needs to be 6 characters or more"
passwordValidationError = qsTrId("password-needs-to-be-6-characters-or-more")
} else {
passwordValidationError = ""
}
if (accountPKeyInput.text === "") {
//% "You need to enter a private key"
privateKeyValidationError = qsTrId("you-need-to-enter-a-private-key")
} else if (!Utils.isPrivateKey(accountPKeyInput.text)) {
//% "Enter a valid private key (64 characters hexadecimal string)"
privateKeyValidationError = qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
} else {
privateKeyValidationError = ""
}
return passwordValidationError === "" && privateKeyValidationError === "" && accountNameInput.valid
}
//% "Add account from private key"
header.title: qsTrId("add-private-key-account")
onOpened: {
passwordInput.text = ""
accountPKeyInput.text = ""
accountNameInput.reset()
accountNameInput.text = ""
accountNameInput.input.icon.emoji = StatusQUtils.Emoji.getRandomEmoji()
passwordValidationError = ""
privateKeyValidationError = ""
accountColorInput.selectedColorIndex = Math.floor(Math.random() * accountColorInput.model.length)
passwordInput.forceActiveFocus(Qt.MouseFocusReason)
}
Connections {
enabled: popup.opened
target: emojiPopup
onEmojiSelected: function (emojiText, atCursor) {
popup.contentItem.accountNameInput.input.icon.emoji = emojiText
}
}
contentItem: Column {
property alias accountNameInput: accountNameInput
width: popup.width
spacing: 8
topPadding: 20
Column {
width: parent.width
spacing: Style.current.xlPadding
// To-Do Password hidden option not supported in StatusQ StatusBaseInput
Input {
id: passwordInput
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
width: parent.width
//% "Enter your password"
placeholderText: qsTrId("enter-your-password…")
//% "Password"
label: qsTrId("password")
textField.echoMode: TextInput.Password
validationError: popup.passwordValidationError
inputLabel.font.pixelSize: 15
inputLabel.font.weight: Font.Normal
}
// To-Do use StatusInput
StyledTextArea {
id: accountPKeyInput
customHeight: 88
anchors.left: parent.left
anchors.right: parent.right
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
validationError: popup.privateKeyValidationError
//% "Private key"
label: qsTrId("private-key")
textField.wrapMode: Text.WordWrap
textField.horizontalAlignment: TextEdit.AlignHCenter
textField.verticalAlignment: TextEdit.AlignVCenter
textField.font.weight: Font.DemiBold
//% "Paste the contents of your private key"
placeholderText: qsTrId("paste-the-contents-of-your-private-key")
textField.placeholderTextColor: Style.current.secondaryText
textField.selectByKeyboard: true
textField.selectionColor: Style.current.secondaryBackground
textField.selectedTextColor: Style.current.secondaryText
}
}
StatusInput {
id: accountNameInput
//% "Enter an account name..."
input.placeholderText: qsTrId("enter-an-account-name...")
//% "Account name"
label: qsTrId("account-name")
input.isIconSelectable: true
input.icon.color: accountColorInput.selectedColor ? accountColorInput.selectedColor : Theme.palette.directColor1
onIconClicked: {
popup.emojiPopup.open()
popup.emojiPopup.x = popup.x + Style.current.padding
popup.emojiPopup.y = popup.y + contentItem.y + accountNameInput.y + accountNameInput.height + Style.current.halfPadding
}
validators: [
StatusMinLengthValidator {
//% "You need to enter an account name"
errorMessage: qsTrId("you-need-to-enter-an-account-name")
minLength: 1
},
StatusRegularExpressionValidator {
regularExpression: /^[^<>]+$/
errorMessage: qsTr("This is not a valid account name")
}
]
charLimit: 40
}
StatusColorSelectorGrid {
id: accountColorInput
anchors.horizontalCenter: parent.horizontalCenter
//% "color"
titleText: qsTr("color").toUpperCase()
}
Item {
width: parent.width
height: 8
}
}
rightButtons: [
StatusButton {
text: loading ?
//% "Loading..."
qsTrId("loading") :
//% "Add account"
qsTrId("add-account")
enabled: !loading && passwordInput.text !== "" && accountNameInput.text !== "" && accountNameInput.valid && accountPKeyInput.text !== ""
MessageDialog {
id: accountError
title: "Adding the account failed"
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
onClicked : {
// TODO the loaidng doesn't work because the function freezes th eview. Might need to use threads
loading = true
if (!validate()) {
return loading = false
}
const errMessage = RootStore.addAccountsFromPrivateKey(accountPKeyInput.text, passwordInput.text, accountNameInput.text, accountColorInput.selectedColor, accountNameInput.input.icon.emoji)
loading = false
if (errMessage) {
Global.playErrorSound();
if (Utils.isInvalidPasswordMessage(errMessage)) {
//% "Wrong password"
popup.passwordValidationError = qsTrId("wrong-password")
} else {
accountError.text = errMessage
accountError.open()
}
return
}
popup.afterAddAccount()
popup.close();
}
}
]
}

View File

@ -1,200 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Dialogs 1.3
import utils 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import shared.controls 1.0
import "../stores"
StatusModal {
id: popup
property string passwordValidationError: ""
property string seedValidationError: ""
property bool loading: false
property var emojiPopup: null
signal afterAddAccount()
function reset() {
passwordInput.text = ""
accountNameInput.text = ""
seedPhraseTextArea.textArea.text = ""
}
function validate() {
if (passwordInput.text === "") {
//% "You need to enter a password"
passwordValidationError = qsTrId("you-need-to-enter-a-password")
} else if (passwordInput.text.length < 6) {
//% "Password needs to be 6 characters or more"
passwordValidationError = qsTrId("password-needs-to-be-6-characters-or-more")
} else {
passwordValidationError = ""
}
if (seedPhraseTextArea.textArea.text === "") {
//% "You need to enter a seed phrase"
seedValidationError = qsTrId("you-need-to-enter-a-seed-phrase")
} else if (!Utils.isMnemonic(seedPhraseTextArea.textArea.text)) {
//% "Enter a valid mnemonic"
seedValidationError = qsTrId("enter-a-valid-mnemonic")
} else {
seedValidationError = ""
}
return passwordValidationError === "" && seedValidationError === "" && accountNameInput.valid
}
//% "Add account with a seed phrase"
header.title: qsTrId("add-seed-account")
onOpened: {
seedPhraseTextArea.textArea.text = ""
passwordInput.text = ""
accountNameInput.text = ""
accountNameInput.reset()
accountNameInput.input.icon.emoji = StatusQUtils.Emoji.getRandomEmoji()
passwordValidationError = ""
seedValidationError = ""
accountColorInput.selectedColorIndex = Math.floor(Math.random() * accountColorInput.model.length)
passwordInput.forceActiveFocus(Qt.MouseFocusReason)
}
Connections {
enabled: popup.opened
target: emojiPopup
onEmojiSelected: function (emojiText, atCursor) {
popup.contentItem.accountNameInput.input.icon.emoji = emojiText
}
}
contentItem: Column {
property alias accountNameInput: accountNameInput
width: popup.width
spacing: 8
topPadding: 20
Column {
width: parent.width
spacing: Style.current.xlPadding
// To-Do Password hidden option not supported in StatusQ StatusBaseInput
Input {
id: passwordInput
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
width: parent.width
//% "Enter your password"
placeholderText: qsTrId("enter-your-password…")
//% "Password"
label: qsTrId("password")
textField.echoMode: TextInput.Password
validationError: popup.passwordValidationError
inputLabel.font.pixelSize: 15
inputLabel.font.weight: Font.Normal
}
// To-Do use StatusInput
SeedPhraseTextArea {
id: seedPhraseTextArea
anchors.left: parent.left
anchors.leftMargin: Style.current.padding
width: parent.width - 2*Style.current.padding
}
}
StatusInput {
id: accountNameInput
//% "Enter an account name..."
input.placeholderText: qsTrId("enter-an-account-name...")
//% "Account name"
label: qsTrId("account-name")
input.isIconSelectable: true
input.icon.color: accountColorInput.selectedColor ? accountColorInput.selectedColor : Theme.palette.directColor1
onIconClicked: {
popup.emojiPopup.open()
popup.emojiPopup.x = popup.x + Style.current.padding
popup.emojiPopup.y = popup.y + contentItem.y + accountNameInput.y + accountNameInput.height + Style.current.halfPadding
}
validators: [
StatusMinLengthValidator {
//% "You need to enter an account name"
errorMessage: qsTrId("you-need-to-enter-an-account-name")
minLength: 1
},
StatusRegularExpressionValidator {
regularExpression: /^[^<>]+$/
errorMessage: qsTr("This is not a valid account name")
}
]
charLimit: 40
}
StatusColorSelectorGrid {
id: accountColorInput
anchors.horizontalCenter: parent.horizontalCenter
//% "color"
titleText: qsTr("color").toUpperCase()
}
Item {
width: parent.width
height: 8
}
}
rightButtons: [
StatusButton {
text: loading ?
//% "Loading..."
qsTrId("loading") :
//% "Add account"
qsTrId("add-account")
enabled: !loading && passwordInput.text !== "" && accountNameInput.text !== "" && accountNameInput.valid && seedPhraseTextArea.correctWordCount
MessageDialog {
id: accountError
title: "Adding the account failed"
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
onClicked : {
// TODO the loading doesn't work because the function freezes the view. Might need to use threads
loading = true
if (!validate() || !seedPhraseTextArea.validateSeed()) {
Global.playErrorSound();
return loading = false
}
const errMessage = RootStore.addAccountsFromSeed(seedPhraseTextArea.textArea.text, passwordInput.text, accountNameInput.text, accountColorInput.selectedColor, accountNameInput.input.icon.emoji)
loading = false
if (errMessage) {
Global.playErrorSound();
if (Utils.isInvalidPasswordMessage(errMessage)) {
//% "Wrong password"
popup.passwordValidationError = qsTrId("wrong-password")
} else {
accountError.text = errMessage
accountError.open()
}
return
}
popup.afterAddAccount()
popup.reset()
popup.close();
}
}
]
}

View File

@ -1,60 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import shared 1.0
import shared.popups 1.0
import utils 1.0
// TODO: replace with StatusPopupMenu
PopupMenu {
id: newAccountMenu
width: 260
closePolicy: Popup.CloseOnEscape | Popup.CloseOnPressOutsideParent
signal generateNewAccountTriggered();
signal addWatchAccountTriggered();
signal enterSeedPhraseTriggered();
signal enterPrivateKeyTriggered();
Action {
//% "Generate an account"
text: qsTrId("generate-a-new-account")
icon.source: Style.svg("generate_account")
icon.width: 19
icon.height: 19
onTriggered: {
newAccountMenu.generateNewAccountTriggered();
}
}
Action {
//% "Add a watch-only address"
text: qsTrId("add-a-watch-account")
icon.source: Style.svg("eye")
icon.width: 19
icon.height: 19
onTriggered: {
newAccountMenu.addWatchAccountTriggered();
}
}
Action {
//% "Enter a seed phrase"
text: qsTrId("enter-a-seed-phrase")
icon.source: Style.svg("enter_seed_phrase")
icon.width: 19
icon.height: 19
onTriggered: {
newAccountMenu.enterSeedPhraseTriggered();
}
}
Action {
//% "Enter a private key"
text: qsTrId("enter-a-private-key")
icon.source: Style.svg("enter_private_key")
icon.width: 19
icon.height: 19
onTriggered: {
newAccountMenu.enterPrivateKeyTriggered();
}
}
}

View File

@ -1,150 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Dialogs 1.3
import utils 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Popups 0.1
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import shared.controls 1.0
import "../stores"
StatusModal {
id: popup
property bool loading: false
property var emojiPopup: null
signal afterAddAccount()
//% "Add a watch-only account"
header.title: qsTrId("add-watch-account")
onOpened: {
addressInput.text = ""
addressInput.reset()
accountNameInput.text = ""
accountNameInput.reset()
accountNameInput.input.icon.emoji = StatusQUtils.Emoji.getRandomEmoji()
accountColorInput.selectedColorIndex = Math.floor(Math.random() * accountColorInput.model.length)
addressInput.forceActiveFocus(Qt.MouseFocusReason)
}
Connections {
enabled: popup.opened
target: emojiPopup
onEmojiSelected: function (emojiText, atCursor) {
popup.contentItem.accountNameInput.input.icon.emoji = emojiText
}
}
contentItem: Column {
property alias accountNameInput: accountNameInput
width: popup.width
spacing: 8
topPadding: 20
StatusInput {
id: addressInput
// TODO add QR code reader for the address
//% "Enter address..."
input.placeholderText: qsTrId("enter-address...")
//% "Account address"
label: qsTrId("wallet-key-title")
validators: [
StatusAddressValidator {
//% "This needs to be a valid address (starting with 0x)"
errorMessage: qsTrId("this-needs-to-be-a-valid-address-(starting-with-0x)")
},
StatusMinLengthValidator {
//% "You need to enter an address"
errorMessage: qsTrId("you-need-to-enter-an-address")
minLength: 1
}
]
}
StatusInput {
id: accountNameInput
//% "Enter an account name..."
input.placeholderText: qsTrId("enter-an-account-name...")
//% "Account name"
label: qsTrId("account-name")
input.isIconSelectable: true
input.icon.color: accountColorInput.selectedColor ? accountColorInput.selectedColor : Theme.palette.directColor1
onIconClicked: {
popup.emojiPopup.open()
popup.emojiPopup.x = popup.x + Style.current.padding
popup.emojiPopup.y = popup.y + contentItem.y + accountNameInput.y + accountNameInput.height + Style.current.halfPadding
}
validators: [
StatusMinLengthValidator {
//% "You need to enter an account name"
errorMessage: qsTrId("you-need-to-enter-an-account-name")
minLength: 1
},
StatusRegularExpressionValidator {
regularExpression: /^[^<>]+$/
errorMessage: qsTr("This is not a valid account name")
}
]
charLimit: 40
}
StatusColorSelectorGrid {
id: accountColorInput
anchors.horizontalCenter: parent.horizontalCenter
//% "color"
titleText: qsTr("color").toUpperCase()
}
Item {
width: parent.width
height: 8
}
}
rightButtons: [
StatusButton {
text: loading ?
//% "Loading..."
qsTrId("loading") :
//% "Add account"
qsTrId("add-account")
enabled: !loading && addressInput.text !== "" && accountNameInput.text !== "" && accountNameInput.valid
MessageDialog {
id: accountError
title: "Adding the account failed"
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
onClicked : {
// TODO the loaidng doesn't work because the function freezes th eview. Might need to use threads
loading = true
if (!addressInput.valid || !accountNameInput.valid) {
Global.playErrorSound();
return loading = false
}
const error = RootStore.addWatchOnlyAccount(addressInput.text, accountNameInput.text, accountColorInput.selectedColor, accountNameInput.input.icon.emoji);
loading = false
if (error) {
Global.playErrorSound();
accountError.text = error
return accountError.open()
}
popup.afterAddAccount()
popup.close();
}
}
]
}

View File

@ -1,173 +0,0 @@
import QtQuick 2.13
import QtQuick.Controls 2.13
import QtQuick.Dialogs 1.3
import utils 1.0
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Core.Utils 0.1 as StatusQUtils
import StatusQ.Controls 0.1
import StatusQ.Controls.Validators 0.1
import StatusQ.Popups 0.1
import shared.controls 1.0
import "../stores"
StatusModal {
id: popup
property int marginBetweenInputs: 38
property string passwordValidationError: ""
property bool loading: false
property var emojiPopup: null
signal afterAddAccount()
//% "Generate an account"
header.title: qsTrId("generate-a-new-account")
function validate() {
if (passwordInput.text === "") {
//% "You need to enter a password"
passwordValidationError = qsTrId("you-need-to-enter-a-password")
} else if (passwordInput.text.length < 6) {
//% "Password needs to be 6 characters or more"
passwordValidationError = qsTrId("password-needs-to-be-6-characters-or-more")
} else {
passwordValidationError = ""
}
return passwordValidationError === "" && accountNameInput.valid
}
onOpened: {
passwordValidationError = "";
passwordInput.text = "";
accountNameInput.reset()
accountNameInput.text = "";
accountNameInput.input.icon.emoji = StatusQUtils.Emoji.getRandomEmoji()
colorSelectionGrid.selectedColorIndex = Math.floor(Math.random() * colorSelectionGrid.model.length)
passwordInput.forceActiveFocus(Qt.MouseFocusReason)
}
Connections {
enabled: popup.opened
target: emojiPopup
onEmojiSelected: function (emojiText, atCursor) {
popup.contentItem.accountNameInput.input.icon.emoji = emojiText
}
}
contentItem: Column {
property alias accountNameInput: accountNameInput
width: popup.width
spacing: 8
topPadding: 20
// To-Do Password hidden option not supported in StatusQ StatusBaseInput
Item {
width: parent.width
height: passwordInput.height
Input {
id: passwordInput
anchors.fill: parent
anchors.leftMargin: Style.current.padding
anchors.rightMargin: Style.current.padding
//% "Enter your password"
placeholderText: qsTrId("enter-your-password…")
//% "Password"
label: qsTrId("password")
textField.echoMode: TextInput.Password
validationError: popup.passwordValidationError
inputLabel.font.pixelSize: 15
inputLabel.font.weight: Font.Normal
}
}
StatusInput {
id: accountNameInput
//% "Enter an account name..."
input.placeholderText: qsTrId("enter-an-account-name...")
//% "Account name"
label: qsTrId("account-name")
input.isIconSelectable: true
input.icon.color: colorSelectionGrid.selectedColor ? colorSelectionGrid.selectedColor : Theme.palette.directColor1
onIconClicked: {
popup.emojiPopup.open()
popup.emojiPopup.x = popup.x + accountNameInput.x + Style.current.padding
popup.emojiPopup.y = popup.y + contentItem.y + accountNameInput.y + accountNameInput.height + Style.current.halfPadding
}
validators: [
StatusMinLengthValidator {
//% "You need to enter an account name"
errorMessage: qsTrId("you-need-to-enter-an-account-name")
minLength: 1
},
StatusRegularExpressionValidator {
regularExpression: /^[^<>]+$/
errorMessage: qsTr("This is not a valid account name")
}
]
charLimit: 40
}
StatusColorSelectorGrid {
id: colorSelectionGrid
anchors.horizontalCenter: parent.horizontalCenter
//% "color"
titleText: qsTr("color").toUpperCase()
}
Item {
width: parent.width
height: 8
}
}
rightButtons: [
StatusButton {
text: loading ?
//% "Loading..."
qsTrId("loading") :
//% "Add account"
qsTrId("add-account")
enabled: !loading && passwordInput.text !== "" && accountNameInput.text !== "" && accountNameInput.valid
MessageDialog {
id: accountError
title: "Adding the account failed"
icon: StandardIcon.Critical
standardButtons: StandardButton.Ok
}
onClicked : {
// TODO the loaidng doesn't work because the function freezes th eview. Might need to use threads
loading = true
if (!validate()) {
Global.playErrorSound();
return loading = false
}
const errMessage = RootStore.generateNewAccount(passwordInput.text, accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.icon.emoji)
console.log(errMessage)
loading = false
if (errMessage) {
Global.playErrorSound();
if (Utils.isInvalidPasswordMessage(errMessage)) {
//% "Wrong password"
popup.passwordValidationError = qsTrId("wrong-password")
} else {
accountError.text = errMessage;
accountError.open();
}
return
}
popup.afterAddAccount();
popup.close();
}
}
]
}

View File

@ -8,6 +8,7 @@ QtObject {
id: root
property var currentAccount: Constants.isCppApp ? walletSectionAccounts.currentAccount: walletSectionCurrent
property var accounts: walletSectionAccounts.model
property var generatedAccounts: walletSectionAccounts.generated
property var appSettings: localAppSettings
property var accountSensitiveSettings: localAccountSensitiveSettings
property string locale: appSettings.locale

View File

@ -0,0 +1,354 @@
import QtQuick 2.14
import QtQuick.Controls 2.14
import QtQuick.Layouts 1.14
import StatusQ.Core 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import StatusQ.Popups 0.1
import StatusQ.Components 0.1
import StatusQ.Core.Utils 0.1
import StatusQ.Controls.Validators 0.1
import utils 1.0
import "../stores"
ColumnLayout {
id: advancedSection
property alias privateKey: privateKey.text
property int addAccountType: AdvancedAddAccountView.AddAccountType.GenerateNew
property string mnemonicText: getSeedPhraseString()
property string errorString: ""
property bool isValid: addAccountType === AdvancedAddAccountView.AddAccountType.ImportSeedPhrase ? grid.isValid :
addAccountType === AdvancedAddAccountView.AddAccountType.ImportPrivateKey ? (privateKey.text !== "" && privateKey.valid) : true
enum AddAccountType {
GenerateNew,
ImportSeedPhrase,
ImportPrivateKey
}
function reset() {
mnemonicText = ""
errorString = ""
select.currentIndex = 0
addAccountType = AdvancedAddAccountView.AddAccountType.GenerateNew
privateKey.text = ""
privateKey.reset()
for(var i = 0; i < grid.model; i++) {
if(grid.itemAtIndex(i)) {
grid.itemAtIndex(i).textEdit.text = ""
grid.itemAtIndex(i).textEdit.reset()
}
}
}
function validate() {
errorString = "";
if(addAccountType == AdvancedAddAccountView.AddAccountType.ImportSeedPhrase) {
mnemonicText = getSeedPhraseString()
if (!Utils.isMnemonic(mnemonicText)) {
//% "Invalid seed phrase"
errorString = qsTrId("custom-seed-phrase")
} else {
errorString = onboardingModule.validateMnemonic(mnemonicText)
const regex = new RegExp('word [a-z]+ not found in the dictionary', 'i');
if (regex.test(errorString)) {
//% "Invalid seed phrase"
errorString = qsTrId("custom-seed-phrase") + '. ' +
//% "This seed phrase doesn't match our supported dictionary. Check for misspelled words."
qsTrId("custom-seed-phrase-text-1")
}
}
return errorString === ""
}
else if(addAccountType == AdvancedAddAccountView.AddAccountType.ImportPrivateKey) {
if (privateKey.text === "") {
//% "You need to enter a private key"
errorString = qsTrId("you-need-to-enter-a-private-key")
} else if (!Utils.isPrivateKey(privateKey.text)) {
//% "Enter a valid private key (64 characters hexadecimal string)"
errorString = qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
} else {
errorString = ""
}
return errorString === ""
}
return true
}
function getSeedPhraseString() {
var seedPhrase = ""
for(var i = 0; i < grid.model; i++) {
seedPhrase += grid.itemAtIndex(i).text + " "
}
return seedPhrase
}
QtObject {
id: _internal
property int seedPhraseInputHeight: 44
property int seedPhraseInputWidth: 220
}
spacing: Style.current.padding
StatusSelect {
id: select
//% "Origin"
label: qsTr("Origin")
Layout.margins: Style.current.padding
property int currentIndex: 0
selectedItemComponent: StatusListItem {
id: selectedItem
icon.background.color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
tagsDelegate: StatusListItemTag {
color: model.color
height: Style.current.bigPadding
radius: 6
closeButtonVisible: false
icon.emoji: model.emoji
icon.emojiSize: Emoji.size.verySmall
icon.isLetterIdenticon: true
title: model.name
titleText.font.pixelSize: 12
titleText.color: Theme.palette.indirectColor1
}
}
model: ListModel {
Component.onCompleted: {
//% "Default"
append({"name": qsTr("Default"), "iconName": "status", "accountsModel": RootStore.generatedAccounts, "enabled": true})
//% "Add new"
append({"name": qsTr("Add new"), "iconName": "", "enabled": false})
//% "Import new Seed Phrase"
append({"name": qsTr("Import new Seed Phrase"), "iconName": "seed-phrase", "enabled": true})
//% "Import new Private Key"
append({"name": qsTr("Import new Private Key"), "iconName": "password", "enabled": true})
selectedItem.title = Qt.binding(function() {return get(select.currentIndex).name})
selectedItem.icon.name = Qt.binding(function() {return get(select.currentIndex).iconName})
selectedItem.tagsModel = Qt.binding(function() {return get(select.currentIndex).accountsModel})
}
}
selectMenu.delegate: StatusListItem {
id: defaultListItem
title: model.name
icon.name: model.iconName
tagsModel : model.accountsModel
enabled: model.enabled
icon.background.color: "transparent"
icon.color: model.accountsModel ? Theme.palette.primaryColor1 : Theme.palette.directColor5
tagsDelegate: StatusListItemTag {
color: model.color
height: 24
radius: 6
closeButtonVisible: false
icon.emoji: model.emoji
icon.emojiSize: Emoji.size.verySmall
icon.isLetterIdenticon: true
title: model.name
titleText.font.pixelSize: 12
titleText.color: Theme.palette.indirectColor1
}
onClicked: {
advancedSection.addAccountType = (index === 2) ? AdvancedAddAccountView.AddAccountType.ImportSeedPhrase :
(index === 3) ? AdvancedAddAccountView.AddAccountType.ImportPrivateKey :
AdvancedAddAccountView.AddAccountType.GenerateNew
select.currentIndex = index
select.selectMenu.close()
}
}
}
StatusInput {
id: privateKey
//% "Private key"
label: qsTrId("private-key")
charLimit: 64
input.multiline: true
input.minimumHeight: 80
input.maximumHeight: 108
//% "Paste the contents of your private key"
input.placeholderText: qsTrId("paste-the-contents-of-your-private-key")
visible: advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.ImportPrivateKey && advancedSection.visible
errorMessage: advancedSection.errorString
validators: [
StatusMinLengthValidator {
minLength: 1
//% "You need to enter a private key"
errorMessage: qsTrId("you-need-to-enter-a-private-key")
},
StatusValidator {
property var validate: function (value) {
return Utils.isPrivateKey(value)
}
//% "Enter a valid private key (64 characters hexadecimal string)"
errorMessage: qsTrId("enter-a-valid-private-key-(64-characters-hexadecimal-string)")
}
]
onVisibleChanged: {
if(visible)
privateKey.input.edit.forceActiveFocus();
}
}
GridView {
id: grid
Layout.preferredWidth: parent.width
Layout.preferredHeight: visible ? (cellHeight * model/2) + footerItem.height: 0
Layout.leftMargin: Style.current.padding
Layout.rightMargin: Style.current.padding
visible: advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.ImportSeedPhrase && advancedSection.visible
cellHeight: _internal.seedPhraseInputHeight + Style.current.halfPadding
cellWidth: _internal.seedPhraseInputWidth + Style.current.halfPadding
model: 12
interactive: false
property bool isValid: checkIsValid()
function checkIsValid() {
var valid = model > 0 ? true: false
for(var i = 0; i < model; i++) {
if(grid.itemAtIndex(i))
valid &= grid.itemAtIndex(i).isValid
}
return valid
}
onVisibleChanged: {
if(visible)
grid.itemAtIndex(0).textEdit.input.edit.forceActiveFocus();
}
// To-do Alex has introduced a model for bip39 dictonary, need to use it once its available
// https://github.com/status-im/status-desktop/pull/5058
delegate: StatusSeedPhraseInput {
id: statusSeedInput
width: _internal.seedPhraseInputWidth
height: _internal.seedPhraseInputHeight
textEdit.errorMessageCmp.visible: false
textEdit.input.anchors.topMargin: 11
leftComponentText: index + 1
property bool isValid: !!text
onIsValidChanged: {
grid.isValid = grid.checkIsValid()
}
onTextChanged: {
if (text !== "") {
grid.currentIndex = index;
}
}
// To-do Alex has introduced a model for bip39 dictonary, need to use it once its available
// https://github.com/status-im/status-desktop/pull/5058
// onDoneInsertingWord: {
// advancedSection.mnemonicText += (index === 0) ? word : (" " + word);
// for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
// if (parseInt(grid.itemAtIndex(i).leftComponentText) === (parseInt(leftComponentText)+1)) {
// grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
// }
// }
// }
onEditClicked: {
grid.currentIndex = index;
grid.itemAtIndex(index).textEdit.input.edit.forceActiveFocus();
}
onKeyPressed: {
if (event.key === Qt.Key_Tab || event.key === Qt.Key_Right) {
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)+1) <= grid.count ? (parseInt(leftComponentText)+1) : grid.count)) {
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
textEdit.input.tabNavItem = grid.itemAtIndex(i).textEdit.input.edit;
}
}
} else if (event.key === Qt.Key_Left) {
for (var i = !grid.atXBeginning ? 12 : 0; i < grid.count; i++) {
if (parseInt(grid.itemAtIndex(i).leftComponentText) === ((parseInt(leftComponentText)-1) >= 0 ? (parseInt(leftComponentText)-1) : 0)) {
grid.itemAtIndex(i).textEdit.input.edit.forceActiveFocus();
}
}
} else if (event.key === Qt.Key_Down) {
grid.itemAtIndex((index+1 < grid.count) ? (index+1) : (grid.count-1)).textEdit.input.edit.forceActiveFocus();
} else if (event.key === Qt.Key_Up) {
grid.itemAtIndex((index-1 >= 0) ? (index-1) : 0).textEdit.input.edit.forceActiveFocus();
}
}
textEdit.validators: [
StatusMinLengthValidator {
errorMessage: qsTr("Enter a valid word")
minLength: 3
}
]
}
footer: Item {
width: grid.width - Style.current.padding
height: button.height + errorMessage.height + 16*2
StatusBaseText {
id: errorMessage
anchors.left: parent.left
anchors.right: parent.right
anchors.top: parent.top
anchors.topMargin: Style.current.padding
height: visible ? implicitHeight : 0
visible: !!text
text: errorString
font.pixelSize: 12
color: Theme.palette.dangerColor1
horizontalAlignment: Text.AlignHCenter
wrapMode: Text.WordWrap
}
StatusButton {
id: button
anchors.top: errorMessage.bottom
anchors.topMargin: Style.current.padding
anchors.horizontalCenter: parent.horizontalCenter
//% "Use 24 word seed phrase"
text: grid.model === 12 ? qsTr("Use 24 word seed phrase"):
qsTr("Use 12 word seed phrase")
onClicked: grid.model = grid.model === 12 ? 24 : 12
}
}
}
RowLayout {
Layout.margins: Style.current.padding
Layout.preferredWidth: parent.width
spacing: Style.current.bigPadding
StatusSelect {
Layout.preferredWidth: 213
//% "Origin"
label: qsTr("Derivation Path")
selectedItemComponent: StatusListItem {
width: parent.width
icon.background.color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
title: "Default"
subTitle: "m/44/61/0/1"
enabled: false
}
enabled: false
}
StatusSelect {
Layout.preferredWidth: 213
//% "Origin"
label: qsTr("Account")
width: parent.width
enabled: false
selectedItemComponent: StatusListItem {
icon.background.color: "transparent"
border.width: 1
border.color: Theme.palette.baseColor2
title: "0x1234...abcd"
subTitle: "No activity"
enabled: false
}
}
}
}

View File

@ -10,6 +10,7 @@ import shared.controls 1.0
import StatusQ.Components 0.1
import StatusQ.Core.Theme 0.1
import StatusQ.Controls 0.1
import "../controls"
import "../popups"
@ -73,66 +74,10 @@ Rectangle {
font.weight: Font.Medium
font.pixelSize: 13
}
AddAccountButton {
id: addAccountButton
anchors.top: parent.top
anchors.right: parent.right
onClicked: {
if (newAccountMenu.opened) {
newAccountMenu.close()
} else {
newAccountMenu.popup(addAccountButton.x + addAccountButton.width/2 - newAccountMenu.width/2 ,
addAccountButton.y + addAccountButton.height + 55)
}
}
}
}
AddNewAccountMenu {
id: newAccountMenu
onAboutToShow: addAccountButton.state = "pressed"
onAboutToHide: {
addAccountButton.state = "default";
addAccountButton.checked = false;
}
onGenerateNewAccountTriggered: {
generateAccountModal.open();
}
onAddWatchAccountTriggered: {
addWatchOnlyAccountModal.open();
}
onEnterSeedPhraseTriggered: {
addAccountWithSeedModal.open();
}
onEnterPrivateKeyTriggered: {
addAccountWithPrivateKeydModal.open();
}
}
GenerateAccountModal {
id: generateAccountModal
anchors.centerIn: parent
onAfterAddAccount: walletInfoContainer.onAfterAddAccount()
emojiPopup: walletInfoContainer.emojiPopup
}
AddAccountWithSeedModal {
id: addAccountWithSeedModal
anchors.centerIn: parent
onAfterAddAccount: walletInfoContainer.onAfterAddAccount()
emojiPopup: walletInfoContainer.emojiPopup
}
AddAccountWithPrivateKeyModal {
id: addAccountWithPrivateKeydModal
anchors.centerIn: parent
onAfterAddAccount: walletInfoContainer.onAfterAddAccount()
emojiPopup: walletInfoContainer.emojiPopup
}
AddWatchOnlyAccountModal {
id: addWatchOnlyAccountModal
AddAccountModal {
id: addAccountModal
anchors.centerIn: parent
onAfterAddAccount: walletInfoContainer.onAfterAddAccount()
emojiPopup: walletInfoContainer.emojiPopup
@ -176,6 +121,20 @@ Rectangle {
}
}
footer: Item {
width: parent.width
height: addAccountBtn.height + Style.current.xlPadding
StatusButton {
id: addAccountBtn
anchors.top: parent.top
anchors.horizontalCenter: parent.horizontalCenter
anchors.margins: Style.current.bigPadding
//% "Add account"
text: qsTrId("add-account")
onClicked: addAccountModal.open()
}
}
model: RootStore.accounts
// model: RootStore.exampleWalletModel
}