Browse Source

kivy: show status with color. show inflight attempts.

master
ThomasV 6 years ago
parent
commit
aac0fe9ae6
  1. 7
      electrum/gui/kivy/main_window.py
  2. 19
      electrum/gui/kivy/uix/dialogs/invoice_dialog.py
  3. 14
      electrum/gui/kivy/uix/dialogs/request_dialog.py
  4. 13
      electrum/gui/kivy/uix/screens.py
  5. 9
      electrum/gui/kivy/uix/ui_screens/send.kv
  6. 20
      electrum/gui/qt/invoice_list.py
  7. 4
      electrum/gui/qt/main_window.py
  8. 7
      electrum/lnworker.py
  9. 9
      electrum/util.py

7
electrum/gui/kivy/main_window.py

@ -216,14 +216,14 @@ class ElectrumWindow(App):
self.show_info(_('Payment Received') + '\n' + key) self.show_info(_('Payment Received') + '\n' + key)
self._trigger_update_history() self._trigger_update_history()
def on_invoice_status(self, event, key, status, log): def on_invoice_status(self, event, key, status):
# todo: update single item # todo: update single item
self.update_tab('send') self.update_tab('send')
if self.invoice_popup and self.invoice_popup.key == key:
self.invoice_popup.set_status(status)
if status == PR_PAID: if status == PR_PAID:
self.show_info(_('Payment was sent')) self.show_info(_('Payment was sent'))
self._trigger_update_history() self._trigger_update_history()
elif status == PR_INFLIGHT:
pass
elif status == PR_FAILED: elif status == PR_FAILED:
self.show_info(_('Payment failed')) self.show_info(_('Payment failed'))
@ -443,6 +443,7 @@ class ElectrumWindow(App):
status = invoice['status'] status = invoice['status']
data = invoice['invoice'] if is_lightning else key data = invoice['invoice'] if is_lightning else key
self.invoice_popup = InvoiceDialog('Invoice', data, key) self.invoice_popup = InvoiceDialog('Invoice', data, key)
self.invoice_popup.set_status(status)
self.invoice_popup.open() self.invoice_popup.open()
def qr_dialog(self, title, data, show_text=False, text_for_clipboard=None): def qr_dialog(self, title, data, show_text=False, text_for_clipboard=None):

19
electrum/gui/kivy/uix/dialogs/invoice_dialog.py

@ -7,7 +7,8 @@ from kivy.app import App
from kivy.clock import Clock from kivy.clock import Clock
from electrum.gui.kivy.i18n import _ from electrum.gui.kivy.i18n import _
from electrum.util import pr_tooltips from electrum.util import pr_tooltips, pr_color
from electrum.util import PR_UNKNOWN, PR_UNPAID
if TYPE_CHECKING: if TYPE_CHECKING:
from electrum.gui.kivy.main_window import ElectrumWindow from electrum.gui.kivy.main_window import ElectrumWindow
@ -18,7 +19,8 @@ Builder.load_string('''
id: popup id: popup
title: '' title: ''
data: '' data: ''
status: 'unknown' status_color: 1,1,1,1
status_str:''
shaded: False shaded: False
show_text: False show_text: False
AnchorLayout: AnchorLayout:
@ -31,7 +33,8 @@ Builder.load_string('''
TopLabel: TopLabel:
text: root.data text: root.data
TopLabel: TopLabel:
text: _('Status') + ': ' + root.status text: _('Status') + ': ' + root.status_str
color: root.status_color
Widget: Widget:
size_hint: 1, 0.2 size_hint: 1, 0.2
BoxLayout: BoxLayout:
@ -57,22 +60,26 @@ Builder.load_string('''
height: '48dp' height: '48dp'
text: _('Pay') text: _('Pay')
on_release: root.do_pay() on_release: root.do_pay()
disabled: not root.can_pay()
''') ''')
class InvoiceDialog(Factory.Popup): class InvoiceDialog(Factory.Popup):
def __init__(self, title, data, key): def __init__(self, title, data, key):
self.status = PR_UNKNOWN
Factory.Popup.__init__(self) Factory.Popup.__init__(self)
self.app = App.get_running_app() # type: ElectrumWindow self.app = App.get_running_app() # type: ElectrumWindow
self.title = title self.title = title
self.data = data self.data = data
self.key = key self.key = key
#def on_open(self): def can_pay(self):
# self.ids.qr.set_data(self.data) return self.status == PR_UNPAID
def set_status(self, status): def set_status(self, status):
self.status = pr_tooltips[status] self.status = status
self.status_str = pr_tooltips[status]
self.status_color = pr_color[status]
def on_dismiss(self): def on_dismiss(self):
self.app.request_popup = None self.app.request_popup = None

14
electrum/gui/kivy/uix/dialogs/request_dialog.py

@ -5,7 +5,8 @@ from kivy.app import App
from kivy.clock import Clock from kivy.clock import Clock
from electrum.gui.kivy.i18n import _ from electrum.gui.kivy.i18n import _
from electrum.util import pr_tooltips from electrum.util import pr_tooltips, pr_color
from electrum.util import PR_UNKNOWN
Builder.load_string(''' Builder.load_string('''
@ -13,7 +14,8 @@ Builder.load_string('''
id: popup id: popup
title: '' title: ''
data: '' data: ''
status: 'unknown' status_str: ''
status_color: 1,1,1,1
shaded: False shaded: False
show_text: False show_text: False
AnchorLayout: AnchorLayout:
@ -33,7 +35,8 @@ Builder.load_string('''
TopLabel: TopLabel:
text: root.data text: root.data
TopLabel: TopLabel:
text: _('Status') + ': ' + root.status text: _('Status') + ': ' + root.status_str
color: root.status_color
Widget: Widget:
size_hint: 1, 0.2 size_hint: 1, 0.2
BoxLayout: BoxLayout:
@ -64,6 +67,7 @@ Builder.load_string('''
class RequestDialog(Factory.Popup): class RequestDialog(Factory.Popup):
def __init__(self, title, data, key): def __init__(self, title, data, key):
self.status = PR_UNKNOWN
Factory.Popup.__init__(self) Factory.Popup.__init__(self)
self.app = App.get_running_app() self.app = App.get_running_app()
self.title = title self.title = title
@ -74,7 +78,9 @@ class RequestDialog(Factory.Popup):
self.ids.qr.set_data(self.data) self.ids.qr.set_data(self.data)
def set_status(self, status): def set_status(self, status):
self.status = pr_tooltips[status] self.status = status
self.status_str = pr_tooltips[status]
self.status_color = pr_color[status]
def on_dismiss(self): def on_dismiss(self):
self.app.request_popup = None self.app.request_popup = None

13
electrum/gui/kivy/uix/screens.py

@ -28,7 +28,7 @@ from electrum.util import PR_TYPE_ONCHAIN, PR_TYPE_LN
from electrum import bitcoin, constants from electrum import bitcoin, constants
from electrum.transaction import TxOutput, Transaction, tx_from_str from electrum.transaction import TxOutput, Transaction, tx_from_str
from electrum.util import send_exception_to_crash_reporter, parse_URI, InvalidBitcoinURI from electrum.util import send_exception_to_crash_reporter, parse_URI, InvalidBitcoinURI
from electrum.util import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED, TxMinedInfo, get_request_status, pr_expiration_values from electrum.util import PR_UNPAID, PR_PAID, PR_UNKNOWN, PR_EXPIRED, PR_INFLIGHT, TxMinedInfo, get_request_status, pr_expiration_values
from electrum.plugin import run_hook from electrum.plugin import run_hook
from electrum.wallet import InternalAddressCorruption from electrum.wallet import InternalAddressCorruption
from electrum import simple_config from electrum import simple_config
@ -223,20 +223,24 @@ class SendScreen(CScreen):
self.set_URI(self.payment_request_queued) self.set_URI(self.payment_request_queued)
self.payment_request_queued = None self.payment_request_queued = None
_list = self.app.wallet.get_invoices() _list = self.app.wallet.get_invoices()
_list = [x for x in _list if x and x.get('status') != PR_PAID or x.get('rhash') in self.app.wallet.lnworker.logs]
payments_container = self.screen.ids.payments_container payments_container = self.screen.ids.payments_container
payments_container.data = [self.get_card(item) for item in _list if item['status'] != PR_PAID] payments_container.data = [self.get_card(item) for item in _list]
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)
def get_card(self, item): def get_card(self, item):
invoice_type = item['type'] invoice_type = item['type']
status = item['status']
status_str = get_request_status(item) # convert to str
if invoice_type == PR_TYPE_LN: if invoice_type == PR_TYPE_LN:
key = item['rhash'] key = item['rhash']
status = get_request_status(item) # convert to str log = self.app.wallet.lnworker.logs.get(key)
if item['status'] == PR_INFLIGHT and log:
status_str += '... (%d)'%len(log)
elif invoice_type == PR_TYPE_ONCHAIN: elif invoice_type == PR_TYPE_ONCHAIN:
key = item['id'] key = item['id']
status = get_request_status(item) # convert to str
else: else:
raise Exception('unknown invoice type') raise Exception('unknown invoice type')
return { return {
@ -244,6 +248,7 @@ class SendScreen(CScreen):
'is_bip70': 'bip70' in item, 'is_bip70': 'bip70' in item,
'screen': self, 'screen': self,
'status': status, 'status': status,
'status_str': status_str,
'key': key, 'key': key,
'memo': item['message'], 'memo': item['message'],
'amount': self.app.format_amount_and_units(item['amount'] or 0), 'amount': self.app.format_amount_and_units(item['amount'] or 0),

9
electrum/gui/kivy/uix/ui_screens/send.kv

@ -1,4 +1,6 @@
#:import _ electrum.gui.kivy.i18n._ #:import _ electrum.gui.kivy.i18n._
#:import pr_color electrum.util.pr_color
#:import PR_UNKNOWN electrum.util.PR_UNKNOWN
#:import Factory kivy.factory.Factory #:import Factory kivy.factory.Factory
#:import Decimal decimal.Decimal #:import Decimal decimal.Decimal
#:set btc_symbol chr(171) #:set btc_symbol chr(171)
@ -15,7 +17,8 @@
key: '' key: ''
memo: '' memo: ''
amount: '' amount: ''
status: '' status: PR_UNKNOWN
status_str: ''
date: '' date: ''
BoxLayout: BoxLayout:
spacing: '8dp' spacing: '8dp'
@ -44,10 +47,10 @@
font_size: '15sp' font_size: '15sp'
Widget Widget
PaymentLabel: PaymentLabel:
text: root.status text: root.status_str
halign: 'right' halign: 'right'
font_size: '13sp' font_size: '13sp'
color: .699, .699, .699, 1 color: pr_color[root.status]
Widget Widget
<PaymentRecycleView>: <PaymentRecycleView>:

20
electrum/gui/qt/invoice_list.py

@ -68,12 +68,11 @@ class InvoiceList(MyTreeView):
super().__init__(parent, self.create_menu, super().__init__(parent, self.create_menu,
stretch_column=self.Columns.DESCRIPTION, stretch_column=self.Columns.DESCRIPTION,
editable_columns=[]) editable_columns=[])
self.logs = {}
self.setSortingEnabled(True) self.setSortingEnabled(True)
self.setModel(QStandardItemModel(self)) self.setModel(QStandardItemModel(self))
self.update() self.update()
def update_item(self, key, status, log): def update_item(self, key, status):
req = self.parent.wallet.get_invoice(key) req = self.parent.wallet.get_invoice(key)
if req is None: if req is None:
return return
@ -86,17 +85,16 @@ class InvoiceList(MyTreeView):
return return
status_item = model.item(row, self.Columns.STATUS) status_item = model.item(row, self.Columns.STATUS)
status_str = get_request_status(req) status_str = get_request_status(req)
if log: log = self.parent.wallet.lnworker.logs.get(key)
self.logs[key] = log if log and status == PR_INFLIGHT:
if status == PR_INFLIGHT: status_str += '... (%d)'%len(log)
status_str += '... (%d)'%len(log)
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(self): def update(self):
_list = self.parent.wallet.get_invoices() _list = self.parent.wallet.get_invoices()
# filter out paid invoices unless we have the log # filter out paid invoices unless we have the log
_list = [x for x in _list if x and x.get('status') != PR_PAID or x.get('rhash') in self.logs] _list = [x for x in _list if x and x.get('status') != PR_PAID or x.get('rhash') in self.parent.wallet.lnworker.logs]
self.model().clear() self.model().clear()
self.update_headers(self.__class__.headers) self.update_headers(self.__class__.headers)
for idx, item in enumerate(_list): for idx, item in enumerate(_list):
@ -157,13 +155,13 @@ class InvoiceList(MyTreeView):
menu.addAction(_("Details"), lambda: self.parent.show_invoice(key)) menu.addAction(_("Details"), lambda: self.parent.show_invoice(key))
if invoice['status'] == PR_UNPAID: if invoice['status'] == PR_UNPAID:
menu.addAction(_("Pay"), lambda: self.parent.do_pay_invoice(invoice)) menu.addAction(_("Pay"), lambda: self.parent.do_pay_invoice(invoice))
if key in self.logs: log = self.parent.wallet.lnworker.logs.get(key)
menu.addAction(_("View log"), lambda: self.show_log(key)) if log:
menu.addAction(_("View log"), lambda: self.show_log(key, log))
menu.addAction(_("Delete"), lambda: self.parent.delete_invoice(key)) menu.addAction(_("Delete"), lambda: self.parent.delete_invoice(key))
menu.exec_(self.viewport().mapToGlobal(position)) menu.exec_(self.viewport().mapToGlobal(position))
def show_log(self, key): def show_log(self, key, log):
log = self.logs.get(key)
d = WindowModalDialog(self, _("Payment log")) d = WindowModalDialog(self, _("Payment log"))
vbox = QVBoxLayout(d) vbox = QVBoxLayout(d)
log_w = QTreeWidget() log_w = QTreeWidget()

4
electrum/gui/qt/main_window.py

@ -1694,10 +1694,10 @@ class ElectrumWindow(QMainWindow, MessageBoxMixin, Logger):
if status == PR_PAID: if status == PR_PAID:
self.notify(_('Payment received') + '\n' + key) self.notify(_('Payment received') + '\n' + key)
def on_invoice_status(self, key, status, log): def on_invoice_status(self, key, status):
if key not in self.wallet.invoices: if key not in self.wallet.invoices:
return return
self.invoice_list.update_item(key, status, log) self.invoice_list.update_item(key, status)
if status == PR_PAID: if status == PR_PAID:
self.show_message(_('Payment succeeded')) self.show_message(_('Payment succeeded'))
self.need_update.set() self.need_update.set()

7
electrum/lnworker.py

@ -320,6 +320,7 @@ class LNWallet(LNWorker):
self.preimages = self.storage.get('lightning_preimages', {}) # RHASH -> preimage self.preimages = self.storage.get('lightning_preimages', {}) # RHASH -> preimage
self.sweep_address = wallet.get_receiving_address() self.sweep_address = wallet.get_receiving_address()
self.lock = threading.RLock() self.lock = threading.RLock()
self.logs = defaultdict(list)
# note: accessing channels (besides simple lookup) needs self.lock! # note: accessing channels (besides simple lookup) needs self.lock!
self.channels = {} # type: Dict[bytes, Channel] self.channels = {} # type: Dict[bytes, Channel]
@ -842,21 +843,21 @@ class LNWallet(LNWorker):
self.save_payment_info(info) self.save_payment_info(info)
self._check_invoice(invoice, amount_sat) self._check_invoice(invoice, amount_sat)
self.wallet.set_label(key, lnaddr.get_description()) self.wallet.set_label(key, lnaddr.get_description())
log = [] log = self.logs[key]
for i in range(attempts): for i in range(attempts):
try: try:
route = await self._create_route_from_invoice(decoded_invoice=lnaddr) route = await self._create_route_from_invoice(decoded_invoice=lnaddr)
except NoPathFound: except NoPathFound:
success = False success = False
break break
self.network.trigger_callback('invoice_status', key, PR_INFLIGHT, log) self.network.trigger_callback('invoice_status', key, PR_INFLIGHT)
success, preimage, failure_log = await self._pay_to_route(route, lnaddr) success, preimage, failure_log = await self._pay_to_route(route, lnaddr)
if success: if success:
log.append((route, True, preimage)) log.append((route, True, preimage))
break break
else: else:
log.append((route, False, failure_log)) log.append((route, False, failure_log))
self.network.trigger_callback('invoice_status', key, PR_PAID if success else PR_FAILED, log) self.network.trigger_callback('invoice_status', key, PR_PAID if success else PR_FAILED)
return success return success
async def _pay_to_route(self, route, lnaddr): async def _pay_to_route(self, route, lnaddr):

9
electrum/util.py

@ -85,6 +85,15 @@ PR_PAID = 3 # send and propagated
PR_INFLIGHT = 4 # unconfirmed PR_INFLIGHT = 4 # unconfirmed
PR_FAILED = 5 PR_FAILED = 5
pr_color = {
PR_UNPAID: (.7, .7, .7, 1),
PR_PAID: (.2, .9, .2, 1),
PR_UNKNOWN: (.7, .7, .7, 1),
PR_EXPIRED: (.9, .2, .2, 1),
PR_INFLIGHT: (.9, .6, .3, 1),
PR_FAILED: (.9, .2, .2, 1),
}
pr_tooltips = { pr_tooltips = {
PR_UNPAID:_('Pending'), PR_UNPAID:_('Pending'),
PR_PAID:_('Paid'), PR_PAID:_('Paid'),

Loading…
Cancel
Save