diff --git a/electrum/gui/qml/components/WalletMainView.qml b/electrum/gui/qml/components/WalletMainView.qml index 1edc8299b..20f2d321b 100644 --- a/electrum/gui/qml/components/WalletMainView.qml +++ b/electrum/gui/qml/components/WalletMainView.qml @@ -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 { diff --git a/electrum/gui/qml/components/controls/InfoBanner.qml b/electrum/gui/qml/components/controls/InfoBanner.qml new file mode 100644 index 000000000..2d41d6f1e --- /dev/null +++ b/electrum/gui/qml/components/controls/InfoBanner.qml @@ -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 + } + +} diff --git a/electrum/gui/qml/components/main.qml b/electrum/gui/qml/components/main.qml index 10cee8886..14df2cf4b 100644 --- a/electrum/gui/qml/components/main.qml +++ b/electrum/gui/qml/components/main.qml @@ -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) + } } } } diff --git a/electrum/gui/qml/qeconfig.py b/electrum/gui/qml/qeconfig.py index e1bf70e4c..590ff20c2 100644 --- a/electrum/gui/qml/qeconfig.py +++ b/electrum/gui/qml/qeconfig.py @@ -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): diff --git a/electrum/gui/qml/qewallet.py b/electrum/gui/qml/qewallet.py index c48cd3dd5..d0bb87ec5 100644 --- a/electrum/gui/qml/qewallet.py +++ b/electrum/gui/qml/qewallet.py @@ -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