Modify views

This commit is contained in:
Daniel 2026-06-20 15:25:14 +02:00
parent 422166b2b4
commit c34ba1552f
6 changed files with 616 additions and 483 deletions

View File

@ -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
}
}
}

View File

@ -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)
}
}
}
}
}

View File

@ -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 }
}

View File

@ -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
}
}
}
}

View File

@ -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
}
}
}
}

View File

@ -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