status-desktop/ui/app/AppLayouts/Wallet/popups/AddAccountModal.qml

353 lines
14 KiB
QML

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"
import "../panels"
StatusModal {
id: root
readonly property int marginBetweenInputs: 38
property var emojiPopup: null
header.title: qsTr("Generate an account")
closePolicy: nextButton.loading? Popup.NoAutoClose : Popup.CloseOnEscape
hasCloseButton: !nextButton.loading
signal afterAddAccount()
Connections {
target: emojiPopup
enabled: root.opened
function onEmojiSelected (emojiText, atCursor) {
accountNameInput.input.asset.emoji = emojiText
}
}
Connections {
target: walletSectionAccounts
function onUserAuthenticationSuccess(password: string) {
validationError.text = ""
d.password = password
RootStore.loggedInUserAuthenticated = true
if (d.selectedAccountType === Constants.AddAccountType.ImportPrivateKey) {
d.generateNewAccount()
}
else {
if (!d.selectedKeyUidMigratedToKeycard) {
d.getDerivedAddressList()
}
}
}
function onUserAuthentiactionFail() {
d.password = ""
RootStore.loggedInUserAuthenticated = false
validationError.text = qsTr("An authentication failed")
nextButton.loading = false
}
}
QtObject {
id: d
readonly property int numOfItems: 100
readonly property int pageNumber: 1
property string password: ""
property int selectedAccountType: Constants.AddAccountType.GenerateNew
property string selectedAccountDerivedFromAddress: ""
property string selectedKeyUid: RootStore.defaultSelectedKeyUid
property bool selectedKeyUidMigratedToKeycard: RootStore.defaultSelectedKeyUidMigratedToKeycard
property string selectedPath: ""
property string selectedAddress: ""
property bool selectedAddressAvailable: false
property bool useFullyCustomPath: false
readonly property bool authenticationNeeded: d.selectedAccountType !== Constants.AddAccountType.WatchOnly &&
d.password === ""
property string addAccountIcon: ""
property bool isLoading: RootStore.derivedAddressesLoading
onIsLoadingChanged: {
if(!isLoading && nextButton.loading) {
d.generateNewAccount()
}
}
function getDerivedAddressList() {
if (d.useFullyCustomPath) {
if(!!d.selectedPath && !!d.selectedAccountDerivedFromAddress
&& (d.password.length > 0)) {
RootStore.getDerivedAddressList(d.password, d.selectedAccountDerivedFromAddress,
d.selectedPath, numOfItems, pageNumber,
!(d.selectedKeyUidMigratedToKeycard || userProfile.isKeycardUser))
}
} else if(d.selectedAccountType === Constants.AddAccountType.ImportSeedPhrase
&& !!advancedSelection.expandableItem.path
&& !!advancedSelection.expandableItem.mnemonicText) {
RootStore.getDerivedAddressListForMnemonic(advancedSelection.expandableItem.mnemonicText,
advancedSelection.expandableItem.path, numOfItems, pageNumber)
} else if(!!d.selectedPath && !!d.selectedAccountDerivedFromAddress
&& (d.password.length > 0)) {
RootStore.getDerivedAddress(d.password, d.selectedAccountDerivedFromAddress, d.selectedPath,
!(d.selectedKeyUidMigratedToKeycard || userProfile.isKeycardUser))
}
}
function generateNewAccount() {
// TODO the loading doesn't work because the function freezes the view. Might need to use threads
if (!advancedSelection.validate()) {
Global.playErrorSound()
return nextButton.loading = false
}
let errMessage = ""
switch(d.selectedAccountType) {
case Constants.AddAccountType.GenerateNew:
if (d.selectedKeyUidMigratedToKeycard) {
errMessage = RootStore.addNewWalletAccountGeneratedFromKeycard(Constants.generatedWalletType,
accountNameInput.text,
colorSelectionGrid.selectedColor,
accountNameInput.input.asset.emoji)
}
else {
let finalFullPath = advancedSelection.expandableItem.completePath
if (!d.useFullyCustomPath) {
finalFullPath = d.selectedPath
}
errMessage = RootStore.generateNewAccount(d.password, accountNameInput.text, colorSelectionGrid.selectedColor,
accountNameInput.input.asset.emoji, finalFullPath,
advancedSelection.expandableItem.derivedFromAddress)
}
break
case Constants.AddAccountType.ImportSeedPhrase:
errMessage = RootStore.addAccountsFromSeed(advancedSelection.expandableItem.mnemonicText, d.password,
accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.asset.emoji,
advancedSelection.expandableItem.completePath)
break
case Constants.AddAccountType.ImportPrivateKey:
errMessage = RootStore.addAccountsFromPrivateKey(advancedSelection.expandableItem.privateKey, d.password,
accountNameInput.text, colorSelectionGrid.selectedColor, accountNameInput.input.asset.emoji)
break
case Constants.AddAccountType.WatchOnly:
errMessage = RootStore.addWatchOnlyAccount(advancedSelection.expandableItem.watchAddress, accountNameInput.text,
colorSelectionGrid.selectedColor, accountNameInput.input.asset.emoji)
break
}
nextButton.loading = false
if (errMessage) {
console.warn(`Unhandled error case. Status-go message: ${errMessage}`)
} else {
root.afterAddAccount()
root.close()
}
}
function nextButtonClicked() {
nextButton.loading = true
if (d.authenticationNeeded) {
d.password = ""
if (d.selectedKeyUidMigratedToKeycard &&
d.selectedAccountType === Constants.AddAccountType.GenerateNew) {
RootStore.authenticateUserAndDeriveAddressOnKeycardForPath(d.selectedKeyUid, d.selectedPath, true)
}
else {
RootStore.authenticateUser()
}
}
else {
d.generateNewAccount()
}
}
}
onOpened: {
RootStore.loggedInUserAuthenticated = false
d.addAccountIcon = "password"
if (RootStore.loggedInUserUsesBiometricLogin()) {
d.addAccountIcon = "touch-id"
}
else if (RootStore.loggedInUserIsKeycardUser()) {
d.addAccountIcon = "keycard"
}
accountNameInput.input.asset.emoji = StatusQUtils.Emoji.getRandomEmoji(StatusQUtils.Emoji.size.verySmall)
colorSelectionGrid.selectedColorIndex = Math.floor(Math.random() * colorSelectionGrid.model.length)
accountNameInput.input.edit.forceActiveFocus()
}
onClosed: {
RootStore.loggedInUserAuthenticated = false
d.password = ""
validationError.text = ""
accountNameInput.reset()
advancedSelection.expanded = false
advancedSelection.reset()
}
contentItem: StatusScrollView {
id: scroll
implicitWidth: root.width
implicitHeight: 400
topPadding: Style.current.halfPadding
bottomPadding: Style.current.halfPadding
leftPadding: Style.current.padding
rightPadding: Style.current.padding
objectName: "AddAccountModalContent"
Column {
property alias accountNameInput: accountNameInput
width: scroll.availableWidth
spacing: Style.current.halfPadding
topPadding: 20
StatusBaseText {
id: validationError
visible: text !== ""
width: parent.width
height: 16
horizontalAlignment: Text.AlignHCenter
font.pixelSize: 12
color: Style.current.danger
wrapMode: TextEdit.Wrap
}
StatusInput {
id: accountNameInput
placeholderText: qsTr("Enter an account name...")
label: qsTr("Account name")
input.isIconSelectable: true
input.asset.color: colorSelectionGrid.selectedColor ? colorSelectionGrid.selectedColor : Theme.palette.directColor1
input.leftPadding: Style.current.padding
onIconClicked: {
root.emojiPopup.open()
root.emojiPopup.emojiSize = StatusQUtils.Emoji.size.verySmall
root.emojiPopup.x = root.x + accountNameInput.x + Style.current.padding
root.emojiPopup.y = root.y + contentItem.y + accountNameInput.y + accountNameInput.height + Style.current.halfPadding
}
validators: [
StatusMinLengthValidator {
errorMessage: qsTr("You need to enter an account name")
minLength: 1
}
]
onKeyPressed: {
if(event.key === Qt.Key_Tab) {
if (nextButton.enabled) {
nextButton.forceActiveFocus(Qt.MouseFocusReason)
event.accepted = true
}
}
}
}
StatusColorSelectorGrid {
id: colorSelectionGrid
anchors.horizontalCenter: parent.horizontalCenter
enabled: accountNameInput.valid
titleText: qsTr("color").toUpperCase()
}
StatusExpandableItem {
id: advancedSelection
property bool isValid: true
function validate() {
return !!expandableItem && expandableItem.validate()
}
function reset() {
return !!expandableItem && expandableItem.reset()
}
anchors.horizontalCenter: parent.horizontalCenter
width: parent.width
primaryText: qsTr("Advanced")
type: StatusExpandableItem.Type.Tertiary
expandable: true
expandableComponent: AdvancedAddAccountView {
width: parent.width
onCalculateDerivedPath: {
d.selectedAccountDerivedFromAddress = derivedFromAddress
d.selectedPath = path
if (d.selectedKeyUidMigratedToKeycard) {
d.password = ""
validationError.text = ""
RootStore.loggedInUserAuthenticated = false
}
else{
d.getDerivedAddressList()
}
}
onEnterPressed: {
if (nextButton.enabled) {
nextButton.clicked(null)
return
}
}
Component.onCompleted: {
d.selectedAccountType = Qt.binding(() => addAccountType)
d.selectedAccountDerivedFromAddress = Qt.binding(() => derivedFromAddress)
d.selectedKeyUid = Qt.binding(() => selectedKeyUid)
d.selectedKeyUidMigratedToKeycard = Qt.binding(() => selectedKeyUidMigratedToKeycard)
d.selectedPath = Qt.binding(() => path)
d.selectedAddress = Qt.binding(() => selectedAddress)
d.selectedAddressAvailable = Qt.binding(() => selectedAddressAvailable)
d.useFullyCustomPath = Qt.binding(() => useFullyCustomPath)
advancedSelection.isValid = Qt.binding(() => isValid)
}
}
}
}
}
rightButtons: [
StatusButton {
id: nextButton
text: {
if (loading) {
return qsTr("Loading...")
}
return qsTr("Add account")
}
enabled: {
if (!accountNameInput.valid ||
loading ||
((root.authenticationNeeded && !d.useFullyCustomPath) && !d.selectedAddressAvailable)) {
return false
}
return advancedSelection.isValid
}
icon.name: d.authenticationNeeded? d.addAccountIcon : ""
highlighted: focus
onClicked : d.nextButtonClicked()
}
]
}