From c34ba1552f795d52baffb26e421054228cd6aace Mon Sep 17 00:00:00 2001 From: Daniel Date: Sat, 20 Jun 2026 15:25:14 +0200 Subject: [PATCH] Modify views --- src/qml/BlockchainView.qml | 377 ++++++++++++++++++---------- src/qml/views/AccountsView.qml | 79 ++++++ src/qml/views/LeaderRewardsView.qml | 87 +++++++ src/qml/views/TransferView.qml | 207 +++++++++++++++ src/qml/views/WalletView.qml | 345 ------------------------- src/qml/views/qmldir | 4 +- 6 files changed, 616 insertions(+), 483 deletions(-) create mode 100644 src/qml/views/AccountsView.qml create mode 100644 src/qml/views/LeaderRewardsView.qml create mode 100644 src/qml/views/TransferView.qml delete mode 100644 src/qml/views/WalletView.qml diff --git a/src/qml/BlockchainView.qml b/src/qml/BlockchainView.qml index 21a2efa..5b738c2 100644 --- a/src/qml/BlockchainView.qml +++ b/src/qml/BlockchainView.qml @@ -170,19 +170,32 @@ Rectangle { } } - // Page 2: Node control, wallet, logs + Channel Deposit (tabbed) + // Page 2: Node information + Wallet operations (tabbed) ColumnLayout { + id: opPage spacing: Theme.spacing.medium + // Selected operation inside the Operations tab's sidebar nav. + // 0 Accounts · 1 Transfer · 2 Leader Rewards · 3 Channel Deposit + property int operationIndex: 0 + + readonly property bool nodeRunning: root.backend + ? root.backend.status === BlockchainBackend.Running + : false + + // Channel Deposit requires a running node. If the node stops while + // it's selected, fall back to Accounts so the user isn't stranded + // on a disabled nav item. + onNodeRunningChanged: { + if (!nodeRunning && operationIndex === 3) + operationIndex = 0 + } + LogosTabBar { id: operationTabBar Layout.fillWidth: true - LogosTabButton { text: qsTr("Node & Wallet") } - LogosTabButton { - text: qsTr("Channel Deposit") - enabled: root.backend - && root.backend.status === BlockchainBackend.Running - } + LogosTabButton { text: qsTr("Node") } + LogosTabButton { text: qsTr("Operations") } } StackLayout { @@ -191,150 +204,240 @@ Rectangle { Layout.fillHeight: true currentIndex: operationTabBar.currentIndex - SplitView { - orientation: Qt.Vertical + // ---- Tab 0: Node information (status + logs) ---- + SplitView { + orientation: Qt.Vertical - ColumnLayout { - SplitView.fillWidth: true - SplitView.minimumHeight: 200 - spacing: Theme.spacing.large + ColumnLayout { + SplitView.fillWidth: true + SplitView.minimumHeight: 120 + spacing: Theme.spacing.large - StatusConfigView { - Layout.fillWidth: true - statusText: root.backend - ? _d.getStatusString(root.backend.status) - : qsTr("Not Connected") - statusColor: root.backend - ? _d.getStatusColor(root.backend.status) - : Theme.palette.error - userConfig: root.backend ? root.backend.userConfig : "" - deploymentConfig: root.backend ? root.backend.deploymentConfig : "" - useGeneratedConfig: root.backend ? root.backend.useGeneratedConfig : false - canStart: root.backend - && !!root.backend.userConfig - && root.backend.status !== BlockchainBackend.Starting - && root.backend.status !== BlockchainBackend.Stopping - isRunning: root.backend - ? root.backend.status === BlockchainBackend.Running - : false + StatusConfigView { + Layout.fillWidth: true + statusText: root.backend + ? _d.getStatusString(root.backend.status) + : qsTr("Not Connected") + statusColor: root.backend + ? _d.getStatusColor(root.backend.status) + : Theme.palette.error + userConfig: root.backend ? root.backend.userConfig : "" + deploymentConfig: root.backend ? root.backend.deploymentConfig : "" + useGeneratedConfig: root.backend ? root.backend.useGeneratedConfig : false + canStart: root.backend + && !!root.backend.userConfig + && root.backend.status !== BlockchainBackend.Starting + && root.backend.status !== BlockchainBackend.Stopping + isRunning: opPage.nodeRunning - onStartRequested: if (root.backend) root.backend.startBlockchain() - onStopRequested: if (root.backend) root.backend.stopBlockchain() - onChangeConfigRequested: _d.currentPage = 0 + onStartRequested: if (root.backend) root.backend.startBlockchain() + onStopRequested: if (root.backend) root.backend.stopBlockchain() + onChangeConfigRequested: _d.currentPage = 0 + } + + Item { + Layout.preferredHeight: Theme.spacing.small + } + } + + LogsView { + SplitView.fillWidth: true + SplitView.minimumHeight: 150 + + logModel: root.logModel + onClearRequested: if (root.backend) root.backend.clearLogs() + onCopyToClipboard: (text) => { + root.copyText(text) + } + } } - WalletView { - id: walletView - accountsModel: root.accountsModel + // ---- Tab 1: Wallet operations (sidebar nav + panels) ---- + RowLayout { + spacing: Theme.spacing.large - onGetBalanceRequested: function(addressHex) { - if (!root.backend) return - logos.watch( - root.backend.getBalance(addressHex), - function(result) { - if (result.success) { - walletView.lastBalanceErrorAddress = "" - walletView.lastBalanceError = "" - } else { - walletView.lastBalanceErrorAddress = addressHex - walletView.lastBalanceError = _d.errorText(result.error) - } - }, - function(error) { - walletView.lastBalanceErrorAddress = addressHex - walletView.lastBalanceError = _d.errorText(error) + // Sidebar navigation + ColumnLayout { + Layout.preferredWidth: 180 + Layout.fillHeight: true + Layout.alignment: Qt.AlignTop + spacing: Theme.spacing.small + + NavItem { label: qsTr("Accounts"); index: 0 } + NavItem { label: qsTr("Transfer"); index: 1 } + NavItem { label: qsTr("Leader Rewards"); index: 2 } + NavItem { + label: qsTr("Channel Deposit") + index: 3 + itemEnabled: opPage.nodeRunning + } + + Item { Layout.fillHeight: true } + } + + Rectangle { + Layout.preferredWidth: 1 + Layout.fillHeight: true + color: Theme.palette.borderSecondary + } + + // Operation panels + StackLayout { + Layout.fillWidth: true + Layout.fillHeight: true + currentIndex: opPage.operationIndex + + AccountsView { + id: accountsView + accountsModel: root.accountsModel + + onGetBalanceRequested: function(addressHex) { + if (!root.backend) return + logos.watch( + root.backend.getBalance(addressHex), + function(result) { + if (result.success) { + accountsView.lastBalanceErrorAddress = "" + accountsView.lastBalanceError = "" + } else { + accountsView.lastBalanceErrorAddress = addressHex + accountsView.lastBalanceError = _d.errorText(result.error) + } + }, + function(error) { + accountsView.lastBalanceErrorAddress = addressHex + accountsView.lastBalanceError = _d.errorText(error) + } + ) } - ) - } - onCopyToClipboard: (text) => { - root.copyText(text) - } - onTransferRequested: function(fromKeyHex, toKeyHex, amount) { - if (!root.backend) return - logos.watch( - root.backend.transferFunds(fromKeyHex, toKeyHex, amount), - function(result) { - if (result.success) { - walletView.setTransferResult(result.value) - } else { - walletView.setTransferResult(_d.errorText(result.error)) - } - }, - function(error) { walletView.setTransferResult(_d.errorText(error)) } - ) - } - onClaimLeaderRewardsRequested: function() { - if (!root.backend) return - logos.watch( - root.backend.claimLeaderRewards(), - function(result) { - if (result.success) { - walletView.setLeaderClaimResult(result.value) - } else { - walletView.setLeaderClaimResult(_d.errorText(result.error)) - } - }, - function(error) { walletView.setLeaderClaimResult(_d.errorText(error)) } - ) - } - onRefreshAccountsRequested: if (root.backend) root.backend.refreshAccounts() - } + onRefreshAccountsRequested: if (root.backend) root.backend.refreshAccounts() + onCopyToClipboard: (text) => { + root.copyText(text) + } + } - Item { - Layout.preferredHeight: Theme.spacing.small + TransferView { + id: transferView + accountsModel: root.accountsModel + + onTransferRequested: function(fromKeyHex, toKeyHex, amount) { + if (!root.backend) return + logos.watch( + root.backend.transferFunds(fromKeyHex, toKeyHex, amount), + function(result) { + if (result.success) { + transferView.setTransferResult(result.value) + } else { + transferView.setTransferResult(_d.errorText(result.error)) + } + }, + function(error) { transferView.setTransferResult(_d.errorText(error)) } + ) + } + onCopyToClipboard: (text) => { + root.copyText(text) + } + } + + LeaderRewardsView { + id: leaderRewardsView + + onClaimLeaderRewardsRequested: function() { + if (!root.backend) return + logos.watch( + root.backend.claimLeaderRewards(), + function(result) { + if (result.success) { + leaderRewardsView.setLeaderClaimResult(result.value) + } else { + leaderRewardsView.setLeaderClaimResult(_d.errorText(result.error)) + } + }, + function(error) { leaderRewardsView.setLeaderClaimResult(_d.errorText(error)) } + ) + } + onCopyToClipboard: (text) => { + root.copyText(text) + } + } + + ChannelDepositView { + id: channelDepositView + accountsModel: root.accountsModel + nodeRunning: opPage.nodeRunning + + onGetNotesRequested: function(addressHex, optionalTipHex) { + if (!root.backend) return + logos.watch( + root.backend.getNotes(addressHex, optionalTipHex), + function(result) { + if (result.success) + channelDepositView.setNotes(result.value) + else + channelDepositView.setNotesError(_d.errorText(result.error)) + }, + function(error) { channelDepositView.setNotesError(_d.errorText(error)) } + ) + } + onSubmitRequested: function(channelIdHex, inputNoteIdHexes, metadataBase58, changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex) { + if (!root.backend) return + logos.watch( + root.backend.channelDepositWithNotes( + channelIdHex, inputNoteIdHexes, metadataBase58, + changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex), + function(result) { + if (result.success) + channelDepositView.setSubmitResult(true, result.value) + else + channelDepositView.setSubmitResult(false, _d.errorText(result.error)) + }, + function(error) { channelDepositView.setSubmitResult(false, _d.errorText(error)) } + ) + } + onCopyToClipboard: (text) => { + root.copyText(text) + } + } + } } } - LogsView { - SplitView.fillWidth: true - SplitView.minimumHeight: 150 + // Sidebar nav entry used by the Operations tab. + component NavItem: Rectangle { + property string label + property int index + property bool itemEnabled: true - logModel: root.logModel - onClearRequested: if (root.backend) root.backend.clearLogs() - onCopyToClipboard: (text) => { - root.copyText(text) + Layout.fillWidth: true + Layout.preferredHeight: 40 + radius: Theme.spacing.radiusSmall + color: opPage.operationIndex === index + ? Theme.palette.backgroundTertiary + : (navMouse.containsMouse ? Theme.palette.backgroundSecondary : "transparent") + opacity: itemEnabled ? 1.0 : 0.4 + + LogosText { + anchors.left: parent.left + anchors.right: parent.right + anchors.leftMargin: Theme.spacing.medium + anchors.rightMargin: Theme.spacing.medium + anchors.verticalCenter: parent.verticalCenter + text: label + elide: Text.ElideRight + font.pixelSize: Theme.typography.secondaryText + font.bold: opPage.operationIndex === index + color: opPage.operationIndex === index + ? Theme.palette.primary + : Theme.palette.text } - } - } - ChannelDepositView { - id: channelDepositView - accountsModel: root.accountsModel - nodeRunning: root.backend - ? root.backend.status === BlockchainBackend.Running - : false - - onGetNotesRequested: function(addressHex, optionalTipHex) { - if (!root.backend) return - logos.watch( - root.backend.getNotes(addressHex, optionalTipHex), - function(result) { - if (result.success) - channelDepositView.setNotes(result.value) - else - channelDepositView.setNotesError(_d.errorText(result.error)) - }, - function(error) { channelDepositView.setNotesError(_d.errorText(error)) } - ) - } - onSubmitRequested: function(channelIdHex, inputNoteIdHexes, metadataBase58, changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex) { - if (!root.backend) return - logos.watch( - root.backend.channelDepositWithNotes( - channelIdHex, inputNoteIdHexes, metadataBase58, - changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex), - function(result) { - if (result.success) - channelDepositView.setSubmitResult(true, result.value) - else - channelDepositView.setSubmitResult(false, _d.errorText(result.error)) - }, - function(error) { channelDepositView.setSubmitResult(false, _d.errorText(error)) } - ) - } - onCopyToClipboard: (text) => { - root.copyText(text) - } + MouseArea { + id: navMouse + anchors.fill: parent + enabled: itemEnabled + hoverEnabled: true + cursorShape: Qt.PointingHandCursor + onClicked: opPage.operationIndex = index } } } diff --git a/src/qml/views/AccountsView.qml b/src/qml/views/AccountsView.qml new file mode 100644 index 0000000..e0df025 --- /dev/null +++ b/src/qml/views/AccountsView.qml @@ -0,0 +1,79 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Logos.Theme +import Logos.Controls + +import "../controls" + +// Accounts panel: the list of known wallet addresses with per-account +// balance refresh and copy. Extracted from the former WalletView. +ColumnLayout { + id: root + + required property var accountsModel + + property string lastBalanceError: "" + property string lastBalanceErrorAddress: "" + + signal getBalanceRequested(string addressHex) + signal refreshAccountsRequested() + signal copyToClipboard(string text) + + spacing: Theme.spacing.large + + Rectangle { + Layout.fillWidth: true + Layout.fillHeight: true + color: Theme.palette.backgroundTertiary + radius: Theme.spacing.radiusLarge + border.color: Theme.palette.border + border.width: 1 + + ColumnLayout { + anchors.fill: parent + anchors.margins: Theme.spacing.large + spacing: Theme.spacing.large + + RowLayout { + Layout.fillWidth: true + LogosText { + text: qsTr("Accounts") + font.pixelSize: Theme.typography.secondaryText + font.bold: true + } + Item { Layout.fillWidth: true } + LogosButton { + text: qsTr("Refresh") + padding: Theme.spacing.small + onClicked: root.refreshAccountsRequested() + } + } + + LogosText { + text: qsTr("Start node to see accounts here.") + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + wrapMode: Text.WordWrap + visible: balanceListView.count === 0 + } + + ListView { + id: balanceListView + Layout.fillWidth: true + Layout.fillHeight: true + clip: true + model: root.accountsModel + spacing: Theme.spacing.small + + delegate: AccountDelegate { + balanceError: root.lastBalanceErrorAddress === model.address ? + root.lastBalanceError : "" + onGetBalanceRequested: (addr) => root.getBalanceRequested(addr) + onCopyRequested: (text) => root.copyToClipboard(text) + } + } + } + } +} diff --git a/src/qml/views/LeaderRewardsView.qml b/src/qml/views/LeaderRewardsView.qml new file mode 100644 index 0000000..e14d6db --- /dev/null +++ b/src/qml/views/LeaderRewardsView.qml @@ -0,0 +1,87 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Logos.Theme +import Logos.Controls + +import "../controls" + +// Leader rewards panel. Extracted from the former WalletView. +ColumnLayout { + id: root + + signal claimLeaderRewardsRequested() + signal copyToClipboard(string text) + + function setLeaderClaimResult(text) { + leaderClaimResultText.text = text + } + + spacing: Theme.spacing.large + + Rectangle { + id: leaderRewardsRect + + Layout.fillWidth: true + Layout.preferredHeight: leaderRewardsCol.height + 2 * Theme.spacing.large + color: Theme.palette.backgroundTertiary + radius: Theme.spacing.radiusLarge + border.color: Theme.palette.border + border.width: 1 + + ColumnLayout { + id: leaderRewardsCol + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: Theme.spacing.large + spacing: Theme.spacing.small + + LogosText { + text: qsTr("Leader rewards") + font.pixelSize: Theme.typography.secondaryText + font.bold: true + } + + RowLayout { + Layout.fillWidth: true + + LogosButton { + id: leaderClaimButton + Layout.preferredWidth: 140 + text: qsTr("Claim") + onClicked: root.claimLeaderRewardsRequested() + } + + LogosButton { + Layout.fillWidth: true + enabled: true + padding: Theme.spacing.small + contentItem: RowLayout { + width: parent.width + anchors.centerIn: parent + LogosText { + id: leaderClaimResultText + Layout.fillWidth: true + color: Theme.palette.textSecondary + font.pixelSize: Theme.typography.secondaryText + font.weight: Theme.typography.weightMedium + wrapMode: Text.WordWrap + elide: Text.ElideRight + } + LogosCopyButton { + Layout.alignment: Qt.AlignRight + Layout.preferredHeight: 40 + Layout.preferredWidth: 40 + onCopyText: root.copyToClipboard(leaderClaimResultText.text) + visible: leaderClaimResultText.text + } + } + } + } + } + } + + Item { Layout.fillHeight: true } +} diff --git a/src/qml/views/TransferView.qml b/src/qml/views/TransferView.qml new file mode 100644 index 0000000..c9e695a --- /dev/null +++ b/src/qml/views/TransferView.qml @@ -0,0 +1,207 @@ +import QtQuick +import QtQuick.Controls +import QtQuick.Layouts + +import Logos.Theme +import Logos.Controls + +import "../controls" + +// Transfer funds panel. Extracted from the former WalletView. +ColumnLayout { + id: root + + required property var accountsModel + + signal transferRequested(string fromKeyHex, string toKeyHex, string amount) + signal copyToClipboard(string text) + + function setTransferResult(text) { + transferResultText.text = text + } + + spacing: Theme.spacing.large + + Rectangle { + id: transferRect + + Layout.fillWidth: true + Layout.preferredHeight: transferCol.height + 2 * Theme.spacing.large + color: Theme.palette.backgroundTertiary + radius: Theme.spacing.radiusLarge + border.color: Theme.palette.border + border.width: 1 + + ColumnLayout { + id: transferCol + anchors.top: parent.top + anchors.left: parent.left + anchors.right: parent.right + anchors.margins: Theme.spacing.large + spacing: Theme.spacing.small + + LogosText { + text: qsTr("Transfer funds") + font.pixelSize: Theme.typography.secondaryText + font.bold: true + } + + StyledAddressComboBox { + id: transferFromCombo + model: root.accountsModel + textRole: "address" + } + + LogosTextField { + id: transferToField + Layout.fillWidth: true + Layout.preferredHeight: 30 + placeholderText: qsTr("To key (64 hex chars)") + } + + LogosTextField { + id: transferAmountField + Layout.fillWidth: true + Layout.preferredHeight: 30 + placeholderText: qsTr("Amount") + } + + RowLayout { + Layout.fillWidth: true + Layout.preferredHeight: transferButton.implicitHeight + + LogosButton { + id: transferButton + Layout.preferredWidth: 60 + Layout.alignment: Qt.AlignRight + text: qsTr("Send") + onClicked: root.transferRequested(transferFromCombo.currentText.trim(), transferToField.text.trim(), transferAmountField.text) + } + + LogosButton { + Layout.fillWidth: true + enabled: true + padding: Theme.spacing.small + contentItem: RowLayout { + width: parent.width + anchors.centerIn: parent + LogosText { + id: transferResultText + Layout.fillWidth: true + color: Theme.palette.textSecondary + font.pixelSize: Theme.typography.secondaryText + font.weight: Theme.typography.weightMedium + wrapMode: Text.WordWrap + elide: Text.ElideRight + } + LogosCopyButton { + Layout.alignment: Qt.AlignRight + Layout.preferredHeight: 40 + Layout.preferredWidth: 40 + onCopyText: root.copyToClipboard(transferResultText.text) + visible: transferResultText.text + } + } + } + } + } + } + + Item { Layout.fillHeight: true } + + component StyledAddressComboBox: ComboBox { + id: comboControl + + Layout.fillWidth: true + padding: Theme.spacing.large + editable: true + font.pixelSize: Theme.typography.secondaryText + + background: Rectangle { + color: Theme.palette.backgroundTertiary + radius: Theme.spacing.radiusLarge + border.color: Theme.palette.border + border.width: 1 + } + indicator: LogosText { + id: comboIndicator + text: "▼" + font.pixelSize: Theme.typography.secondaryText + color: Theme.palette.textSecondary + x: comboControl.width - width - Theme.spacing.small + y: (comboControl.height - height) / 2 + visible: comboControl.count > 0 + } + contentItem: Item { + implicitWidth: 200 + implicitHeight: 30 + + TextField { + id: comboTextField + anchors.fill: parent + leftPadding: 0 + rightPadding: comboControl.count > 0 ? comboIndicator.width + Theme.spacing.small : Theme.spacing.small + topPadding: 0 + bottomPadding: 0 + verticalAlignment: Text.AlignVCenter + font.pixelSize: Theme.typography.secondaryText + text: comboControl.editText + onTextChanged: if (text !== comboControl.editText) comboControl.editText = text + selectByMouse: true + color: Theme.palette.text + background: Item { } + } + MouseArea { + anchors.fill: parent + visible: comboControl.count > 0 + z: 1 + onPressed: { + comboControl.popup.visible ? comboControl.popup.close() : comboControl.popup.open() + } + } + } + delegate: ItemDelegate { + id: comboDelegate + width: comboControl.width + contentItem: LogosText { + width: parent.width + height: contentHeight + Theme.spacing.large + font.pixelSize: Theme.typography.secondaryText + font.bold: true + text: (typeof model.address !== "undefined" ? model.address : modelData) || "" + elide: Text.ElideMiddle + horizontalAlignment: Text.AlignHCenter + verticalAlignment: Text.AlignVCenter + } + background: Rectangle { + color: comboDelegate.highlighted ? + Theme.palette.backgroundTertiary : + Theme.palette.backgroundSecondary + } + highlighted: comboControl.highlightedIndex === index + } + popup: Popup { + y: comboControl.height - 1 + width: comboControl.width + height: contentItem.implicitHeight + padding: 1 + + onOpened: if (comboControl.count === 0) close() + + contentItem: ListView { + clip: true + implicitHeight: contentHeight + model: comboControl.popup.visible ? comboControl.delegateModel : null + ScrollIndicator.vertical: ScrollIndicator { } + highlightFollowsCurrentItem: false + } + + background: Rectangle { + color: Theme.palette.backgroundSecondary + border.color: Theme.palette.border + border.width: 1 + radius: Theme.spacing.radiusLarge + } + } + } +} diff --git a/src/qml/views/WalletView.qml b/src/qml/views/WalletView.qml deleted file mode 100644 index 345ea9e..0000000 --- a/src/qml/views/WalletView.qml +++ /dev/null @@ -1,345 +0,0 @@ -import QtQuick -import QtQuick.Controls -import QtQuick.Layouts - -import Logos.Theme -import Logos.Controls - -import "../controls" - -RowLayout { - id: root - - required property var accountsModel - - property string lastBalanceError: "" - property string lastBalanceErrorAddress: "" - - signal getBalanceRequested(string addressHex) - signal refreshAccountsRequested() - signal transferRequested(string fromKeyHex, string toKeyHex, string amount) - signal claimLeaderRewardsRequested() - signal copyToClipboard(string text) - - function setTransferResult(text) { - transferResultText.text = text - } - - function setLeaderClaimResult(text) { - leaderClaimResultText.text = text - } - - spacing: Theme.spacing.medium - - // Get balance card - Rectangle { - Layout.fillWidth: true - implicitHeight: actionsCol.height - Layout.preferredHeight: Math.min(implicitHeight, 400) - color: Theme.palette.backgroundTertiary - radius: Theme.spacing.radiusLarge - border.color: Theme.palette.border - border.width: 1 - - ColumnLayout { - id: balanceCol - anchors.left: parent.left - anchors.right: parent.right - anchors.top: parent.top - anchors.margins: Theme.spacing.large - spacing: Theme.spacing.large - - RowLayout { - Layout.fillWidth: true - LogosText { - text: qsTr("Accounts") - font.pixelSize: Theme.typography.secondaryText - font.bold: true - } - Item { Layout.fillWidth: true } - LogosButton { - text: qsTr("Refresh") - padding: Theme.spacing.small - onClicked: root.refreshAccountsRequested() - } - } - - LogosText { - text: qsTr("Start node to see accounts here.") - font.pixelSize: Theme.typography.secondaryText - color: Theme.palette.textSecondary - wrapMode: Text.WordWrap - visible: balanceListView.count === 0 - } - - ListView { - id: balanceListView - Layout.fillWidth: true - Layout.preferredHeight: Math.min(contentHeight, 320) - clip: true - model: root.accountsModel - spacing: Theme.spacing.small - - delegate: AccountDelegate { - balanceError: root.lastBalanceErrorAddress === model.address ? - root.lastBalanceError: "" - onGetBalanceRequested: (addr) => root.getBalanceRequested(addr) - onCopyRequested: (text) => root.copyToClipboard(text) - } - } - } - } - - ColumnLayout { - id: actionsCol - - Layout.fillWidth: true - spacing: Theme.spacing.large - - // Transfer funds card - Rectangle { - id: transferRect - - Layout.fillWidth: true - Layout.preferredHeight: transferCol.height + 2 * Theme.spacing.large - color: Theme.palette.backgroundTertiary - radius: Theme.spacing.radiusLarge - border.color: Theme.palette.border - border.width: 1 - - ColumnLayout { - id: transferCol - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: Theme.spacing.large - spacing: Theme.spacing.small - - LogosText { - text: qsTr("Transfer funds") - font.pixelSize: Theme.typography.secondaryText - font.bold: true - } - - StyledAddressComboBox { - id: transferFromCombo - model: root.accountsModel - textRole: "address" - } - - LogosTextField { - id: transferToField - Layout.fillWidth: true - Layout.preferredHeight: 30 - placeholderText: qsTr("To key (64 hex chars)") - } - - LogosTextField { - id: transferAmountField - Layout.fillWidth: true - Layout.preferredHeight: 30 - placeholderText: qsTr("Amount") - } - - RowLayout { - Layout.fillWidth: true - Layout.preferredHeight: transferButton.implicitHeight - - LogosButton { - id: transferButton - Layout.preferredWidth: 60 - Layout.alignment: Qt.AlignRight - text: qsTr("Send") - onClicked: root.transferRequested(transferFromCombo.currentText.trim(), transferToField.text.trim(), transferAmountField.text) - } - - LogosButton { - Layout.fillWidth: true - enabled: true - padding: Theme.spacing.small - contentItem: RowLayout { - width: parent.width - anchors.centerIn: parent - LogosText { - id: transferResultText - Layout.fillWidth: true - color: Theme.palette.textSecondary - font.pixelSize: Theme.typography.secondaryText - font.weight: Theme.typography.weightMedium - wrapMode: Text.WordWrap - elide: Text.ElideRight - } - LogosCopyButton { - Layout.alignment: Qt.AlignRight - Layout.preferredHeight: 40 - Layout.preferredWidth: 40 - onCopyText: root.copyToClipboard(transferResultText.text) - visible: transferResultText.text - } - } - } - } - } - } - - // Leader rewards card - Rectangle { - id: leaderRewardsRect - - Layout.fillWidth: true - Layout.preferredHeight: leaderRewardsCol.height + 2 * Theme.spacing.large - color: Theme.palette.backgroundTertiary - radius: Theme.spacing.radiusLarge - border.color: Theme.palette.border - border.width: 1 - - ColumnLayout { - id: leaderRewardsCol - anchors.top: parent.top - anchors.left: parent.left - anchors.right: parent.right - anchors.margins: Theme.spacing.large - spacing: Theme.spacing.small - - LogosText { - text: qsTr("Leader rewards") - font.pixelSize: Theme.typography.secondaryText - font.bold: true - } - - RowLayout { - Layout.fillWidth: true - - LogosButton { - id: leaderClaimButton - Layout.preferredWidth: 140 - text: qsTr("Claim") - onClicked: root.claimLeaderRewardsRequested() - } - - LogosButton { - Layout.fillWidth: true - enabled: true - padding: Theme.spacing.small - contentItem: RowLayout { - width: parent.width - anchors.centerIn: parent - LogosText { - id: leaderClaimResultText - Layout.fillWidth: true - color: Theme.palette.textSecondary - font.pixelSize: Theme.typography.secondaryText - font.weight: Theme.typography.weightMedium - wrapMode: Text.WordWrap - elide: Text.ElideRight - } - LogosCopyButton { - Layout.alignment: Qt.AlignRight - Layout.preferredHeight: 40 - Layout.preferredWidth: 40 - onCopyText: root.copyToClipboard(leaderClaimResultText.text) - visible: leaderClaimResultText.text - } - } - } - } - } - } - } - - component StyledAddressComboBox: ComboBox { - id: comboControl - - Layout.fillWidth: true - padding: Theme.spacing.large - editable: true - font.pixelSize: Theme.typography.secondaryText - - background: Rectangle { - color: Theme.palette.backgroundTertiary - radius: Theme.spacing.radiusLarge - border.color: Theme.palette.border - border.width: 1 - } - indicator: LogosText { - id: comboIndicator - text: "▼" - font.pixelSize: Theme.typography.secondaryText - color: Theme.palette.textSecondary - x: comboControl.width - width - Theme.spacing.small - y: (comboControl.height - height) / 2 - visible: comboControl.count > 0 - } - contentItem: Item { - implicitWidth: 200 - implicitHeight: 30 - - TextField { - id: comboTextField - anchors.fill: parent - leftPadding: 0 - rightPadding: comboControl.count > 0 ? comboIndicator.width + Theme.spacing.small : Theme.spacing.small - topPadding: 0 - bottomPadding: 0 - verticalAlignment: Text.AlignVCenter - font.pixelSize: Theme.typography.secondaryText - text: comboControl.editText - onTextChanged: if (text !== comboControl.editText) comboControl.editText = text - selectByMouse: true - color: Theme.palette.text - background: Item { } - } - MouseArea { - anchors.fill: parent - visible: comboControl.count > 0 - z: 1 - onPressed: { - comboControl.popup.visible ? comboControl.popup.close() : comboControl.popup.open() - } - } - } - delegate: ItemDelegate { - id: comboDelegate - width: comboControl.width - contentItem: LogosText { - width: parent.width - height: contentHeight + Theme.spacing.large - font.pixelSize: Theme.typography.secondaryText - font.bold: true - text: (typeof model.address !== "undefined" ? model.address : modelData) || "" - elide: Text.ElideMiddle - horizontalAlignment: Text.AlignHCenter - verticalAlignment: Text.AlignVCenter - } - background: Rectangle { - color: comboDelegate.highlighted ? - Theme.palette.backgroundTertiary : - Theme.palette.backgroundSecondary - } - highlighted: comboControl.highlightedIndex === index - } - popup: Popup { - y: comboControl.height - 1 - width: comboControl.width - height: contentItem.implicitHeight - padding: 1 - - onOpened: if (comboControl.count === 0) close() - - contentItem: ListView { - clip: true - implicitHeight: contentHeight - model: comboControl.popup.visible ? comboControl.delegateModel : null - ScrollIndicator.vertical: ScrollIndicator { } - highlightFollowsCurrentItem: false - } - - background: Rectangle { - color: Theme.palette.backgroundSecondary - border.color: Theme.palette.border - border.width: 1 - radius: Theme.spacing.radiusLarge - } - } - } -} diff --git a/src/qml/views/qmldir b/src/qml/views/qmldir index fbca773..bcf9820 100644 --- a/src/qml/views/qmldir +++ b/src/qml/views/qmldir @@ -1,7 +1,9 @@ module views StatusConfigView 1.0 StatusConfigView.qml LogsView 1.0 LogsView.qml -WalletView 1.0 WalletView.qml +AccountsView 1.0 AccountsView.qml +TransferView 1.0 TransferView.qml +LeaderRewardsView 1.0 LeaderRewardsView.qml GenerateConfigView 1.0 GenerateConfigView.qml ConfigChoiceView 1.0 ConfigChoiceView.qml SetConfigPathView 1.0 SetConfigPathView.qml