/* * This file is part of the Flowee project * Copyright (C) 2021-2025 Tom Zander * * This program is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ import QtQuick import QtQuick.Controls.Basic as QQC2 import QtQuick.Layouts import "../Flowee" as Flowee import Flowee.org.pay import "../Utils.js" as Utils Item { id: root implicitWidth: { var w = columnWidth // columnWidth is defined by loader in NewAccountPane if (privKeyColumn.visible) w += columnWidth + 10 return w; } implicitHeight: 200 states: [ State { name: "inputState" PropertyChanges { target: privKeyColumn; opacity: 0 } PropertyChanges { target: seedDetailsColumn; opacity: 0 } }, State { name: "privKeyDetailsState" PropertyChanges { target: inputColumn; opacity: 0.65 } PropertyChanges { target: privKeyColumn; opacity: 1 } PropertyChanges { target: seedDetailsColumn; opacity: 0 } StateChangeScript { script: { if (accountName.visible) accountName.forceActiveFocus(); else ageButton.forceActiveFocus(); } } }, State { name: "seedDetailsState" PropertyChanges { target: inputColumn; opacity: 0.65 } PropertyChanges { target: privKeyColumn; opacity: 0 } PropertyChanges { target: seedDetailsColumn; opacity: 1 } StateChangeScript { script: { if (accountName2.visible) accountName2.forceActiveFocus(); else seedCheckButton.forceActiveFocus(); } } } ] state: "inputState" function toNextPage() { var type = inputColumn.typedData; if (type === Wallet.PrivateKey) { state = "privKeyDetailsState"; } else if (type === Wallet.CorrectMnemonic || type === Wallet.ElectrumMnemonic) { state = "seedDetailsState"; } } Column { id: inputColumn width: columnWidth spacing: 10 property var typedData: Pay.identifyString(secretText.totalText) property bool finished: typedData === Wallet.PrivateKey || typedData === Wallet.CorrectMnemonic || typedData === Wallet.ElectrumMnemonic onFinishedChanged: root.toNextPage(); Flowee.GroupBox { id: buttonsBox title: qsTr("Select import method") collapsable: false width: parent.width Item { width: parent.width height: scanButton.height Flowee.ImageButton { id: scanButton source: "qrc:/qr-code-scan" + (Pay.useDarkSkin ? "-light.svg" : ".svg"); onClicked: scanner.start(); iconSize: Math.min(inputColumn.width / 4, 100) x: (parent.width - width) / 2 // while NFC is not enabled.. // x: (parent.width - width * 2 - 20) / 2 text: qsTr("Camera") } QRScanner { id: scanner onFinished: { if ((scanType === QRScanner.Seed || scanType === QRScanner.PrivateKeyWIF) && scanResult !== "") secretText.text = scanResult; scanButton.forceActiveFocus(); } } Rectangle { id: nfcButton width: scanButton.width visible: false height: width radius: 90 color: "#00000000" border.color: "yellow" border.width: 2 x: (parent.width - width * 2 - 20) / 2 + width + 20 Flowee.Label { anchors.centerIn: parent text: "NFC" } } } } Row { spacing: 15 x: (inputColumn.width - width) / 2 Rectangle { width: 50 height: 1 color: palette.button anchors.verticalCenter: parent.verticalCenter } Flowee.Label { text: qsTr("OR") } Rectangle { width: 50 height: 1 color: palette.button anchors.verticalCenter: parent.verticalCenter } } Flowee.GroupBox { id: textSecretBox title: qsTr("Secret as text", "The seed-phrase or private key") collapsable: false width: parent.width Item { width: parent.width height: pasteButton.height / 3 * 2 + secretText.height Flowee.MultilineTextField { id: secretText width: parent.width clip: true height: Math.max((pasteButton.height - 10) * 2.3, implicitHeight) font.family: "monospace" inputMethodHints: Qt.ImhNoAutoUppercase } Flowee.TextPasteDecorator { id: pasteButton buddy: secretText clipboardTypes: ClipboardHelper.PrivateKey + ClipboardHelper.MnemonicSeed } } Flow { width: parent.width spacing: 10 Repeater { model: Pay.mnemonicProposals Rectangle { width: txt.width + 16 height: txt.height + 6 color: mainWindow.floweeBlue Flowee.Label { id: txt text: modelData anchors.centerIn: parent color: "white" } MouseArea { anchors.fill: parent onClicked: { var widget = secretText var bareText = widget.text; // if inputMethodComposing true, that makes it simple to avoid the word that // is being edited the 'text' property of secretText omits that one. if (!secretText.inputMethodComposing) { let lastWordPos = bareText.lastIndexOf(' '); if (lastWordPos > 0) bareText = bareText.substr(0, lastWordPos); } var newText = bareText + " " + modelData + " "; widget.text = newText widget.cursorPosition = newText.length } } } } } Flowee.Label { text: inputColumn.typedData === Wallet.PartialMnemonicWithTypo ? qsTr("Unknown word(s) found") : "" color: mainWindow.errorRed visible: text !== "" } } Behavior on opacity { NumberAnimation { } } } Item { id: privKeyColumn anchors.left: inputColumn.right anchors.leftMargin: 10 width: columnWidth height: parent.height enabled: visible visible: opacity > 0.1 ColumnLayout { spacing: 10 width: parent.width Flowee.GroupBox { title: qsTr("Address to import") collapsable: false Layout.fillWidth: true Flowee.LabelWithClipboard { // this shows the bitcoincash address matching the private key font.pixelSize: singleAddress.font.pixelSize * 0.9 text: { if (root.state !== "privKeyDetailsState") return ""; return Pay.addressForPrivKey(secretText.text); } } } Flowee.GroupBox { title: qsTr("New Wallet Name") collapsable: false Layout.fillWidth: true visible: { // don't ask for a name when the user imports a // wallet the first thing in a new instance. var all = portfolio.rawAccounts; if (all.length === 1 && !all[0].isUserOwned) return false; return true; } Flowee.TextField { id: accountName Layout.fillWidth: true } } Flowee.CheckBox { id: singleAddress Layout.fillWidth: true text: qsTr("Force Single Address"); toolTipText: qsTr("When enabled, no extra addresses will be auto-generated in this wallet.\nChange will come back to the imported key.") checked: true } Flowee.GroupBox { title: qsTr("Oldest Transaction") collapsable: false Layout.fillWidth: true Item { implicitWidth: parent.width implicitHeight: ageButton.height Flowee.BigButton { id: ageButton text: qsTr("Check Age", "online check for wallet age") enabled: !privKeyImportHelper.checking isMainButton: true; onClicked: { // setting new values here will start the check. privKeyImportHelper.secretType = Wallet.PrivateKey privKeyImportHelper.secret = secretText.text } ImportHelper { id: privKeyImportHelper onCheckingChanged: { if (checking) return; emptyPrivKeyWarningLabel.visible = false; ageButton.isMainButton = false; if (resultCount === 0) { emptyPrivKeyWarningLabel.visible = true; } else if (resultCount === 1) { let height = startHeight(0); oldestTransactionChooser.item.month.currentIndex = monthOnHeight(height) - 1; oldestTransactionChooser.item.year.currentIndex = yearOnHeight(height) - 2011; oldestTransactionChooser.item.enabled = false; privImportStartButton.isMainButton = true; } } } // TODO add warning } } Loader { id: oldestTransactionChooser Layout.fillWidth: true sourceComponent: oldestTransactionChooser_component } } Flowee.Label { id: emptyPrivKeyWarningLabel color: mainWindow.errorRed text: qsTr("Nothing found for wallet") visible: false } Flowee.BigButton { id: privImportStartButton text: qsTr("Start") Layout.alignment: Qt.AlignRight onClicked: { if (privKeyImportHelper.resultCount > 0) { var options = Pay.createImportedWallet(secretText.text, accountName.text, privKeyImportHelper.startHeight(0)) } else { var sh = new Date(oldestTransactionChooser.item.year.currentIndex + 2011, oldestTransactionChooser.item.month.currentIndex, 1) var height = Utils.heightOfBlockAtTime(sh) options = Pay.createImportedWallet(secretText.text, accountName.text, height) } options.forceSingleAddress = singleAddress.checked; for (let a of portfolio.accounts) { if (a.id === options.accountId) { portfolio.current = a; break; } } newAccountsPane.visible = false; } } } Behavior on opacity { NumberAnimation { } } } Column { id: seedDetailsColumn anchors.left: inputColumn.right anchors.leftMargin: 10 width: columnWidth height: parent.height spacing: 10 enabled: visible visible: opacity > 0.1 Flowee.GroupBox { title: qsTr("New Wallet Name") width: parent.width collapsable: false visible: { // don't ask for a name when the user imports a // wallet the first thing in a new instance. var all = portfolio.rawAccounts; if (all.length === 1 && !all[0].isUserOwned) return false; return true; } Flowee.TextField { id: accountName2 Layout.fillWidth: true } } Flowee.BigButton { id: seedCheckButton text: qsTr("Discover Details", "online check for wallet details") enabled: !seedImportHelper.checking isMainButton: true; x: 20 onClicked: { // setting new values here will start the check. seedImportHelper.secretType = inputColumn.typedData seedImportHelper.secret = secretText.text seedImportHelper.password = passwordField.text } ImportHelper { id: seedImportHelper onCheckingChanged: { if (checking) return; emptySeedWarningLabel.visible = false; if (failed || resultCount === 0) { // failed or empty seedCheckButton.isMainButton = false; emptySeedWarningLabel.visible = true; passwordBox.collapsed = false; } else if (resultCount >= 1) { // TODO what to do if there are more then 1? let height = startHeight(0); oldestTransactionChooser2.item.month.currentIndex = monthOnHeight(height) - 1; oldestTransactionChooser2.item.year.currentIndex = yearOnHeight(height) - 2011; oldestTransactionChooser2.item.enabled = false; derivationPath.text = derivation(0); derivationPath.enabled = false; seedCheckButton.isMainButton = false; seedStartButton.isMainButton = true; } } } } Flowee.GroupBox { title: qsTr("Oldest Transaction") width: parent.width collapsable: false Loader { id: oldestTransactionChooser2 Layout.fillWidth: true sourceComponent: oldestTransactionChooser_component } } Flowee.GroupBox { id: derivationLabel title: qsTr("Derivation") width: parent.width collapsable: false Flowee.TextField { id: derivationPath property bool derivationOk: Pay.checkDerivation(text); Layout.fillWidth: true text: "m/44'/0'/0'" // What most BCH wallets are created with color: derivationOk ? palette.text : "red" } } Flowee.Label { id: emptySeedWarningLabel color: mainWindow.errorRed text: seedImportHelper.failed ? qsTr("Discover failed. Set start date manually") : qsTr("Nothing found for seed. Does it have a password?") visible: false width: parent.width wrapMode: Text.Wrap } Flowee.GroupBox { id: passwordBox title: qsTr("Password") width: parent.width collapsable: true collapsed: true Flowee.TextField { id: passwordField Layout.fillWidth: true placeholderText: qsTr("imported wallet password") onDisplayTextChanged: seedCheckButton.isMainButton = true; } } Flowee.BigButton { id: seedStartButton text: qsTr("Start") anchors.right: parent.right onClicked: { if (seedImportHelper.resultCount > 0) { var options = Pay.createImportedHDWallet(secretText.text, passwordField.text, derivationPath.text, accountName2.text, seedImportHelper.startHeight(0), seedImportHelper.isElectrumSeed(0)); } else { var sh = new Date(oldestTransactionChooser2.item.year.currentIndex + 2011, oldestTransactionChooser2.item.month.currentIndex, 1); var height = Utils.heightOfBlockAtTime(sh) options = Pay.createImportedHDWallet(secretText.text, passwordField.text, derivationPath.text, accountName2.text, height); } for (let a of portfolio.accounts) { if (a.id === options.accountId) { portfolio.current = a; break; } } newAccountsPane.visible = false; tabbar.currentIndex = 0; } } Behavior on opacity { NumberAnimation { } } } Item { // non-gui items below. Component { id: oldestTransactionChooser_component Flow { property alias month: inner_month property alias year: inner_year width: parent.width spacing: 10 Flowee.ComboBox { id: inner_month model: { let locale = Qt.locale(); var list = []; for (let i = QQC2.Calendar.January; i <= QQC2.Calendar.December; ++i) { list.push(locale.monthName(i)); } return list; } width: implicitWidth * 1.3 // this makes it fit for bigger fonts. } Flowee.ComboBox { id: inner_year model: { var list = []; let last = new Date().getFullYear(); for (let i = 2011; i <= last; ++i) { list.push(i); } return list; } currentIndex: 12; } } } } }