kivy: show status with color. show inflight attempts.
This commit is contained in:
@@ -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):
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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),
|
||||||
|
|||||||
@@ -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>:
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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()
|
||||||
|
|||||||
@@ -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):
|
||||||
|
|||||||
@@ -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'),
|
||||||
|
|||||||
Reference in New Issue
Block a user