diff --git a/components/DaemonConsole.qml b/components/DaemonConsole.qml index 8d4f90b6..ba07f523 100644 --- a/components/DaemonConsole.qml +++ b/components/DaemonConsole.qml @@ -106,7 +106,6 @@ Window { releasedColor: "#FF6C3C" pressedColor: "#FF4304" text: qsTr("Close") - KeyNavigation.tab: cancelButton onClicked: { root.close() root.accepted() diff --git a/components/LineEdit.qml b/components/LineEdit.qml index 96954344..1d83e617 100644 --- a/components/LineEdit.qml +++ b/components/LineEdit.qml @@ -39,6 +39,7 @@ Item { property bool error: false signal editingFinished() signal accepted(); + signal textUpdated(); height: 37 @@ -71,5 +72,6 @@ Item { font.pixelSize: parent.fontSize onEditingFinished: item.editingFinished() onAccepted: item.accepted(); + onTextChanged: item.textUpdated() } } diff --git a/src/libwalletqt/WalletManager.cpp b/src/libwalletqt/WalletManager.cpp index 45aff22e..1db216cf 100644 --- a/src/libwalletqt/WalletManager.cpp +++ b/src/libwalletqt/WalletManager.cpp @@ -86,6 +86,22 @@ Wallet *WalletManager::recoveryWallet(const QString &path, const QString &memo, return m_currentWallet; } +Wallet *WalletManager::createWalletFromKeys(const QString &path, const QString &language, bool testnet, + const QString &address, const QString &viewkey, const QString &spendkey, + quint64 restoreHeight) +{ + QMutexLocker locker(&m_mutex); + if (m_currentWallet) { + qDebug() << "Closing open m_currentWallet" << m_currentWallet; + delete m_currentWallet; + m_currentWallet = NULL; + } + Monero::Wallet * w = m_pimpl->createWalletFromKeys(path.toStdString(), language.toStdString(), testnet, restoreHeight, + address.toStdString(), viewkey.toStdString(), spendkey.toStdString()); + m_currentWallet = new Wallet(w); + return m_currentWallet; +} + QString WalletManager::closeWallet() { @@ -183,6 +199,16 @@ bool WalletManager::addressValid(const QString &address, bool testnet) const return Monero::Wallet::addressValid(address.toStdString(), testnet); } +bool WalletManager::keyValid(const QString &key, const QString &address, bool isViewKey, bool testnet) const +{ + std::string error; + if(!Monero::Wallet::keyValid(key.toStdString(), address.toStdString(), isViewKey, testnet, error)){ + qDebug() << QString::fromStdString(error); + return false; + } + return true; +} + QString WalletManager::paymentIdFromAddress(const QString &address, bool testnet) const { return QString::fromStdString(Monero::Wallet::paymentIdFromAddress(address.toStdString(), testnet)); diff --git a/src/libwalletqt/WalletManager.h b/src/libwalletqt/WalletManager.h index 20d2bec7..6bba0ce6 100644 --- a/src/libwalletqt/WalletManager.h +++ b/src/libwalletqt/WalletManager.h @@ -53,6 +53,14 @@ public: Q_INVOKABLE Wallet * recoveryWallet(const QString &path, const QString &memo, bool testnet = false, quint64 restoreHeight = 0); + Q_INVOKABLE Wallet * createWalletFromKeys(const QString &path, + const QString &language, + bool testnet, + const QString &address, + const QString &viewkey, + const QString &spendkey = "", + quint64 restoreHeight = 0); + /*! * \brief closeWallet - closes current open wallet and frees memory * \return wallet address @@ -92,6 +100,8 @@ public: Q_INVOKABLE bool paymentIdValid(const QString &payment_id) const; Q_INVOKABLE bool addressValid(const QString &address, bool testnet) const; + Q_INVOKABLE bool keyValid(const QString &key, const QString &address, bool isViewKey, bool testnet) const; + Q_INVOKABLE QString paymentIdFromAddress(const QString &address, bool testnet) const; Q_INVOKABLE QString checkPayment(const QString &address, const QString &txid, const QString &txkey, const QString &daemon_address) const; diff --git a/wizard/WizardCreateViewOnlyWallet.qml b/wizard/WizardCreateViewOnlyWallet.qml index dc1b813e..c95ced8e 100644 --- a/wizard/WizardCreateViewOnlyWallet.qml +++ b/wizard/WizardCreateViewOnlyWallet.qml @@ -80,11 +80,12 @@ Item { WizardManageWalletUI { id: uiItem - titleText: qsTr("Give your view only wallet a name") + translationManager.emptyString + titleText: qsTr("Create view only wallet") + translationManager.emptyString wordsTextItem.visible: false restoreHeightVisible:false walletName: appWindow.walletName + "-viewonly" progressDotsModel: dotsModel + recoverMode: false } diff --git a/wizard/WizardCreateWallet.qml b/wizard/WizardCreateWallet.qml index 110eeb87..084bc635 100644 --- a/wizard/WizardCreateWallet.qml +++ b/wizard/WizardCreateWallet.qml @@ -96,12 +96,13 @@ Item { WizardManageWalletUI { id: uiItem - titleText: qsTr("Give your new wallet a name") + translationManager.emptyString + titleText: qsTr("Create a new wallet") + translationManager.emptyString wordsTextTitle: qsTr("Here is your wallet's 25 word mnemonic seed") + translationManager.emptyString wordsTextItem.clipboardButtonVisible: true wordsTextItem.tipTextVisible: true wordsTextItem.memoTextReadOnly: true restoreHeightVisible:false + recoverMode: false } Component.onCompleted: { diff --git a/wizard/WizardMain.qml b/wizard/WizardMain.qml index 85407558..99558b36 100644 --- a/wizard/WizardMain.qml +++ b/wizard/WizardMain.qml @@ -29,10 +29,12 @@ import QtQuick 2.2 import Qt.labs.settings 1.0 import QtQuick.Dialogs 1.2 +import QtQuick.Layouts 1.2 import "../components" -Rectangle { +GridLayout { + anchors.fill: parent id: wizard property alias nextButton : nextButton property var settings : ({}) @@ -53,9 +55,9 @@ Rectangle { signal wizardRestarted(); signal useMoneroClicked() signal openWalletFromFileClicked() - border.color: "#DBDBDB" - border.width: 1 - color: "#FFFFFF" +// border.color: "#DBDBDB" +// border.width: 1 +// color: "#FFFFFF" function restart(){ wizard.currentPage = 0; diff --git a/wizard/WizardManageWalletUI.qml b/wizard/WizardManageWalletUI.qml index d3bfb558..9ad80451 100644 --- a/wizard/WizardManageWalletUI.qml +++ b/wizard/WizardManageWalletUI.qml @@ -29,12 +29,16 @@ import QtQuick 2.2 import moneroComponents.TranslationManager 1.0 import QtQuick.Dialogs 1.2 -import QtQuick.Layouts 1.1 +import QtQuick.Layouts 1.2 import "../components" +import 'utils.js' as Utils // Reusable component for managing wallet (account name, path, private key) -Item { +ColumnLayout { + anchors.left: parent.left + anchors.right: parent.right + spacing: 5 property alias titleText: titleText.text property alias accountNameText: accountName.text @@ -45,15 +49,49 @@ Item { property alias restoreHeightVisible: restoreHeightItem.visible property alias walletName : accountName.text property alias progressDotsModel : progressDots.model + property alias recoverFromKeysAddress: addressLine.text; + property alias recoverFromKeysViewKey: viewKeyLine.text; + property alias recoverFromKeysSpendKey: spendKeyLine.text; + // recover mode or create new wallet + property bool recoverMode: false + // Recover form seed or keys + property bool recoverFromSeedMode: true - // TODO extend properties if needed + function checkFields(){ + var addressOK = walletManager.addressValid(addressLine.text, wizard.settings.testnet) + var viewKeyOK = walletManager.keyValid(viewKeyLine.text, addressLine.text, true, wizard.settings.testnet) + // Spendkey is optional + var spendKeyOK = (spendKeyLine.text.length > 0)? walletManager.keyValid(spendKeyLine.text, addressLine.text, false, wizard.settings.testnet) : true - anchors.fill: parent - Row { + addressLine.error = !addressOK && addressLine.text.length != 0 + viewKeyLine.error = !viewKeyOK && viewKeyLine.text.length != 0 + spendKeyLine.error = !spendKeyOK && spendKeyLine.text.length != 0 + + return addressOK && viewKeyOK && spendKeyOK + } + + function checkNextButton(){ + wizard.nextButton.enabled = false + console.log("check next", recoverFromSeed.visible) + if(recoverMode && !recoverFromSeedMode) { + console.log("checking key fields") + wizard.nextButton.enabled = checkFields(); + } else if (recoverMode && recoverFromSeedMode) { + wizard.nextButton.enabled = checkSeed() + } else + wizard.nextButton.enabled = true; + } + + function checkSeed() { + console.log("Checking seed") + var wordsArray = Utils.lineBreaksToSpaces(uiItem.wordsTextItem.memoText).split(" "); + return wordsArray.length === 25 + } + + RowLayout { id: dotsRow - anchors.top: parent.top - anchors.right: parent.right - anchors.topMargin: 85 + Layout.topMargin: 85 + Layout.alignment: Qt.AlignRight spacing: 6 ListModel { @@ -75,180 +113,173 @@ Item { } } - Column { + RowLayout { id: headerColumn - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - anchors.top: parent.top - anchors.topMargin: 74 - spacing: 24 - Text { + Layout.fillWidth: true + horizontalAlignment: Text.AlignHCenter id: titleText - anchors.left: parent.left - width: headerColumn.width - dotsRow.width - 16 font.family: "Arial" font.pixelSize: 28 wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter - //renderType: Text.NativeRendering color: "#3F3F3F" } + } - Text { - anchors.left: parent.left - anchors.right: parent.right - font.family: "Arial" - font.pixelSize: 18 - wrapMode: Text.Wrap - horizontalAlignment: Text.AlignHCenter - //renderType: Text.NativeRendering - color: "#4A4646" - text: qsTr("or use the name suggested below:") + translationManager.emptyString + ColumnLayout { + + Label { + Layout.fillWidth: true + Layout.topMargin: 20 + fontSize: 14 + text: qsTr("Wallet name") + + translationManager.emptyString + } + + LineEdit { + id: accountName + Layout.fillWidth: true + text: defaultAccountName + onTextUpdated: checkNextButton() } } - Item { - id: walletNameItem - anchors.top: headerColumn.bottom - anchors.horizontalCenter: parent.horizontalCenter - anchors.topMargin: 24 - width: 300 - height: 62 + RowLayout{ + visible: recoverMode + StandardButton { + id: recoverFromSeedButton + text: qsTr("Restore from seed") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled: recoverFromKeys.visible + onClicked: { + recoverFromSeedMode = true; + checkNextButton(); + } + } + + StandardButton { + id: recoverFromKeysButton + text: qsTr("Restore from keys") + translationManager.emptyString + shadowReleasedColor: "#FF4304" + shadowPressedColor: "#B32D00" + releasedColor: "#FF6C3C" + pressedColor: "#FF4304" + enabled: recoverFromSeed.visible + onClicked: { + recoverFromSeedMode = false; + checkNextButton(); + } + } + } + + // Recover from seed + RowLayout { + id: recoverFromSeed + visible: !recoverMode || ( recoverMode && recoverFromSeedMode) + WizardMemoTextInput { + id : memoTextItem + Layout.fillWidth: true + Layout.preferredWidth: 600 + Layout.minimumWidth: 300 + } + } + + // Recover from keys + GridLayout { + id: recoverFromKeys + visible: recoverMode && !recoverFromSeedMode + columns: 1 + LineEdit { + Layout.fillWidth: true + id: addressLine + placeholderText: qsTr("Account address (public)") + onTextUpdated: checkNextButton() + } + LineEdit { + Layout.fillWidth: true + id: viewKeyLine + placeholderText: qsTr("View key (private)") + onTextUpdated: checkNextButton() + + } + LineEdit { + Layout.fillWidth: true + id: spendKeyLine + placeholderText: qsTr("Spend key (private)") + onTextUpdated: checkNextButton() + } + } + + RowLayout { + Text { + visible: false + Layout.fillWidth: true + id: frameHeader + font.family: "Arial" + font.pixelSize: 24 + font.bold: true + wrapMode: Text.Wrap + color: "#4A4646" + } + } + + RowLayout { + // Restore Height + LineEdit { + id: restoreHeightItem + Layout.preferredWidth: 250 + placeholderText: qsTr("Restore height (optional)") + validator: IntValidator { + bottom:0 + } + } + } + + ColumnLayout { + + Label { + Layout.fillWidth: true + Layout.topMargin: 20 + fontSize: 14 + text: qsTr("Your wallet is stored in") + translationManager.emptyString + + translationManager.emptyString + } TextInput { - id: accountName - anchors.fill: parent - horizontalAlignment: TextInput.AlignHCenter - verticalAlignment: TextInput.AlignVCenter - font.family: "Arial" - font.pixelSize: 32 - renderType: Text.NativeRendering - color: "#FF6C3C" - focus: true - text: defaultAccountName - selectByMouse: true - - Keys.onReleased: { - wizard.nextButton.enabled = (accountName.length > 0) - } - - } - - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 1 - color: "#DBDBDB" - } - } - - Text { - id: frameHeader - anchors.left: parent.left - anchors.right: parent.right - anchors.leftMargin: 16 - anchors.rightMargin: 16 - anchors.top: walletNameItem.bottom - anchors.topMargin: 24 - font.family: "Arial" - font.pixelSize: 24 - font.bold: true - //renderType: Text.NativeRendering - color: "#4A4646" - elide: Text.ElideRight - horizontalAlignment: Text.AlignHCenter - } - - - WizardMemoTextInput { - id : memoTextItem - width: parent.width - anchors.top : frameHeader.bottom - anchors.topMargin: 16 - } - - // Restore Height - LineEdit { - id: restoreHeightItem - anchors.top: memoTextItem.bottom - width: 250 - anchors.topMargin: 20 - placeholderText: qsTr("Restore height (optional)") - Layout.alignment: Qt.AlignCenter - validator: IntValidator { - bottom:0 - } - } - Row { - anchors.left: parent.left - anchors.right: parent.right - anchors.top: (restoreHeightItem.visible)? restoreHeightItem.bottom : (memoTextItem.visible)? memoTextItem.bottom : frameHeader.bottom - anchors.topMargin: 24 - spacing: 16 - - Text { - anchors.verticalCenter: parent.verticalCenter + Layout.fillWidth: true + id: fileUrlInput + clip: true font.family: "Arial" font.pixelSize: 18 - //renderType: Text.NativeRendering - color: "#4A4646" - text: qsTr("Your wallet is stored in") + translationManager.emptyString + color: "#6B0072" + selectByMouse: true + text: moneroAccountsDir + "/" + // workaround for the bug "filechooser only opens once" + MouseArea { + anchors.fill: parent + onClicked: { + mouse.accepted = false + fileDialog.folder = walletManager.localPathToUrl(fileUrlInput.text) + fileDialog.open() + fileUrlInput.focus = true + } + } } - Item { - anchors.verticalCenter: parent.verticalCenter - width: parent.width - x - height: 34 - - FileDialog { - id: fileDialog - selectMultiple: false - selectFolder: true - title: qsTr("Please choose a directory") + translationManager.emptyString - onAccepted: { - fileUrlInput.text = walletManager.urlToLocalPath(fileDialog.folder) - fileDialog.visible = false - } - onRejected: { - fileDialog.visible = false - } + FileDialog { + id: fileDialog + selectMultiple: false + selectFolder: true + title: qsTr("Please choose a directory") + translationManager.emptyString + onAccepted: { + fileUrlInput.text = walletManager.urlToLocalPath(fileDialog.folder) + fileDialog.visible = false } - - TextInput { - id: fileUrlInput - anchors.fill: parent - anchors.leftMargin: 5 - anchors.rightMargin: 5 - clip: true - font.family: "Arial" - font.pixelSize: 18 - color: "#6B0072" - verticalAlignment: Text.AlignVCenter - selectByMouse: true - - text: moneroAccountsDir + "/" - // workaround for the bug "filechooser only opens once" - MouseArea { - anchors.fill: parent - onClicked: { - mouse.accepted = false - fileDialog.folder = walletManager.localPathToUrl(fileUrlInput.text) - fileDialog.open() - fileUrlInput.focus = true - } - } - } - - Rectangle { - anchors.left: parent.left - anchors.right: parent.right - anchors.bottom: parent.bottom - height: 1 - color: "#DBDBDB" + onRejected: { + fileDialog.visible = false } } } diff --git a/wizard/WizardMemoTextInput.qml b/wizard/WizardMemoTextInput.qml index 13540c80..dba96ada 100644 --- a/wizard/WizardMemoTextInput.qml +++ b/wizard/WizardMemoTextInput.qml @@ -24,15 +24,27 @@ Column { TextEdit { id: memoTextInput + property alias placeholderText: memoTextPlaceholder.text textMargin: 8 text: "" font.family: "Arial" - font.pointSize: 16 + font.pixelSize: 16 wrapMode: TextInput.Wrap width: parent.width selectByMouse: true property int minimumHeight: 100 height: contentHeight > minimumHeight ? contentHeight : minimumHeight + + Text { + id: memoTextPlaceholder + anchors.fill:parent + font.pixelSize: 16 + anchors.margins: 8 + font.bold:true + text: qsTr("Enter your 25 word mnemonic seed") + color: "#BABABA" + visible: !memoTextInput.text/* && !parent.focus*/ + } } Image { id : clipboardButton diff --git a/wizard/WizardOptions.qml b/wizard/WizardOptions.qml index e0e66adc..622f9adb 100644 --- a/wizard/WizardOptions.qml +++ b/wizard/WizardOptions.qml @@ -185,7 +185,7 @@ Item { font.pixelSize: 16 color: "#4A4949" horizontalAlignment: Text.AlignHCenter - text: qsTr("Restore wallet from 25 word mnemonic seed") + translationManager.emptyString + text: qsTr("Restore wallet from keys or mnemonic seed") + translationManager.emptyString width:page.buttonSize wrapMode: Text.WordWrap } diff --git a/wizard/WizardRecoveryWallet.qml b/wizard/WizardRecoveryWallet.qml index 185a0cf1..92e1e751 100644 --- a/wizard/WizardRecoveryWallet.qml +++ b/wizard/WizardRecoveryWallet.qml @@ -49,39 +49,52 @@ Item { } function onPageOpened(settingsObject) { - checkNextButton(); - } - - function checkNextButton() { - var wordsArray = Utils.lineBreaksToSpaces(uiItem.wordsTextItem.memoText).split(" "); - wizard.nextButton.enabled = wordsArray.length === 25; + console.log("on page opened") + uiItem.checkNextButton(); } function onPageClosed(settingsObject) { settingsObject['account_name'] = uiItem.accountNameText settingsObject['words'] = Utils.lineBreaksToSpaces(uiItem.wordsTextItem.memoText) settingsObject['wallet_path'] = uiItem.walletPath + settingsObject['recover_address'] = uiItem.recoverFromKeysAddress + settingsObject['recover_viewkey'] = uiItem.recoverFromKeysViewKey + settingsObject['recover_spendkey'] = uiItem.recoverFromKeysSpendKey + + var restoreHeight = parseInt(uiItem.restoreHeight); settingsObject['restore_height'] = isNaN(restoreHeight)? 0 : restoreHeight var walletFullPath = wizard.createWalletPath(uiItem.walletPath,uiItem.accountNameText); if(!wizard.walletPathValid(walletFullPath)){ return false } - return recoveryWallet(settingsObject) + return recoveryWallet(settingsObject, uiItem.recoverFromSeedMode) } - function recoveryWallet(settingsObject) { + function recoveryWallet(settingsObject, fromSeed) { var testnet = appWindow.persistentSettings.testnet; var restoreHeight = settingsObject.restore_height; var tmp_wallet_filename = oshelper.temporaryFilename() console.log("Creating temporary wallet", tmp_wallet_filename) - var wallet = walletManager.recoveryWallet(tmp_wallet_filename, settingsObject.words, testnet, restoreHeight); + + // From seed or keys + if(fromSeed) + var wallet = walletManager.recoveryWallet(tmp_wallet_filename, settingsObject.words, testnet, restoreHeight) + else + var wallet = walletManager.createWalletFromKeys(tmp_wallet_filename, settingsObject.language, testnet, + settingsObject.recover_address, settingsObject.recover_viewkey, + settingsObject.recover_spendkey, restoreHeight) + + var success = wallet.status === Wallet.Status_Ok; if (success) { settingsObject['wallet'] = wallet; settingsObject['is_recovering'] = true; settingsObject['tmp_wallet_filename'] = tmp_wallet_filename } else { + console.log(wallet.errorString) + walletErrorDialog.text = wallet.errorString; + walletErrorDialog.open(); walletManager.closeWallet(); } return success; @@ -92,13 +105,15 @@ Item { WizardManageWalletUI { id: uiItem accountNameText: defaultAccountName - titleText: qsTr("Give your restored wallet a name") + translationManager.emptyString + titleText: qsTr("Restore wallet") + translationManager.emptyString wordsTextTitle: qsTr("Enter your 25 word mnemonic seed:") + translationManager.emptyString wordsTextItem.clipboardButtonVisible: false wordsTextItem.tipTextVisible: false wordsTextItem.memoTextReadOnly: false wordsTextItem.memoText: "" + wordsTextItem.visible: true restoreHeightVisible: true + recoverMode: true wordsTextItem.onMemoTextChanged: { checkNextButton(); }