/* * 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 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("Important: Never share your seed-phrase with others!") textFormat: Text.StyledText wrapMode: Text.WrapAtWordBoundaryOrAnywhere } } } }