mirror of
https://github.com/logos-blockchain/logos-blockchain-ui.git
synced 2026-07-08 08:49:35 +00:00
Implement deposit ui
This commit is contained in:
parent
2caa5baa1b
commit
098a3c8dd5
@ -277,6 +277,39 @@ int BlockchainBackend::generateConfig(
|
||||
return result.isValid() ? result.toInt() : -1;
|
||||
}
|
||||
|
||||
QString BlockchainBackend::getNotes(QString walletAddressHex, QString optionalTipHex)
|
||||
{
|
||||
if (!m_blockchainClient)
|
||||
return QStringLiteral("Error: Module not initialized.");
|
||||
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(
|
||||
BLOCKCHAIN_MODULE_NAME, "wallet_get_notes",
|
||||
walletAddressHex, optionalTipHex);
|
||||
return result.isValid() ? result.toString()
|
||||
: QStringLiteral("Error: Call failed.");
|
||||
}
|
||||
|
||||
QString BlockchainBackend::channelDepositWithNotes(
|
||||
QString channelIdHex, QStringList inputNoteIdHexes, QString metadataHex,
|
||||
QString changePublicKeyHex, QStringList fundingPublicKeyHexes,
|
||||
QString maxTxFee, QString optionalTipHex)
|
||||
{
|
||||
if (!m_blockchainClient)
|
||||
return QStringLiteral("Error: Module not initialized.");
|
||||
|
||||
// 7 positional args exceed the variadic invokeRemoteMethod overloads
|
||||
// (max 5), so pass them through the QVariantList form.
|
||||
QVariantList args;
|
||||
args << channelIdHex << inputNoteIdHexes << metadataHex << changePublicKeyHex
|
||||
<< fundingPublicKeyHexes << maxTxFee << optionalTipHex;
|
||||
|
||||
QVariant result = m_blockchainClient->invokeRemoteMethod(
|
||||
BLOCKCHAIN_MODULE_NAME, QStringLiteral("channel_deposit_with_notes"),
|
||||
args);
|
||||
return result.isValid() ? result.toString()
|
||||
: QStringLiteral("Error: Call failed.");
|
||||
}
|
||||
|
||||
void BlockchainBackend::clearLogs()
|
||||
{
|
||||
m_logModel->clear();
|
||||
|
||||
@ -47,6 +47,14 @@ public slots:
|
||||
int blendPort, QString httpAddr, QString externalAddress,
|
||||
bool noPublicIpCheck, int deploymentMode,
|
||||
QString deploymentConfigPath, QString statePath) override;
|
||||
QString getNotes(QString walletAddressHex, QString optionalTipHex) override;
|
||||
QString channelDepositWithNotes(QString channelIdHex,
|
||||
QStringList inputNoteIdHexes,
|
||||
QString metadataHex,
|
||||
QString changePublicKeyHex,
|
||||
QStringList fundingPublicKeyHexes,
|
||||
QString maxTxFee,
|
||||
QString optionalTipHex) override;
|
||||
void clearLogs() override;
|
||||
void copyToClipboard(QString text) override;
|
||||
|
||||
|
||||
@ -14,6 +14,8 @@ class BlockchainBackend
|
||||
SLOT(QString getBalance(QString addressHex))
|
||||
SLOT(QString transferFunds(QString fromKeyHex, QString toKeyHex, QString amountStr))
|
||||
SLOT(int generateConfig(QString outputPath, QStringList initialPeers, int netPort, int blendPort, QString httpAddr, QString externalAddress, bool noPublicIpCheck, int deploymentMode, QString deploymentConfigPath, QString statePath))
|
||||
SLOT(QString getNotes(QString walletAddressHex, QString optionalTipHex))
|
||||
SLOT(QString channelDepositWithNotes(QString channelIdHex, QStringList inputNoteIdHexes, QString metadataHex, QString changePublicKeyHex, QStringList fundingPublicKeyHexes, QString maxTxFee, QString optionalTipHex))
|
||||
SLOT(void clearLogs())
|
||||
SLOT(void copyToClipboard(QString text))
|
||||
}
|
||||
|
||||
@ -3,6 +3,7 @@ import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
// BlockchainStatus enum (NotStarted/Starting/Running/.../ErrorSubscribeFailed)
|
||||
// declared in BlockchainBackend.rep — registered with QML by the replica
|
||||
// factory plugin.
|
||||
@ -155,7 +156,27 @@ Rectangle {
|
||||
}
|
||||
}
|
||||
|
||||
// Page 2: Node control, wallet, logs
|
||||
// Page 2: Node control, wallet, logs + Channel Deposit (tabbed)
|
||||
ColumnLayout {
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
LogosTabBar {
|
||||
id: operationTabBar
|
||||
Layout.fillWidth: true
|
||||
LogosTabButton { text: qsTr("Node & Wallet") }
|
||||
LogosTabButton {
|
||||
text: qsTr("Channel Deposit")
|
||||
enabled: root.backend
|
||||
&& root.backend.status === BlockchainBackend.Running
|
||||
}
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
id: operationStack
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
currentIndex: operationTabBar.currentIndex
|
||||
|
||||
SplitView {
|
||||
orientation: Qt.Vertical
|
||||
|
||||
@ -239,6 +260,38 @@ Rectangle {
|
||||
if (root.backend) root.backend.copyToClipboard(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) { channelDepositView.setNotesResult(result) },
|
||||
function(error) { channelDepositView.setNotesResult("Error: " + error) }
|
||||
)
|
||||
}
|
||||
onSubmitRequested: function(channelIdHex, inputNoteIdHexes, metadataHex, changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex) {
|
||||
if (!root.backend) return
|
||||
logos.watch(
|
||||
root.backend.channelDepositWithNotes(
|
||||
channelIdHex, inputNoteIdHexes, metadataHex,
|
||||
changePublicKeyHex, fundingPublicKeyHexes, maxTxFee, optionalTipHex),
|
||||
function(result) { channelDepositView.setSubmitResult(result) },
|
||||
function(error) { channelDepositView.setSubmitResult("Error: " + error) }
|
||||
)
|
||||
}
|
||||
onCopyToClipboard: (text) => {
|
||||
if (root.backend) root.backend.copyToClipboard(text)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
162
src/qml/controls/NoteSelector.qml
Normal file
162
src/qml/controls/NoteSelector.qml
Normal file
@ -0,0 +1,162 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
|
||||
// Widget for selecting spendable notes (UTXOs) to consume in a channel deposit.
|
||||
//
|
||||
// The owner sets `notes` (a JS array of { id, value } parsed from
|
||||
// wallet_get_notes) and reads back the selection via selectedIds() and the
|
||||
// selectedCount / selectedTotal properties.
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Array of { id: "<hex>", value: "<u64-string>" }. Assign to (re)populate.
|
||||
property var notes: []
|
||||
property bool loading: false
|
||||
property string errorText: ""
|
||||
|
||||
// Observable selection summary — bindings can't track ListModel reads, so
|
||||
// these properties are recomputed explicitly on every selection change.
|
||||
property int selectedCount: 0
|
||||
property double selectedTotal: 0
|
||||
|
||||
function selectedIds() {
|
||||
var ids = []
|
||||
for (var i = 0; i < notesModel.count; ++i) {
|
||||
var n = notesModel.get(i)
|
||||
if (n.selected)
|
||||
ids.push(n.noteId)
|
||||
}
|
||||
return ids
|
||||
}
|
||||
|
||||
function recompute() {
|
||||
var c = 0
|
||||
var total = 0
|
||||
for (var i = 0; i < notesModel.count; ++i) {
|
||||
var n = notesModel.get(i)
|
||||
if (n.selected) {
|
||||
c++
|
||||
total += Number(n.value)
|
||||
}
|
||||
}
|
||||
root.selectedCount = c
|
||||
root.selectedTotal = total
|
||||
}
|
||||
|
||||
function clearSelection() {
|
||||
for (var i = 0; i < notesModel.count; ++i)
|
||||
notesModel.setProperty(i, "selected", false)
|
||||
recompute()
|
||||
}
|
||||
|
||||
onNotesChanged: {
|
||||
notesModel.clear()
|
||||
var arr = root.notes || []
|
||||
for (var i = 0; i < arr.length; ++i) {
|
||||
notesModel.append({
|
||||
noteId: String(arr[i].id),
|
||||
value: String(arr[i].value),
|
||||
selected: false
|
||||
})
|
||||
}
|
||||
recompute()
|
||||
}
|
||||
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
ListModel { id: notesModel }
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
visible: root.loading
|
||||
text: qsTr("Loading notes…")
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
visible: !root.loading && root.errorText !== ""
|
||||
text: root.errorText
|
||||
color: Theme.palette.error
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
visible: !root.loading && root.errorText === "" && notesModel.count === 0
|
||||
text: qsTr("No notes found for this address. Load notes for a funded address.")
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 220
|
||||
visible: notesModel.count > 0
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
ListView {
|
||||
id: notesList
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacing.small
|
||||
clip: true
|
||||
model: notesModel
|
||||
spacing: Theme.spacing.tiny
|
||||
|
||||
delegate: RowLayout {
|
||||
width: notesList.width
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
LogosCheckbox {
|
||||
checked: model.selected
|
||||
onClicked: {
|
||||
notesModel.setProperty(index, "selected", checked)
|
||||
root.recompute()
|
||||
}
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: model.noteId
|
||||
elide: Text.ElideMiddle
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.alignment: Qt.AlignRight
|
||||
text: model.value
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: notesModel.count > 0
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
LogosText {
|
||||
text: qsTr("%1 selected").arg(root.selectedCount)
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
LogosText {
|
||||
text: qsTr("Total: %1").arg(root.selectedTotal)
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
font.bold: true
|
||||
}
|
||||
}
|
||||
}
|
||||
514
src/qml/views/ChannelDepositView.qml
Normal file
514
src/qml/views/ChannelDepositView.qml
Normal file
@ -0,0 +1,514 @@
|
||||
import QtQuick
|
||||
import QtQuick.Controls
|
||||
import QtQuick.Layouts
|
||||
|
||||
import Logos.Theme
|
||||
import Logos.Controls
|
||||
|
||||
import "../controls"
|
||||
|
||||
// Multi-step wizard for channel_deposit_with_notes:
|
||||
// 1. Select notes → wallet_get_notes, pick UTXOs to consume
|
||||
// 2. Fill fields → channel id, change/funding keys, fee, metadata, tip
|
||||
// 3. Confirm → review the exact payload
|
||||
// 4. Result → tx hash (copyable) or error
|
||||
ColumnLayout {
|
||||
id: root
|
||||
|
||||
// Known wallet addresses (auto-remoted accounts model). Also used as
|
||||
// public keys for the change/funding key pickers.
|
||||
property var accountsModel: null
|
||||
property bool nodeRunning: false
|
||||
|
||||
signal getNotesRequested(string addressHex, string optionalTipHex)
|
||||
signal submitRequested(string channelIdHex, var inputNoteIdHexes, string metadataHex, string changePublicKeyHex, var fundingPublicKeyHexes, string maxTxFee, string optionalTipHex)
|
||||
signal copyToClipboard(string text)
|
||||
|
||||
// --- Called by the parent after async backend calls return ---
|
||||
|
||||
function setNotesLoading() {
|
||||
noteSelector.loading = true
|
||||
noteSelector.errorText = ""
|
||||
}
|
||||
|
||||
function setNotesResult(jsonStr) {
|
||||
noteSelector.loading = false
|
||||
var s = jsonStr || ""
|
||||
if (s.indexOf("Error") === 0) {
|
||||
noteSelector.errorText = s
|
||||
noteSelector.notes = []
|
||||
return
|
||||
}
|
||||
try {
|
||||
var parsed = JSON.parse(s)
|
||||
d.notesTip = parsed.tip || ""
|
||||
noteSelector.errorText = ""
|
||||
noteSelector.notes = parsed.notes || []
|
||||
} catch (e) {
|
||||
noteSelector.errorText = qsTr("Failed to parse notes: %1").arg(s)
|
||||
noteSelector.notes = []
|
||||
}
|
||||
}
|
||||
|
||||
function setSubmitResult(resultStr) {
|
||||
d.resultPending = false
|
||||
var s = resultStr || ""
|
||||
d.resultSuccess = (s.indexOf("Error") !== 0)
|
||||
d.resultText = s
|
||||
}
|
||||
|
||||
spacing: Theme.spacing.large
|
||||
|
||||
QtObject {
|
||||
id: d
|
||||
|
||||
property int step: 0
|
||||
readonly property int stepCount: 4
|
||||
property string notesTip: ""
|
||||
|
||||
// result state
|
||||
property bool resultPending: false
|
||||
property bool resultSuccess: false
|
||||
property string resultText: ""
|
||||
|
||||
function fundingKeyList() {
|
||||
return fundingKeysArea.text.split("\n")
|
||||
.map(function(s) { return s.trim() })
|
||||
.filter(function(s) { return s.length > 0 })
|
||||
}
|
||||
|
||||
function canAdvance() {
|
||||
switch (step) {
|
||||
case 0:
|
||||
return noteSelector.selectedCount > 0
|
||||
case 1:
|
||||
return channelIdField.text.trim().length > 0
|
||||
&& changeKeyField.text.trim().length > 0
|
||||
&& fundingKeyList().length > 0
|
||||
&& maxFeeField.text.trim().length > 0
|
||||
default:
|
||||
return true
|
||||
}
|
||||
}
|
||||
|
||||
function goNext() {
|
||||
if (step === 1) {
|
||||
// entering confirm — nothing else
|
||||
}
|
||||
if (step < stepCount - 1)
|
||||
step++
|
||||
// Prefill key fields from the selected wallet when first reaching step 1.
|
||||
if (step === 1) {
|
||||
if (changeKeyField.text.trim() === "")
|
||||
changeKeyField.text = walletField.text.trim()
|
||||
if (fundingKeysArea.text.trim() === "" && walletField.text.trim() !== "")
|
||||
fundingKeysArea.text = walletField.text.trim()
|
||||
}
|
||||
}
|
||||
|
||||
function goBack() {
|
||||
if (step > 0)
|
||||
step--
|
||||
}
|
||||
|
||||
function submit() {
|
||||
d.resultPending = true
|
||||
d.resultSuccess = false
|
||||
d.resultText = ""
|
||||
step = 3
|
||||
root.submitRequested(
|
||||
channelIdField.text.trim(),
|
||||
noteSelector.selectedIds(),
|
||||
metadataField.text.trim(),
|
||||
changeKeyField.text.trim(),
|
||||
fundingKeyList(),
|
||||
maxFeeField.text.trim(),
|
||||
tipField.text.trim())
|
||||
}
|
||||
|
||||
function reset() {
|
||||
noteSelector.clearSelection()
|
||||
noteSelector.notes = []
|
||||
noteSelector.errorText = ""
|
||||
channelIdField.text = ""
|
||||
changeKeyField.text = ""
|
||||
fundingKeysArea.text = ""
|
||||
maxFeeField.text = ""
|
||||
metadataField.text = ""
|
||||
tipField.text = ""
|
||||
notesTip = ""
|
||||
resultText = ""
|
||||
resultPending = false
|
||||
step = 0
|
||||
}
|
||||
|
||||
function summaryLines() {
|
||||
var ids = noteSelector.selectedIds()
|
||||
return [
|
||||
{ k: qsTr("Channel ID"), v: channelIdField.text.trim() },
|
||||
{ k: qsTr("Notes to consume (%1)").arg(ids.length), v: ids.join("\n") },
|
||||
{ k: qsTr("Total amount"), v: String(noteSelector.selectedTotal) },
|
||||
{ k: qsTr("Change public key"), v: changeKeyField.text.trim() },
|
||||
{ k: qsTr("Funding public keys"), v: fundingKeyList().join("\n") },
|
||||
{ k: qsTr("Max tx fee"), v: maxFeeField.text.trim() },
|
||||
{ k: qsTr("Metadata hex"), v: metadataField.text.trim() || qsTr("(none)") },
|
||||
{ k: qsTr("Optional tip hex"), v: tipField.text.trim() || qsTr("(current tip)") }
|
||||
]
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Header / step indicator ----
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Channel Deposit")
|
||||
font.pixelSize: Theme.typography.primaryText
|
||||
font.bold: true
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
LogosText {
|
||||
text: qsTr("Step %1 of %2").arg(d.step + 1).arg(d.stepCount)
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
visible: !root.nodeRunning
|
||||
text: qsTr("Start the node before making a deposit.")
|
||||
color: Theme.palette.warning
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
StackLayout {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
currentIndex: d.step
|
||||
|
||||
// ---- Step 0: Select notes ----
|
||||
ColumnLayout {
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Select the notes (UTXOs) to deposit into the channel. Their full value is consumed.")
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
LogosComboBox {
|
||||
Layout.preferredWidth: 200
|
||||
placeholderText: qsTr("Known address…")
|
||||
model: root.accountsModel
|
||||
textRole: "address"
|
||||
currentIndex: -1
|
||||
onActivated: function(index) { walletField.text = currentText }
|
||||
}
|
||||
LogosTextField {
|
||||
id: walletField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Wallet address hex")
|
||||
}
|
||||
LogosButton {
|
||||
text: qsTr("Load notes")
|
||||
enabled: root.nodeRunning && walletField.text.trim().length > 0
|
||||
onClicked: {
|
||||
root.setNotesLoading()
|
||||
root.getNotesRequested(walletField.text.trim(), "")
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NoteSelector {
|
||||
id: noteSelector
|
||||
Layout.fillWidth: true
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
}
|
||||
|
||||
// ---- Step 1: Fields ----
|
||||
ScrollView {
|
||||
id: fieldsScroll
|
||||
clip: true
|
||||
ColumnLayout {
|
||||
width: fieldsScroll.availableWidth
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Channel ID hex")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
LogosTextField {
|
||||
id: channelIdField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Channel ID hex")
|
||||
}
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Change public key (receives change)")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.small
|
||||
LogosComboBox {
|
||||
Layout.preferredWidth: 200
|
||||
placeholderText: qsTr("Known address…")
|
||||
model: root.accountsModel
|
||||
textRole: "address"
|
||||
currentIndex: -1
|
||||
onActivated: function(index) { changeKeyField.text = currentText }
|
||||
}
|
||||
LogosTextField {
|
||||
id: changeKeyField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Change public key hex")
|
||||
}
|
||||
}
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Funding public keys (one per line, fund the gas fee)")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.small
|
||||
Item { Layout.fillWidth: true }
|
||||
LogosComboBox {
|
||||
Layout.preferredWidth: 220
|
||||
placeholderText: qsTr("Add known address…")
|
||||
model: root.accountsModel
|
||||
textRole: "address"
|
||||
currentIndex: -1
|
||||
onActivated: function(index) {
|
||||
var t = fundingKeysArea.text.trim()
|
||||
fundingKeysArea.text = (t.length > 0 ? t + "\n" : "") + currentText
|
||||
}
|
||||
}
|
||||
}
|
||||
ScrollView {
|
||||
Layout.fillWidth: true
|
||||
Layout.preferredHeight: 80
|
||||
clip: true
|
||||
TextArea {
|
||||
id: fundingKeysArea
|
||||
background: Rectangle {
|
||||
radius: Theme.spacing.radiusSmall
|
||||
color: Theme.palette.backgroundSecondary
|
||||
border.width: 1
|
||||
border.color: Theme.palette.backgroundElevated
|
||||
}
|
||||
placeholderText: qsTr("Funding public key hex, one per line")
|
||||
placeholderTextColor: Theme.palette.textTertiary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.text
|
||||
}
|
||||
}
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Max tx fee")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
LogosTextField {
|
||||
id: maxFeeField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Maximum transaction fee")
|
||||
}
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Metadata hex (optional)")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
LogosTextField {
|
||||
id: metadataField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Metadata hex")
|
||||
}
|
||||
|
||||
LogosText {
|
||||
text: qsTr("Optional tip hex (leave empty for current tip)")
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.small
|
||||
LogosTextField {
|
||||
id: tipField
|
||||
Layout.fillWidth: true
|
||||
placeholderText: qsTr("Tip hex")
|
||||
}
|
||||
LogosButton {
|
||||
text: qsTr("Use query tip")
|
||||
enabled: d.notesTip !== ""
|
||||
onClicked: tipField.text = d.notesTip
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Step 2: Confirm ----
|
||||
ColumnLayout {
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: qsTr("Review the deposit. This is the exact payload that will be submitted.")
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
Layout.fillHeight: true
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusLarge
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
|
||||
ScrollView {
|
||||
id: confirmScroll
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacing.large
|
||||
clip: true
|
||||
ColumnLayout {
|
||||
width: confirmScroll.availableWidth
|
||||
spacing: Theme.spacing.medium
|
||||
Repeater {
|
||||
// Re-evaluated when the confirm step is shown so it
|
||||
// reflects the latest field values.
|
||||
model: d.step === 2 ? d.summaryLines() : []
|
||||
delegate: ColumnLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.tiny
|
||||
LogosText {
|
||||
text: modelData.k
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
color: Theme.palette.textSecondary
|
||||
}
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
text: modelData.v
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
wrapMode: Text.WrapAnywhere
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Step 3: Result ----
|
||||
ColumnLayout {
|
||||
spacing: Theme.spacing.medium
|
||||
|
||||
LogosText {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: d.resultPending
|
||||
text: qsTr("Submitting deposit…")
|
||||
color: Theme.palette.textSecondary
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
}
|
||||
BusyIndicator {
|
||||
Layout.alignment: Qt.AlignHCenter
|
||||
visible: d.resultPending
|
||||
running: d.resultPending
|
||||
}
|
||||
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
visible: !d.resultPending
|
||||
text: d.resultSuccess ? qsTr("Deposit submitted") : qsTr("Deposit failed")
|
||||
color: d.resultSuccess ? Theme.palette.success : Theme.palette.error
|
||||
font.pixelSize: Theme.typography.primaryText
|
||||
font.bold: true
|
||||
}
|
||||
|
||||
// Success: tx hash + copy
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
visible: !d.resultPending && d.resultSuccess
|
||||
spacing: Theme.spacing.small
|
||||
Rectangle {
|
||||
Layout.fillWidth: true
|
||||
implicitHeight: txHashText.implicitHeight + Theme.spacing.medium
|
||||
color: Theme.palette.backgroundTertiary
|
||||
radius: Theme.spacing.radiusSmall
|
||||
border.color: Theme.palette.border
|
||||
border.width: 1
|
||||
LogosText {
|
||||
id: txHashText
|
||||
anchors.fill: parent
|
||||
anchors.margins: Theme.spacing.small
|
||||
text: d.resultText
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
wrapMode: Text.WrapAnywhere
|
||||
verticalAlignment: Text.AlignVCenter
|
||||
}
|
||||
}
|
||||
LogosCopyButton {
|
||||
Layout.preferredHeight: 40
|
||||
Layout.preferredWidth: 40
|
||||
onCopyText: root.copyToClipboard(d.resultText)
|
||||
}
|
||||
}
|
||||
|
||||
// Error
|
||||
LogosText {
|
||||
Layout.fillWidth: true
|
||||
visible: !d.resultPending && !d.resultSuccess && d.resultText !== ""
|
||||
text: d.resultText
|
||||
color: Theme.palette.error
|
||||
font.pixelSize: Theme.typography.secondaryText
|
||||
wrapMode: Text.WordWrap
|
||||
}
|
||||
|
||||
Item { Layout.fillHeight: true }
|
||||
}
|
||||
}
|
||||
|
||||
// ---- Footer navigation ----
|
||||
RowLayout {
|
||||
Layout.fillWidth: true
|
||||
spacing: Theme.spacing.small
|
||||
|
||||
LogosButton {
|
||||
text: qsTr("Back")
|
||||
visible: d.step > 0 && d.step < 3
|
||||
onClicked: d.goBack()
|
||||
}
|
||||
Item { Layout.fillWidth: true }
|
||||
|
||||
// Steps 0 & 1: Next
|
||||
LogosButton {
|
||||
text: qsTr("Next")
|
||||
visible: d.step < 2
|
||||
enabled: d.canAdvance()
|
||||
onClicked: d.goNext()
|
||||
}
|
||||
// Step 2: Confirm & submit
|
||||
LogosButton {
|
||||
text: qsTr("Confirm & deposit")
|
||||
visible: d.step === 2
|
||||
enabled: root.nodeRunning
|
||||
onClicked: d.submit()
|
||||
}
|
||||
// Step 3: New deposit (after completion)
|
||||
LogosButton {
|
||||
text: qsTr("New deposit")
|
||||
visible: d.step === 3 && !d.resultPending
|
||||
onClicked: d.reset()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -4,4 +4,5 @@ LogsView 1.0 LogsView.qml
|
||||
WalletView 1.0 WalletView.qml
|
||||
GenerateConfigView 1.0 GenerateConfigView.qml
|
||||
ConfigChoiceView 1.0 ConfigChoiceView.qml
|
||||
SetConfigPathView 1.0 SetConfigPathView.qml
|
||||
SetConfigPathView 1.0 SetConfigPathView.qml
|
||||
ChannelDepositView 1.0 ChannelDepositView.qml
|
||||
Loading…
x
Reference in New Issue
Block a user