Browse Source

kivy updates

patch-4
ThomasV 3 years ago
parent
commit
cbb51b31d8
  1. 6
      electrum/gui/kivy/main_window.py
  2. 2
      electrum/gui/kivy/uix/dialogs/invoice_dialog.py
  3. 5
      electrum/gui/kivy/uix/dialogs/lightning_tx_dialog.py
  4. 88
      electrum/gui/kivy/uix/dialogs/request_dialog.py
  5. 57
      electrum/gui/kivy/uix/screens.py
  6. 40
      electrum/gui/kivy/uix/ui_screens/receive.kv

6
electrum/gui/kivy/main_window.py

@ -505,17 +505,17 @@ class ElectrumWindow(App, Logger):
tab = self.tabs.ids[name + '_tab']
panel.switch_to(tab)
def show_request(self, is_lightning, key):
def show_request(self, key):
from .uix.dialogs.request_dialog import RequestDialog
self.request_popup = RequestDialog('Request', key)
self.request_popup.open()
def show_invoice(self, is_lightning, key):
def show_invoice(self, key):
from .uix.dialogs.invoice_dialog import InvoiceDialog
invoice = self.wallet.get_invoice(key)
if not invoice:
return
data = invoice.invoice if is_lightning else key
data = invoice.lightning_invoice if invoice.is_lightning() else key
self.invoice_popup = InvoiceDialog('Invoice', data, key)
self.invoice_popup.open()

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

@ -8,7 +8,7 @@ from kivy.clock import Clock
from electrum.gui.kivy.i18n import _
from electrum.invoices import pr_tooltips, pr_color
from electrum.invoices import PR_UNKNOWN, PR_UNPAID, PR_FAILED, PR_TYPE_LN
from electrum.invoices import PR_UNKNOWN, PR_UNPAID, PR_FAILED
if TYPE_CHECKING:
from electrum.gui.kivy.main_window import ElectrumWindow

5
electrum/gui/kivy/uix/dialogs/lightning_tx_dialog.py

@ -13,7 +13,7 @@ from kivy.uix.dropdown import DropDown
from kivy.uix.button import Button
from electrum.gui.kivy.i18n import _
from electrum.invoices import LNInvoice
from electrum.invoices import Invoice
if TYPE_CHECKING:
@ -121,7 +121,6 @@ class LightningTxDialog(Factory.Popup):
invoice = (self.app.wallet.get_invoice(self.payment_hash)
or self.app.wallet.get_request(self.payment_hash))
if invoice:
assert isinstance(invoice, LNInvoice), f"{self.invoice!r}"
self.invoice = invoice.invoice
self.invoice = invoice.lightning_invoice or ''
else:
self.invoice = ''

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

@ -5,15 +5,22 @@ from kivy.lang import Builder
from kivy.core.clipboard import Clipboard
from kivy.app import App
from kivy.clock import Clock
from kivy.properties import NumericProperty, StringProperty
from electrum.gui.kivy.i18n import _
from electrum.invoices import pr_tooltips, pr_color
from electrum.invoices import PR_UNKNOWN, PR_UNPAID, PR_FAILED, PR_TYPE_LN
from electrum.invoices import PR_UNKNOWN, PR_UNPAID, PR_FAILED
if TYPE_CHECKING:
from ...main_window import ElectrumWindow
MODE_ADDRESS = 0
MODE_URI = 1
MODE_LIGHTNING = 2
Builder.load_string('''
#:import KIVY_GUI_PATH electrum.gui.kivy.KIVY_GUI_PATH
@ -22,8 +29,9 @@ Builder.load_string('''
amount_str: ''
title: ''
description:''
is_lightning: False
mode:0
key:''
data:''
warning: ''
status_str: ''
status_color: 1,1,1,1
@ -43,15 +51,37 @@ Builder.load_string('''
on_touch_down:
touch = args[1]
if self.collide_point(*touch.pos): self.shaded = not self.shaded
TopLabel:
text: root.data[0:70] + ('...' if len(root.data)>70 else '')
BoxLayout:
size_hint: 1, None
height: '48dp'
ToggleButton:
id: b0
group:'g'
size_hint: 1, None
height: '48dp'
text: _('Address')
on_release: root.mode = 0
ToggleButton:
id: b1
group:'g'
size_hint: 1, None
height: '48dp'
text: _('URI')
on_release: root.mode = 1
state: 'down'
ToggleButton:
id: b2
group:'g'
size_hint: 1, None
height: '48dp'
text: _('Lightning')
on_release: root.mode = 2
TopLabel:
text: _('Description') + ': ' + root.description or _('None')
TopLabel:
text: _('Amount') + ': ' + root.amount_str
TopLabel:
text: (_('Address') if not root.is_lightning else _('Payment hash')) + ': '
RefLabel:
data: root.key
name: (_('Address') if not root.is_lightning else _('Payment hash'))
TopLabel:
text: _('Status') + ': ' + root.status_str
color: root.status_color
@ -87,6 +117,9 @@ Builder.load_string('''
class RequestDialog(Factory.Popup):
mode = NumericProperty(0)
data = StringProperty('')
def __init__(self, title, key):
self.status = PR_UNKNOWN
Factory.Popup.__init__(self)
@ -94,33 +127,50 @@ class RequestDialog(Factory.Popup):
self.title = title
self.key = key
r = self.app.wallet.get_request(key)
self.is_lightning = r.is_lightning()
self.data = r.invoice if self.is_lightning else self.app.wallet.get_request_URI(r)
self.amount_sat = r.get_amount_sat()
self.amount_str = self.app.format_amount_and_units(self.amount_sat)
self.description = r.message
self.mode = 1
self.on_mode(0, 0)
self.ids.b0.pressed = True
self.update_status()
def on_open(self):
data = self.data
if self.is_lightning:
def on_mode(self, instance, x):
r = self.app.wallet.get_request(self.key)
if self.mode == MODE_ADDRESS:
self.data = r.get_address() or ''
elif self.mode == MODE_URI:
self.data = self.app.wallet.get_request_URI(r) or ''
else:
self.data = r.lightning_invoice or ''
qr_data = self.data
if self.mode == MODE_LIGHTNING:
# encode lightning invoices as uppercase so QR encoding can use
# alphanumeric mode; resulting in smaller QR codes
data = data.upper()
self.ids.qr.set_data(data)
qr_data = qr_data.upper()
if qr_data:
self.ids.qr.set_data(qr_data)
self.ids.qr.opacity = 1
else:
self.ids.qr.opacity = 0
self.update_status()
def update_status(self):
req = self.app.wallet.get_request(self.key)
self.status = self.app.wallet.get_request_status(self.key)
self.status_str = req.get_status_str(self.status)
self.status_color = pr_color[self.status]
if self.status == PR_UNPAID and self.is_lightning and self.app.wallet.lnworker:
warning = ''
if self.status == PR_UNPAID and self.mode == MODE_LIGHTNING and self.app.wallet.lnworker:
if self.amount_sat and self.amount_sat > self.app.wallet.lnworker.num_sats_can_receive():
self.warning = _('Warning') + ': ' + _('This amount exceeds the maximum you can currently receive with your channels')
if self.status == PR_UNPAID and not self.is_lightning:
warning = _('Warning') + ': ' + _('This amount exceeds the maximum you can currently receive with your channels')
if not self.mode == MODE_LIGHTNING:
address = req.get_address()
if self.app.wallet.is_used(address):
self.warning = _('Warning') + ': ' + _('This address is being reused')
if not address:
warning = _('Warning') + ': ' + _('This request cannot be paid on-chain')
elif self.app.wallet.is_used(address):
warning = _('Warning') + ': ' + _('This address is being reused')
self.warning = warning
def on_dismiss(self):
self.app.request_popup = None

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

@ -9,10 +9,11 @@ from kivy.properties import ObjectProperty
from kivy.lang import Builder
from kivy.factory import Factory
from kivy.uix.recycleview import RecycleView
from kivy.properties import StringProperty
from electrum.invoices import (PR_TYPE_ONCHAIN, PR_TYPE_LN, PR_DEFAULT_EXPIRATION_WHEN_CREATING,
from electrum.invoices import (PR_DEFAULT_EXPIRATION_WHEN_CREATING,
PR_PAID, PR_UNKNOWN, PR_EXPIRED, PR_INFLIGHT,
LNInvoice, pr_expiration_values, Invoice, OnchainInvoice)
pr_expiration_values, Invoice)
from electrum import bitcoin, constants
from electrum.transaction import tx_from_any, PartialTxOutput
from electrum.util import (parse_URI, InvalidBitcoinURI, TxMinedInfo, maybe_extract_bolt11_invoice,
@ -224,15 +225,14 @@ class SendScreen(CScreen, Logger):
payments_container.refresh_from_data()
def show_item(self, obj):
self.app.show_invoice(obj.is_lightning, obj.key)
self.app.show_invoice(obj.key)
def get_card(self, item: Invoice) -> Dict[str, Any]:
status = self.app.wallet.get_invoice_status(item)
status_str = item.get_status_str(status)
is_lightning = item.type == PR_TYPE_LN
is_lightning = item.is_lightning()
key = self.app.wallet.get_key_for_outgoing_invoice(item)
if is_lightning:
assert isinstance(item, LNInvoice)
address = item.rhash
if self.app.wallet.lnworker:
log = self.app.wallet.lnworker.logs.get(key)
@ -240,7 +240,6 @@ class SendScreen(CScreen, Logger):
status_str += '... (%d)'%len(log)
is_bip70 = False
else:
assert isinstance(item, OnchainInvoice)
address = item.get_address()
is_bip70 = bool(item.bip70)
return {
@ -313,7 +312,7 @@ class SendScreen(CScreen, Logger):
message = self.message
try:
if self.is_lightning:
return LNInvoice.from_bech32(address)
return Invoice.from_bech32(address)
else: # on-chain
if self.payment_request:
outputs = self.payment_request.get_outputs()
@ -358,10 +357,11 @@ class SendScreen(CScreen, Logger):
else:
self._do_pay_onchain(invoice)
def _do_pay_lightning(self, invoice: LNInvoice, pw) -> None:
def _do_pay_lightning(self, invoice: Invoice, pw) -> None:
amount_msat = invoice.get_amount_msat()
def pay_thread():
try:
coro = self.app.wallet.lnworker.pay_invoice(invoice.invoice, attempts=10)
coro = self.app.wallet.lnworker.pay_invoice(invoice.lightning_invoice, amount_msat=amount_msat, attempts=10)
fut = asyncio.run_coroutine_threadsafe(coro, self.app.network.asyncio_loop)
fut.result()
except Exception as e:
@ -369,7 +369,7 @@ class SendScreen(CScreen, Logger):
self.save_invoice(invoice)
threading.Thread(target=pay_thread).start()
def _do_pay_onchain(self, invoice: OnchainInvoice) -> None:
def _do_pay_onchain(self, invoice: Invoice) -> None:
outputs = invoice.outputs
amount = sum(map(lambda x: x.value, outputs)) if not any(parse_max_spend(x.value) for x in outputs) else '!'
coins = self.app.wallet.get_spendable_coins(None)
@ -399,11 +399,17 @@ class SendScreen(CScreen, Logger):
class ReceiveScreen(CScreen):
kvname = 'receive'
expiration_text = StringProperty('')
def __init__(self, **kwargs):
super(ReceiveScreen, self).__init__(**kwargs)
Clock.schedule_interval(lambda dt: self.update(), 5)
self.is_max = False # not used for receiving (see app.amount_dialog)
self.expiration_text = pr_expiration_values[self.expiry()]
def on_open(self):
c = self.expiry()
self.expiration_text = pr_expiration_values[c]
def expiry(self):
return self.app.electrum_config.get('request_expiry', PR_DEFAULT_EXPIRATION_WHEN_CREATING)
@ -437,17 +443,14 @@ class ReceiveScreen(CScreen):
self.app._clipboard.copy(uri)
self.app.show_info(_('Request copied to clipboard'))
def new_request(self, lightning):
amount = self.amount
amount = self.app.get_amount(amount) if amount else 0
def new_request(self):
amount_str = self.amount
amount_sat = self.app.get_amount(amount_str) if amount_str else 0
message = self.message
lnworker = self.app.wallet.lnworker
try:
if lightning:
if lnworker:
key = lnworker.add_request(amount, message, self.expiry())
else:
self.app.show_error(_("Lightning payments are not available for this wallet"))
expiry = self.expiry()
if amount_sat and amount_sat < self.wallet.dust_threshold():
self.address = ''
if not self.app.wallet.has_lightning():
return
else:
addr = self.address or self.app.wallet.get_unused_address()
@ -458,24 +461,21 @@ class ReceiveScreen(CScreen):
self.app.show_info(_('No address available. Please remove some of your pending requests.'))
return
self.address = addr
req = self.app.wallet.make_payment_request(addr, amount, message, self.expiry())
self.app.wallet.add_payment_request(req)
key = addr
try:
key = self.app.wallet.create_request(amount_sat, message, expiry, self.address, lightning=self.app.wallet.has_lightning())
except InvoiceError as e:
self.app.show_error(_('Error creating payment request') + ':\n' + str(e))
return
self.clear()
self.update()
self.app.show_request(lightning, key)
self.app.show_request(key)
def get_card(self, req: Invoice) -> Dict[str, Any]:
is_lightning = req.is_lightning()
if not is_lightning:
assert isinstance(req, OnchainInvoice)
address = req.get_address()
else:
assert isinstance(req, LNInvoice)
address = req.invoice
address = req.lightning_invoice
key = self.app.wallet.get_key_for_receive_request(req)
amount = req.get_amount_sat()
description = req.message
@ -513,12 +513,13 @@ class ReceiveScreen(CScreen):
payments_container.refresh_from_data()
def show_item(self, obj):
self.app.show_request(obj.is_lightning, obj.key)
self.app.show_request(obj.key)
def expiration_dialog(self, obj):
from .dialogs.choice_dialog import ChoiceDialog
def callback(c):
self.app.electrum_config.set_key('request_expiry', c)
self.expiration_text = pr_expiration_values[c]
d = ChoiceDialog(_('Expiration date'), pr_expiration_values, self.expiry(), callback)
d.open()

40
electrum/gui/kivy/uix/ui_screens/receive.kv

@ -67,7 +67,7 @@
amount: ''
message: ''
status: ''
is_lightning: False
expiration_text: _('Expiry')
BoxLayout
padding: '12dp', '12dp', '12dp', '12dp'
@ -83,33 +83,34 @@
height: blue_bottom.item_height
spacing: '5dp'
Image:
source: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/lightning' if root.is_lightning else f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/globe'
source: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/calculator'
opacity: 0.7
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
BlueButton:
id: address_label
text: _('Lightning') if root.is_lightning else (s.address if s.address else _('Bitcoin Address'))
shorten: True
on_release: root.is_lightning = not root.is_lightning if app.wallet.has_lightning() else False
id: amount_label
default_text: _('Amount')
text: s.amount if s.amount else _('Amount')
on_release: Clock.schedule_once(lambda dt: app.amount_dialog(s, False))
CardSeparator:
opacity: message_selection.opacity
color: blue_bottom.foreground_color
BoxLayout:
id: message_selection
opacity: 1
size_hint: 1, None
height: blue_bottom.item_height
spacing: '5dp'
Image:
source: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/calculator'
opacity: 0.7
source: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/pen'
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
BlueButton:
id: amount_label
default_text: _('Amount')
text: s.amount if s.amount else _('Amount')
on_release: Clock.schedule_once(lambda dt: app.amount_dialog(s, False))
id: description
text: s.message if s.message else _('Description')
on_release: Clock.schedule_once(lambda dt: app.description_dialog(s))
CardSeparator:
opacity: message_selection.opacity
color: blue_bottom.foreground_color
@ -120,32 +121,27 @@
height: blue_bottom.item_height
spacing: '5dp'
Image:
source: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/pen'
source: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/clock1'
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
BlueButton:
id: description
text: s.message if s.message else _('Description')
on_release: Clock.schedule_once(lambda dt: app.description_dialog(s))
text: s.expiration_text
on_release: Clock.schedule_once(lambda dt: s.expiration_dialog(s))
BoxLayout:
size_hint: 1, None
height: '48dp'
IconButton:
icon: f'atlas://{KIVY_GUI_PATH}/theming/atlas/light/clock1'
size_hint: 0.5, None
height: '48dp'
on_release: Clock.schedule_once(lambda dt: s.expiration_dialog(s))
Button:
text: _('Clear')
size_hint: 1, None
height: '48dp'
on_release: Clock.schedule_once(lambda dt: s.clear())
Button:
text: _('Request')
text: _('New Request')
size_hint: 1, None
height: '48dp'
on_release: Clock.schedule_once(lambda dt: s.new_request(root.is_lightning))
on_release: Clock.schedule_once(lambda dt: s.new_request())
Widget:
size_hint: 1, 0.1
RequestRecycleView:

Loading…
Cancel
Save