auto-remove paid invoices from GUI
- delay 3 seconds in GUI - kivy remove 'delete' buttons from send/receive screens
This commit is contained in:
@@ -242,9 +242,12 @@ class ElectrumWindow(App, Logger):
|
|||||||
self._trigger_update_history()
|
self._trigger_update_history()
|
||||||
|
|
||||||
def on_request_status(self, event, wallet, key, status):
|
def on_request_status(self, event, wallet, key, status):
|
||||||
if key not in self.wallet.receive_requests:
|
req = self.wallet.receive_requests.get(key)
|
||||||
|
if req is None:
|
||||||
return
|
return
|
||||||
self.update_tab('receive')
|
if self.receive_screen:
|
||||||
|
self.receive_screen.update_item(key, req)
|
||||||
|
Clock.schedule_once(lambda dt: self.receive_screen.update(), 3)
|
||||||
if self.request_popup and self.request_popup.key == key:
|
if self.request_popup and self.request_popup.key == key:
|
||||||
self.request_popup.update_status()
|
self.request_popup.update_status()
|
||||||
if status == PR_PAID:
|
if status == PR_PAID:
|
||||||
@@ -255,9 +258,10 @@ class ElectrumWindow(App, Logger):
|
|||||||
req = self.wallet.get_invoice(key)
|
req = self.wallet.get_invoice(key)
|
||||||
if req is None:
|
if req is None:
|
||||||
return
|
return
|
||||||
status = self.wallet.get_invoice_status(req)
|
if self.send_screen:
|
||||||
# todo: update single item
|
self.send_screen.update_item(key, req)
|
||||||
self.update_tab('send')
|
Clock.schedule_once(lambda dt: self.send_screen.update(), 3)
|
||||||
|
|
||||||
if self.invoice_popup and self.invoice_popup.key == key:
|
if self.invoice_popup and self.invoice_popup.key == key:
|
||||||
self.invoice_popup.update_status()
|
self.invoice_popup.update_status()
|
||||||
|
|
||||||
|
|||||||
@@ -218,11 +218,23 @@ class SendScreen(CScreen, Logger):
|
|||||||
def update(self):
|
def update(self):
|
||||||
if self.app.wallet is None:
|
if self.app.wallet is None:
|
||||||
return
|
return
|
||||||
_list = self.app.wallet.get_invoices()
|
_list = self.app.wallet.get_unpaid_invoices()
|
||||||
_list.reverse()
|
_list.reverse()
|
||||||
payments_container = self.ids.payments_container
|
payments_container = self.ids.payments_container
|
||||||
payments_container.data = [self.get_card(item) for item in _list]
|
payments_container.data = [self.get_card(item) for item in _list]
|
||||||
|
|
||||||
|
def update_item(self, key, invoice):
|
||||||
|
payments_container = self.ids.payments_container
|
||||||
|
data = payments_container.data
|
||||||
|
for item in data:
|
||||||
|
if item['key'] == key:
|
||||||
|
status = self.app.wallet.get_invoice_status(invoice)
|
||||||
|
status_str = invoice.get_status_str(status)
|
||||||
|
item['status'] = status
|
||||||
|
item['status_str'] = status_str
|
||||||
|
payments_container.data = data
|
||||||
|
payments_container.refresh_from_data()
|
||||||
|
|
||||||
def show_item(self, obj):
|
def show_item(self, obj):
|
||||||
self.app.show_invoice(obj.is_lightning, obj.key)
|
self.app.show_invoice(obj.is_lightning, obj.key)
|
||||||
|
|
||||||
@@ -421,20 +433,6 @@ class SendScreen(CScreen, Logger):
|
|||||||
else:
|
else:
|
||||||
self.app.tx_dialog(tx)
|
self.app.tx_dialog(tx)
|
||||||
|
|
||||||
def clear_invoices_dialog(self):
|
|
||||||
invoices = self.app.wallet.get_invoices()
|
|
||||||
if not invoices:
|
|
||||||
return
|
|
||||||
def callback(c):
|
|
||||||
if c:
|
|
||||||
for req in invoices:
|
|
||||||
key = req.rhash if req.is_lightning() else req.get_address()
|
|
||||||
self.app.wallet.delete_invoice(key)
|
|
||||||
self.update()
|
|
||||||
n = len(invoices)
|
|
||||||
d = Question(_('Delete {} invoices?').format(n), callback)
|
|
||||||
d.open()
|
|
||||||
|
|
||||||
|
|
||||||
class ReceiveScreen(CScreen):
|
class ReceiveScreen(CScreen):
|
||||||
|
|
||||||
@@ -531,11 +529,23 @@ class ReceiveScreen(CScreen):
|
|||||||
def update(self):
|
def update(self):
|
||||||
if self.app.wallet is None:
|
if self.app.wallet is None:
|
||||||
return
|
return
|
||||||
_list = self.app.wallet.get_sorted_requests()
|
_list = self.app.wallet.get_unpaid_requests()
|
||||||
_list.reverse()
|
_list.reverse()
|
||||||
requests_container = self.ids.requests_container
|
requests_container = self.ids.requests_container
|
||||||
requests_container.data = [self.get_card(item) for item in _list]
|
requests_container.data = [self.get_card(item) for item in _list]
|
||||||
|
|
||||||
|
def update_item(self, key, request):
|
||||||
|
payments_container = self.ids.requests_container
|
||||||
|
data = payments_container.data
|
||||||
|
for item in data:
|
||||||
|
if item['key'] == key:
|
||||||
|
status = self.app.wallet.get_request_status(key)
|
||||||
|
status_str = request.get_status_str(status)
|
||||||
|
item['status'] = status
|
||||||
|
item['status_str'] = status_str
|
||||||
|
payments_container.data = data # needed?
|
||||||
|
payments_container.refresh_from_data()
|
||||||
|
|
||||||
def show_item(self, obj):
|
def show_item(self, obj):
|
||||||
self.app.show_request(obj.is_lightning, obj.key)
|
self.app.show_request(obj.is_lightning, obj.key)
|
||||||
|
|
||||||
@@ -546,19 +556,6 @@ class ReceiveScreen(CScreen):
|
|||||||
d = ChoiceDialog(_('Expiration date'), pr_expiration_values, self.expiry(), callback)
|
d = ChoiceDialog(_('Expiration date'), pr_expiration_values, self.expiry(), callback)
|
||||||
d.open()
|
d.open()
|
||||||
|
|
||||||
def clear_requests_dialog(self):
|
|
||||||
requests = self.app.wallet.get_sorted_requests()
|
|
||||||
if not requests:
|
|
||||||
return
|
|
||||||
def callback(c):
|
|
||||||
if c:
|
|
||||||
self.app.wallet.clear_requests()
|
|
||||||
self.update()
|
|
||||||
n = len(requests)
|
|
||||||
d = Question(_('Delete {} requests?').format(n), callback)
|
|
||||||
d.open()
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class TabbedCarousel(Factory.TabbedPanel):
|
class TabbedCarousel(Factory.TabbedPanel):
|
||||||
'''Custom TabbedPanel using a carousel used in the Main Screen
|
'''Custom TabbedPanel using a carousel used in the Main Screen
|
||||||
|
|||||||
@@ -134,11 +134,6 @@
|
|||||||
BoxLayout:
|
BoxLayout:
|
||||||
size_hint: 1, None
|
size_hint: 1, None
|
||||||
height: '48dp'
|
height: '48dp'
|
||||||
IconButton:
|
|
||||||
icon: f'atlas://{KIVY_GUI_PATH}/theming/light/delete'
|
|
||||||
size_hint: 0.5, None
|
|
||||||
height: '48dp'
|
|
||||||
on_release: Clock.schedule_once(lambda dt: s.clear_requests_dialog())
|
|
||||||
IconButton:
|
IconButton:
|
||||||
icon: f'atlas://{KIVY_GUI_PATH}/theming/light/clock1'
|
icon: f'atlas://{KIVY_GUI_PATH}/theming/light/clock1'
|
||||||
size_hint: 0.5, None
|
size_hint: 0.5, None
|
||||||
|
|||||||
@@ -150,10 +150,6 @@
|
|||||||
BoxLayout:
|
BoxLayout:
|
||||||
size_hint: 1, None
|
size_hint: 1, None
|
||||||
height: '48dp'
|
height: '48dp'
|
||||||
IconButton:
|
|
||||||
icon: f'atlas://{KIVY_GUI_PATH}/theming/light/delete'
|
|
||||||
size_hint: 0.5, 1
|
|
||||||
on_release: Clock.schedule_once(lambda dt: s.clear_invoices_dialog())
|
|
||||||
IconButton:
|
IconButton:
|
||||||
size_hint: 0.5, 1
|
size_hint: 0.5, 1
|
||||||
on_release: s.do_save()
|
on_release: s.do_save()
|
||||||
|
|||||||
@@ -98,7 +98,7 @@ class InvoiceList(MyTreeView):
|
|||||||
self.proxy.setDynamicSortFilter(False) # temp. disable re-sorting after every change
|
self.proxy.setDynamicSortFilter(False) # temp. disable re-sorting after every change
|
||||||
self.std_model.clear()
|
self.std_model.clear()
|
||||||
self.update_headers(self.__class__.headers)
|
self.update_headers(self.__class__.headers)
|
||||||
for idx, item in enumerate(self.parent.wallet.get_invoices()):
|
for idx, item in enumerate(self.parent.wallet.get_unpaid_invoices()):
|
||||||
if item.is_lightning():
|
if item.is_lightning():
|
||||||
key = item.rhash
|
key = item.rhash
|
||||||
icon_name = 'lightning.png'
|
icon_name = 'lightning.png'
|
||||||
|
|||||||
@@ -40,6 +40,7 @@ from typing import Optional, TYPE_CHECKING, Sequence, List, Union
|
|||||||
|
|
||||||
from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont
|
from PyQt5.QtGui import QPixmap, QKeySequence, QIcon, QCursor, QFont
|
||||||
from PyQt5.QtCore import Qt, QRect, QStringListModel, QSize, pyqtSignal
|
from PyQt5.QtCore import Qt, QRect, QStringListModel, QSize, pyqtSignal
|
||||||
|
from PyQt5.QtCore import QTimer
|
||||||
from PyQt5.QtWidgets import (QMessageBox, QComboBox, QSystemTrayIcon, QTabWidget,
|
from PyQt5.QtWidgets import (QMessageBox, QComboBox, QSystemTrayIcon, QTabWidget,
|
||||||
QMenuBar, QFileDialog, QCheckBox, QLabel,
|
QMenuBar, QFileDialog, QCheckBox, QLabel,
|
||||||
QVBoxLayout, QGridLayout, QLineEdit,
|
QVBoxLayout, QGridLayout, QLineEdit,
|
||||||
@@ -1516,8 +1517,16 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||||||
def on_request_status(self, wallet, key, status):
|
def on_request_status(self, wallet, key, status):
|
||||||
if wallet != self.wallet:
|
if wallet != self.wallet:
|
||||||
return
|
return
|
||||||
if key not in self.wallet.receive_requests:
|
req = self.wallet.receive_requests.get(key)
|
||||||
|
if req is None:
|
||||||
return
|
return
|
||||||
|
# update item
|
||||||
|
self.request_list.update_item(key, req)
|
||||||
|
# update list later
|
||||||
|
self.timer = QTimer()
|
||||||
|
self.timer.timeout.connect(self.request_list.update)
|
||||||
|
self.timer.start(3000)
|
||||||
|
|
||||||
if status == PR_PAID:
|
if status == PR_PAID:
|
||||||
self.notify(_('Payment received') + '\n' + key)
|
self.notify(_('Payment received') + '\n' + key)
|
||||||
self.need_update.set()
|
self.need_update.set()
|
||||||
@@ -1528,7 +1537,12 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
|
|||||||
req = self.wallet.get_invoice(key)
|
req = self.wallet.get_invoice(key)
|
||||||
if req is None:
|
if req is None:
|
||||||
return
|
return
|
||||||
|
# update item
|
||||||
self.invoice_list.update_item(key, req)
|
self.invoice_list.update_item(key, req)
|
||||||
|
# update list later.
|
||||||
|
self.timer = QTimer()
|
||||||
|
self.timer.timeout.connect(self.invoice_list.update)
|
||||||
|
self.timer.start(3000)
|
||||||
|
|
||||||
def on_payment_succeeded(self, wallet, key):
|
def on_payment_succeeded(self, wallet, key):
|
||||||
description = self.wallet.get_label(key)
|
description = self.wallet.get_label(key)
|
||||||
|
|||||||
@@ -34,6 +34,7 @@ from electrum.i18n import _
|
|||||||
from electrum.util import format_time
|
from electrum.util import format_time
|
||||||
from electrum.invoices import PR_TYPE_ONCHAIN, PR_TYPE_LN, LNInvoice, OnchainInvoice
|
from electrum.invoices import PR_TYPE_ONCHAIN, PR_TYPE_LN, LNInvoice, OnchainInvoice
|
||||||
from electrum.plugin import run_hook
|
from electrum.plugin import run_hook
|
||||||
|
from electrum.invoices import Invoice
|
||||||
|
|
||||||
from .util import MyTreeView, pr_icons, read_QIcon, webopen, MySortModel
|
from .util import MyTreeView, pr_icons, read_QIcon, webopen, MySortModel
|
||||||
|
|
||||||
@@ -126,13 +127,27 @@ class RequestList(MyTreeView):
|
|||||||
status_item.setText(status_str)
|
status_item.setText(status_str)
|
||||||
status_item.setIcon(read_QIcon(pr_icons.get(status)))
|
status_item.setIcon(read_QIcon(pr_icons.get(status)))
|
||||||
|
|
||||||
|
def update_item(self, key, invoice: Invoice):
|
||||||
|
model = self.std_model
|
||||||
|
for row in range(0, model.rowCount()):
|
||||||
|
item = model.item(row, 0)
|
||||||
|
if item.data(ROLE_KEY) == key:
|
||||||
|
break
|
||||||
|
else:
|
||||||
|
return
|
||||||
|
status_item = model.item(row, self.Columns.STATUS)
|
||||||
|
status = self.parent.wallet.get_request_status(key)
|
||||||
|
status_str = invoice.get_status_str(status)
|
||||||
|
status_item.setText(status_str)
|
||||||
|
status_item.setIcon(read_QIcon(pr_icons.get(status)))
|
||||||
|
|
||||||
def update(self):
|
def update(self):
|
||||||
# not calling maybe_defer_update() as it interferes with conditional-visibility
|
# not calling maybe_defer_update() as it interferes with conditional-visibility
|
||||||
self.parent.update_receive_address_styling()
|
self.parent.update_receive_address_styling()
|
||||||
self.proxy.setDynamicSortFilter(False) # temp. disable re-sorting after every change
|
self.proxy.setDynamicSortFilter(False) # temp. disable re-sorting after every change
|
||||||
self.std_model.clear()
|
self.std_model.clear()
|
||||||
self.update_headers(self.__class__.headers)
|
self.update_headers(self.__class__.headers)
|
||||||
for req in self.wallet.get_sorted_requests():
|
for req in self.wallet.get_unpaid_requests():
|
||||||
if req.is_lightning():
|
if req.is_lightning():
|
||||||
assert isinstance(req, LNInvoice)
|
assert isinstance(req, LNInvoice)
|
||||||
key = req.rhash
|
key = req.rhash
|
||||||
|
|||||||
@@ -761,10 +761,13 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
|
|
||||||
def get_invoices(self):
|
def get_invoices(self):
|
||||||
out = list(self.invoices.values())
|
out = list(self.invoices.values())
|
||||||
#out = list(filter(None, out)) filter out ln
|
|
||||||
out.sort(key=lambda x:x.time)
|
out.sort(key=lambda x:x.time)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def get_unpaid_invoices(self):
|
||||||
|
invoices = self.get_invoices()
|
||||||
|
return [x for x in invoices if self.get_invoice_status(x) != PR_PAID]
|
||||||
|
|
||||||
def get_invoice(self, key):
|
def get_invoice(self, key):
|
||||||
return self.invoices.get(key)
|
return self.invoices.get(key)
|
||||||
|
|
||||||
@@ -2035,6 +2038,12 @@ class Abstract_Wallet(AddressSynchronizer, ABC):
|
|||||||
out.sort(key=lambda x: x.time)
|
out.sort(key=lambda x: x.time)
|
||||||
return out
|
return out
|
||||||
|
|
||||||
|
def get_unpaid_requests(self):
|
||||||
|
out = [self.get_request(x) for x in self.receive_requests.keys() if self.get_request_status(x) != PR_PAID]
|
||||||
|
out = [x for x in out if x is not None]
|
||||||
|
out.sort(key=lambda x: x.time)
|
||||||
|
return out
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_fingerprint(self) -> str:
|
def get_fingerprint(self) -> str:
|
||||||
"""Returns a string that can be used to identify this wallet.
|
"""Returns a string that can be used to identify this wallet.
|
||||||
|
|||||||
Reference in New Issue
Block a user