Files

551 lines
20 KiB
QML
Raw Permalink Normal View History

2021-11-30 13:51:01 +01:00
/*
* This file is part of the Flowee project
* Copyright (C) 2021-2025 Tom Zander <tom@flowee.org>
2021-11-30 13:51:01 +01:00
*
* 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 <http://www.gnu.org/licenses/>.
*/
2022-11-26 10:46:57 +01:00
import QtQuick
2025-06-18 17:48:29 +02:00
import QtQuick.Controls.Basic as QQC2
2022-11-26 10:46:57 +01:00
import QtQuick.Layouts
2022-11-14 21:19:31 +01:00
import "../Flowee" as Flowee
2022-11-26 10:46:57 +01:00
import Flowee.org.pay
2025-11-14 15:59:47 +01:00
import "../Utils.js" as Utils
2024-05-03 22:15:51 +02:00
Item {
id: root
2024-05-03 22:15:51 +02:00
implicitWidth: {
var w = columnWidth // columnWidth is defined by loader in NewAccountPane
if (privKeyColumn.visible)
w += columnWidth + 10
return w;
}
implicitHeight: 200
2024-05-03 22:15:51 +02:00
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 }
2024-10-28 13:39:57 +01:00
StateChangeScript { script: {
if (accountName.visible)
accountName.forceActiveFocus();
else
ageButton.forceActiveFocus();
}
}
2024-05-03 22:15:51 +02:00
},
State {
name: "seedDetailsState"
PropertyChanges { target: inputColumn; opacity: 0.65 }
PropertyChanges { target: privKeyColumn; opacity: 0 }
PropertyChanges { target: seedDetailsColumn; opacity: 1 }
2024-10-28 13:39:57 +01:00
StateChangeScript { script: {
if (accountName2.visible)
accountName2.forceActiveFocus();
else
seedCheckButton.forceActiveFocus();
}
}
}
2024-05-03 22:15:51 +02:00
]
state: "inputState"
2024-05-03 22:15:51 +02:00
function toNextPage() {
var type = inputColumn.typedData;
if (type === Wallet.PrivateKey) {
state = "privKeyDetailsState";
} else if (type === Wallet.CorrectMnemonic || type === Wallet.ElectrumMnemonic) {
state = "seedDetailsState";
}
}
2024-05-03 22:15:51 +02:00
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: {
2026-05-05 16:02:28 +02:00
if ((scanType === QRScanner.Seed || scanType === QRScanner.PrivateKeyWIF)
2025-01-02 15:51:13 +01:00
&& scanResult !== "")
2024-05-03 22:15:51 +02:00
secretText.text = scanResult;
scanButton.forceActiveFocus();
}
}
2024-05-03 22:15:51 +02:00
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
2024-05-03 22:15:51 +02:00
Flowee.Label {
anchors.centerIn: parent
text: "NFC"
}
}
}
}
2024-05-03 22:15:51 +02:00
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
}
2021-10-25 19:42:13 +02:00
}
2024-05-03 22:15:51 +02:00
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"
2026-02-13 14:05:21 +01:00
inputMethodHints: Qt.ImhNoAutoUppercase
2024-05-03 22:15:51 +02:00
}
Flowee.TextPasteDecorator {
id: pasteButton
buddy: secretText
clipboardTypes: ClipboardHelper.PrivateKey + ClipboardHelper.MnemonicSeed
}
}
2025-04-07 15:11:36 +02:00
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"
2024-05-03 22:15:51 +02:00
}
2025-04-07 15:11:36 +02:00
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
}
}
2024-05-03 22:15:51 +02:00
}
}
2025-04-07 15:11:36 +02:00
}
Flowee.Label {
text: inputColumn.typedData === Wallet.PartialMnemonicWithTypo ? qsTr("Unknown word(s) found") : ""
2024-05-03 22:15:51 +02:00
color: mainWindow.errorRed
visible: text !== ""
}
2021-10-25 19:42:13 +02:00
}
2024-05-03 22:15:51 +02:00
Behavior on opacity { NumberAnimation { } }
}
Item {
id: privKeyColumn
anchors.left: inputColumn.right
anchors.leftMargin: 10
width: columnWidth
height: parent.height
enabled: visible
2025-12-23 23:05:24 +01:00
visible: opacity > 0.1
2024-05-03 22:15:51 +02:00
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);
}
}
}
2024-10-28 13:39:57 +01:00
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
}
}
2024-05-03 22:15:51 +02:00
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
2024-06-30 23:26:25 +02:00
= yearOnHeight(height) - 2011;
2024-05-03 22:15:51 +02:00
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
}
2024-06-30 23:26:25 +02:00
2024-05-03 22:15:51 +02:00
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 {
2025-11-14 15:59:47 +01:00
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)
2024-05-03 22:15:51 +02:00
}
options.forceSingleAddress = singleAddress.checked;
2024-05-03 22:15:51 +02:00
for (let a of portfolio.accounts) {
if (a.id === options.accountId) {
portfolio.current = a;
break;
}
}
newAccountsPane.visible = false;
}
}
2021-10-25 19:42:13 +02:00
}
2024-05-03 22:15:51 +02:00
Behavior on opacity { NumberAnimation { } }
}
Column {
id: seedDetailsColumn
anchors.left: inputColumn.right
anchors.leftMargin: 10
width: columnWidth
height: parent.height
2024-10-28 13:39:57 +01:00
spacing: 10
2024-05-03 22:15:51 +02:00
enabled: visible
2025-12-23 23:05:24 +01:00
visible: opacity > 0.1
2024-05-03 22:15:51 +02:00
Flowee.GroupBox {
2024-10-28 13:39:57 +01:00
title: qsTr("New Wallet Name")
2024-06-30 23:26:25 +02:00
width: parent.width
collapsable: false
2024-10-28 13:39:57 +01:00
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;
}
2024-06-30 23:26:25 +02:00
Flowee.TextField {
2024-10-28 13:39:57 +01:00
id: accountName2
2024-06-30 23:26:25 +02:00
Layout.fillWidth: true
}
}
2024-10-28 13:39:57 +01:00
2024-06-30 23:26:25 +02:00
Flowee.BigButton {
id: seedCheckButton
text: qsTr("Discover Details", "online check for wallet details")
enabled: !seedImportHelper.checking
isMainButton: true;
2024-10-28 13:39:57 +01:00
x: 20
2024-06-30 23:26:25 +02:00
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
2024-06-30 23:26:25 +02:00
seedCheckButton.isMainButton = false;
emptySeedWarningLabel.visible = true;
2024-10-28 13:39:57 +01:00
passwordBox.collapsed = false;
2024-06-30 23:26:25 +02:00
}
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?")
2024-06-30 23:26:25 +02:00
visible: false
width: parent.width
2024-10-28 13:39:57 +01:00
wrapMode: Text.Wrap
2024-06-30 23:26:25 +02:00
}
Flowee.GroupBox {
2024-10-28 13:39:57 +01:00
id: passwordBox
title: qsTr("Password")
2024-05-03 22:15:51 +02:00
width: parent.width
2024-10-28 13:39:57 +01:00
collapsable: true
collapsed: true
2024-05-03 22:15:51 +02:00
Flowee.TextField {
2024-10-28 13:39:57 +01:00
id: passwordField
2024-05-03 22:15:51 +02:00
Layout.fillWidth: true
2024-10-28 13:39:57 +01:00
placeholderText: qsTr("imported wallet password")
2026-02-13 14:05:21 +01:00
onDisplayTextChanged: seedCheckButton.isMainButton = true;
2024-05-03 22:15:51 +02:00
}
}
2024-05-03 22:15:51 +02:00
2024-06-30 23:26:25 +02:00
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);
2025-11-14 15:59:47 +01:00
var height = Utils.heightOfBlockAtTime(sh)
2024-06-30 23:26:25 +02:00
options = Pay.createImportedHDWallet(secretText.text, passwordField.text,
2025-11-14 15:59:47 +01:00
derivationPath.text, accountName2.text, height);
2024-05-03 22:15:51 +02:00
}
2024-06-30 23:26:25 +02:00
for (let a of portfolio.accounts) {
if (a.id === options.accountId) {
portfolio.current = a;
break;
2024-05-03 22:15:51 +02:00
}
}
2024-06-30 23:26:25 +02:00
newAccountsPane.visible = false;
2024-10-18 15:50:29 +02:00
tabbar.currentIndex = 0;
2024-05-03 22:15:51 +02:00
}
}
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();
2024-06-30 23:26:25 +02:00
for (let i = 2011; i <= last; ++i) {
2024-05-03 22:15:51 +02:00
list.push(i);
}
return list;
}
2024-06-30 23:26:25 +02:00
currentIndex: 12;
2024-05-03 22:15:51 +02:00
}
}
2023-04-17 14:27:54 +02:00
}
}
}