1
0

qml: introduce InfoBanner allowing a clickable sticky message to stay below header and

implement ln utxo reserve check with warning. Clicking shows a suggestion to swap.
This commit is contained in:
Sander van Grieken
2025-03-24 20:20:55 +01:00
committed by ThomasV
parent 3fd64b60ab
commit f76218ea83
5 changed files with 181 additions and 12 deletions

View File

@@ -447,6 +447,7 @@ Item {
Connections { Connections {
target: Daemon target: Daemon
function onWalletLoaded() { function onWalletLoaded() {
infobanner.hide() // start hidden when switching wallets
if (_intentUri) { if (_intentUri) {
invoiceParser.recipient = _intentUri invoiceParser.recipient = _intentUri
_intentUri = '' _intentUri = ''
@@ -497,6 +498,27 @@ Item {
}) })
dialog.open() dialog.open()
} }
function onBalanceChanged() {
// ln low reserve warning
if (Daemon.currentWallet.isLowReserve) {
var message = [
qsTr('You do not have enough on-chain funds to protect your Lightning channels.'),
qsTr('You should have at least %1 on-chain in order to be able to sweep channel outputs.').arg(Config.formatSats(Config.lnUtxoReserve) + ' ' + Config.baseUnit)
].join(' ')
infobanner.show(message, function() {
var dialog = app.messageDialog.createObject(app, {
text: message + '\n\n' + qsTr('Do you want to perform a swap?'),
yesno: true
})
dialog.accepted.connect(function() {
app.startSwap()
})
dialog.open()
})
} else {
infobanner.hide()
}
}
} }
Component { Component {

View File

@@ -0,0 +1,124 @@
import QtQuick
import QtQuick.Layouts
import QtQuick.Controls
import QtQuick.Controls.Material
import QtQuick.Controls.Material.impl
Item {
id: root
property string message
property bool autohide: false
property color color: constants.colorAlpha(constants.colorWarning, 0.1)
property url icon: Qt.resolvedUrl('../../../icons/warning.png')
property alias font: messageLabel.font
property bool _hide: true
property var _clicked_fn
clip:true
z: 1
layer.enabled: height > 0
layer.effect: ElevationEffect {
elevation: constants.paddingXLarge
fullWidth: true
}
state: 'hidden'
states: [
State {
name: 'hidden'; when: _hide
PropertyChanges { target: root; implicitHeight: 0 }
},
State {
name: 'expanded'; when: !_hide
PropertyChanges { target: root; implicitHeight: layout.implicitHeight }
}
]
transitions: [
Transition {
from: 'hidden'; to: 'expanded'
SequentialAnimation {
PropertyAction { target: root; property: 'visible'; value: true }
NumberAnimation { target: root; properties: 'implicitHeight'; duration: 300; easing.type: Easing.OutQuad }
}
},
Transition {
from: 'expanded'; to: 'hidden'
SequentialAnimation {
NumberAnimation { target: root; properties: 'implicitHeight'; duration: 100; easing.type: Easing.OutQuad }
PropertyAction { target: root; property: 'visible'; value: false }
}
}
]
function show(message, on_clicked=undefined) {
root.message = message
root._clicked_fn = on_clicked
root._hide = false
if (autohide)
closetimer.start()
}
function hide() {
closetimer.stop()
root._hide = true
}
Rectangle {
id: rect
width: root.width
height: layout.height
color: root.color
anchors.bottom: root.bottom
ColumnLayout {
id: layout
width: parent.width
spacing: 0
RowLayout {
Layout.margins: constants.paddingLarge
spacing: constants.paddingSmall
Image {
source: root.icon
Layout.preferredWidth: constants.iconSizeLarge
Layout.preferredHeight: constants.iconSizeLarge
}
Label {
id: messageLabel
Layout.fillWidth: true
font.pixelSize: constants.fontSizeSmall
color: Material.foreground
wrapMode: Text.Wrap
text: root.message
}
}
Rectangle {
Layout.preferredHeight: 2
Layout.fillWidth: true
color: Material.accentColor
}
}
}
MouseArea {
anchors.fill: parent
onClicked: {
if (root._clicked_fn)
root._clicked_fn()
}
}
Timer {
id: closetimer
interval: 5000
repeat: false
onTriggered: _hide = true
}
}

View File

@@ -33,6 +33,7 @@ ApplicationWindow
property alias stack: mainStackView property alias stack: mainStackView
property alias keyboardFreeZone: _keyboardFreeZone property alias keyboardFreeZone: _keyboardFreeZone
property alias infobanner: _infobanner
property variant activeDialogs: [] property variant activeDialogs: []
@@ -244,22 +245,34 @@ ApplicationWindow
} }
} }
StackView { ColumnLayout {
id: mainStackView
width: parent.width width: parent.width
height: _keyboardFreeZone.height - header.height height: _keyboardFreeZone.height - header.height
initialItem: Component { spacing: 0
WalletMainView {}
InfoBanner {
id: _infobanner
Layout.fillWidth: true
} }
function getRoot() { StackView {
return mainStackView.get(0) id: mainStackView
} Layout.fillHeight: true
function pushOnRoot(item) { Layout.fillWidth: true
if (mainStackView.depth > 1) {
mainStackView.replace(mainStackView.get(1), item) initialItem: Component {
} else { WalletMainView {}
mainStackView.push(item) }
function getRoot() {
return mainStackView.get(0)
}
function pushOnRoot(item) {
if (mainStackView.depth > 1) {
mainStackView.replace(mainStackView.get(1), item)
} else {
mainStackView.push(item)
}
} }
} }
} }

View File

@@ -291,6 +291,12 @@ class QEConfig(AuthMixin, QObject):
self.config.SWAPSERVER_NPUB = swapserver_npub self.config.SWAPSERVER_NPUB = swapserver_npub
self.swapServerNPubChanged.emit() self.swapServerNPubChanged.emit()
lnUtxoReserveChanged = pyqtSignal()
@pyqtProperty(QEAmount, notify=lnUtxoReserveChanged)
def lnUtxoReserve(self):
self._lnutxoreserve = QEAmount(amount_sat=self.config.LN_UTXO_RESERVE)
return self._lnutxoreserve
@pyqtSlot('qint64', result=str) @pyqtSlot('qint64', result=str)
@pyqtSlot(QEAmount, result=str) @pyqtSlot(QEAmount, result=str)
def formatSatsForEditing(self, satoshis): def formatSatsForEditing(self, satoshis):

View File

@@ -513,6 +513,10 @@ class QEWallet(AuthMixin, QObject, QtEventListener):
self._lightningcanreceive.satsInt = int(self.wallet.lnworker.num_sats_can_receive()) self._lightningcanreceive.satsInt = int(self.wallet.lnworker.num_sats_can_receive())
return self._lightningcanreceive return self._lightningcanreceive
@pyqtProperty(bool, notify=balanceChanged)
def isLowReserve(self):
return self.wallet.is_low_reserve()
@pyqtProperty(QEAmount, notify=dataChanged) @pyqtProperty(QEAmount, notify=dataChanged)
def minChannelFunding(self): def minChannelFunding(self):
return self._minchannelfunding return self._minchannelfunding