d396e1bdfd
This redoes the "AccountDetails" page, matching ideas from the mobile wallet and fixing a lot of bad interactions. This also looked at the colors in desktop to be consistent and nicer. Lots of smaller fixes and UI changes.
565 lines
20 KiB
QML
565 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
|
|
}
|
|
Flowee.CloseIcon {
|
|
anchors.right: parent.right
|
|
anchors.rightMargin: 6
|
|
anchors.bottom: pageTitle.baseline
|
|
onClicked: thePile.clear();
|
|
color: "#bbbbbb"
|
|
}
|
|
}
|
|
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.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
|
|
|
|
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: 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: ""
|
|
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
|
|
}
|
|
}
|
|
}
|
|
}
|