status-desktop/ui/app/AppLayouts/Wallet/views/AdvancedAddAccountView.qml

389 lines
16 KiB
QML
Raw Normal View History

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 alias watchAddress: addressInput.text
property string errorString: ""
property bool isValid: addAccountType === AdvancedAddAccountView.AddAccountType.ImportSeedPhrase ? grid.isValid :
addAccountType === AdvancedAddAccountView.AddAccountType.ImportPrivateKey ? (privateKey.text !== "" && privateKey.valid) :
advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.WatchOnly ? (addressInput.text !== "" && addressInput.valid) : true
enum AddAccountType {
GenerateNew,
ImportSeedPhrase,
ImportPrivateKey,
WatchOnly
}
function reset() {
mnemonicText = ""
errorString = ""
select.currentIndex = 0
addAccountType = AdvancedAddAccountView.AddAccountType.GenerateNew
privateKey.text = ""
privateKey.reset()
addressInput.text = ""
addressInput.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 === ""
}
else if(advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.WatchOnly) {
return addressInput.valid
}
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})
//% "Import new Private Key"
append({"name": qsTrId("add-a-watch-account"), "iconName": "show", "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 :
(index === 4) ? AdvancedAddAccountView.AddAccountType.WatchOnly :
AdvancedAddAccountView.AddAccountType.GenerateNew
select.currentIndex = index
select.selectMenu.close()
}
}
}
StatusInput {
id: privateKey
//% "Private key"
label: qsTrId("private-key")
charLimit: 66
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
2022-04-08 15:06:56 +00:00
visible: false
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
}
}
}
StatusInput {
id: addressInput
visible: advancedSection.addAccountType === AdvancedAddAccountView.AddAccountType.WatchOnly && advancedSection.visible
//% "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
}
]
}
RowLayout {
Layout.margins: Style.current.padding
Layout.preferredWidth: parent.width
spacing: Style.current.bigPadding
visible: advancedSection.addAccountType !== AdvancedAddAccountView.AddAccountType.WatchOnly &&
advancedSection.addAccountType !== AdvancedAddAccountView.AddAccountType.ImportPrivateKey
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
}
}
}
}