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 {
target: Daemon
function onWalletLoaded() {
infobanner.hide() // start hidden when switching wallets
if (_intentUri) {
invoiceParser.recipient = _intentUri
_intentUri = ''
@@ -497,6 +498,27 @@ Item {
})
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 {

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 keyboardFreeZone: _keyboardFreeZone
property alias infobanner: _infobanner
property variant activeDialogs: []
@@ -244,22 +245,34 @@ ApplicationWindow
}
}
StackView {
id: mainStackView
ColumnLayout {
width: parent.width
height: _keyboardFreeZone.height - header.height
initialItem: Component {
WalletMainView {}
spacing: 0
InfoBanner {
id: _infobanner
Layout.fillWidth: true
}
function getRoot() {
return mainStackView.get(0)
}
function pushOnRoot(item) {
if (mainStackView.depth > 1) {
mainStackView.replace(mainStackView.get(1), item)
} else {
mainStackView.push(item)
StackView {
id: mainStackView
Layout.fillHeight: true
Layout.fillWidth: true
initialItem: Component {
WalletMainView {}
}
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.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(QEAmount, result=str)
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())
return self._lightningcanreceive
@pyqtProperty(bool, notify=balanceChanged)
def isLowReserve(self):
return self.wallet.is_low_reserve()
@pyqtProperty(QEAmount, notify=dataChanged)
def minChannelFunding(self):
return self._minchannelfunding