diff --git a/.gitignore b/.gitignore
index 34f4b5908..a865b2c91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -17,8 +17,12 @@ bin/
# icons
electrum/gui/kivy/theming/light-0.png
+electrum/gui/kivy/theming/light-1.png
electrum/gui/kivy/theming/light.atlas
electrum/gui/kivy/theming/light/network.png
+electrum/gui/kivy/theming/light/lightning_switch_off.png
+electrum/gui/kivy/theming/light/lightning_switch_on.png
+electrum/gui/kivy/theming/light/lightning.png
# tests/tox
.tox/
diff --git a/electrum/gui/kivy/Makefile b/electrum/gui/kivy/Makefile
index 44667efd1..6c8226095 100644
--- a/electrum/gui/kivy/Makefile
+++ b/electrum/gui/kivy/Makefile
@@ -5,7 +5,9 @@ PYTHON = python3
.PHONY: theming apk clean
theming:
- bash -c "convert -background none theming/light/network.{svg,png}"
+ bash -c 'for i in network lightning; do convert -background none theming/light/$$i.{svg,png}; done'
+ convert -background none -crop +0+390 theming/light/lightning_switch.svg theming/light/lightning_switch_off.png
+ convert -background none -crop 840x390+0+0 theming/light/lightning_switch.svg theming/light/lightning_switch_on.png
$(PYTHON) -m kivy.atlas theming/light 1024 theming/light/*.png
prepare:
# running pre build setup
diff --git a/electrum/gui/kivy/main_window.py b/electrum/gui/kivy/main_window.py
index 7acba95cc..519eeb4e3 100644
--- a/electrum/gui/kivy/main_window.py
+++ b/electrum/gui/kivy/main_window.py
@@ -1016,6 +1016,15 @@ class ElectrumWindow(App):
popup = AmountDialog(show_max, amount, cb)
popup.open()
+ def lightning_invoices_dialog(self, cb):
+ from .uix.dialogs.lightning_invoices import LightningInvoicesDialog
+ report = self.wallet.lnworker._list_invoices()
+ if not report['unsettled']:
+ self.show_info(_('No unsettled invoices. Type in an amount to generate a new one.'))
+ return
+ popup = LightningInvoicesDialog(report, cb)
+ popup.open()
+
def invoices_dialog(self, screen):
from .uix.dialogs.invoices import InvoicesDialog
if len(self.wallet.invoices.sorted_list()) == 0:
diff --git a/electrum/gui/kivy/theming/light/lightning.svg b/electrum/gui/kivy/theming/light/lightning.svg
new file mode 100644
index 000000000..a2ba24a8c
--- /dev/null
+++ b/electrum/gui/kivy/theming/light/lightning.svg
@@ -0,0 +1,6 @@
+
+
diff --git a/electrum/gui/kivy/theming/light/lightning_switch.svg b/electrum/gui/kivy/theming/light/lightning_switch.svg
new file mode 100644
index 000000000..a7e473a0a
--- /dev/null
+++ b/electrum/gui/kivy/theming/light/lightning_switch.svg
@@ -0,0 +1,288 @@
+
+
+
+
diff --git a/electrum/gui/kivy/uix/dialogs/lightning_invoices.py b/electrum/gui/kivy/uix/dialogs/lightning_invoices.py
new file mode 100644
index 000000000..56c628d3d
--- /dev/null
+++ b/electrum/gui/kivy/uix/dialogs/lightning_invoices.py
@@ -0,0 +1,65 @@
+from kivy.factory import Factory
+from kivy.lang import Builder
+from electrum.gui.kivy.i18n import _
+from kivy.uix.recycleview import RecycleView
+from electrum.gui.kivy.uix.context_menu import ContextMenu
+
+Builder.load_string('''
+-
+ addr: ''
+ desc: ''
+ screen: None
+ BoxLayout:
+ orientation: 'vertical'
+ Label
+ text: root.addr
+ text_size: self.width, None
+ shorten: True
+ Label
+ text: root.desc if root.desc else _('No description')
+ text_size: self.width, None
+ shorten: True
+ font_size: '10dp'
+
+
+ id: popup
+ title: _('Lightning Invoices')
+ BoxLayout:
+ orientation: 'vertical'
+ id: box
+ RecycleView:
+ viewclass: 'Item'
+ id: recycleview
+ data: []
+ RecycleBoxLayout:
+ default_size: None, dp(56)
+ default_size_hint: 1, None
+ size_hint_y: None
+ height: self.minimum_height
+ orientation: 'vertical'
+''')
+
+class LightningInvoicesDialog(Factory.Popup):
+
+ def __init__(self, report, callback):
+ super().__init__()
+ self.context_menu = None
+ self.callback = callback
+ self.menu_actions = [(_('Show'), self.do_show)]
+ for addr, preimage, pay_req in report['unsettled']:
+ self.ids.recycleview.data.append({'screen': self, 'addr': pay_req, 'desc': dict(addr.tags).get('d', '')})
+
+ def do_show(self, obj):
+ self.hide_menu()
+ self.dismiss()
+ self.callback(obj.addr)
+
+ def show_menu(self, obj):
+ self.hide_menu()
+ self.context_menu = ContextMenu(obj, self.menu_actions)
+ self.ids.box.add_widget(self.context_menu)
+
+ def hide_menu(self):
+ if self.context_menu is not None:
+ self.ids.box.remove_widget(self.context_menu)
+ self.context_menu = None
diff --git a/electrum/gui/kivy/uix/screens.py b/electrum/gui/kivy/uix/screens.py
index f531aee6b..c2fbfb00b 100644
--- a/electrum/gui/kivy/uix/screens.py
+++ b/electrum/gui/kivy/uix/screens.py
@@ -15,6 +15,8 @@ from kivy.properties import (ObjectProperty, DictProperty, NumericProperty,
from kivy.uix.recycleview import RecycleView
from kivy.uix.label import Label
+from kivy.uix.behaviors import ToggleButtonBehavior
+from kivy.uix.image import Image
from kivy.lang import Builder
from kivy.factory import Factory
@@ -398,6 +400,7 @@ class ReceiveScreen(CScreen):
self.screen.address = ''
self.screen.amount = ''
self.screen.message = ''
+ self.screen.lnaddr = ''
def get_new_address(self) -> bool:
"""Sets the address field, and returns whether the set address
@@ -440,18 +443,30 @@ class ReceiveScreen(CScreen):
@profiler
def update_qr(self):
- uri = self.get_URI()
qr = self.screen.ids.qr
- qr.set_data(uri)
+ if self.screen.ids.lnbutton.state == 'down':
+ qr.set_data(self.screen.lnaddr)
+ else:
+ uri = self.get_URI()
+ qr.set_data(uri)
def do_share(self):
- uri = self.get_URI()
- self.app.do_share(uri, _("Share Bitcoin Request"))
+ if self.screen.ids.lnbutton.state == 'down':
+ if self.screen.lnaddr:
+ self.app.do_share('lightning://' + self.lnaddr, _('Share Lightning invoice'))
+ else:
+ uri = self.get_URI()
+ self.app.do_share(uri, _("Share Bitcoin Request"))
def do_copy(self):
- uri = self.get_URI()
- self.app._clipboard.copy(uri)
- self.app.show_info(_('Request copied to clipboard'))
+ if self.screen.ids.lnbutton.state == 'down':
+ if self.screen.lnaddr:
+ self.app._clipboard.copy(self.screen.lnaddr)
+ self.app.show_info(_('Invoice copied to clipboard'))
+ else:
+ uri = self.get_URI()
+ self.app._clipboard.copy(uri)
+ self.app.show_info(_('Request copied to clipboard'))
def save_request(self):
addr = self.screen.address
@@ -472,6 +487,9 @@ class ReceiveScreen(CScreen):
return added_request
def on_amount_or_message(self):
+ if self.screen.ids.lnbutton.state == 'down':
+ if self.screen.amount:
+ self.screen.lnaddr = self.app.wallet.lnworker.add_invoice(self.app.get_amount(self.screen.amount), self.screen.message)
Clock.schedule_once(lambda dt: self.update_qr())
def do_new(self):
@@ -483,6 +501,13 @@ class ReceiveScreen(CScreen):
if self.save_request():
self.app.show_info(_('Request was saved.'))
+ def do_open_lnaddr(self, lnaddr):
+ self.clear()
+ self.screen.lnaddr = lnaddr
+ obj = lndecode(lnaddr, expected_hrp=constants.net.SEGWIT_HRP)
+ self.screen.message = dict(obj.tags).get('d', '')
+ self.screen.amount = self.app.format_amount_and_units(int(obj.amount * bitcoin.COIN))
+ self.on_amount_or_message()
class TabbedCarousel(Factory.TabbedPanel):
'''Custom TabbedPanel using a carousel used in the Main Screen
@@ -556,3 +581,15 @@ class TabbedCarousel(Factory.TabbedPanel):
self.carousel.add_widget(widget)
return
super(TabbedCarousel, self).add_widget(widget, index=index)
+
+class LightningButton(ToggleButtonBehavior, Image):
+ def __init__(self, **kwargs):
+ super().__init__(**kwargs)
+ self.source = 'atlas://electrum/gui/kivy/theming/light/lightning_switch_off'
+
+ def on_state(self, widget, value):
+ self.state = value
+ if value == 'down':
+ self.source = 'atlas://electrum/gui/kivy/theming/light/lightning_switch_on'
+ else:
+ self.source = 'atlas://electrum/gui/kivy/theming/light/lightning_switch_off'
diff --git a/electrum/gui/kivy/uix/ui_screens/receive.kv b/electrum/gui/kivy/uix/ui_screens/receive.kv
index 574e16c00..81d47a58b 100644
--- a/electrum/gui/kivy/uix/ui_screens/receive.kv
+++ b/electrum/gui/kivy/uix/ui_screens/receive.kv
@@ -14,6 +14,7 @@ ReceiveScreen:
amount: ''
message: ''
status: ''
+ lnaddr: ''
on_address:
self.parent.on_address(self.address)
@@ -30,6 +31,7 @@ ReceiveScreen:
FloatLayout:
id: bl
QRCodeWidget:
+ opacity: 0 if lnbutton.state == 'down' and not s.lnaddr else 1
id: qr
size_hint: None, 1
width: min(self.height, bl.width)
@@ -62,15 +64,15 @@ ReceiveScreen:
height: blue_bottom.item_height
spacing: '5dp'
Image:
- source: 'atlas://electrum/gui/kivy/theming/light/globe'
+ source: 'atlas://electrum/gui/kivy/theming/light/lightning' if lnbutton.state == 'down' else 'atlas://electrum/gui/kivy/theming/light/globe'
size_hint: None, None
size: '22dp', '22dp'
pos_hint: {'center_y': .5}
BlueButton:
id: address_label
- text: s.address if s.address else _('Bitcoin Address')
+ text: (s.address if s.address else _('Bitcoin Address')) if lnbutton.state != 'down' else (s.lnaddr if s.lnaddr else _('Please enter amount'))
shorten: True
- on_release: Clock.schedule_once(lambda dt: app.addresses_dialog(s))
+ on_release: Clock.schedule_once(lambda dt: app.addresses_dialog(s) if lnbutton.state != 'down' else s.parent.do_copy())
CardSeparator:
opacity: message_selection.opacity
color: blue_bottom.foreground_color
@@ -111,15 +113,17 @@ ReceiveScreen:
size_hint: 1, None
height: '48dp'
IconButton:
- icon: 'atlas://electrum/gui/kivy/theming/light/save'
- size_hint: 0.6, None
+ opacity: 1 if lnbutton.state != 'down' else 0
+ icon: 'atlas://electrum/gui/kivy/theming/light/save' if lnbutton.state != 'down' else ''
+ size_hint: (0 if lnbutton.state == 'down' else 0.6), None
height: '48dp'
- on_release: s.parent.do_save()
+ on_release: s.parent.do_save() if lnbutton.state != 'down' else None
+ width: (0 if lnbutton.state == 'down' else 100)
Button:
- text: _('Requests')
- size_hint: 1, None
+ text: _('Requests') if lnbutton.state != 'down' else _('Lightning Invoices')
+ size_hint: 1 + (.6 if lnbutton.state == 'down' else 0), None
height: '48dp'
- on_release: Clock.schedule_once(lambda dt: app.requests_dialog(s))
+ on_release: Clock.schedule_once(lambda dt: app.requests_dialog(s) if lnbutton.state != 'down' else app.lightning_invoices_dialog(s.parent.do_open_lnaddr))
Button:
text: _('Copy')
size_hint: 1, None
@@ -133,8 +137,11 @@ ReceiveScreen:
BoxLayout:
size_hint: 1, None
height: '48dp'
+ LightningButton
+ id: lnbutton
+ on_state: s.parent.on_amount_or_message()
Widget
- size_hint: 2, 1
+ size_hint: 1, 1
Button:
text: _('New')
size_hint: 1, None
diff --git a/electrum/lnworker.py b/electrum/lnworker.py
index 36d17be66..6b7b2e243 100644
--- a/electrum/lnworker.py
+++ b/electrum/lnworker.py
@@ -115,7 +115,8 @@ class LNWorker(PrintError):
if report['unsettled']:
yield 'Your unsettled invoices:'
yield '------------------------'
- for addr, preimage in report['unsettled']:
+ for addr, preimage, pay_req in report['unsettled']:
+ yield pay_req
yield str(addr)
yield 'Preimage: ' + bh2u(preimage)
yield ''
@@ -143,7 +144,7 @@ class LNWorker(PrintError):
settled.append((datetime.fromtimestamp(date, timezone.utc), HTLCOwner(direction), htlcobj, preimage))
for preimage, pay_req in invoices.values():
addr = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
- unsettled.append((addr, bfh(preimage)))
+ unsettled.append((addr, bfh(preimage), pay_req))
for pay_req, amount_sat in self.paying.values():
addr = lndecode(pay_req, expected_hrp=constants.net.SEGWIT_HRP)
if amount_sat is not None: