567 lines
20 KiB
QML
567 lines
20 KiB
QML
/*
|
|
* This file is part of the Flowee project
|
|
* Copyright (C) 2021-2025 Tom Zander <tom@flowee.org>
|
|
*
|
|
* 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/>.
|
|
*/
|
|
import QtQuick
|
|
import QtQuick.Controls.Basic as QQC2
|
|
import QtQuick.Layouts
|
|
import "../Flowee" as Flowee
|
|
|
|
Item {
|
|
id: root
|
|
property QtObject account: portfolio.current
|
|
focus: true
|
|
|
|
Item {
|
|
// non-layoutable items.
|
|
|
|
Flowee.Popup {
|
|
id: qrPopup
|
|
width: 270
|
|
height: 270
|
|
x: (root.width - width) / 2
|
|
y: 100
|
|
modal: true
|
|
closePolicy: QQC2.Popup.CloseOnEscape | QQC2.Popup.CloseOnPressOutsideParent
|
|
background: Rectangle {
|
|
color: palette.light
|
|
border.color: palette.midlight
|
|
border.width: 1
|
|
radius: 5
|
|
}
|
|
Flowee.QRWidget {
|
|
id: theQrWidget
|
|
qrSize: 250
|
|
textVisible: false
|
|
useRawString: true
|
|
anchors.centerIn: parent
|
|
}
|
|
}
|
|
Flowee.Popup {
|
|
id: pluginPopup
|
|
width: root.width - 40
|
|
height: {
|
|
let page = thePile.currentItem;
|
|
let h = root.height - 40;
|
|
let headerHeight = pageTitle.height + 10
|
|
if (page != null)
|
|
return Math.min(headerHeight + page.implicitHeight, h);
|
|
return h;
|
|
}
|
|
x: (root.width - width) / 2
|
|
modal: true
|
|
closePolicy: QQC2.Popup.CloseOnEscape
|
|
background: Rectangle {
|
|
color: palette.window
|
|
border.color: palette.midlight
|
|
border.width: 1
|
|
radius: 6
|
|
Flowee.Label {
|
|
id: pageTitle
|
|
font.pixelSize: pluginPopup.font.pixelSize * 2
|
|
width: parent.width
|
|
horizontalAlignment: Qt.AlignHCenter
|
|
}
|
|
}
|
|
QQC2.StackView {
|
|
id: thePile
|
|
anchors.fill: parent
|
|
anchors.topMargin: pageTitle.height + 10
|
|
anchors.margins: 10
|
|
onCurrentItemChanged: {
|
|
if (currentItem != null) {
|
|
pluginPopup.open();
|
|
currentItem.takeFocus();
|
|
pageTitle.text = currentItem.title
|
|
}
|
|
else {
|
|
pluginPopup.close();
|
|
}
|
|
}
|
|
pushEnter: Transition { }
|
|
popExit: Transition { }
|
|
pushExit: Transition { }
|
|
popEnter: Transition { }
|
|
}
|
|
Flowee.CloseIcon {
|
|
anchors.right: thePile.right
|
|
anchors.rightMargin: 6
|
|
y: pageTitle.baselineOffset - height + 6
|
|
onClicked: thePile.clear();
|
|
color: "#bbbbbb"
|
|
}
|
|
}
|
|
}
|
|
|
|
Flowee.Label {
|
|
id: walletDetailsLabel
|
|
text: qsTr("Wallet Details")
|
|
font.pointSize: 18
|
|
color: "white"
|
|
anchors.horizontalCenter: parent.horizontalCenter
|
|
}
|
|
|
|
Flowee.CloseIcon {
|
|
id: closeIcon
|
|
anchors.bottom: walletDetailsLabel.bottom
|
|
anchors.margins: 6
|
|
anchors.right: parent.right
|
|
onClicked: accountOverlay.state = "showTransactions"
|
|
color: "#bbbbbb"
|
|
}
|
|
|
|
GridLayout {
|
|
id: basicProperties
|
|
anchors.top: walletDetailsLabel.bottom
|
|
anchors.topMargin: 20
|
|
x: 20
|
|
width: parent.width - 30
|
|
columns: 2
|
|
rowSpacing: 10
|
|
visible: !portfolio.singleAccountSetup
|
|
|
|
Flowee.Label {
|
|
text: qsTr("Name") + ":"
|
|
}
|
|
Flowee.TextField {
|
|
id: accountNameEdit
|
|
text: root.account.name
|
|
onTextEdited: root.account.name = text
|
|
Layout.fillWidth: true
|
|
focus: true
|
|
}
|
|
}
|
|
|
|
Column {
|
|
id: detailsPane
|
|
anchors.top: portfolio.singleAccountSetup ? walletDetailsLabel.bottom : basicProperties.bottom
|
|
anchors.left: parent.left
|
|
anchors.right: parent.right
|
|
anchors.bottom: parent.bottom
|
|
anchors.margins: 10
|
|
spacing: 10
|
|
GridLayout {
|
|
width: parent.width - 20
|
|
x: 10
|
|
columns: 2
|
|
|
|
Flowee.AccountTypeLabel {
|
|
Layout.columnSpan: 2
|
|
account: root.account
|
|
}
|
|
|
|
Flowee.Label {
|
|
text: qsTr("Sync Status") + ":"
|
|
Layout.alignment: Qt.AlignRight
|
|
}
|
|
Flowee.Label {
|
|
id: syncLabel
|
|
property string time: ""
|
|
Layout.fillWidth: true
|
|
text: {
|
|
var height = root.account.lastBlockSynched
|
|
if (height < 1)
|
|
return ""
|
|
var time = "";
|
|
if (syncLabel.time !== "")
|
|
time = " (" + syncLabel.time + ")";
|
|
return height + " / " + Pay.chainHeight + time;
|
|
}
|
|
Connections {
|
|
target: root.account;
|
|
function onLastBlockSynchedChanged() {
|
|
if (timeTimer.interval === 30000) {
|
|
// if it just changed, fetch the new time shortly thereafter.
|
|
// this makes us show the latest time much more often when doing a sync.
|
|
timeTimer.stop();
|
|
timeTimer.interval = 500;
|
|
timeTimer.start();
|
|
}
|
|
}
|
|
}
|
|
Timer {
|
|
// the lastBlockSynchedTime does not change,
|
|
// but since we render it as '12 minutes ago'
|
|
// we need to actually re-interpret that
|
|
// ever so often to keep the relative time.
|
|
id: timeTimer
|
|
running: !root.account.isArchived
|
|
interval: 30000 // 30 sec
|
|
repeat: true
|
|
triggeredOnStart: true
|
|
onTriggered: {
|
|
syncLabel.time = Pay.formatDateTime(
|
|
root.account.lastBlockSynchedTime);
|
|
interval = 30000; // 30 sec
|
|
}
|
|
}
|
|
}
|
|
|
|
Flowee.Label {
|
|
id: encLabel
|
|
text: qsTr("Encryption") + ":"
|
|
visible: encStatus.visible
|
|
Layout.alignment: Qt.AlignRight
|
|
}
|
|
WalletEncryptionStatus {
|
|
id: encStatus
|
|
Layout.fillWidth: true
|
|
account: root.account
|
|
}
|
|
|
|
Flowee.Label {
|
|
id: pwdLabel
|
|
text: qsTr("Password") + ":"
|
|
visible: encStatus.visible
|
|
Layout.alignment: Qt.AlignRight
|
|
}
|
|
Flowee.TextField {
|
|
id: passwordField
|
|
onAccepted: decryptButton.clicked()
|
|
enabled: !root.account.isDecrypted
|
|
Layout.fillWidth: true
|
|
echoMode: TextInput.Password
|
|
visible: pwdLabel.visible
|
|
}
|
|
Item {
|
|
visible: pwdLabel.visible
|
|
Layout.fillWidth: true
|
|
Layout.columnSpan: 2
|
|
implicitHeight: Math.max(decryptWarning.implicitHeight, decryptButton.implicitHeight)
|
|
|
|
Flowee.WarningLabel {
|
|
id: decryptWarning
|
|
anchors.left: parent.left
|
|
anchors.leftMargin: 20
|
|
anchors.right: decryptButton.left
|
|
anchors.rightMargin: 10
|
|
anchors.baseline: decryptButton.baseline
|
|
}
|
|
|
|
Flowee.Button {
|
|
id: decryptButton
|
|
anchors.right: parent.right
|
|
text: qsTr("Open")
|
|
enabled: passwordField.text.length > 3
|
|
onClicked: {
|
|
var rc = root.account.decrypt(passwordField.text);
|
|
if (rc) {
|
|
// decrypt went Ok
|
|
decryptWarning.text = ""
|
|
passwordField.text = ""
|
|
}
|
|
else {
|
|
decryptWarning.text = qsTr("Invalid PIN")
|
|
passwordField.forceActiveFocus();
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
Flowee.CheckBox {
|
|
id: balanceSetting
|
|
checked: root.account.countBalance
|
|
onCheckedChanged: root.account.countBalance = checked
|
|
visible: !portfolio.singleAccountSetup
|
|
Layout.alignment: Qt.AlignRight
|
|
}
|
|
Flowee.CheckBoxLabel {
|
|
Layout.fillWidth: true
|
|
buddy: balanceSetting
|
|
text: qsTr("Include balance in total")
|
|
visible: balanceSetting.visible
|
|
}
|
|
Flowee.CheckBox {
|
|
id: privateCB
|
|
checked: root.account.isPrivate
|
|
onClicked: root.account.isPrivate = checked
|
|
visible: balanceSetting.visible
|
|
Layout.alignment: Qt.AlignRight
|
|
}
|
|
Flowee.CheckBoxLabel {
|
|
Layout.fillWidth: true
|
|
buddy: privateCB
|
|
text: qsTr("Hide in private mode")
|
|
visible: balanceSetting.visible
|
|
}
|
|
}
|
|
|
|
Flowee.GroupBox {
|
|
width: parent.width
|
|
title: qsTr("xpub")
|
|
visible: root.account.isHDWallet
|
|
enabled: !root.account.needsPinToOpen || root.account.isDecrypted
|
|
collapsed: false
|
|
collapsable: false
|
|
columns: 2
|
|
|
|
Flowee.Label {
|
|
id: titleText
|
|
text: qsTr("To connect this wallet")
|
|
font.italic: true
|
|
Layout.columnSpan: 2
|
|
}
|
|
Flowee.Label {
|
|
text: root.account.xpub
|
|
wrapMode: Text.WrapAnywhere
|
|
Layout.fillWidth: true
|
|
font.pixelSize: titleText.font.pixelSize * 0.9
|
|
}
|
|
Image {
|
|
Layout.maximumWidth: 28
|
|
Layout.maximumHeight: 28
|
|
source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg");
|
|
Layout.alignment: Qt.AlignTop
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
theQrWidget.qrText = root.account.xpub
|
|
qrPopup.open();
|
|
}
|
|
}
|
|
}
|
|
|
|
Flowee.Label {
|
|
Layout.columnSpan: 2
|
|
text: qsTr("Be careful who you share the xpub with!")
|
|
font.italic: true
|
|
}
|
|
}
|
|
|
|
Flowee.GroupBox {
|
|
width: parent.width
|
|
title: qsTr("Address List")
|
|
collapsable: false
|
|
enabled: root.account.isDecrypted || !root.account.needsPinToOpen
|
|
columns: 2
|
|
Flowee.Label {
|
|
text: qsTr("Browse addresses owned by this wallet.")
|
|
Layout.fillWidth: true
|
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
|
}
|
|
Flowee.Button {
|
|
text: qsTr("Open")
|
|
onClicked: thePile.push(addressesPane)
|
|
}
|
|
}
|
|
|
|
Flowee.GroupBox {
|
|
width: parent.width
|
|
title: qsTr("Backup details")
|
|
collapsable: false
|
|
visible: root.account.isHDWallet
|
|
enabled: root.account.isDecrypted || !root.account.needsPinToOpen
|
|
columns: 2
|
|
Flowee.Label {
|
|
text: qsTr("Write down a copy of your wallet seed-phrase in order to be able to restore the wallet and the money in it.")
|
|
Layout.fillWidth: true
|
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
|
}
|
|
Flowee.Button {
|
|
text: qsTr("Open")
|
|
onClicked: thePile.push(paperBackupPanel)
|
|
}
|
|
Flowee.Label {
|
|
visible: !root.account.isDecrypted
|
|
width: parent.width
|
|
text: qsTr("This wallet is protected by password (pin-to-pay). To see the backup details you need to provide the password.")
|
|
textFormat: Text.StyledText
|
|
Layout.fillWidth: true
|
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
|
}
|
|
}
|
|
|
|
Flowee.GroupBox {
|
|
id: backupSync
|
|
width: parent.width
|
|
title: module != null ? module.title : ""
|
|
visible: module != null && root.account.isHDWallet
|
|
collapsable: false
|
|
enabled: root.account.isDecrypted || !root.account.needsPinToOpen
|
|
columns: 2
|
|
|
|
property QtObject module: ModuleManager.moduleInfo("backupSyncModule");
|
|
|
|
Flowee.Label {
|
|
text: backupSync.module != null ? backupSync.module.description : ""
|
|
Layout.fillWidth: true
|
|
}
|
|
Flowee.Button {
|
|
text: qsTr("Open")
|
|
onClicked: thePile.push(backupSync.module.sectionUrl("walletOptions"),
|
|
{ "pageData": root.account })
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: addressesPane
|
|
FocusScope {
|
|
property string title: qsTr("Address List")
|
|
function takeFocus() { historyView.forceActiveFocus(); }
|
|
implicitWidth: 600
|
|
implicitHeight: checkboxes.implicitHeight + 10 + historyView.implicitHeight
|
|
|
|
Column {
|
|
id: checkboxes
|
|
width: parent.width
|
|
Flowee.CheckBox {
|
|
id: changeAddresses
|
|
text: qsTr("Change Addresses")
|
|
visible: root.account.isHDWallet
|
|
onClicked: root.account.secrets.showChangeChain = checked
|
|
toolTipText: qsTr("Switches between addresses others can pay you on, and addresses the wallet uses to send change back to yourself.")
|
|
}
|
|
Flowee.CheckBox {
|
|
id: usedAddresses
|
|
text: qsTr("Used Addresses");
|
|
visible: !root.account.isSingleAddressAccount
|
|
onClicked: root.account.secrets.showUsedAddresses = checked
|
|
toolTipText: qsTr("Switches between unused and used Bitcoin addresses")
|
|
}
|
|
}
|
|
Flowee.WalletSecretsView {
|
|
id: historyView
|
|
width: parent.width
|
|
y: checkboxes.height + 10
|
|
implicitHeight: contentHeight
|
|
height: parent.height - y
|
|
|
|
clip: true
|
|
QQC2.ScrollBar.vertical: Flowee.ScrollThumb {
|
|
id: thumb
|
|
minimumSize: 20 / activityView.height
|
|
visible: size < 0.9
|
|
}
|
|
account: root.account
|
|
}
|
|
Keys.forwardTo: Flowee.ListViewKeyHandler {
|
|
target: historyView
|
|
}
|
|
}
|
|
}
|
|
|
|
Component {
|
|
id: paperBackupPanel
|
|
Item {
|
|
width: parent.width
|
|
property string title: qsTr("Paper Backup Details")
|
|
implicitHeight: grid.height + helpText.height + warningText.height + 40
|
|
function takeFocus() { phrase.forceActiveFocus() }
|
|
GridLayout {
|
|
id: grid
|
|
visible: root.account.isDecrypted
|
|
width: parent.width
|
|
columns: 3
|
|
Flowee.Label {
|
|
text: qsTr("Seed-phrase") + ":"
|
|
Layout.alignment: Qt.AlignTop
|
|
}
|
|
QQC2.TextArea {
|
|
id: phrase
|
|
readOnly: true
|
|
text: root.account.mnemonic
|
|
Layout.fillWidth: true
|
|
selectByMouse: true
|
|
mouseSelectionMode: TextEdit.SelectWords
|
|
wrapMode: TextEdit.WordWrap
|
|
padding: 0
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
acceptedButtons: Qt.RightButton
|
|
onClicked: menu.popup(parent);
|
|
|
|
QQC2.Menu {
|
|
id: menu
|
|
QQC2.MenuItem {
|
|
text: qsTr("Copy")
|
|
onTriggered: Pay.copyToClipboard(root.account.mnemonic)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
Image {
|
|
Layout.maximumWidth: 28
|
|
Layout.maximumHeight: 28
|
|
source: "qrc:/qr-code" + (Pay.useDarkSkin ? "-light.svg" : ".svg");
|
|
Layout.alignment: Qt.AlignTop
|
|
MouseArea {
|
|
anchors.fill: parent
|
|
onClicked: {
|
|
theQrWidget.qrText = root.account.mnemonic
|
|
qrPopup.open();
|
|
}
|
|
}
|
|
}
|
|
Flowee.Label {
|
|
text: qsTr("Password") + ":"
|
|
visible: root.account.mnemonicPwd !== ""
|
|
}
|
|
Flowee.Label {
|
|
Layout.columnSpan: 2
|
|
text: root.account.mnemonicPwd
|
|
visible: text !== ""
|
|
}
|
|
Flowee.Label {
|
|
text: qsTr("Seed format") + ":"
|
|
visible: root.account.isElectrumMnemonic
|
|
}
|
|
Flowee.Label {
|
|
Layout.columnSpan: 2
|
|
id: seedPhraseFormat
|
|
font.bold: true
|
|
text: "Electrum"
|
|
visible: root.account.isElectrumMnemonic
|
|
}
|
|
Flowee.Label {
|
|
text: qsTr("Starting Height" + ":", "height refers to block-height")
|
|
}
|
|
Flowee.LabelWithClipboard {
|
|
Layout.columnSpan: 2
|
|
text: root.account.accountStartBlockHeight
|
|
}
|
|
Flowee.Label {
|
|
text: qsTr("Derivation") + ":"
|
|
}
|
|
Flowee.LabelWithClipboard {
|
|
Layout.columnSpan: 2
|
|
text: root.account.hdDerivationPath
|
|
}
|
|
}
|
|
Flowee.Label {
|
|
id: helpText
|
|
visible: grid.visible
|
|
width: parent.width
|
|
anchors.top: grid.bottom
|
|
anchors.topMargin: 10
|
|
text: qsTr("Please save the seed-phrase on paper, in the right order, with the derivation path. This seed will allow you to recover your wallet in case of computer failure.")
|
|
textFormat: Text.StyledText
|
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
|
}
|
|
Flowee.Label {
|
|
id: warningText
|
|
visible: grid.visible
|
|
width: parent.width
|
|
anchors.top: helpText.bottom
|
|
anchors.topMargin: 10
|
|
text: qsTr("<b>Important</b>: Never share your seed-phrase with others!")
|
|
textFormat: Text.StyledText
|
|
wrapMode: Text.WrapAtWordBoundaryOrAnywhere
|
|
}
|
|
}
|
|
}
|
|
}
|